aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.codeclimate.yml27
-rw-r--r--.gitattributes2
-rw-r--r--.github/issue_template.md15
-rw-r--r--.github/pull_request_template.md18
-rw-r--r--.rubocop.yml27
-rw-r--r--.travis.yml75
-rw-r--r--CONTRIBUTING.md4
-rw-r--r--Gemfile40
-rw-r--r--Gemfile.lock366
-rw-r--r--RAILS_VERSION2
-rw-r--r--README.md6
-rw-r--r--RELEASING_RAILS.md42
-rw-r--r--Rakefile10
-rw-r--r--actioncable/.gitignore2
-rw-r--r--actioncable/CHANGELOG.md14
-rw-r--r--actioncable/README.md198
-rw-r--r--actioncable/Rakefile28
-rw-r--r--actioncable/actioncable.gemspec10
-rw-r--r--actioncable/app/assets/javascripts/action_cable.coffee.erb (renamed from actioncable/lib/assets/javascripts/action_cable.coffee.erb)19
-rw-r--r--actioncable/app/assets/javascripts/action_cable/connection.coffee116
-rw-r--r--actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee95
-rw-r--r--actioncable/app/assets/javascripts/action_cable/consumer.coffee (renamed from actioncable/lib/assets/javascripts/action_cable/consumer.coffee)31
-rw-r--r--actioncable/app/assets/javascripts/action_cable/subscription.coffee (renamed from actioncable/lib/assets/javascripts/action_cable/subscription.coffee)28
-rw-r--r--actioncable/app/assets/javascripts/action_cable/subscriptions.coffee (renamed from actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee)14
-rw-r--r--actioncable/blade.yml34
-rw-r--r--actioncable/lib/action_cable.rb9
-rw-r--r--actioncable/lib/action_cable/channel/base.rb68
-rw-r--r--actioncable/lib/action_cable/channel/naming.rb1
-rw-r--r--actioncable/lib/action_cable/channel/periodic_timers.rb54
-rw-r--r--actioncable/lib/action_cable/channel/streams.rb122
-rw-r--r--actioncable/lib/action_cable/connection.rb7
-rw-r--r--actioncable/lib/action_cable/connection/base.rb115
-rw-r--r--actioncable/lib/action_cable/connection/client_socket.rb155
-rw-r--r--actioncable/lib/action_cable/connection/faye_client_socket.rb48
-rw-r--r--actioncable/lib/action_cable/connection/faye_event_loop.rb44
-rw-r--r--actioncable/lib/action_cable/connection/identification.rb2
-rw-r--r--actioncable/lib/action_cable/connection/internal_channel.rb8
-rw-r--r--actioncable/lib/action_cable/connection/message_buffer.rb9
-rw-r--r--actioncable/lib/action_cable/connection/stream.rb57
-rw-r--r--actioncable/lib/action_cable/connection/stream_event_loop.rb103
-rw-r--r--actioncable/lib/action_cable/connection/subscriptions.rb12
-rw-r--r--actioncable/lib/action_cable/connection/web_socket.rb26
-rw-r--r--actioncable/lib/action_cable/engine.rb43
-rw-r--r--actioncable/lib/action_cable/gem_version.rb4
-rw-r--r--actioncable/lib/action_cable/helpers/action_cable_helper.rb27
-rw-r--r--actioncable/lib/action_cable/process/logging.rb7
-rw-r--r--actioncable/lib/action_cable/remote_connections.rb14
-rw-r--r--actioncable/lib/action_cable/server.rb4
-rw-r--r--actioncable/lib/action_cable/server/base.rb58
-rw-r--r--actioncable/lib/action_cable/server/broadcasting.rb47
-rw-r--r--actioncable/lib/action_cable/server/configuration.rb41
-rw-r--r--actioncable/lib/action_cable/server/connections.rb15
-rw-r--r--actioncable/lib/action_cable/server/worker.rb58
-rw-r--r--actioncable/lib/action_cable/server/worker/active_record_connection_management.rb5
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/async.rb13
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/evented_redis.rb79
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/inline.rb11
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/postgresql.rb14
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/redis.rb164
-rw-r--r--actioncable/lib/assets/javascripts/action_cable/connection.coffee81
-rw-r--r--actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee79
-rw-r--r--actioncable/lib/rails/generators/channel/USAGE2
-rw-r--r--actioncable/lib/rails/generators/channel/channel_generator.rb25
-rw-r--r--actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb6
-rw-r--r--actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb6
-rw-r--r--actioncable/lib/rails/generators/channel/templates/assets/cable.js13
-rw-r--r--actioncable/lib/rails/generators/channel/templates/assets/channel.js18
-rw-r--r--actioncable/lib/rails/generators/channel/templates/channel.rb2
-rw-r--r--actioncable/package.json24
-rw-r--r--actioncable/test/channel/base_test.rb79
-rw-r--r--actioncable/test/channel/periodic_timers_test.rb48
-rw-r--r--actioncable/test/channel/rejection_test.rb2
-rw-r--r--actioncable/test/channel/stream_test.rb179
-rw-r--r--actioncable/test/client_test.rb275
-rw-r--r--actioncable/test/connection/authorization_test.rb2
-rw-r--r--actioncable/test/connection/base_test.rb44
-rw-r--r--actioncable/test/connection/client_socket_test.rb64
-rw-r--r--actioncable/test/connection/cross_site_forgery_test.rb2
-rw-r--r--actioncable/test/connection/identifier_test.rb12
-rw-r--r--actioncable/test/connection/multiple_identifiers_test.rb6
-rw-r--r--actioncable/test/connection/stream_test.rb66
-rw-r--r--actioncable/test/connection/string_identifier_test.rb2
-rw-r--r--actioncable/test/connection/subscriptions_test.rb5
-rw-r--r--actioncable/test/javascript/src/test.coffee3
-rw-r--r--actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee37
-rw-r--r--actioncable/test/javascript/src/test_helpers/index.coffee8
-rw-r--r--actioncable/test/javascript/src/unit/action_cable_test.coffee41
-rw-r--r--actioncable/test/javascript/src/unit/consumer_test.coffee11
-rw-r--r--actioncable/test/javascript/vendor/mock-socket.js4533
-rw-r--r--actioncable/test/server/broadcasting_test.rb15
-rw-r--r--actioncable/test/stubs/test_connection.rb24
-rw-r--r--actioncable/test/stubs/test_server.rb29
-rw-r--r--actioncable/test/subscription_adapter/base_test.rb6
-rw-r--r--actioncable/test/subscription_adapter/common.rb29
-rw-r--r--actioncable/test/subscription_adapter/evented_redis_test.rb21
-rw-r--r--actioncable/test/subscription_adapter/postgresql_test.rb8
-rw-r--r--actioncable/test/subscription_adapter/redis_test.rb8
-rw-r--r--actioncable/test/test_helper.rb71
-rw-r--r--actioncable/test/worker_test.rb18
-rw-r--r--actionmailer/CHANGELOG.md96
-rw-r--r--actionmailer/Rakefile2
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionmailer/lib/action_mailer/base.rb37
-rw-r--r--actionmailer/lib/action_mailer/delivery_job.rb21
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb2
-rw-r--r--actionmailer/lib/action_mailer/gem_version.rb4
-rw-r--r--actionmailer/lib/action_mailer/inline_preview_interceptor.rb2
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb61
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb25
-rw-r--r--actionmailer/lib/action_mailer/rescuable.rb27
-rw-r--r--actionmailer/lib/action_mailer/test_case.rb20
-rw-r--r--actionmailer/lib/rails/generators/mailer/mailer_generator.rb17
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb6
-rw-r--r--actionmailer/test/abstract_unit.rb1
-rw-r--r--actionmailer/test/caching_test.rb271
-rw-r--r--actionmailer/test/delivery_methods_test.rb2
-rw-r--r--actionmailer/test/fixtures/caching_mailer/_partial.html.erb3
-rw-r--r--actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb3
-rw-r--r--actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb1
-rw-r--r--actionmailer/test/fixtures/caching_mailer/fragment_caching_options.html.erb3
-rw-r--r--actionmailer/test/fixtures/caching_mailer/multipart_cache.html.erb3
-rw-r--r--actionmailer/test/fixtures/caching_mailer/multipart_cache.text.erb3
-rw-r--r--actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb3
-rw-r--r--actionmailer/test/i18n_with_controller_test.rb4
-rw-r--r--actionmailer/test/mailers/caching_mailer.rb23
-rw-r--r--actionmailer/test/mailers/delayed_mailer.rb20
-rw-r--r--actionmailer/test/message_delivery_test.rb59
-rw-r--r--actionmailer/test/test_case_test.rb38
-rw-r--r--actionmailer/test/url_test.rb14
-rw-r--r--actionpack/CHANGELOG.md653
-rw-r--r--actionpack/Rakefile2
-rw-r--r--actionpack/actionpack.gemspec4
-rw-r--r--actionpack/lib/abstract_controller.rb6
-rw-r--r--actionpack/lib/abstract_controller/base.rb13
-rw-r--r--actionpack/lib/abstract_controller/caching.rb62
-rw-r--r--actionpack/lib/abstract_controller/caching/fragments.rb (renamed from actionpack/lib/action_controller/caching/fragments.rb)11
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb2
-rw-r--r--actionpack/lib/abstract_controller/error.rb4
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb3
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb7
-rw-r--r--actionpack/lib/action_controller.rb10
-rw-r--r--actionpack/lib/action_controller/api.rb39
-rw-r--r--actionpack/lib/action_controller/base.rb9
-rw-r--r--actionpack/lib/action_controller/caching.rb67
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb4
-rw-r--r--actionpack/lib/action_controller/metal.rb6
-rw-r--r--actionpack/lib/action_controller/metal/basic_implicit_render.rb2
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb73
-rw-r--r--actionpack/lib/action_controller/metal/cookies.rb2
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb19
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb8
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb9
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb11
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb72
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb9
-rw-r--r--actionpack/lib/action_controller/metal/live.rb86
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb2
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb2
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb13
-rw-r--r--actionpack/lib/action_controller/metal/rescue.rb13
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb85
-rw-r--r--actionpack/lib/action_controller/renderer.rb8
-rw-r--r--actionpack/lib/action_controller/test_case.rb62
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb58
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb12
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb18
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb58
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb21
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb34
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb24
-rw-r--r--actionpack/lib/action_dispatch/journey/backwards.rb5
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb9
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb7
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb11
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb35
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/executor.rb19
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/load_interlock.rb21
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb66
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb22
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb8
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb2
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing.rb31
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb36
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb20
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb31
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertion_response.rb16
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb172
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb1
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb22
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb6
-rw-r--r--actionpack/lib/action_pack/gem_version.rb4
-rw-r--r--actionpack/test/abstract_unit.rb55
-rw-r--r--actionpack/test/assertions/response_assertions_test.rb2
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb20
-rw-r--r--actionpack/test/controller/api/with_cookies_test.rb21
-rw-r--r--actionpack/test/controller/base_test.rb15
-rw-r--r--actionpack/test/controller/caching_test.rb37
-rw-r--r--actionpack/test/controller/flash_hash_test.rb4
-rw-r--r--actionpack/test/controller/flash_test.rb4
-rw-r--r--actionpack/test/controller/force_ssl_test.rb9
-rw-r--r--actionpack/test/controller/helper_test.rb16
-rw-r--r--actionpack/test/controller/integration_test.rb113
-rw-r--r--actionpack/test/controller/live_stream_test.rb14
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb6
-rw-r--r--actionpack/test/controller/metal/renderers_test.rb6
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb108
-rw-r--r--actionpack/test/controller/new_base/bare_metal_test.rb16
-rw-r--r--actionpack/test/controller/new_base/content_type_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_body_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_html_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_plain_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_template_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_test.rb8
-rw-r--r--actionpack/test/controller/new_base/render_text_test.rb4
-rw-r--r--actionpack/test/controller/parameters/accessors_test.rb84
-rw-r--r--actionpack/test/controller/parameters/always_permitted_parameters_test.rb6
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb30
-rw-r--r--actionpack/test/controller/redirect_test.rb30
-rw-r--r--actionpack/test/controller/render_test.rb119
-rw-r--r--actionpack/test/controller/render_xml_test.rb5
-rw-r--r--actionpack/test/controller/renderer_test.rb8
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb166
-rw-r--r--actionpack/test/controller/rescue_test.rb54
-rw-r--r--actionpack/test/controller/resources_test.rb16
-rw-r--r--actionpack/test/controller/routing_test.rb248
-rw-r--r--actionpack/test/controller/send_file_test.rb15
-rw-r--r--actionpack/test/controller/test_case_test.rb55
-rw-r--r--actionpack/test/controller/url_for_integration_test.rb15
-rw-r--r--actionpack/test/controller/url_for_test.rb15
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb4
-rw-r--r--actionpack/test/controller/webservice_test.rb2
-rw-r--r--actionpack/test/dispatch/callbacks_test.rb14
-rw-r--r--actionpack/test/dispatch/cookies_test.rb4
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb89
-rw-r--r--actionpack/test/dispatch/executor_test.rb134
-rw-r--r--actionpack/test/dispatch/live_response_test.rb2
-rw-r--r--actionpack/test/dispatch/mapper_test.rb13
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb2
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb10
-rw-r--r--actionpack/test/dispatch/reloader_test.rb67
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb47
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb8
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb8
-rw-r--r--actionpack/test/dispatch/request/session_test.rb26
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request_test.rb66
-rw-r--r--actionpack/test/dispatch/response_test.rb68
-rw-r--r--actionpack/test/dispatch/routing/concerns_test.rb2
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb93
-rw-r--r--actionpack/test/dispatch/routing_test.rb172
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb4
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb4
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb4
-rw-r--r--actionpack/test/dispatch/ssl_test.rb30
-rw-r--r--actionpack/test/dispatch/test_request_test.rb27
-rw-r--r--actionpack/test/fixtures/collection_cache/index.html.erb2
-rw-r--r--actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb3
-rw-r--r--actionpack/test/fixtures/implicit_render_test/empty_action_with_mobile_variant.html+mobile.erb1
-rw-r--r--actionpack/test/fixtures/implicit_render_test/empty_action_with_template.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/variant_with_implicit_template_rendering.html+mobile.erb (renamed from actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb)0
-rw-r--r--actionpack/test/journey/router_test.rb130
-rw-r--r--actionview/CHANGELOG.md277
-rw-r--r--actionview/Rakefile2
-rw-r--r--actionview/actionview.gemspec10
-rw-r--r--actionview/lib/action_view/dependency_tracker.rb24
-rw-r--r--actionview/lib/action_view/digestor.rb157
-rw-r--r--actionview/lib/action_view/flows.rb9
-rw-r--r--actionview/lib/action_view/gem_version.rb4
-rw-r--r--actionview/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb54
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb23
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb8
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb9
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb14
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb11
-rw-r--r--actionview/lib/action_view/helpers/output_safety_helper.rb30
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb1
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb65
-rw-r--r--actionview/lib/action_view/log_subscriber.rb36
-rw-r--r--actionview/lib/action_view/lookup_context.rb63
-rw-r--r--actionview/lib/action_view/railtie.rb14
-rw-r--r--actionview/lib/action_view/renderer/abstract_renderer.rb10
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb26
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb47
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb6
-rw-r--r--actionview/lib/action_view/tasks/cache_digests.rake (renamed from actionview/lib/action_view/tasks/dependencies.rake)4
-rw-r--r--actionview/lib/action_view/template.rb23
-rw-r--r--actionview/lib/action_view/template/error.rb8
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb25
-rw-r--r--actionview/lib/action_view/template/resolver.rb37
-rw-r--r--actionview/lib/action_view/template/types.rb1
-rw-r--r--actionview/lib/action_view/view_paths.rb4
-rw-r--r--actionview/test/abstract_unit.rb14
-rw-r--r--actionview/test/actionpack/abstract/layouts_test.rb191
-rw-r--r--actionview/test/actionpack/controller/render_test.rb5
-rw-r--r--actionview/test/activerecord/debug_helper_test.rb5
-rw-r--r--actionview/test/fixtures/digestor/api/comments/_comment.json.erb1
-rw-r--r--actionview/test/fixtures/digestor/api/comments/_comments.json.erb1
-rw-r--r--actionview/test/fixtures/digestor/messages/peek.html.erb2
-rw-r--r--actionview/test/fixtures/digestor/messages/thread.json.erb1
-rw-r--r--actionview/test/fixtures/test/_customer.mobile.erb1
-rw-r--r--actionview/test/fixtures/test/_🍣.erb1
-rw-r--r--actionview/test/lib/controller/fake_models.rb21
-rw-r--r--actionview/test/template/date_helper_test.rb404
-rw-r--r--actionview/test/template/digestor_test.rb145
-rw-r--r--actionview/test/template/form_helper_test.rb56
-rw-r--r--actionview/test/template/form_options_helper_test.rb2
-rw-r--r--actionview/test/template/form_tag_helper_test.rb14
-rw-r--r--actionview/test/template/log_subscriber_test.rb29
-rw-r--r--actionview/test/template/output_safety_helper_test.rb55
-rw-r--r--actionview/test/template/render_test.rb70
-rw-r--r--actionview/test/template/resolver_cache_test.rb7
-rw-r--r--actionview/test/template/resolver_patterns_test.rb19
-rw-r--r--actionview/test/template/tag_helper_test.rb6
-rw-r--r--actionview/test/template/template_test.rb32
-rw-r--r--actionview/test/template/url_helper_test.rb50
-rw-r--r--activejob/CHANGELOG.md134
-rw-r--r--activejob/README.md7
-rw-r--r--activejob/Rakefile5
-rw-r--r--activejob/activejob.gemspec2
-rw-r--r--activejob/lib/active_job.rb1
-rw-r--r--activejob/lib/active_job/arguments.rb5
-rw-r--r--activejob/lib/active_job/async_job.rb77
-rw-r--r--activejob/lib/active_job/callbacks.rb9
-rw-r--r--activejob/lib/active_job/core.rb4
-rw-r--r--activejob/lib/active_job/enqueuing.rb2
-rw-r--r--activejob/lib/active_job/execution.rb8
-rw-r--r--activejob/lib/active_job/gem_version.rb4
-rw-r--r--activejob/lib/active_job/queue_adapter.rb8
-rw-r--r--activejob/lib/active_job/queue_adapters.rb3
-rw-r--r--activejob/lib/active_job/queue_adapters/async_adapter.rb105
-rw-r--r--activejob/lib/active_job/queue_adapters/inline_adapter.rb2
-rw-r--r--activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb2
-rw-r--r--activejob/lib/active_job/railtie.rb11
-rw-r--r--activejob/lib/active_job/test_helper.rb582
-rw-r--r--activejob/lib/rails/generators/job/job_generator.rb15
-rw-r--r--activejob/lib/rails/generators/job/templates/application_job.rb4
-rw-r--r--activejob/test/adapters/async.rb4
-rw-r--r--activejob/test/cases/argument_serialization_test.rb13
-rw-r--r--activejob/test/cases/async_job_test.rb42
-rw-r--r--activejob/test/cases/job_serialization_test.rb23
-rw-r--r--activejob/test/cases/test_helper_test.rb12
-rw-r--r--activejob/test/helper.rb2
-rw-r--r--activejob/test/integration/queuing_test.rb4
-rw-r--r--activejob/test/support/integration/adapters/async.rb3
-rw-r--r--activejob/test/support/integration/dummy_app_template.rb2
-rw-r--r--activejob/test/support/integration/test_case_helpers.rb4
-rw-r--r--activemodel/CHANGELOG.md132
-rw-r--r--activemodel/Rakefile2
-rw-r--r--activemodel/activemodel.gemspec2
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/conversion.rb4
-rw-r--r--activemodel/lib/active_model/dirty.rb14
-rw-r--r--activemodel/lib/active_model/errors.rb64
-rw-r--r--activemodel/lib/active_model/gem_version.rb4
-rw-r--r--activemodel/lib/active_model/type.rb2
-rw-r--r--activemodel/lib/active_model/type/decimal.rb12
-rw-r--r--activemodel/lib/active_model/type/integer.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb2
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb6
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb6
-rw-r--r--activemodel/lib/active_model/validations/clusivity.rb7
-rw-r--r--activemodel/lib/active_model/validations/length.rb2
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb2
-rw-r--r--activemodel/lib/active_model/validator.rb6
-rw-r--r--activemodel/test/cases/errors_test.rb18
-rw-r--r--activemodel/test/cases/helper.rb3
-rw-r--r--activemodel/test/cases/type/decimal_test.rb7
-rw-r--r--activemodel/test/cases/type/integer_test.rb12
-rw-r--r--activemodel/test/cases/type/unsigned_integer_test.rb2
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb26
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations_test.rb16
-rw-r--r--activemodel/test/models/project.rb3
-rw-r--r--activerecord/CHANGELOG.md1816
-rw-r--r--activerecord/RUNNING_UNIT_TESTS.rdoc6
-rw-r--r--activerecord/Rakefile2
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/examples/performance.rb1
-rw-r--r--activerecord/examples/simple.rb1
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/aggregations.rb9
-rw-r--r--activerecord/lib/active_record/association_relation.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb26
-rw-r--r--activerecord/lib/active_record/associations/association.rb5
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb3
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb3
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb28
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb30
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb8
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb20
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb1
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb23
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute.rb34
-rw-r--r--activerecord/lib/active_record/attribute/user_provided_default.rb15
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb28
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb22
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb16
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb15
-rw-r--r--activerecord/lib/active_record/attribute_set.rb3
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb2
-rw-r--r--activerecord/lib/active_record/attribute_set/yaml_encoder.rb39
-rw-r--r--activerecord/lib/active_record/attributes.rb13
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-rw-r--r--activerecord/lib/active_record/base.rb7
-rw-r--r--activerecord/lib/active_record/callbacks.rb8
-rw-r--r--activerecord/lib/active_record/coders/json.rb2
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb113
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb167
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb74
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb366
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb125
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb70
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/quoting.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb77
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb43
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.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/rails_5_1_point.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb92
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb102
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb95
-rw-r--r--activerecord/lib/active_record/connection_adapters/statement_pool.rb10
-rw-r--r--activerecord/lib/active_record/connection_handling.rb47
-rw-r--r--activerecord/lib/active_record/core.rb34
-rw-r--r--activerecord/lib/active_record/enum.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb24
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb8
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/gem_version.rb4
-rw-r--r--activerecord/lib/active_record/inheritance.rb2
-rw-r--r--activerecord/lib/active_record/internal_metadata.rb27
-rw-r--r--activerecord/lib/active_record/legacy_yaml_adapter.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb11
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb8
-rw-r--r--activerecord/lib/active_record/migration.rb24
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb32
-rw-r--r--activerecord/lib/active_record/model_schema.rb21
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb22
-rw-r--r--activerecord/lib/active_record/null_relation.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb8
-rw-r--r--activerecord/lib/active_record/query_cache.rb33
-rw-r--r--activerecord/lib/active_record/querying.rb8
-rw-r--r--activerecord/lib/active_record/railtie.rb21
-rw-r--r--activerecord/lib/active_record/railties/databases.rake10
-rw-r--r--activerecord/lib/active_record/reflection.rb182
-rw-r--r--activerecord/lib/active_record/relation.rb71
-rw-r--r--activerecord/lib/active_record/relation/batches.rb46
-rw-r--r--activerecord/lib/active_record/relation/batches/batch_enumerator.rb4
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb20
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb6
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb137
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb13
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb11
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb57
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb56
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb3
-rw-r--r--activerecord/lib/active_record/sanitization.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb65
-rw-r--r--activerecord/lib/active_record/schema_migration.rb17
-rw-r--r--activerecord/lib/active_record/scoping/default.rb3
-rw-r--r--activerecord/lib/active_record/scoping/named.rb10
-rw-r--r--activerecord/lib/active_record/statement_cache.rb2
-rw-r--r--activerecord/lib/active_record/suppressor.rb9
-rw-r--r--activerecord/lib/active_record/table_metadata.rb9
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb14
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb3
-rw-r--r--activerecord/lib/active_record/type.rb2
-rw-r--r--activerecord/lib/active_record/type/internal/abstract_json.rb6
-rw-r--r--activerecord/lib/active_record/type/serialized.rb8
-rw-r--r--activerecord/lib/active_record/type/time.rb12
-rw-r--r--activerecord/lib/active_record/validations.rb2
-rw-r--r--activerecord/lib/active_record/validations/absence.rb1
-rw-r--r--activerecord/lib/active_record/validations/length.rb12
-rw-r--r--activerecord/lib/active_record/validations/presence.rb1
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb23
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb33
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/application_record.rb5
-rw-r--r--activerecord/test/cases/adapter_test.rb29
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/boolean_test.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb9
-rw-r--r--activerecord/test/cases/adapters/mysql2/charset_collation_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb56
-rw-r--r--activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb45
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb5
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb32
-rw-r--r--activerecord/test/cases/adapters/mysql2/json_test.rb15
-rw-r--r--activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb29
-rw-r--r--activerecord/test/cases/adapters/mysql2/quoting_test.rb21
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb5
-rw-r--r--activerecord/test/cases/adapters/mysql2/transaction_test.rb62
-rw-r--r--activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb24
-rw-r--r--activerecord/test/cases/adapters/postgresql/bit_string_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/extension_migration_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb7
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb17
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb150
-rw-r--r--activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb22
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb54
-rw-r--r--activerecord/test/cases/adapters/postgresql/serial_test.rb30
-rw-r--r--activerecord/test/cases/adapters/postgresql/transaction_test.rb72
-rw-r--r--activerecord/test/cases/adapters/postgresql/type_lookup_test.rb4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb32
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb173
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb24
-rw-r--r--activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb28
-rw-r--r--activerecord/test/cases/aggregations_test.rb5
-rw-r--r--activerecord/test/cases/ar_schema_test.rb2
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb15
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb8
-rw-r--r--activerecord/test/cases/associations/eager_test.rb51
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb19
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb16
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb18
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb18
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb14
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb28
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb2
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb9
-rw-r--r--activerecord/test/cases/attribute_test.rb7
-rw-r--r--activerecord/test/cases/attributes_test.rb22
-rw-r--r--activerecord/test/cases/autosave_association_test.rb4
-rw-r--r--activerecord/test/cases/base_test.rb12
-rw-r--r--activerecord/test/cases/batches_test.rb40
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb3
-rw-r--r--activerecord/test/cases/calculations_test.rb8
-rw-r--r--activerecord/test/cases/coders/json_test.rb15
-rw-r--r--activerecord/test/cases/comment_test.rb139
-rw-r--r--activerecord/test/cases/connection_adapters/adapter_leasing_test.rb2
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb71
-rw-r--r--activerecord/test/cases/connection_adapters/connection_specification_test.rb2
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb10
-rw-r--r--activerecord/test/cases/connection_adapters/type_lookup_test.rb2
-rw-r--r--activerecord/test/cases/connection_management_test.rb70
-rw-r--r--activerecord/test/cases/connection_pool_test.rb7
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb27
-rw-r--r--activerecord/test/cases/counter_cache_test.rb2
-rw-r--r--activerecord/test/cases/database_statements_test.rb6
-rw-r--r--activerecord/test/cases/date_time_precision_test.rb2
-rw-r--r--activerecord/test/cases/defaults_test.rb3
-rw-r--r--activerecord/test/cases/dirty_test.rb32
-rw-r--r--activerecord/test/cases/finder_test.rb123
-rw-r--r--activerecord/test/cases/fixture_set/file_test.rb6
-rw-r--r--activerecord/test/cases/fixtures_test.rb2
-rw-r--r--activerecord/test/cases/helper.rb2
-rw-r--r--activerecord/test/cases/hot_compatibility_test.rb88
-rw-r--r--activerecord/test/cases/inheritance_test.rb5
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb15
-rw-r--r--activerecord/test/cases/locking_test.rb8
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb6
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb2
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb44
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb7
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb46
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb5
-rw-r--r--activerecord/test/cases/migration/table_and_index_test.rb24
-rw-r--r--activerecord/test/cases/migration_test.rb90
-rw-r--r--activerecord/test/cases/modules_test.rb4
-rw-r--r--activerecord/test/cases/multiparameter_attributes_test.rb14
-rw-r--r--activerecord/test/cases/multiple_db_test.rb13
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb32
-rw-r--r--activerecord/test/cases/persistence_test.rb2
-rw-r--r--activerecord/test/cases/primary_keys_test.rb40
-rw-r--r--activerecord/test/cases/query_cache_test.rb78
-rw-r--r--activerecord/test/cases/quoting_test.rb6
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb4
-rw-r--r--activerecord/test/cases/relation/or_test.rb6
-rw-r--r--activerecord/test/cases/relation/record_fetch_warning_test.rb34
-rw-r--r--activerecord/test/cases/relation/where_test.rb14
-rw-r--r--activerecord/test/cases/relations_test.rb73
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb68
-rw-r--r--activerecord/test/cases/schema_loading_test.rb52
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb24
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb35
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb33
-rw-r--r--activerecord/test/cases/statement_cache_test.rb12
-rw-r--r--activerecord/test/cases/store_test.rb3
-rw-r--r--activerecord/test/cases/suppressor_test.rb25
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb44
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb41
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb38
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb31
-rw-r--r--activerecord/test/cases/test_case.rb4
-rw-r--r--activerecord/test/cases/time_precision_test.rb2
-rw-r--r--activerecord/test/cases/timestamp_test.rb11
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb26
-rw-r--r--activerecord/test/cases/validations/absence_validation_test.rb18
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb12
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb22
-rw-r--r--activerecord/test/cases/validations/presence_validation_test.rb36
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb114
-rw-r--r--activerecord/test/cases/validations_test.rb2
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb10
-rw-r--r--activerecord/test/fixtures/price_estimates.yml11
-rw-r--r--activerecord/test/migrations/to_copy/1_people_have_hobbies.rb2
-rw-r--r--activerecord/test/migrations/to_copy/2_people_have_descriptions.rb2
-rw-r--r--activerecord/test/migrations/to_copy2/2_create_comments.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb2
-rw-r--r--activerecord/test/models/author.rb10
-rw-r--r--activerecord/test/models/car.rb2
-rw-r--r--activerecord/test/models/cat.rb10
-rw-r--r--activerecord/test/models/comment.rb1
-rw-r--r--activerecord/test/models/customer.rb7
-rw-r--r--activerecord/test/models/notification.rb1
-rw-r--r--activerecord/test/models/owner.rb3
-rw-r--r--activerecord/test/models/pet.rb3
-rw-r--r--activerecord/test/models/pet_treasure.rb6
-rw-r--r--activerecord/test/models/tag.rb6
-rw-r--r--activerecord/test/models/uuid_item.rb6
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb12
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb5
-rw-r--r--activerecord/test/schema/schema.rb33
-rw-r--r--activerecord/test/schema/sqlite_specific_schema.rb4
-rw-r--r--activesupport/CHANGELOG.md549
-rw-r--r--activesupport/Rakefile3
-rw-r--r--activesupport/activesupport.gemspec6
-rw-r--r--activesupport/lib/active_support.rb12
-rw-r--r--activesupport/lib/active_support/array_inquirer.rb6
-rw-r--r--activesupport/lib/active_support/benchmarkable.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb92
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb16
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb2
-rw-r--r--activesupport/lib/active_support/callbacks.rb17
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb88
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb27
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/zones.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/compatibility.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/zones.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb36
-rw-r--r--activesupport/lib/active_support/core_ext/hash/compact.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/concern.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb76
-rw-r--r--activesupport/lib/active_support/core_ext/module/deprecation.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb108
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/time/compatibility.rb5
-rw-r--r--activesupport/lib/active_support/dependencies.rb67
-rw-r--r--activesupport/lib/active_support/dependencies/interlock.rb24
-rw-r--r--activesupport/lib/active_support/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb17
-rw-r--r--activesupport/lib/active_support/duration.rb26
-rw-r--r--activesupport/lib/active_support/duration/iso8601_parser.rb122
-rw-r--r--activesupport/lib/active_support/duration/iso8601_serializer.rb51
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb80
-rw-r--r--activesupport/lib/active_support/execution_wrapper.rb117
-rw-r--r--activesupport/lib/active_support/executor.rb6
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb24
-rw-r--r--activesupport/lib/active_support/gem_version.rb4
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb10
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb4
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb9
-rw-r--r--activesupport/lib/active_support/logger.rb25
-rw-r--r--activesupport/lib/active_support/logger_silence.rb25
-rw-r--r--activesupport/lib/active_support/logger_thread_safe_level.rb31
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb2
-rw-r--r--activesupport/lib/active_support/message_verifier.rb6
-rw-r--r--activesupport/lib/active_support/number_helper.rb127
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb8
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_phone_converter.rb13
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb8
-rw-r--r--activesupport/lib/active_support/ordered_options.rb2
-rw-r--r--activesupport/lib/active_support/reloader.rb129
-rw-r--r--activesupport/lib/active_support/rescuable.rb136
-rw-r--r--activesupport/lib/active_support/test_case.rb9
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb2
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb11
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb23
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb19
-rw-r--r--activesupport/lib/active_support/xml_mini.rb33
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb4
-rw-r--r--activesupport/test/abstract_unit.rb10
-rw-r--r--activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb4
-rw-r--r--activesupport/test/autoloading_fixtures/throws.rb4
-rw-r--r--activesupport/test/benchmarkable_test.rb14
-rw-r--r--activesupport/test/broadcast_logger_test.rb134
-rw-r--r--activesupport/test/caching_test.rb27
-rw-r--r--activesupport/test/core_ext/array/access_test.rb2
-rw-r--r--activesupport/test/core_ext/array/conversions_test.rb6
-rw-r--r--activesupport/test/core_ext/array/grouping_test.rb13
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb2
-rw-r--r--activesupport/test/core_ext/date_and_time_behavior.rb10
-rw-r--r--activesupport/test/core_ext/date_and_time_compatibility_test.rb127
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb32
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb58
-rw-r--r--activesupport/test/core_ext/duration_test.rb101
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb102
-rw-r--r--activesupport/test/core_ext/hash/transform_keys_test.rb16
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb50
-rw-r--r--activesupport/test/core_ext/marshal_test.rb34
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb6
-rw-r--r--activesupport/test/core_ext/module_test.rb67
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb7
-rw-r--r--activesupport/test/core_ext/object/deep_dup_test.rb2
-rw-r--r--activesupport/test/core_ext/object/json_gem_encoding_test.rb2
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb51
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb16
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb5
-rw-r--r--activesupport/test/dependencies_test.rb35
-rw-r--r--activesupport/test/deprecation_test.rb8
-rw-r--r--activesupport/test/evented_file_update_checker_test.rb44
-rw-r--r--activesupport/test/executor_test.rb183
-rw-r--r--activesupport/test/file_update_checker_shared_tests.rb32
-rw-r--r--activesupport/test/inflector_test_cases.rb3
-rw-r--r--activesupport/test/json/encoding_test.rb5
-rw-r--r--activesupport/test/logger_test.rb46
-rw-r--r--activesupport/test/multibyte_conformance_test.rb2
-rw-r--r--activesupport/test/multibyte_grapheme_break_conformance_test.rb2
-rw-r--r--activesupport/test/multibyte_normalization_conformance_test.rb2
-rw-r--r--activesupport/test/number_helper_test.rb3
-rw-r--r--activesupport/test/reloader_test.rb85
-rw-r--r--activesupport/test/rescuable_test.rb21
-rw-r--r--activesupport/test/share_lock_test.rb245
-rw-r--r--activesupport/test/time_zone_test.rb16
-rw-r--r--activesupport/test/time_zone_test_helpers.rb8
-rw-r--r--activesupport/test/xml_mini/rexml_engine_test.rb26
-rwxr-xr-xci/travis.rb21
-rw-r--r--guides/CHANGELOG.md23
-rw-r--r--guides/assets/images/favicon.icobin5430 -> 16958 bytes
-rw-r--r--guides/assets/images/getting_started/rails_welcome.pngbin142320 -> 1053549 bytes
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css2
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb2
-rw-r--r--guides/bug_report_templates/action_controller_master.rb5
-rw-r--r--guides/bug_report_templates/active_record_gem.rb2
-rw-r--r--guides/bug_report_templates/active_record_master.rb9
-rw-r--r--guides/bug_report_templates/generic_gem.rb2
-rw-r--r--guides/bug_report_templates/generic_master.rb8
-rw-r--r--guides/source/2_2_release_notes.md24
-rw-r--r--guides/source/2_3_release_notes.md38
-rw-r--r--guides/source/3_0_release_notes.md19
-rw-r--r--guides/source/3_1_release_notes.md2
-rw-r--r--guides/source/4_0_release_notes.md2
-rw-r--r--guides/source/4_2_release_notes.md4
-rw-r--r--guides/source/5_0_release_notes.md282
-rw-r--r--guides/source/action_cable_overview.md665
-rw-r--r--guides/source/action_controller_overview.md21
-rw-r--r--guides/source/action_mailer_basics.md34
-rw-r--r--guides/source/action_view_overview.md22
-rw-r--r--guides/source/active_job_basics.md42
-rw-r--r--guides/source/active_model_basics.md4
-rw-r--r--guides/source/active_record_basics.md2
-rw-r--r--guides/source/active_record_callbacks.md4
-rw-r--r--guides/source/active_record_migrations.md23
-rw-r--r--guides/source/active_record_postgresql.md8
-rw-r--r--guides/source/active_record_querying.md90
-rw-r--r--guides/source/active_record_validations.md72
-rw-r--r--guides/source/active_support_core_extensions.md113
-rw-r--r--guides/source/active_support_instrumentation.md4
-rw-r--r--guides/source/api_app.md136
-rw-r--r--guides/source/api_documentation_guidelines.md2
-rw-r--r--guides/source/asset_pipeline.md81
-rw-r--r--guides/source/association_basics.md72
-rw-r--r--guides/source/autoloading_and_reloading_constants.md11
-rw-r--r--guides/source/caching_with_rails.md61
-rw-r--r--guides/source/command_line.md13
-rw-r--r--guides/source/configuring.md268
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md36
-rw-r--r--guides/source/credits.html.erb2
-rw-r--r--guides/source/debugging_rails_applications.md340
-rw-r--r--guides/source/development_dependencies_install.md1
-rw-r--r--guides/source/documents.yaml15
-rw-r--r--guides/source/engines.md24
-rw-r--r--guides/source/form_helpers.md4
-rw-r--r--guides/source/getting_started.md68
-rw-r--r--guides/source/i18n.md17
-rw-r--r--guides/source/initialization.md8
-rw-r--r--guides/source/layout.html.erb14
-rw-r--r--guides/source/layouts_and_rendering.md23
-rw-r--r--guides/source/rails_application_templates.md28
-rw-r--r--guides/source/rails_on_rack.md29
-rw-r--r--guides/source/routing.md27
-rw-r--r--guides/source/security.md56
-rw-r--r--guides/source/testing.md123
-rw-r--r--guides/source/upgrading_ruby_on_rails.md30
-rw-r--r--guides/source/working_with_javascript_in_rails.md20
-rw-r--r--load_paths.rb3
-rw-r--r--rails.gemspec2
-rw-r--r--railties/CHANGELOG.md452
-rw-r--r--railties/RDOC_MAIN.rdoc8
-rw-r--r--railties/Rakefile2
-rw-r--r--railties/lib/rails/all.rb4
-rw-r--r--railties/lib/rails/app_loader.rb2
-rw-r--r--railties/lib/rails/application.rb27
-rw-r--r--railties/lib/rails/application/bootstrap.rb7
-rw-r--r--railties/lib/rails/application/configuration.rb10
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb22
-rw-r--r--railties/lib/rails/application/finisher.rb94
-rw-r--r--railties/lib/rails/code_statistics.rb31
-rw-r--r--railties/lib/rails/command.rb70
-rw-r--r--railties/lib/rails/commands.rb5
-rw-r--r--railties/lib/rails/commands/dev_cache.rb21
-rw-r--r--railties/lib/rails/commands/runner.rb11
-rw-r--r--railties/lib/rails/commands/server.rb28
-rw-r--r--railties/lib/rails/console/app.rb3
-rw-r--r--railties/lib/rails/dev_caching.rb43
-rw-r--r--railties/lib/rails/engine.rb2
-rw-r--r--railties/lib/rails/engine/commands.rb35
-rw-r--r--railties/lib/rails/engine/commands_tasks.rb116
-rw-r--r--railties/lib/rails/engine/configuration.rb1
-rw-r--r--railties/lib/rails/gem_version.rb4
-rw-r--r--railties/lib/rails/generators.rb6
-rw-r--r--railties/lib/rails/generators/actions.rb28
-rw-r--r--railties/lib/rails/generators/actions/create_migration.rb1
-rw-r--r--railties/lib/rails/generators/app_base.rb44
-rw-r--r--railties/lib/rails/generators/erb/mailer/mailer_generator.rb9
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/layout.html.erb.tt13
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/layout.text.erb.tt1
-rw-r--r--railties/lib/rails/generators/named_base.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb46
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README.md2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Rakefile2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee11
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js13
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/rails2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/update2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config.ru5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config.ru.tt11
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/boot.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/cable.yml13
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml1
-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/databases/oracle.yml1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environment.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt16
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt22
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt34
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/puma.rb47
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/secrets.yml8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/spring.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon-precomposed.png0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon.png0
-rw-r--r--railties/lib/rails/generators/rails/controller/controller_generator.rb3
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb11
-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.tt1
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt3
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/application.rb8
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb4
-rw-r--r--railties/lib/rails/generators/rails/scaffold/templates/scaffold.css12
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/mailer_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/api_functional_test.rb10
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb6
-rw-r--r--railties/lib/rails/rack/logger.rb10
-rw-r--r--railties/lib/rails/railtie.rb86
-rw-r--r--railties/lib/rails/tasks.rb1
-rw-r--r--railties/lib/rails/tasks/dev.rake8
-rw-r--r--railties/lib/rails/tasks/engine.rake4
-rw-r--r--railties/lib/rails/tasks/framework.rake25
-rw-r--r--railties/lib/rails/tasks/misc.rake29
-rw-r--r--railties/lib/rails/tasks/restart.rake9
-rw-r--r--railties/lib/rails/tasks/routes.rake35
-rw-r--r--railties/lib/rails/tasks/statistics.rake1
-rw-r--r--railties/lib/rails/tasks/tmp.rake10
-rw-r--r--railties/lib/rails/templates/rails/welcome/index.html.erb326
-rw-r--r--railties/lib/rails/test_unit/line_filtering.rb16
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb26
-rw-r--r--railties/lib/rails/test_unit/reporter.rb20
-rw-r--r--railties/railties.gemspec2
-rw-r--r--railties/test/abstract_unit.rb2
-rw-r--r--railties/test/application/assets_test.rb25
-rw-r--r--railties/test/application/bin_setup_test.rb4
-rw-r--r--railties/test/application/configuration_test.rb46
-rw-r--r--railties/test/application/console_test.rb11
-rw-r--r--railties/test/application/generators_test.rb10
-rw-r--r--railties/test/application/initializers/frameworks_test.rb2
-rw-r--r--railties/test/application/initializers/i18n_test.rb8
-rw-r--r--railties/test/application/integration_test_case_test.rb46
-rw-r--r--railties/test/application/loading_test.rb8
-rw-r--r--railties/test/application/middleware/exceptions_test.rb4
-rw-r--r--railties/test/application/middleware/remote_ip_test.rb6
-rw-r--r--railties/test/application/middleware/session_test.rb28
-rw-r--r--railties/test/application/middleware_test.rb28
-rw-r--r--railties/test/application/per_request_digest_cache_test.rb7
-rw-r--r--railties/test/application/rake/dbs_test.rb8
-rw-r--r--railties/test/application/rake/dev_test.rb43
-rw-r--r--railties/test/application/rake/restart_test.rb9
-rw-r--r--railties/test/application/rake_test.rb111
-rw-r--r--railties/test/application/routing_test.rb3
-rw-r--r--railties/test/application/test_runner_test.rb86
-rw-r--r--railties/test/commands/dev_cache_test.rb32
-rw-r--r--railties/test/commands/server_test.rb17
-rw-r--r--railties/test/generators/actions_test.rb48
-rw-r--r--railties/test/generators/api_app_generator_test.rb33
-rw-r--r--railties/test/generators/app_generator_test.rb158
-rw-r--r--railties/test/generators/channel_generator_test.rb54
-rw-r--r--railties/test/generators/job_generator_test.rb7
-rw-r--r--railties/test/generators/mailer_generator_test.rb14
-rw-r--r--railties/test/generators/model_generator_test.rb32
-rw-r--r--railties/test/generators/namespaced_generators_test.rb12
-rw-r--r--railties/test/generators/plugin_generator_test.rb99
-rw-r--r--railties/test/generators/plugin_test_runner_test.rb2
-rw-r--r--railties/test/generators/resource_generator_test.rb4
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb4
-rw-r--r--railties/test/generators/scaffold_generator_test.rb18
-rw-r--r--railties/test/generators/test_runner_in_engine_test.rb2
-rw-r--r--railties/test/isolation/abstract_unit.rb33
-rw-r--r--railties/test/rails_info_controller_test.rb6
-rw-r--r--railties/test/railties/engine_test.rb2
-rw-r--r--railties/test/railties/generators_test.rb6
-rw-r--r--railties/test/test_unit/reporter_test.rb21
-rw-r--r--tasks/release.rb36
-rw-r--r--tools/README.md3
-rwxr-xr-xtools/console4
-rw-r--r--tools/test.rb5
-rw-r--r--version.rb4
1008 files changed, 24348 insertions, 12131 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml
new file mode 100644
index 0000000000..877c67873d
--- /dev/null
+++ b/.codeclimate.yml
@@ -0,0 +1,27 @@
+engines:
+ rubocop:
+ enabled: true
+
+ratings:
+ paths:
+ - "**.rb"
+
+exclude_paths:
+ - actioncable/lib/rails/generators/
+ - actioncable/test/
+ - actionmailer/lib/rails/generators/
+ - actionmailer/test/
+ - actionpack/test/
+ - actionview/test/
+ - activejob/lib/rails/generators/
+ - activejob/test/
+ - activemodel/test/
+ - activerecord/lib/rails/generators/
+ - activerecord/test/
+ - activesupport/test/
+ - railties/lib/rails/generators/
+ - railties/test/
+ - ci/
+ - guides/
+ - tasks/
+ - tools/
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000..f6276855ce
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+*.rb diff=ruby
+*.gemspec diff=ruby
diff --git a/.github/issue_template.md b/.github/issue_template.md
new file mode 100644
index 0000000000..2d071d4a71
--- /dev/null
+++ b/.github/issue_template.md
@@ -0,0 +1,15 @@
+### Steps to reproduce
+
+(Guidelines for creating a bug report are [available
+here](http://guides.rubyonrails.org/contributing_to_ruby_on_rails.html#creating-a-bug-report))
+
+### Expected behavior
+Tell us what should happen
+
+### Actual behavior
+Tell us what happens instead
+
+### System configuration
+**Rails version**:
+
+**Ruby version**:
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000000..48f7b0e214
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,18 @@
+### Summary
+
+Provide a general description of the code changes in your pull
+request... were there any bugs you had fixed? If so, mention them. If
+these bugs have open GitHub issues, be sure to tag them here as well,
+to keep the conversation linked together.
+
+### Other Information
+
+If there's anything else that's important and relevant to your pull
+request, mention that information here. This could include
+benchmarks, or other information.
+
+Finally, if your pull request affects documentation or any non-code
+changes, guidelines for those changes are [available
+here](http://guides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation)
+
+Thanks for contributing to Rails!
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000000..dd8db6af3a
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,27 @@
+AllCops:
+ TargetRubyVersion: 2.3
+ DisabledByDefault: true
+
+# Two spaces, no tabs (for indentation).
+Style/IndentationWidth:
+ enabled: true
+
+# No trailing whitespace.
+Style/TrailingWhitespace:
+ enabled: true
+
+# Blank lines should not have any spaces.
+Style/TrailingBlankLines:
+ enabled: true
+
+# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
+Style/HashSyntax:
+ enabled: true
+
+# Prefer &&/|| over and/or.
+Style/AndOr:
+ enabled: true
+
+# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
+Lint/RequireParentheses:
+ enabled: true
diff --git a/.travis.yml b/.travis.yml
index ae38617b99..461bd172c1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,36 +1,81 @@
language: ruby
sudo: false
-script: 'ci/travis.rb'
+
+cache:
+ bundler: true
+ directories:
+ - /tmp/cache/unicode_conformance
+ - /tmp/beanstalkd-1.10
+
+services:
+ - memcached
+ - redis
+ - rabbitmq
+
+addons:
+ postgresql: "9.4"
+
+bundler_args: --without test --jobs 3 --retry 3
+#FIXME: Remove bundler uninstall on Travis when https://github.com/bundler/bundler/issues/4493 is fixed.
before_install:
- - gem install bundler
+ - rvm @global do gem uninstall bundler --all --ignore-dependencies --executables
+ - rvm @global do gem install bundler -v '1.11.2'
+ - bundle --version
- "rm ${BUNDLE_GEMFILE}.lock"
- - curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp
- - cd /tmp/beanstalkd-1.10/
- - make
- - ./beanstalkd &
- - cd $TRAVIS_BUILD_DIR
+ - "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)"
+ - "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd"
+
before_script:
- bundle update
-cache: bundler
+
+ # Set Sauce Labs username and access key. Obfuscated, purposefully not encrypted.
+ # Decodes to e.g. `export VARIABLE=VALUE`
+ - $(base64 --decode <<< "ZXhwb3J0IFNBVUNFX0FDQ0VTU19LRVk9YTAzNTM0M2YtZTkyMi00MGIzLWFhM2MtMDZiM2VhNjM1YzQ4")
+ - $(base64 --decode <<< "ZXhwb3J0IFNBVUNFX1VTRVJOQU1FPXJ1YnlvbnJhaWxz")
+
+script: 'ci/travis.rb'
+
env:
matrix:
- "GEM=railties"
- "GEM=ap"
- "GEM=ac"
+ - "GEM=ac FAYE=1"
+ - "GEM=ac:integration"
+ - "GEM=ac:integration FAYE=1"
- "GEM=am,amo,as,av,aj"
+ - "GEM=as PRESERVE_TIMEZONES=1"
- "GEM=ar:mysql2"
- "GEM=ar:sqlite3"
- "GEM=ar:postgresql"
- "GEM=aj:integration"
- "GEM=guides"
+
rvm:
- - 2.2.4
- - 2.3.0
+ - 2.2.5
+ - 2.3.1
- ruby-head
+
matrix:
+ include:
+ # Latest compiled version in http://rubies.travis-ci.org
+ - rvm: 2.3.1
+ env:
+ - "GEM=ar:mysql2"
+ addons:
+ mariadb: 10.0
+ - rvm: jruby-9.0.5.0
+ jdk: oraclejdk8
+ env:
+ - "JRUBY_OPTS='--dev -J-Xmx1024M'"
+ - "GEM='ap'"
allow_failures:
- rvm: ruby-head
+ - rvm: jruby-9.0.5.0
+ - env: "GEM=ac:integration"
+ - env: "GEM=ac:integration FAYE=1"
fast_finish: true
+
notifications:
email: false
irc:
@@ -43,10 +88,6 @@ notifications:
on_failure: always
rooms:
- secure: "YA1alef1ESHWGFNVwvmVGCkMe4cUy4j+UcNvMUESraceiAfVyRMAovlQBGs6\n9kBRm7DHYBUXYC2ABQoJbQRLDr/1B5JPf/M8+Qd7BKu8tcDC03U01SMHFLpO\naOs/HLXcDxtnnpL07tGVsm0zhMc5N8tq4/L3SHxK7Vi+TacwQzI="
-bundler_args: --without test --jobs 3 --retry 3
-services:
- - memcached
- - redis
- - rabbitmq
-addons:
- postgresql: "9.4"
+
+git:
+ depth: 1
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 961b48733c..f6ebef7e89 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -21,6 +21,10 @@
* Before submitting, please read the [Contributing to Ruby on Rails](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) guide to know more about coding conventions and benchmarks.
+#### **Did you fix whitespace, format code, or make a purely cosmetic patch?**
+
+Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted (read more about [our rationales behind this decision](https://github.com/rails/rails/pull/13771#issuecomment-32746700)).
+
#### **Do you intend to add a new feature or change an existing one?**
* Suggest your change in the [rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code.
diff --git a/Gemfile b/Gemfile
index 9dd841d535..5650147099 100644
--- a/Gemfile
+++ b/Gemfile
@@ -3,27 +3,21 @@ source 'https://rubygems.org'
gemspec
# We need a newish Rake since Active Job sets its test tasks' descriptions.
-gem 'rake', '>= 10.3'
+gem 'rake', '>= 11.1'
-# This needs to be with require false to ensure correct loading order, as has to
+# This needs to be with require false to ensure correct loading order, as it has to
# be loaded after loading the test library.
gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
gem 'jquery-rails'
-gem 'coffee-rails', '~> 4.1.0'
-gem 'turbolinks'
+gem 'coffee-rails', github: 'rails/coffee-rails'
+gem 'turbolinks', github: 'turbolinks/turbolinks-rails'
# require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid Active Model (and by extension the entire framework)
# being dependent on a binary library.
-platforms :mingw, :x64_mingw, :mswin, :mswin64 do
- gem 'bcrypt-ruby', '~> 3.0.0', require: false
-end
-
-platforms :ruby, :jruby, :rbx do
- gem 'bcrypt', '~> 3.1.10', require: false
-end
+gem 'bcrypt', '~> 3.1.11', require: false
# This needs to be with require false to avoid it being automatically loaded by
# sprockets.
@@ -45,18 +39,19 @@ gem 'listen', '~> 3.0.5', require: false
# Active Job.
group :job do
- gem 'resque', require: false
+ gem 'resque', '< 1.26', require: false
gem 'resque-scheduler', require: false
gem 'sidekiq', require: false
gem 'sucker_punch', require: false
- gem 'delayed_job', require: false
+ gem 'delayed_job', require: false, github: 'collectiveidea/delayed_job'
gem 'queue_classic', github: "QueueClassic/queue_classic", branch: 'master', require: false, platforms: :ruby
gem 'sneakers', require: false
gem 'que', require: false
gem 'backburner', require: false
- gem 'qu-rails', github: "bkeepers/qu", branch: "master", require: false
+ #TODO: add qu after it support Rails 5.1
+ # gem 'qu-rails', github: "bkeepers/qu", branch: "master", require: false
gem 'qu-redis', require: false
- gem 'delayed_job_active_record', require: false
+ gem 'delayed_job_active_record', require: false, github: 'collectiveidea/delayed_job_active_record'
gem 'sequel', require: false
end
@@ -65,7 +60,16 @@ group :cable do
gem 'puma', require: false
gem 'em-hiredis', require: false
+ gem 'hiredis', require: false
gem 'redis', require: false
+
+ gem 'faye-websocket', require: false
+
+ # Lock to 1.1.1 until the fix for https://github.com/faye/faye/issues/394 is released
+ gem 'faye', '1.1.1', require: false
+
+ gem 'blade', require: false
+ gem 'blade-sauce_labs_plugin', require: false
end
# Add your own local bundler stuff.
@@ -85,7 +89,7 @@ group :test do
end
platforms :ruby, :mswin, :mswin64, :mingw, :x64_mingw do
- gem 'nokogiri', '>= 1.6.7.1'
+ gem 'nokogiri', '>= 1.6.8'
# Needed for compiling the ActionDispatch::Journey parser.
gem 'racc', '>=1.4.6', require: false
@@ -95,12 +99,11 @@ platforms :ruby, :mswin, :mswin64, :mingw, :x64_mingw do
group :db do
gem 'pg', '>= 0.18.0'
- gem 'mysql2', '>= 0.4.0'
+ gem 'mysql2', '>= 0.4.4'
end
end
platforms :jruby do
- gem 'json'
if ENV['AR_JDBC']
gem 'activerecord-jdbcsqlite3-adapter', github: 'jruby/activerecord-jdbc-adapter', branch: 'master'
group :db do
@@ -133,3 +136,4 @@ end
# A gem necessary for Active Record tests with IBM DB.
gem 'ibm_db' if ENV['IBM_DB']
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+gem 'wdm', '>= 0.1.0', platforms: [:mingw, :mswin, :x64_mingw, :mswin64]
diff --git a/Gemfile.lock b/Gemfile.lock
index f02a85f70d..8b31934301 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,90 +1,103 @@
GIT
remote: git://github.com/QueueClassic/queue_classic.git
- revision: d144db29f1436e9e8b3c7a1a1ecd4442316a9ecd
+ revision: c26f2c9f6f6133b946fbcdd7b7ec905a4aca9f94
branch: master
specs:
- queue_classic (3.2.0.alpha)
+ queue_classic (3.2.0.RC1)
pg (>= 0.17, < 0.19)
GIT
- remote: git://github.com/bkeepers/qu.git
- revision: d098e2657c92e89a6413bebd9c033930759c061f
- branch: master
+ remote: git://github.com/collectiveidea/delayed_job.git
+ revision: 71f1d5faf934d3057abca942f0d410327bc69087
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
+ delayed_job (4.1.1)
+ activesupport (>= 3.0, < 5.1)
+
+GIT
+ remote: git://github.com/collectiveidea/delayed_job_active_record.git
+ revision: 61e688e03b2ef4004b08de6d1e0a123fda8fffad
+ specs:
+ delayed_job_active_record (4.1.0)
+ activerecord (>= 3.0, < 5.1)
+ delayed_job (>= 3.0, < 5)
+
+GIT
+ remote: git://github.com/rails/coffee-rails.git
+ revision: aa2e623cbda4f3c789a0a15d1f707239e68f5736
+ specs:
+ coffee-rails (4.1.1)
+ coffee-script (>= 2.2.0)
+ railties (>= 4.0.0, < 5.2.x)
GIT
remote: git://github.com/sass/sass.git
- revision: bce9509f396225d721501ea1070a6871b708abb1
+ revision: 3fda1cbe70d615e7ef96e28db4fd1f8a3ebb5505
branch: stable
specs:
- sass (3.4.20)
+ sass (3.4.22)
+
+GIT
+ remote: git://github.com/turbolinks/turbolinks-rails.git
+ revision: 65884729016dbb4d032f12bb01b7e7c1ddeb68ac
+ specs:
+ turbolinks (5.0.0.beta2)
+ turbolinks-source
PATH
remote: .
specs:
- actioncable (5.0.0.beta1.1)
- actionpack (= 5.0.0.beta1.1)
- coffee-rails (~> 4.1.0)
- eventmachine (~> 1.0)
- faye-websocket (~> 0.10.0)
+ actioncable (5.1.0.alpha)
+ actionpack (= 5.1.0.alpha)
+ nio4r (~> 1.2)
websocket-driver (~> 0.6.1)
- actionmailer (5.0.0.beta1.1)
- actionpack (= 5.0.0.beta1.1)
- actionview (= 5.0.0.beta1.1)
- activejob (= 5.0.0.beta1.1)
+ actionmailer (5.1.0.alpha)
+ actionpack (= 5.1.0.alpha)
+ actionview (= 5.1.0.alpha)
+ activejob (= 5.1.0.alpha)
mail (~> 2.5, >= 2.5.4)
- rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (5.0.0.beta1.1)
- actionview (= 5.0.0.beta1.1)
- activesupport (= 5.0.0.beta1.1)
+ rails-dom-testing (~> 2.0)
+ actionpack (5.1.0.alpha)
+ actionview (= 5.1.0.alpha)
+ activesupport (= 5.1.0.alpha)
rack (~> 2.x)
rack-test (~> 0.6.3)
- rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (5.0.0.beta1.1)
- activesupport (= 5.0.0.beta1.1)
+ actionview (5.1.0.alpha)
+ activesupport (= 5.1.0.alpha)
builder (~> 3.1)
erubis (~> 2.7.0)
- rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- activejob (5.0.0.beta1.1)
- activesupport (= 5.0.0.beta1.1)
+ activejob (5.1.0.alpha)
+ activesupport (= 5.1.0.alpha)
globalid (>= 0.3.6)
- activemodel (5.0.0.beta1.1)
- activesupport (= 5.0.0.beta1.1)
- activerecord (5.0.0.beta1.1)
- activemodel (= 5.0.0.beta1.1)
- activesupport (= 5.0.0.beta1.1)
+ activemodel (5.1.0.alpha)
+ activesupport (= 5.1.0.alpha)
+ activerecord (5.1.0.alpha)
+ activemodel (= 5.1.0.alpha)
+ activesupport (= 5.1.0.alpha)
arel (~> 7.0)
- activesupport (5.0.0.beta1.1)
- concurrent-ruby (~> 1.0)
+ activesupport (5.1.0.alpha)
+ concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7)
- json (~> 1.7, >= 1.7.7)
- method_source
minitest (~> 5.1)
tzinfo (~> 1.1)
- rails (5.0.0.beta1.1)
- actioncable (= 5.0.0.beta1.1)
- actionmailer (= 5.0.0.beta1.1)
- actionpack (= 5.0.0.beta1.1)
- actionview (= 5.0.0.beta1.1)
- activejob (= 5.0.0.beta1.1)
- activemodel (= 5.0.0.beta1.1)
- activerecord (= 5.0.0.beta1.1)
- activesupport (= 5.0.0.beta1.1)
+ rails (5.1.0.alpha)
+ actioncable (= 5.1.0.alpha)
+ actionmailer (= 5.1.0.alpha)
+ actionpack (= 5.1.0.alpha)
+ actionview (= 5.1.0.alpha)
+ activejob (= 5.1.0.alpha)
+ activemodel (= 5.1.0.alpha)
+ activerecord (= 5.1.0.alpha)
+ activesupport (= 5.1.0.alpha)
bundler (>= 1.3.0, < 2.0)
- railties (= 5.0.0.beta1.1)
+ railties (= 5.1.0.alpha)
sprockets-rails (>= 2.0.0)
- railties (5.0.0.beta1.1)
- actionpack (= 5.0.0.beta1.1)
- activesupport (= 5.0.0.beta1.1)
+ railties (5.1.0.alpha)
+ actionpack (= 5.1.0.alpha)
+ activesupport (= 5.1.0.alpha)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
@@ -92,43 +105,79 @@ PATH
GEM
remote: https://rubygems.org/
specs:
- amq-protocol (2.0.0)
+ addressable (2.4.0)
+ amq-protocol (2.0.1)
arel (7.0.0)
- backburner (1.2.0)
+ backburner (1.3.0)
beaneater (~> 1.0)
dante (> 0.1.5)
- bcrypt (3.1.10)
- bcrypt-ruby (3.0.1)
- bcrypt-ruby (3.0.1-x86-mingw32)
+ bcrypt (3.1.11)
+ bcrypt (3.1.11-x64-mingw32)
+ bcrypt (3.1.11-x86-mingw32)
beaneater (1.0.0)
- benchmark-ips (2.3.0)
+ benchmark-ips (2.6.1)
+ blade (0.5.6)
+ activesupport (>= 3.0.0)
+ blade-qunit_adapter (~> 1.20.0)
+ coffee-script
+ coffee-script-source
+ curses (~> 1.0.0)
+ eventmachine
+ faye
+ sprockets (>= 3.0)
+ sprockets-export (~> 0.9.1)
+ thin (>= 1.6.0)
+ thor (~> 0.19.1)
+ useragent (~> 0.16.7)
+ blade-qunit_adapter (1.20.0)
+ blade-sauce_labs_plugin (0.5.2)
+ childprocess
+ faraday
+ selenium-webdriver
builder (3.2.2)
- bunny (2.2.1)
- amq-protocol (>= 2.0.0)
- byebug (8.2.1)
- coffee-rails (4.1.0)
- coffee-script (>= 2.2.0)
- railties (>= 4.0.0, < 5.0)
+ bunny (2.2.2)
+ amq-protocol (>= 2.0.1)
+ byebug (8.2.5)
+ childprocess (0.5.9)
+ ffi (~> 1.0, >= 1.0.11)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.10.0)
- concurrent-ruby (1.0.0)
+ concurrent-ruby (1.0.2)
connection_pool (2.2.0)
- dalli (2.7.5)
+ cookiejar (0.3.0)
+ curses (1.0.2)
+ daemons (1.2.3)
+ dalli (2.7.6)
dante (0.2.0)
- delayed_job (4.1.1)
- activesupport (>= 3.0, < 5.0)
- delayed_job_active_record (4.1.0)
- activerecord (>= 3.0, < 5)
- delayed_job (>= 3.0, < 5)
- em-hiredis (0.3.0)
+ em-hiredis (0.3.1)
eventmachine (~> 1.0)
- hiredis (~> 0.5.0)
+ hiredis (~> 0.6.0)
+ em-http-request (1.1.3)
+ addressable (>= 2.3.4)
+ cookiejar (<= 0.3.0)
+ em-socksify (>= 0.3)
+ eventmachine (>= 1.0.3)
+ http_parser.rb (>= 0.6.0)
+ em-socksify (0.3.1)
+ eventmachine (>= 1.0.0.beta.4)
erubis (2.7.0)
- eventmachine (1.0.9.1)
- execjs (2.6.0)
- faye-websocket (0.10.2)
+ eventmachine (1.2.0.1)
+ eventmachine (1.2.0.1-x64-mingw32)
+ eventmachine (1.2.0.1-x86-mingw32)
+ execjs (2.7.0)
+ faraday (0.9.2)
+ multipart-post (>= 1.2, < 3)
+ faye (1.1.1)
+ cookiejar (>= 0.3.0)
+ em-http-request (>= 0.3.0)
+ eventmachine (>= 0.12.0)
+ faye-websocket (>= 0.9.1)
+ multi_json (>= 1.0.0)
+ rack (>= 1.0.0)
+ websocket-driver (>= 0.5.1)
+ faye-websocket (0.10.4)
eventmachine (>= 0.12.0)
websocket-driver (>= 0.5.1)
ffi (1.9.10)
@@ -136,70 +185,83 @@ GEM
ffi (1.9.10-x86-mingw32)
globalid (0.3.6)
activesupport (>= 4.1.0)
- hiredis (0.5.2)
+ hiredis (0.6.1)
+ http_parser.rb (0.6.0)
i18n (0.7.0)
- jquery-rails (4.0.5)
- rails-dom-testing (~> 1.0)
+ jquery-rails (4.1.1)
+ rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (1.8.3)
kindlerb (0.1.1)
mustache
nokogiri
- listen (3.0.5)
+ listen (3.0.7)
rb-fsevent (>= 0.9.3)
- rb-inotify (>= 0.9)
+ rb-inotify (>= 0.9.7)
loofah (2.0.3)
nokogiri (>= 1.5.9)
- mail (2.6.3)
- mime-types (>= 1.16, < 3)
+ mail (2.6.4)
+ mime-types (>= 1.16, < 4)
metaclass (0.0.4)
method_source (0.8.2)
- mime-types (2.99)
- mini_portile2 (2.0.0)
+ mime-types (3.0)
+ mime-types-data (~> 3.2015)
+ mime-types-data (3.2016.0221)
+ mini_portile2 (2.1.0)
minitest (5.3.3)
mocha (0.14.0)
metaclass (~> 0.0.1)
mono_logger (1.1.0)
- multi_json (1.11.2)
- mustache (1.0.2)
- mysql2 (0.4.2)
- mysql2 (0.4.2-x64-mingw32)
- mysql2 (0.4.2-x86-mingw32)
- nokogiri (1.6.7.1)
- mini_portile2 (~> 2.0.0.rc2)
- nokogiri (1.6.7.1-x64-mingw32)
- mini_portile2 (~> 2.0.0.rc2)
- nokogiri (1.6.7.1-x86-mingw32)
- mini_portile2 (~> 2.0.0.rc2)
+ multi_json (1.12.1)
+ multipart-post (2.0.0)
+ mustache (1.0.3)
+ mysql2 (0.4.4)
+ mysql2 (0.4.4-x64-mingw32)
+ mysql2 (0.4.4-x86-mingw32)
+ nio4r (1.2.1)
+ nokogiri (1.6.8)
+ mini_portile2 (~> 2.1.0)
+ pkg-config (~> 1.1.7)
+ nokogiri (1.6.8-x64-mingw32)
+ mini_portile2 (~> 2.1.0)
+ pkg-config (~> 1.1.7)
+ nokogiri (1.6.8-x86-mingw32)
+ mini_portile2 (~> 2.1.0)
+ pkg-config (~> 1.1.7)
pg (0.18.4)
pg (0.18.4-x64-mingw32)
pg (0.18.4-x86-mingw32)
- psych (2.0.16)
- puma (2.15.3)
- que (0.11.2)
+ pkg-config (1.1.7)
+ psych (2.0.17)
+ puma (3.4.0)
+ qu (0.2.0)
+ multi_json
+ qu-redis (0.2.0)
+ qu (= 0.2.0)
+ redis-namespace
+ simple_uuid
+ que (0.11.4)
racc (1.4.14)
- rack (2.0.0.alpha)
+ rack (2.0.0.rc1)
json
- rack-cache (1.5.1)
+ rack-cache (1.6.1)
rack (>= 0.4)
rack-test (0.6.3)
rack (>= 1.0)
- rails-deprecated_sanitizer (1.0.3)
- activesupport (>= 4.2.0.alpha)
- rails-dom-testing (1.0.7)
- activesupport (>= 4.2.0.beta, < 5.0)
+ rails-dom-testing (2.0.0)
+ activesupport (>= 4.2.0, < 6.0)
nokogiri (~> 1.6.0)
- rails-deprecated_sanitizer (>= 1.0.1)
- rails-html-sanitizer (1.0.2)
+ rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- rake (10.4.2)
- rb-fsevent (0.9.6)
- rb-inotify (0.9.5)
+ rake (11.1.2)
+ rb-fsevent (0.9.7)
+ rb-inotify (0.9.7)
ffi (>= 0.5.0)
- rdoc (4.2.0)
+ rdoc (4.2.2)
+ json (~> 1.4)
redcarpet (3.2.3)
- redis (3.2.2)
+ redis (3.3.0)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
resque (1.25.2)
@@ -208,24 +270,29 @@ GEM
redis-namespace (~> 1.3)
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
- resque-scheduler (4.0.0)
+ resque-scheduler (4.2.0)
mono_logger (~> 1.0)
redis (~> 3.0)
resque (~> 1.25)
- rufus-scheduler (~> 3.0)
- rufus-scheduler (3.1.10)
+ rufus-scheduler (~> 3.2)
+ rubyzip (1.2.0)
+ rufus-scheduler (3.2.1)
sdoc (0.4.1)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
- sequel (4.29.0)
+ selenium-webdriver (2.53.0)
+ childprocess (~> 0.5)
+ rubyzip (~> 1.0)
+ websocket (~> 1.0)
+ sequel (4.34.0)
serverengine (1.5.11)
sigdump (~> 0.2.2)
- sidekiq (4.0.1)
+ sidekiq (4.1.2)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
- json (~> 1.0)
redis (~> 3.2, >= 3.2.1)
- sigdump (0.2.3)
+ sigdump (0.2.4)
+ simple_uuid (0.4.0)
sinatra (1.0)
rack (>= 1.0)
sneakers (2.3.5)
@@ -233,37 +300,43 @@ GEM
serverengine (~> 1.5.11)
thor
thread (~> 0.1.7)
- sprockets (3.5.2)
+ sprockets (3.6.0)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
- sprockets-rails (3.0.0)
+ sprockets-export (0.9.1)
+ sprockets-rails (3.0.4)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sqlite3 (1.3.11)
sqlite3 (1.3.11-x64-mingw32)
sqlite3 (1.3.11-x86-mingw32)
- stackprof (0.2.7)
- sucker_punch (2.0.0)
+ stackprof (0.2.9)
+ sucker_punch (2.0.2)
concurrent-ruby (~> 1.0.0)
+ thin (1.7.0)
+ daemons (~> 1.0, >= 1.0.9)
+ eventmachine (~> 1.0, >= 1.0.4)
+ rack (>= 1, < 3)
thor (0.19.1)
thread (0.1.7)
thread_safe (0.3.5)
- turbolinks (2.5.3)
- coffee-rails
+ turbolinks-source (5.0.0.beta4)
tzinfo (1.2.2)
thread_safe (~> 0.1)
- tzinfo-data (1.2015.7)
+ tzinfo-data (1.2016.4)
tzinfo (>= 1.0.0)
- uglifier (2.7.2)
- execjs (>= 0.3.0)
- json (>= 1.8.0)
+ uglifier (3.0.0)
+ execjs (>= 0.3.0, < 3)
+ useragent (0.16.7)
vegas (0.1.11)
rack (>= 1.0.0)
w3c_validators (1.2)
json
nokogiri
- websocket-driver (0.6.3)
+ wdm (0.1.1)
+ websocket (1.2.3)
+ websocket-driver (0.6.4)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
@@ -277,37 +350,39 @@ DEPENDENCIES
activerecord-jdbcpostgresql-adapter (>= 1.3.0)
activerecord-jdbcsqlite3-adapter (>= 1.3.0)
backburner
- bcrypt (~> 3.1.10)
- bcrypt-ruby (~> 3.0.0)
+ bcrypt (~> 3.1.11)
benchmark-ips
+ blade
+ blade-sauce_labs_plugin
byebug
- coffee-rails (~> 4.1.0)
+ coffee-rails!
dalli (>= 2.2.1)
- delayed_job
- delayed_job_active_record
+ delayed_job!
+ delayed_job_active_record!
em-hiredis
+ faye (= 1.1.1)
+ faye-websocket
+ hiredis
jquery-rails
- json
kindlerb (= 0.1.1)
listen (~> 3.0.5)
minitest (< 5.3.4)
mocha (~> 0.14)
- mysql2 (>= 0.4.0)
- nokogiri (>= 1.6.7.1)
+ mysql2 (>= 0.4.4)
+ nokogiri (>= 1.6.8)
pg (>= 0.18.0)
psych (~> 2.0)
puma
- qu-rails!
qu-redis
que
queue_classic!
racc (>= 1.4.6)
rack-cache (~> 1.2)
rails!
- rake (>= 10.3)
+ rake (>= 11.1)
redcarpet (~> 3.2.3)
redis
- resque
+ resque (< 1.26)
resque-scheduler
sass!
sdoc (~> 0.4.0)
@@ -317,10 +392,11 @@ DEPENDENCIES
sqlite3 (~> 1.3.6)
stackprof
sucker_punch
- turbolinks
+ turbolinks!
tzinfo-data
uglifier (>= 1.3.0)
w3c_validators
+ wdm (>= 0.1.0)
BUNDLED WITH
1.11.2
diff --git a/RAILS_VERSION b/RAILS_VERSION
index 8422265fdd..8ea1016081 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-5.0.0.beta1.1
+5.1.0.alpha
diff --git a/README.md b/README.md
index b4f32940fc..a2b726ea6c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-## Welcome to Rails
+# Welcome to Rails
Rails is a web-application framework that includes everything needed to
create database-backed web applications according to the
@@ -35,7 +35,7 @@ or to generate the body of an email. In Rails, View generation is handled by Act
You can read more about Action View in its [README](actionview/README.rdoc).
Active Record, Active Model, Action Pack, and Action View can each be used independently outside Rails.
-In addition to them, Rails also comes with Action Mailer ([README](actionmailer/README.rdoc)), a library
+In addition to that, Rails also comes with Action Mailer ([README](actionmailer/README.rdoc)), a library
to generate and send emails; Active Job ([README](activejob/README.md)), a
framework for declaring jobs and making them run on a variety of queueing
backends; Action Cable ([README](actioncable/README.md)), a framework to
@@ -64,7 +64,7 @@ and may also be used independently outside Rails.
Run with `--help` or `-h` for options.
4. Using a browser, go to `http://localhost:3000` and you'll see:
-"Welcome aboard: You're riding Ruby on Rails!"
+"Yay! You’re on Rails!"
5. Follow the guidelines to start developing your application. You may find
the following resources handy:
diff --git a/RELEASING_RAILS.md b/RELEASING_RAILS.md
index faf8fa7f0d..4e6b811d3e 100644
--- a/RELEASING_RAILS.md
+++ b/RELEASING_RAILS.md
@@ -19,13 +19,13 @@ http://travis-ci.org/rails/rails
### Is Sam Ruby happy? If not, make him happy.
-Sam Ruby keeps a test suite that makes sure the code samples in his book (Agile
-Web Development with Rails) all work. These are valuable integration tests
-for Rails. You can check the status of his tests here:
+Sam Ruby keeps a [test suite](https://github.com/rubys/awdwr) that makes
+sure the code samples in his book
+([Agile Web Development with Rails](https://pragprog.com/titles/rails5/agile-web-development-with-rails-5th-edition))
+all work. These are valuable system tests
+for Rails. You can check the status of these tests here:
-```
-http://intertwingly.net/projects/dashboard.html
-```
+[http://intertwingly.net/projects/dashboard.html](http://intertwingly.net/projects/dashboard.html)
Do not release with Red AWDwR tests.
@@ -36,7 +36,7 @@ Obviously Rails cannot be released when it depends on unreleased code.
Contact the authors of those particular gems and work out a release date that
suits them.
-### Contact the security team (either Koz or tenderlove)
+### Contact the security team (either tenderlove or rafaelfranca)
Let them know of your plans to release. There may be security issues to be
addressed, and that can impact your release date.
@@ -47,7 +47,7 @@ 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 is only required for major and minor releases, bugfix releases aren't a
-big enough deal, and are supposed to be backwards compatible.
+big enough deal, and are supposed to be backward compatible.
Send an email just giving a heads up about the upcoming release to these
lists:
@@ -103,18 +103,24 @@ branch.
Run `rake install` to generate the gems and install them locally. Then try
generating a new app and ensure that nothing explodes.
+Verify that Action Cable's package.json is updated with the RC version.
+
This will stop you from looking silly when you push an RC to rubygems.org and
then realize it is broken.
-### Release the gem.
+### Release to RubyGems and NPM.
+
+IMPORTANT: The Action Cable client is released as an NPM package, so you must
+have Node.js installed, have an NPM account (npmjs.com), and be an actioncable
+package owner (`npm owner ls actioncable`) to do a full release. Do not release
+until you're set up with NPM!
-IMPORTANT: Due to YAML parse problems on the rubygems.org server, it is safest
-to use Ruby 1.8 when releasing.
+Run `rake release`. This will populate the gemspecs and NPM package.json with
+the current RAILS_VERSION, commit the changes, tag it, and push the gems to
+rubygems.org.
-Run `rake release`. This will populate the gemspecs with data from
-RAILS_VERSION, commit the changes, tag it, and push the gems to rubygems.org.
-Here are the commands that `rake release` should use, so you can understand
-what to do in case anything goes wrong:
+Here are the commands that `rake release` uses so you can understand what to do
+in case anything goes wrong:
```
$ rake all:build
@@ -122,7 +128,7 @@ $ git commit -am'updating RAILS_VERSION'
$ git tag -m 'v3.0.10.rc1 release' v3.0.10.rc1
$ git push
$ git push --tags
-$ for i in $(ls pkg); do gem push $i; done
+$ for i in $(ls pkg); do gem push $i; npm publish; done
```
### Send Rails release announcements
@@ -144,7 +150,7 @@ break existing applications.
### Post the announcement to the Rails blog.
-If you used Markdown format for your email, you can just paste it in to the
+If you used Markdown format for your email, you can just paste it into the
blog.
* http://weblog.rubyonrails.org
@@ -212,4 +218,4 @@ There are two simple steps for fixing the CI:
1. Identify the problem
2. Fix it
-Repeat these steps until the CI is green. \ No newline at end of file
+Repeat these steps until the CI is green.
diff --git a/Rakefile b/Rakefile
index b4517e2545..1d70ce96ea 100644
--- a/Rakefile
+++ b/Rakefile
@@ -21,7 +21,10 @@ task :default => %w(test test:isolated)
task task_name do
errors = []
FRAMEWORKS.each do |project|
- system(%(cd #{project} && #{$0} #{task_name})) || errors << project
+ system(%(cd #{project} && #{$0} #{task_name} --trace)) || errors << project
+ end
+ if task_name =~ /test/
+ system(%(cd actioncable && env FAYE=1 #{$0} #{task_name} --trace)) || errors << 'actioncable-faye'
end
fail("Errors in #{errors.join(', ')}") unless errors.empty?
end
@@ -30,9 +33,10 @@ end
desc "Smoke-test all projects"
task :smoke do
(FRAMEWORKS - %w(activerecord)).each do |project|
- system %(cd #{project} && #{$0} test:isolated)
+ system %(cd #{project} && #{$0} test:isolated --trace)
end
- system %(cd activerecord && #{$0} sqlite3:isolated_test)
+ system %(cd activerecord && #{$0} sqlite3:isolated_test --trace)
+ system %(cd actioncable && env FAYE=1 #{$0} test:isolated --trace)
end
desc "Install gems for all projects."
diff --git a/actioncable/.gitignore b/actioncable/.gitignore
new file mode 100644
index 0000000000..0a04b29786
--- /dev/null
+++ b/actioncable/.gitignore
@@ -0,0 +1,2 @@
+/lib/assets/compiled
+/tmp
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
index a33b7b6b4b..a767857607 100644
--- a/actioncable/CHANGELOG.md
+++ b/actioncable/CHANGELOG.md
@@ -1,14 +1,2 @@
-* Create notion of an `ActionCable::SubscriptionAdapter`.
- Separate out Redis functionality into
- `ActionCable::SubscriptionAdapter::Redis`, and add a
- PostgreSQL adapter as well. Configuration file for
- ActionCable was changed from`config/redis/cable.yml` to
- `config/cable.yml`.
- *Jon Moss*
-
-## Rails 5.0.0.beta1 (December 18, 2015) ##
-
-* Added to Rails!
-
- *DHH*
+Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actioncable/CHANGELOG.md) for previous changes.
diff --git a/actioncable/README.md b/actioncable/README.md
index cad71ddf94..58d23d2834 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -7,7 +7,6 @@ and scalable. It's a full-stack offering that provides both a client-side
JavaScript framework and a server-side Ruby framework. You have access to your full
domain model written with Active Record or your ORM of choice.
-
## Terminology
A single Action Cable server can handle multiple connection instances. It has one
@@ -17,7 +16,7 @@ The client of a WebSocket connection is called the consumer.
Each consumer can in turn subscribe to multiple cable channels. Each channel encapsulates
a logical unit of work, similar to what a controller does in a regular MVC setup. For example,
-you could have a `ChatChannel` and a `AppearancesChannel`, and a consumer could be subscribed to either
+you could have a `ChatChannel` and an `AppearancesChannel`, and a consumer could be subscribed to either
or to both of these channels. At the very least, a consumer should be subscribed to one channel.
When the consumer is subscribed to a channel, they act as a subscriber. The connection between
@@ -39,7 +38,7 @@ reflections of each unit.
### A full-stack example
The first thing you must do is define your `ApplicationCable::Connection` class in Ruby. This
-is the place where you authorize the incoming connection, and proceed to establish it
+is the place where you authorize the incoming connection, and proceed to establish it,
if all is well. Here's the simplest example starting with the server-side connection class:
```ruby
@@ -73,7 +72,7 @@ use that to set the `current_user`. By identifying the connection by this same c
you're also ensuring that you can later retrieve all open connections by a given user (and
potentially disconnect them all if the user is deleted or deauthorized).
-Then you should define your `ApplicationCable::Channel` class in Ruby. This is the place where you put
+Next, you should define your `ApplicationCable::Channel` class in Ruby. This is the place where you put
shared logic between your channels.
```ruby
@@ -86,15 +85,20 @@ end
The client-side needs to setup a consumer instance of this connection. That's done like so:
-```coffeescript
-# app/assets/javascripts/cable.coffee
-#= require action_cable
+```js
+// app/assets/javascripts/cable.js
+//= require action_cable
+//= require_self
+//= require_tree ./channels
+
+(function() {
+ this.App || (this.App = {});
-@App = {}
-App.cable = ActionCable.createConsumer("ws://cable.example.com")
+ App.cable = ActionCable.createConsumer("ws://cable.example.com");
+}).call(this);
```
-The ws://cable.example.com address must point to your set of Action Cable servers, and it
+The `ws://cable.example.com` address must point to your Action Cable server(s), and it
must share a cookie namespace with the rest of the application (which may live under http://example.com).
This ensures that the signed cookie will be correctly sent.
@@ -105,8 +109,8 @@ is defined by declaring channels on the server and allowing the consumer to subs
### Channel example 1: User appearances
-Here's a simple example of a channel that tracks whether a user is online or not and what page they're on.
-(This is useful for creating presence features like showing a green dot next to a user name if they're online).
+Here's a simple example of a channel that tracks whether a user is online or not, and also what page they are currently on.
+(This is useful for creating presence features like showing a green dot next to a user's name if they're online).
First you declare the server-side channel:
@@ -178,9 +182,9 @@ App.cable.subscriptions.create "AppearanceChannel",
```
Simply calling `App.cable.subscriptions.create` will setup the subscription, which will call `AppearanceChannel#subscribed`,
-which in turn is linked to original `App.cable` -> `ApplicationCable::Connection` instances.
+which in turn is linked to the original `App.cable` -> `ApplicationCable::Connection` instances.
-We then link the client-side `appear` method to `AppearanceChannel#appear(data)`. This is possible because the server-side
+Next, we link the client-side `appear` method to `AppearanceChannel#appear(data)`. This is possible because the server-side
channel instance will automatically expose the public methods declared on the class (minus the callbacks), so that these
can be reached as remote procedure calls via a subscription's `perform` method.
@@ -188,7 +192,7 @@ can be reached as remote procedure calls via a subscription's `perform` method.
The appearance example was all about exposing server functionality to client-side invocation over the WebSocket connection.
But the great thing about WebSockets is that it's a two-way street. So now let's show an example where the server invokes
-action on the client.
+an action on the client.
This is a web notification channel that allows you to trigger client-side web notifications when you broadcast to the right
streams:
@@ -215,7 +219,7 @@ ActionCable.server.broadcast \
"web_notifications_#{current_user.id}", { title: 'New things!', body: 'All the news that is fit to print' }
```
-The `ActionCable.server.broadcast` call places a message in the Redis' pubsub queue under a separate broadcasting name for each user. For a user with an ID of 1, the broadcasting name would be `web_notifications_1`.
+The `ActionCable.server.broadcast` call places a message in the Action Cable pubsub queue under a separate broadcasting name for each user. For a user with an ID of 1, the broadcasting name would be `web_notifications_1`.
The channel has been instructed to stream everything that arrives at `web_notifications_1` directly to the client by invoking the
`#received(data)` callback. The data is the hash sent as the second parameter to the server-side broadcast call, JSON encoded for the trip
across the wire, and unpacked for the data argument arriving to `#received`.
@@ -234,7 +238,7 @@ class ChatChannel < ApplicationCable::Channel
end
```
-Pass an object as the first argument to `subscriptions.create`, and that object will become your params hash in your cable channel. The keyword `channel` is required.
+If you pass an object as the first argument to `subscriptions.create`, that object will become the params hash in your cable channel. The keyword `channel` is required.
```coffeescript
# Client-side, which assumes you've already requested the right to send web notifications
@@ -293,165 +297,17 @@ The rebroadcast will be received by all connected clients, _including_ the clien
### More complete examples
-See the [rails/actioncable-examples](http://github.com/rails/actioncable-examples) repository for a full example of how to setup Action Cable in a Rails app and adding channels.
-
-
-## Configuration
-
-Action Cable has three required configurations: the Redis connection, allowed request origins, and the cable server url (which can optionally be set on the client side).
-
-### Redis
-
-By default, `ActionCable::Server::Base` will look for a configuration file in `Rails.root.join('config/cable.yml')`.
-This file must specify a Redis url for each Rails environment. It may use the following format:
-
-```yaml
-production: &production
- url: redis://10.10.3.153:6381
-development: &development
- url: redis://localhost:6379
-test: *development
-```
-
-You can also change the location of the Redis config file in a Rails initializer with something like:
-
-```ruby
-Rails.application.paths.add "config/cable", with: "somewhere/else/cable.yml"
-```
-
-### Allowed Request Origins
-
-Action Cable will only accept requests from specified origins, which are passed to the server config as an array. The origins can be instances of strings or regular expressions, against which a check for match will be performed.
-
-```ruby
-ActionCable.server.config.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/]
-```
-
-When running in the development environment, this defaults to "http://localhost:3000".
-
-To disable and allow requests from any origin:
-
-```ruby
-ActionCable.server.config.disable_request_forgery_protection = true
-```
-
-### Consumer Configuration
-
-Once you have decided how to run your cable server (see below), you must provide the server url (or path) to your client-side setup.
-There are two ways you can do this.
-
-The first is to simply pass it in when creating your consumer. For a standalone server,
-this would be something like: `App.cable = ActionCable.createConsumer("ws://example.com:28080")`, and for an in-app server,
-something like: `App.cable = ActionCable.createConsumer("/cable")`.
-
-The second option is to pass the server url through the `action_cable_meta_tag` in your layout.
-This uses a url or path typically set via `config.action_cable.url` in the environment configuration files, or defaults to "/cable".
-
-This method is especially useful if your websocket url might change between environments. If you host your production server via https, you will need to use the wss scheme
-for your ActionCable server, but development might remain http and use the ws scheme. You might use localhost in development and your
-domain in production.
-
-In any case, to vary the websocket url between environments, add the following configuration to each environment:
-
-```ruby
-config.action_cable.url = "ws://example.com:28080"
-```
-
-Then add the following line to your layout before your JavaScript tag:
-
-```erb
-<%= action_cable_meta_tag %>
-```
-
-And finally, create your consumer like so:
-
-```coffeescript
-App.cable = ActionCable.createConsumer()
-```
-
-### Other Configurations
-
-The other common option to configure is the log tags applied to the per-connection logger. Here's close to what we're using in Basecamp:
-
-```ruby
-ActionCable.server.config.log_tags = [
- -> request { request.env['bc.account_id'] || "no-account" },
- :action_cable,
- -> request { request.uuid }
-]
-```
-
-For a full list of all configuration options, see the `ActionCable::Server::Configuration` class.
-
-Also note that your server must provide at least the same number of database connections as you have workers. The default worker pool is set to 100, so that means you have to make at least that available. You can change that in `config/database.yml` through the `pool` attribute.
-
-
-## Running the cable server
-
-### Standalone
-The cable server(s) is separated from your normal application server. It's still a rack application, but it is its own rack
-application. The recommended basic setup is as follows:
-
-```ruby
-# cable/config.ru
-require ::File.expand_path('../../config/environment', __FILE__)
-Rails.application.eager_load!
-
-require 'action_cable/process/logging'
-
-run ActionCable.server
-```
-
-Then you start the server using a binstub in bin/cable ala:
-```
-#!/bin/bash
-bundle exec puma -p 28080 cable/config.ru
-```
-
-The above will start a cable server on port 28080.
-
-### In app
-
-If you are using a threaded server like Puma or Thin, the current implementation of ActionCable can run side-along with your Rails application. For example, to listen for WebSocket requests on `/cable`, mount the server at that path:
-
-```ruby
-# config/routes.rb
-Example::Application.routes.draw do
- mount ActionCable.server => '/cable'
-end
-```
-
-For every instance of your server you create and for every worker your server spawns, you will also have a new instance of ActionCable, but the use of Redis keeps messages synced across connections.
-
-### Notes
-
-Beware that currently the cable server will _not_ auto-reload any changes in the framework. As we've discussed, long-running cable connections mean long-running objects. We don't yet have a way of reloading the classes of those objects in a safe manner. So when you change your channels, or the model your channels use, you must restart the cable server.
-
-We'll get all this abstracted properly when the framework is integrated into Rails.
-
-The WebSocket server doesn't have access to the session, but it has access to the cookies. This can be used when you need to handle authentication. You can see one way of doing that with Devise in this [article](http://www.rubytutorial.io/actioncable-devise-authentication).
-
-## Dependencies
-
-Action Cable is currently tied to Redis through its use of the pubsub feature to route
-messages back and forth over the WebSocket cable connection. This dependency may well
-be alleviated in the future, but for the moment that's what it is. So be sure to have
-Redis installed and running.
+See the [rails/actioncable-examples](https://github.com/rails/actioncable-examples) repository for a full example of how to setup Action Cable in a Rails app, and how to add channels.
-The Ruby side of things is built on top of [faye-websocket](https://github.com/faye/faye-websocket-ruby) and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).
+## Download and installation
+The latest version of Action Cable can be installed with RubyGems:
-## Deployment
+ $ gem install actioncable
-Action Cable is powered by a combination of websockets and threads. All of the
-connection management is handled internally by utilizing Ruby’s native thread
-support, which means you can use all your regular Rails models with no problems
-as long as you haven’t committed any thread-safety sins.
+Source code can be downloaded as part of the Rails project on GitHub
-But this also means that Action Cable needs to run in its own server process.
-So you'll have one set of server processes for your normal web work, and another
-set of server processes for the Action Cable. The former can be single-threaded,
-like Unicorn, but the latter must be multi-threaded, like Puma.
+* https://github.com/rails/rails/tree/master/actioncable
## License
diff --git a/actioncable/Rakefile b/actioncable/Rakefile
index b6c56e9195..a72142deb5 100644
--- a/actioncable/Rakefile
+++ b/actioncable/Rakefile
@@ -1,9 +1,14 @@
require 'rake/testtask'
+require 'pathname'
+require 'action_cable'
+require 'blade'
dir = File.dirname(__FILE__)
task :default => :test
+task :package => "assets:compile"
+
Rake::TestTask.new do |t|
t.libs << "test"
t.test_files = Dir.glob("#{dir}/test/**/*_test.rb")
@@ -11,3 +16,26 @@ Rake::TestTask.new do |t|
t.verbose = true
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
+
+namespace :test do
+ task :isolated do
+ Dir.glob("test/**/*_test.rb").all? do |file|
+ sh(Gem.ruby, '-w', '-Ilib:test', file)
+ end or raise "Failures"
+ end
+
+ task :integration do
+ if ENV['CI']
+ Blade.start(interface: :ci)
+ else
+ Blade.start(interface: :runner)
+ end
+ end
+end
+
+namespace :assets do
+ desc "Compile Action Cable assets"
+ task :compile do
+ Blade.build
+ end
+end
diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec
index a36acc8f6f..c65ff7871f 100644
--- a/actioncable/actioncable.gemspec
+++ b/actioncable/actioncable.gemspec
@@ -20,14 +20,6 @@ Gem::Specification.new do |s|
s.add_dependency 'actionpack', version
- s.add_dependency 'coffee-rails', '~> 4.1.0'
- s.add_dependency 'eventmachine', '~> 1.0'
- s.add_dependency 'faye-websocket', '~> 0.10.0'
+ s.add_dependency 'nio4r', '~> 1.2'
s.add_dependency 'websocket-driver', '~> 0.6.1'
-
- s.add_development_dependency 'em-hiredis', '~> 0.3.0'
- s.add_development_dependency 'mocha'
- s.add_development_dependency 'pg'
- s.add_development_dependency 'puma'
- s.add_development_dependency 'redis', '~> 3.0'
end
diff --git a/actioncable/lib/assets/javascripts/action_cable.coffee.erb b/actioncable/app/assets/javascripts/action_cable.coffee.erb
index 7daea4ebcd..e0758dae72 100644
--- a/actioncable/lib/assets/javascripts/action_cable.coffee.erb
+++ b/actioncable/app/assets/javascripts/action_cable.coffee.erb
@@ -1,10 +1,14 @@
+#= export ActionCable
#= require_self
-#= require action_cable/consumer
+#= require ./action_cable/consumer
@ActionCable =
INTERNAL: <%= ActionCable::INTERNAL.to_json %>
+ WebSocket: window.WebSocket
+ logger: window.console
- createConsumer: (url = @getConfig("url")) ->
+ createConsumer: (url) ->
+ url ?= @getConfig("url") ? @INTERNAL.default_mount_path
new ActionCable.Consumer @createWebSocketURL(url)
getConfig: (name) ->
@@ -21,3 +25,14 @@
a.href
else
url
+
+ startDebugging: ->
+ @debugging = true
+
+ stopDebugging: ->
+ @debugging = null
+
+ log: (messages...) ->
+ if @debugging
+ messages.push(Date.now())
+ @logger.log("[ActionCable]", messages...)
diff --git a/actioncable/app/assets/javascripts/action_cable/connection.coffee b/actioncable/app/assets/javascripts/action_cable/connection.coffee
new file mode 100644
index 0000000000..29ad676290
--- /dev/null
+++ b/actioncable/app/assets/javascripts/action_cable/connection.coffee
@@ -0,0 +1,116 @@
+#= require ./connection_monitor
+
+# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation.
+
+{message_types, protocols} = ActionCable.INTERNAL
+[supportedProtocols..., unsupportedProtocol] = protocols
+
+class ActionCable.Connection
+ @reopenDelay: 500
+
+ constructor: (@consumer) ->
+ {@subscriptions} = @consumer
+ @monitor = new ActionCable.ConnectionMonitor this
+ @disconnected = true
+
+ send: (data) ->
+ if @isOpen()
+ @webSocket.send(JSON.stringify(data))
+ true
+ else
+ false
+
+ open: =>
+ if @isActive()
+ ActionCable.log("Attempted to open WebSocket, but existing socket is #{@getState()}")
+ throw new Error("Existing connection must be closed before opening")
+ else
+ ActionCable.log("Opening WebSocket, current state is #{@getState()}, subprotocols: #{protocols}")
+ @uninstallEventHandlers() if @webSocket?
+ @webSocket = new ActionCable.WebSocket(@consumer.url, protocols)
+ @installEventHandlers()
+ @monitor.start()
+ true
+
+ close: ({allowReconnect} = {allowReconnect: true}) ->
+ @monitor.stop() unless allowReconnect
+ @webSocket?.close() if @isActive()
+
+ reopen: ->
+ ActionCable.log("Reopening WebSocket, current state is #{@getState()}")
+ if @isActive()
+ try
+ @close()
+ catch error
+ ActionCable.log("Failed to reopen WebSocket", error)
+ finally
+ ActionCable.log("Reopening WebSocket in #{@constructor.reopenDelay}ms")
+ setTimeout(@open, @constructor.reopenDelay)
+ else
+ @open()
+
+ getProtocol: ->
+ @webSocket?.protocol
+
+ isOpen: ->
+ @isState("open")
+
+ isActive: ->
+ @isState("open", "connecting")
+
+ # Private
+
+ isProtocolSupported: ->
+ @getProtocol() in supportedProtocols
+
+ isState: (states...) ->
+ @getState() in states
+
+ getState: ->
+ return state.toLowerCase() for state, value of WebSocket when value is @webSocket?.readyState
+ null
+
+ installEventHandlers: ->
+ for eventName of @events
+ handler = @events[eventName].bind(this)
+ @webSocket["on#{eventName}"] = handler
+ return
+
+ uninstallEventHandlers: ->
+ for eventName of @events
+ @webSocket["on#{eventName}"] = ->
+ return
+
+ events:
+ message: (event) ->
+ return unless @isProtocolSupported()
+ {identifier, message, type} = JSON.parse(event.data)
+ switch type
+ when message_types.welcome
+ @monitor.recordConnect()
+ @subscriptions.reload()
+ when message_types.ping
+ @monitor.recordPing()
+ when message_types.confirmation
+ @subscriptions.notify(identifier, "connected")
+ when message_types.rejection
+ @subscriptions.reject(identifier)
+ else
+ @subscriptions.notify(identifier, "received", message)
+
+ open: ->
+ ActionCable.log("WebSocket onopen event, using '#{@getProtocol()}' subprotocol")
+ @disconnected = false
+ if not @isProtocolSupported()
+ ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.")
+ @close(allowReconnect: false)
+
+ close: (event) ->
+ ActionCable.log("WebSocket onclose event")
+ return if @disconnected
+ @disconnected = true
+ @monitor.recordDisconnect()
+ @subscriptions.notifyAll("disconnected", {willAttemptReconnect: @monitor.isRunning()})
+
+ error: ->
+ ActionCable.log("WebSocket onerror event")
diff --git a/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee b/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee
new file mode 100644
index 0000000000..0cc675fa94
--- /dev/null
+++ b/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee
@@ -0,0 +1,95 @@
+# Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting
+# revival reconnections if things go astray. Internal class, not intended for direct user manipulation.
+class ActionCable.ConnectionMonitor
+ @pollInterval:
+ min: 3
+ max: 30
+
+ @staleThreshold: 6 # Server::Connections::BEAT_INTERVAL * 2 (missed two pings)
+
+ constructor: (@connection) ->
+ @reconnectAttempts = 0
+
+ start: ->
+ unless @isRunning()
+ @startedAt = now()
+ delete @stoppedAt
+ @startPolling()
+ document.addEventListener("visibilitychange", @visibilityDidChange)
+ ActionCable.log("ConnectionMonitor started. pollInterval = #{@getPollInterval()} ms")
+
+ stop: ->
+ if @isRunning()
+ @stoppedAt = now()
+ @stopPolling()
+ document.removeEventListener("visibilitychange", @visibilityDidChange)
+ ActionCable.log("ConnectionMonitor stopped")
+
+ isRunning: ->
+ @startedAt? and not @stoppedAt?
+
+ recordPing: ->
+ @pingedAt = now()
+
+ recordConnect: ->
+ @reconnectAttempts = 0
+ @recordPing()
+ delete @disconnectedAt
+ ActionCable.log("ConnectionMonitor recorded connect")
+
+ recordDisconnect: ->
+ @disconnectedAt = now()
+ ActionCable.log("ConnectionMonitor recorded disconnect")
+
+ # Private
+
+ startPolling: ->
+ @stopPolling()
+ @poll()
+
+ stopPolling: ->
+ clearTimeout(@pollTimeout)
+
+ poll: ->
+ @pollTimeout = setTimeout =>
+ @reconnectIfStale()
+ @poll()
+ , @getPollInterval()
+
+ getPollInterval: ->
+ {min, max} = @constructor.pollInterval
+ interval = 5 * Math.log(@reconnectAttempts + 1)
+ Math.round(clamp(interval, min, max) * 1000)
+
+ reconnectIfStale: ->
+ if @connectionIsStale()
+ ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = #{@reconnectAttempts}, pollInterval = #{@getPollInterval()} ms, time disconnected = #{secondsSince(@disconnectedAt)} s, stale threshold = #{@constructor.staleThreshold} s")
+ @reconnectAttempts++
+ if @disconnectedRecently()
+ ActionCable.log("ConnectionMonitor skipping reopening recent disconnect")
+ else
+ ActionCable.log("ConnectionMonitor reopening")
+ @connection.reopen()
+
+ connectionIsStale: ->
+ secondsSince(@pingedAt ? @startedAt) > @constructor.staleThreshold
+
+ disconnectedRecently: ->
+ @disconnectedAt and secondsSince(@disconnectedAt) < @constructor.staleThreshold
+
+ visibilityDidChange: =>
+ if document.visibilityState is "visible"
+ setTimeout =>
+ if @connectionIsStale() or not @connection.isOpen()
+ ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = #{document.visibilityState}")
+ @connection.reopen()
+ , 200
+
+ now = ->
+ new Date().getTime()
+
+ secondsSince = (time) ->
+ (now() - time) / 1000
+
+ clamp = (number, min, max) ->
+ Math.max(min, Math.min(max, number))
diff --git a/actioncable/lib/assets/javascripts/action_cable/consumer.coffee b/actioncable/app/assets/javascripts/action_cable/consumer.coffee
index fcd8d0fb6c..3298be717f 100644
--- a/actioncable/lib/assets/javascripts/action_cable/consumer.coffee
+++ b/actioncable/app/assets/javascripts/action_cable/consumer.coffee
@@ -1,7 +1,6 @@
-#= require action_cable/connection
-#= require action_cable/connection_monitor
-#= require action_cable/subscriptions
-#= require action_cable/subscription
+#= require ./connection
+#= require ./subscriptions
+#= require ./subscription
# The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established,
# the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates.
@@ -15,11 +14,33 @@
# App.appearance = App.cable.subscriptions.create "AppearanceChannel"
#
# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.
+#
+# When a consumer is created, it automatically connects with the server.
+#
+# To disconnect from the server, call
+#
+# App.cable.disconnect()
+#
+# and to restart the connection:
+#
+# App.cable.connect()
+#
+# Any channel subscriptions which existed prior to disconnecting will
+# automatically resubscribe.
class ActionCable.Consumer
constructor: (@url) ->
@subscriptions = new ActionCable.Subscriptions this
@connection = new ActionCable.Connection this
- @connectionMonitor = new ActionCable.ConnectionMonitor this
send: (data) ->
@connection.send(data)
+
+ connect: ->
+ @connection.open()
+
+ disconnect: ->
+ @connection.close(allowReconnect: false)
+
+ ensureActiveConnection: ->
+ unless @connection.isActive()
+ @connection.open()
diff --git a/actioncable/lib/assets/javascripts/action_cable/subscription.coffee b/actioncable/app/assets/javascripts/action_cable/subscription.coffee
index 339d676933..8e0805a174 100644
--- a/actioncable/lib/assets/javascripts/action_cable/subscription.coffee
+++ b/actioncable/app/assets/javascripts/action_cable/subscription.coffee
@@ -1,5 +1,5 @@
-# A new subscription is created through the ActionCable.Subscriptions instance available on the consumer.
-# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding
+# A new subscription is created through the ActionCable.Subscriptions instance available on the consumer.
+# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding
# Channel instance on the server side.
#
# An example demonstrates the basic functionality:
@@ -7,13 +7,19 @@
# App.appearance = App.cable.subscriptions.create "AppearanceChannel",
# connected: ->
# # Called once the subscription has been successfully completed
-#
+#
+# disconnected: ({ willAttemptReconnect: boolean }) ->
+# # Called when the client has disconnected with the server.
+# # The object will have an `willAttemptReconnect` property which
+# # says whether the client has the intention of attempting
+# # to reconnect.
+#
# appear: ->
# @perform 'appear', appearing_on: @appearingOn()
-#
+#
# away: ->
# @perform 'away'
-#
+#
# appearingOn: ->
# $('main').data 'appearing-on'
#
@@ -27,15 +33,15 @@
# def subscribed
# current_user.appear
# end
-#
+#
# def unsubscribed
# current_user.disappear
# end
-#
+#
# def appear(data)
# current_user.appear on: data['appearing_on']
# end
-#
+#
# def away
# current_user.away
# end
@@ -44,11 +50,9 @@
# The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name.
# The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method.
class ActionCable.Subscription
- constructor: (@subscriptions, params = {}, mixin) ->
+ constructor: (@consumer, params = {}, mixin) ->
@identifier = JSON.stringify(params)
extend(this, mixin)
- @subscriptions.add(this)
- @consumer = @subscriptions.consumer
# Perform a channel action with the optional data passed as an attribute
perform: (action, data = {}) ->
@@ -59,7 +63,7 @@ class ActionCable.Subscription
@consumer.send(command: "message", identifier: @identifier, data: JSON.stringify(data))
unsubscribe: ->
- @subscriptions.remove(this)
+ @consumer.subscriptions.remove(this)
extend = (object, properties) ->
if properties?
diff --git a/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee b/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee
index ae041ffa2b..aa052bf5d8 100644
--- a/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee
+++ b/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee
@@ -13,28 +13,33 @@ class ActionCable.Subscriptions
create: (channelName, mixin) ->
channel = channelName
params = if typeof channel is "object" then channel else {channel}
- new ActionCable.Subscription this, params, mixin
+ subscription = new ActionCable.Subscription @consumer, params, mixin
+ @add(subscription)
# Private
add: (subscription) ->
@subscriptions.push(subscription)
+ @consumer.ensureActiveConnection()
@notify(subscription, "initialized")
@sendCommand(subscription, "subscribe")
+ subscription
remove: (subscription) ->
@forget(subscription)
-
unless @findAll(subscription.identifier).length
@sendCommand(subscription, "unsubscribe")
+ subscription
reject: (identifier) ->
for subscription in @findAll(identifier)
@forget(subscription)
@notify(subscription, "rejected")
+ subscription
forget: (subscription) ->
@subscriptions = (s for s in @subscriptions when s isnt subscription)
+ subscription
findAll: (identifier) ->
s for s in @subscriptions when s.identifier is identifier
@@ -58,7 +63,4 @@ class ActionCable.Subscriptions
sendCommand: (subscription, command) ->
{identifier} = subscription
- if identifier is ActionCable.INTERNAL.identifiers.ping
- @consumer.connection.isOpen()
- else
- @consumer.send({command, identifier})
+ @consumer.send({command, identifier})
diff --git a/actioncable/blade.yml b/actioncable/blade.yml
new file mode 100644
index 0000000000..e38e9b2f1d
--- /dev/null
+++ b/actioncable/blade.yml
@@ -0,0 +1,34 @@
+load_paths:
+ - app/assets/javascripts
+ - test/javascript/src
+ - test/javascript/vendor
+
+logical_paths:
+ - test.js
+
+build:
+ logical_paths:
+ - action_cable.js
+ path: lib/assets/compiled
+ clean: true
+
+plugins:
+ sauce_labs:
+ browsers:
+ Google Chrome:
+ os: Mac, Windows
+ version: -1
+ Firefox:
+ os: Mac, Windows
+ version: -1
+ Safari:
+ platform: Mac
+ version: -1
+ Microsoft Edge:
+ version: -1
+ Internet Explorer:
+ version: 11
+ iPhone:
+ version: -1
+ Motorola Droid 4 Emulator:
+ version: -1
diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb
index 1dc66ef3ad..b6d2842867 100644
--- a/actioncable/lib/action_cable.rb
+++ b/actioncable/lib/action_cable.rb
@@ -29,13 +29,14 @@ module ActionCable
extend ActiveSupport::Autoload
INTERNAL = {
- identifiers: {
- ping: '_ping'.freeze
- },
message_types: {
+ welcome: 'welcome'.freeze,
+ ping: 'ping'.freeze,
confirmation: 'confirm_subscription'.freeze,
rejection: 'reject_subscription'.freeze
- }
+ },
+ default_mount_path: '/cable'.freeze,
+ protocols: ["actioncable-v1-json".freeze, "actioncable-unsupported".freeze].freeze
}
# Singleton instance of the server
diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb
index 88cdc1cab1..845b747fc5 100644
--- a/actioncable/lib/action_cable/channel/base.rb
+++ b/actioncable/lib/action_cable/channel/base.rb
@@ -32,8 +32,11 @@ module ActionCable
#
# == Action processing
#
- # Unlike Action Controllers, channels do not follow a REST constraint form for its actions. It's a remote-procedure call model. You can
- # declare any public method on the channel (optionally taking a data argument), and this method is automatically exposed as callable to the client.
+ # Unlike subclasses of ActionController::Base, channels do not follow a RESTful
+ # constraint form for their actions. Instead, Action Cable operates through a
+ # remote-procedure call model. You can declare any public method on the
+ # channel (optionally taking a <tt>data</tt> argument), and this method is
+ # automatically exposed as callable to the client.
#
# Example:
#
@@ -60,18 +63,22 @@ module ActionCable
# end
# end
#
- # In this example, subscribed/unsubscribed are not callable methods, as they were already declared in ActionCable::Channel::Base, but #appear/away
- # are. #generate_connection_token is also not callable as its a private method. You'll see that appear accepts a data parameter, which it then
- # uses as part of its model call. #away does not, it's simply a trigger action.
+ # In this example, the subscribed and unsubscribed methods are not callable methods, as they
+ # were already declared in ActionCable::Channel::Base, but <tt>#appear</tt>
+ # and <tt>#away</tt> are. <tt>#generate_connection_token</tt> is also not
+ # callable, since it's a private method. You'll see that appear accepts a data
+ # parameter, which it then uses as part of its model call. <tt>#away</tt>
+ # does not, since it's simply a trigger action.
#
- # Also note that in this example, current_user is available because it was marked as an identifying attribute on the connection.
- # All such identifiers will automatically create a delegation method of the same name on the channel instance.
+ # Also note that in this example, <tt>current_user</tt> is available because
+ # it was marked as an identifying attribute on the connection. All such
+ # identifiers will automatically create a delegation method of the same name
+ # on the channel instance.
#
# == Rejecting subscription requests
#
- # A channel can reject a subscription request in the #subscribed callback by invoking #reject!
- #
- # Example:
+ # A channel can reject a subscription request in the #subscribed callback by
+ # invoking the #reject method:
#
# class ChatChannel < ApplicationCable::Channel
# def subscribed
@@ -80,8 +87,10 @@ module ActionCable
# end
# end
#
- # In this example, the subscription will be rejected if the current_user does not have access to the chat room.
- # On the client-side, Channel#rejected callback will get invoked when the server rejects the subscription request.
+ # In this example, the subscription will be rejected if the
+ # <tt>current_user</tt> does not have access to the chat room. On the
+ # client-side, the <tt>Channel#rejected</tt> callback will get invoked when
+ # the server rejects the subscription request.
class Base
include Callbacks
include PeriodicTimers
@@ -116,7 +125,7 @@ module ActionCable
protected
# action_methods are cached and there is sometimes need to refresh
# them. ::clear_action_methods! allows you to do that, so next time
- # you run action_methods, they will be recalculated
+ # you run action_methods, they will be recalculated.
def clear_action_methods!
@action_methods = nil
end
@@ -151,15 +160,18 @@ module ActionCable
action = extract_action(data)
if processable_action?(action)
- dispatch_action(action, data)
+ payload = { channel_class: self.class.name, action: action, data: data }
+ ActiveSupport::Notifications.instrument("perform_action.action_cable", payload) do
+ dispatch_action(action, data)
+ end
else
logger.error "Unable to process #{action_signature(action, data)}"
end
end
- # Called by the cable connection when its cut so the channel has a chance to cleanup with callbacks.
+ # Called by the cable connection when it's cut, so the channel has a chance to cleanup with callbacks.
# This method is not intended to be called directly by the user. Instead, overwrite the #unsubscribed callback.
- def unsubscribe_from_channel
+ def unsubscribe_from_channel # :nodoc:
run_callbacks :unsubscribe do
unsubscribed
end
@@ -174,7 +186,7 @@ module ActionCable
end
# Called once a consumer has cut its cable connection. Can be used for cleaning up connections or marking
- # people as offline or the like.
+ # users as offline or the like.
def unsubscribed
# Override in subclasses
end
@@ -182,8 +194,12 @@ module ActionCable
# Transmit a hash of data to the subscriber. The hash will automatically be wrapped in a JSON envelope with
# the proper channel identifier marked as the recipient.
def transmit(data, via: nil)
- logger.info "#{self.class.name} transmitting #{data.inspect}".tap { |m| m << " (via #{via})" if via }
- connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, message: data)
+ logger.info "#{self.class.name} transmitting #{data.inspect.truncate(300)}".tap { |m| m << " (via #{via})" if via }
+
+ payload = { channel_class: self.class.name, data: data, via: via }
+ ActiveSupport::Notifications.instrument("transmit.action_cable", payload) do
+ connection.transmit identifier: @identifier, message: data
+ end
end
def defer_subscription_confirmation!
@@ -215,7 +231,6 @@ module ActionCable
end
end
-
def subscribe_to_channel
run_callbacks :subscribe do
subscribed
@@ -228,7 +243,6 @@ module ActionCable
end
end
-
def extract_action(data)
(data['action'].presence || :receive).to_sym
end
@@ -258,8 +272,11 @@ module ActionCable
def transmit_subscription_confirmation
unless subscription_confirmation_sent?
logger.info "#{self.class.name} is transmitting the subscription confirmation"
- connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:confirmation])
- @subscription_confirmation_sent = true
+
+ ActiveSupport::Notifications.instrument("transmit_subscription_confirmation.action_cable", channel_class: self.class.name) do
+ connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:confirmation]
+ @subscription_confirmation_sent = true
+ end
end
end
@@ -270,7 +287,10 @@ module ActionCable
def transmit_subscription_rejection
logger.info "#{self.class.name} is transmitting the subscription rejection"
- connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:rejection])
+
+ ActiveSupport::Notifications.instrument("transmit_subscription_rejection.action_cable", channel_class: self.class.name) do
+ connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:rejection]
+ end
end
end
end
diff --git a/actioncable/lib/action_cable/channel/naming.rb b/actioncable/lib/action_cable/channel/naming.rb
index 4c9d53b15a..8e1b2a4af0 100644
--- a/actioncable/lib/action_cable/channel/naming.rb
+++ b/actioncable/lib/action_cable/channel/naming.rb
@@ -10,6 +10,7 @@ module ActionCable
#
# ChatChannel.channel_name # => 'chat'
# Chats::AppearancesChannel.channel_name # => 'chats:appearances'
+ # FooChats::BarAppearancesChannel.channel_name # => 'foo_chats:bar_appearances'
def channel_name
@channel_name ||= name.sub(/Channel$/, '').gsub('::',':').underscore
end
diff --git a/actioncable/lib/action_cable/channel/periodic_timers.rb b/actioncable/lib/action_cable/channel/periodic_timers.rb
index 7f0fb37afc..dab604440f 100644
--- a/actioncable/lib/action_cable/channel/periodic_timers.rb
+++ b/actioncable/lib/action_cable/channel/periodic_timers.rb
@@ -12,11 +12,42 @@ module ActionCable
end
module ClassMethods
- # Allow you to call a private method <tt>every</tt> so often seconds. This periodic timer can be useful
- # for sending a steady flow of updates to a client based off an object that was configured on subscription.
- # It's an alternative to using streams if the channel is able to do the work internally.
- def periodically(callback, every:)
- self.periodic_timers += [ [ callback, every: every ] ]
+ # Periodically performs a task on the channel, like updating an online
+ # user counter, polling a backend for new status messages, sending
+ # regular "heartbeat" messages, or doing some internal work and giving
+ # progress updates.
+ #
+ # Pass a method name or lambda argument or provide a block to call.
+ # Specify the calling period in seconds using the <tt>every:</tt>
+ # keyword argument.
+ #
+ # periodically :transmit_progress, every: 5.seconds
+ #
+ # periodically every: 3.minutes do
+ # transmit action: :update_count, count: current_count
+ # end
+ #
+ def periodically(callback_or_method_name = nil, every:, &block)
+ callback =
+ if block_given?
+ raise ArgumentError, 'Pass a block or provide a callback arg, not both' if callback_or_method_name
+ block
+ else
+ case callback_or_method_name
+ when Proc
+ callback_or_method_name
+ when Symbol
+ -> { __send__ callback_or_method_name }
+ else
+ raise ArgumentError, "Expected a Symbol method name or a Proc, got #{callback_or_method_name.inspect}"
+ end
+ end
+
+ unless every.kind_of?(Numeric) && every > 0
+ raise ArgumentError, "Expected every: to be a positive number of seconds, got #{every.inspect}"
+ end
+
+ self.periodic_timers += [[ callback, every: every ]]
end
end
@@ -27,14 +58,21 @@ module ActionCable
def start_periodic_timers
self.class.periodic_timers.each do |callback, options|
- active_periodic_timers << EventMachine::PeriodicTimer.new(options[:every]) do
- connection.worker_pool.async_run_periodic_timer(self, callback)
+ active_periodic_timers << start_periodic_timer(callback, every: options.fetch(:every))
+ end
+ end
+
+ def start_periodic_timer(callback, every:)
+ connection.server.event_loop.timer every do
+ connection.worker_pool.async_invoke connection do
+ instance_exec(&callback)
end
end
end
def stop_periodic_timers
- active_periodic_timers.each { |timer| timer.cancel }
+ active_periodic_timers.each { |timer| timer.shutdown }
+ active_periodic_timers.clear
end
end
end
diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb
index e2876ef6fa..561750d713 100644
--- a/actioncable/lib/action_cable/channel/streams.rb
+++ b/actioncable/lib/action_cable/channel/streams.rb
@@ -1,8 +1,8 @@
module ActionCable
module Channel
- # Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pub/sub queue where any data
- # put into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not
- # streaming a broadcasting at the very moment it sends out an update, you'll not get that update when connecting later.
+ # Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pubsub queue where any data
+ # placed into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not
+ # streaming a broadcasting at the very moment it sends out an update, you will not get that update, even if you connect after it has been sent.
#
# Most commonly, the streamed broadcast is sent straight to the subscriber on the client-side. The channel just acts as a connector between
# the two parties (the broadcaster and the channel subscriber). Here's an example of a channel that allows subscribers to get all new
@@ -18,13 +18,15 @@ module ActionCable
# end
# end
#
- # So the subscribers of this channel will get whatever data is put into the, let's say, `comments_for_45` broadcasting as soon as it's put there.
- # That looks like so from that side of things:
+ # Based on the above example, the subscribers of this channel will get whatever data is put into the,
+ # let's say, <tt>comments_for_45</tt> broadcasting as soon as it's put there.
+ #
+ # An example broadcasting for this channel looks like so:
#
# ActionCable.server.broadcast "comments_for_45", author: 'DHH', content: 'Rails is just swell'
#
# If you have a stream that is related to a model, then the broadcasting used can be generated from the model and channel.
- # The following example would subscribe to a broadcasting like `comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE`
+ # The following example would subscribe to a broadcasting like <tt>comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE</tt>.
#
# class CommentsChannel < ApplicationCable::Channel
# def subscribed
@@ -37,26 +39,25 @@ module ActionCable
#
# CommentsChannel.broadcast_to(@post, @comment)
#
- # If you don't just want to parlay the broadcast unfiltered to the subscriber, you can supply a callback that lets you alter what goes out.
- # Example below shows how you can use this to provide performance introspection in the process:
+ # If you don't just want to parlay the broadcast unfiltered to the subscriber, you can also supply a callback that lets you alter what is sent out.
+ # The below example shows how you can use this to provide performance introspection in the process:
#
# class ChatChannel < ApplicationCable::Channel
- # def subscribed
- # @room = Chat::Room[params[:room_number]]
- #
- # stream_for @room, -> (encoded_message) do
- # message = ActiveSupport::JSON.decode(encoded_message)
+ # def subscribed
+ # @room = Chat::Room[params[:room_number]]
#
- # if message['originated_at'].present?
- # elapsed_time = (Time.now.to_f - message['originated_at']).round(2)
+ # stream_for @room, coder: ActiveSupport::JSON do |message|
+ # if message['originated_at'].present?
+ # elapsed_time = (Time.now.to_f - message['originated_at']).round(2)
#
- # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing
- # logger.info "Message took #{elapsed_time}s to arrive"
- # end
+ # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing
+ # logger.info "Message took #{elapsed_time}s to arrive"
+ # end
#
- # transmit message
- # end
- # end
+ # transmit message
+ # end
+ # end
+ # end
#
# You can stop streaming from all broadcasts by calling #stop_all_streams.
module Streams
@@ -68,15 +69,21 @@ module ActionCable
# Start streaming from the named <tt>broadcasting</tt> pubsub queue. Optionally, you can pass a <tt>callback</tt> that'll be used
# instead of the default of just transmitting the updates straight to the subscriber.
- def stream_from(broadcasting, callback = nil)
- # Hold off the confirmation until pubsub#subscribe is successful
+ # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to the callback.
+ # Defaults to `coder: nil` which does no decoding, passes raw messages.
+ def stream_from(broadcasting, callback = nil, coder: nil, &block)
+ broadcasting = String(broadcasting)
+
+ # Don't send the confirmation until pubsub#subscribe is successful
defer_subscription_confirmation!
- callback ||= default_stream_callback(broadcasting)
- streams << [ broadcasting, callback ]
+ # Build a stream handler by wrapping the user-provided callback with
+ # a decoder or defaulting to a JSON-decoding retransmitter.
+ handler = worker_pool_stream_handler(broadcasting, callback || block, coder: coder)
+ streams << [ broadcasting, handler ]
- EM.next_tick do
- pubsub.subscribe(broadcasting, callback, lambda do
+ connection.server.event_loop.post do
+ pubsub.subscribe(broadcasting, handler, lambda do
transmit_subscription_confirmation
logger.info "#{self.class.name} is streaming from #{broadcasting}"
end)
@@ -86,10 +93,14 @@ module ActionCable
# Start streaming the pubsub queue for the <tt>model</tt> in this channel. Optionally, you can pass a
# <tt>callback</tt> that'll be used instead of the default of just transmitting the updates straight
# to the subscriber.
- def stream_for(model, callback = nil)
- stream_from(broadcasting_for([ channel_name, model ]), callback)
+ #
+ # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to the callback.
+ # Defaults to `coder: nil` which does no decoding, passes raw messages.
+ def stream_for(model, callback = nil, coder: nil, &block)
+ stream_from(broadcasting_for([ channel_name, model ]), callback || block, coder: coder)
end
+ # Unsubscribes all streams associated with this channel from the pubsub queue.
def stop_all_streams
streams.each do |broadcasting, callback|
pubsub.unsubscribe broadcasting, callback
@@ -104,11 +115,60 @@ module ActionCable
@_streams ||= []
end
- def default_stream_callback(broadcasting)
+ # Always wrap the outermost handler to invoke the user handler on the
+ # worker pool rather than blocking the event loop.
+ def worker_pool_stream_handler(broadcasting, user_handler, coder: nil)
+ handler = stream_handler(broadcasting, user_handler, coder: coder)
+
+ -> message do
+ connection.worker_pool.async_invoke handler, :call, message, connection: connection
+ end
+ end
+
+ # May be overridden to add instrumentation, logging, specialized error
+ # handling, or other forms of handler decoration.
+ #
+ # TODO: Tests demonstrating this.
+ def stream_handler(broadcasting, user_handler, coder: nil)
+ if user_handler
+ stream_decoder user_handler, coder: coder
+ else
+ default_stream_handler broadcasting, coder: coder
+ end
+ end
+
+ # May be overridden to change the default stream handling behavior
+ # which decodes JSON and transmits to the client.
+ #
+ # TODO: Tests demonstrating this.
+ #
+ # TODO: Room for optimization. Update transmit API to be coder-aware
+ # so we can no-op when pubsub and connection are both JSON-encoded.
+ # Then we can skip decode+encode if we're just proxying messages.
+ def default_stream_handler(broadcasting, coder:)
+ coder ||= ActiveSupport::JSON
+ stream_transmitter stream_decoder(coder: coder), broadcasting: broadcasting
+ end
+
+ def stream_decoder(handler = identity_handler, coder:)
+ if coder
+ -> message { handler.(coder.decode(message)) }
+ else
+ handler
+ end
+ end
+
+ def stream_transmitter(handler = identity_handler, broadcasting:)
+ via = "streamed from #{broadcasting}"
+
-> (message) do
- transmit ActiveSupport::JSON.decode(message), via: "streamed from #{broadcasting}"
+ transmit handler.(message), via: via
end
end
+
+ def identity_handler
+ -> message { message }
+ end
end
end
end
diff --git a/actioncable/lib/action_cable/connection.rb b/actioncable/lib/action_cable/connection.rb
index b672e00682..5f813cf8e0 100644
--- a/actioncable/lib/action_cable/connection.rb
+++ b/actioncable/lib/action_cable/connection.rb
@@ -5,12 +5,17 @@ module ActionCable
eager_autoload do
autoload :Authorization
autoload :Base
+ autoload :ClientSocket
autoload :Identification
autoload :InternalChannel
+ autoload :FayeClientSocket
+ autoload :FayeEventLoop
autoload :MessageBuffer
- autoload :WebSocket
+ autoload :Stream
+ autoload :StreamEventLoop
autoload :Subscriptions
autoload :TaggedLoggerProxy
+ autoload :WebSocket
end
end
end
diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb
index bb8850aaa0..75c1299e36 100644
--- a/actioncable/lib/action_cable/connection/base.rb
+++ b/actioncable/lib/action_cable/connection/base.rb
@@ -2,9 +2,9 @@ require 'action_dispatch'
module ActionCable
module Connection
- # For every WebSocket the cable server is accepting, a Connection object will be instantiated. This instance becomes the parent
- # of all the channel subscriptions that are created from there on. Incoming messages are then routed to these channel subscriptions
- # based on an identifier sent by the cable consumer. The Connection itself does not deal with any specific application logic beyond
+ # For every WebSocket connection the Action Cable server accepts, a Connection object will be instantiated. This instance becomes the parent
+ # of all of the channel subscriptions that are created from there on. Incoming messages are then routed to these channel subscriptions
+ # based on an identifier sent by the Action Cable consumer. The Connection itself does not deal with any specific application logic beyond
# authentication and authorization.
#
# Here's a basic example:
@@ -33,14 +33,14 @@ module ActionCable
# end
# end
#
- # First, we declare that this connection can be identified by its current_user. This allows us later to be able to find all connections
- # established for that current_user (and potentially disconnect them if the user was removed from an account). You can declare as many
- # identification indexes as you like. Declaring an identification means that a attr_accessor is automatically set for that key.
+ # First, we declare that this connection can be identified by its current_user. This allows us to later be able to find all connections
+ # established for that current_user (and potentially disconnect them). You can declare as many
+ # identification indexes as you like. Declaring an identification means that an attr_accessor is automatically set for that key.
#
# Second, we rely on the fact that the WebSocket connection is established with the cookies from the domain being sent along. This makes
# it easy to use signed cookies that were set when logging in via a web interface to authorize the WebSocket connection.
#
- # Finally, we add a tag to the connection-specific logger with name of the current user to easily distinguish their messages in the log.
+ # Finally, we add a tag to the connection-specific logger with the name of the current user to easily distinguish their messages in the log.
#
# Pretty simple, eh?
class Base
@@ -48,15 +48,16 @@ module ActionCable
include InternalChannel
include Authorization
- attr_reader :server, :env, :subscriptions, :logger
- delegate :worker_pool, :pubsub, to: :server
+ attr_reader :server, :env, :subscriptions, :logger, :worker_pool, :protocol
+ delegate :event_loop, :pubsub, to: :server
- def initialize(server, env)
- @server, @env = server, env
+ def initialize(server, env, coder: ActiveSupport::JSON)
+ @server, @env, @coder = server, env, coder
+ @worker_pool = server.worker_pool
@logger = new_tagged_logger
- @websocket = ActionCable::Connection::WebSocket.new(env)
+ @websocket = ActionCable::Connection::WebSocket.new(env, self, event_loop, server.config.client_socket_class)
@subscriptions = ActionCable::Connection::Subscriptions.new(self)
@message_buffer = ActionCable::Connection::MessageBuffer.new(self)
@@ -65,35 +66,33 @@ module ActionCable
end
# Called by the server when a new WebSocket connection is established. This configures the callbacks intended for overwriting by the user.
- # This method should not be called directly. Rely on the #connect (and #disconnect) callback instead.
- def process
+ # This method should not be called directly -- instead rely upon on the #connect (and #disconnect) callbacks.
+ def process #:nodoc:
logger.info started_request_message
if websocket.possible? && allow_request_origin?
- websocket.on(:open) { |event| send_async :on_open }
- websocket.on(:message) { |event| on_message event.data }
- websocket.on(:close) { |event| send_async :on_close }
-
respond_to_successful_request
else
respond_to_invalid_request
end
end
- # Data received over the cable is handled by this method. It's expected that everything inbound is JSON encoded.
- # The data is routed to the proper channel that the connection has subscribed to.
- def receive(data_in_json)
+ # Decodes WebSocket messages and dispatches them to subscribed channels.
+ # WebSocket message transfer encoding is always JSON.
+ def receive(websocket_message) #:nodoc:
+ send_async :dispatch_websocket_message, websocket_message
+ end
+
+ def dispatch_websocket_message(websocket_message) #:nodoc:
if websocket.alive?
- subscriptions.execute_command ActiveSupport::JSON.decode(data_in_json)
+ subscriptions.execute_command decode(websocket_message)
else
- logger.error "Received data without a live WebSocket (#{data_in_json.inspect})"
+ logger.error "Ignoring message processed after the WebSocket was closed: #{websocket_message.inspect})"
end
end
- # Send raw data straight back down the WebSocket. This is not intended to be called directly. Use the #transmit available on the
- # Channel instead, as that'll automatically address the correct subscriber and wrap the message in JSON.
- def transmit(data)
- websocket.transmit data
+ def transmit(cable_message) # :nodoc:
+ websocket.transmit encode(cable_message)
end
# Close the WebSocket connection.
@@ -118,7 +117,23 @@ module ActionCable
end
def beat
- transmit ActiveSupport::JSON.encode(identifier: ActionCable::INTERNAL[:identifiers][:ping], message: Time.now.to_i)
+ transmit type: ActionCable::INTERNAL[:message_types][:ping], message: Time.now.to_i
+ end
+
+ def on_open # :nodoc:
+ send_async :handle_open
+ end
+
+ def on_message(message) # :nodoc:
+ message_buffer.append message
+ end
+
+ def on_error(message) # :nodoc:
+ # ignore
+ end
+
+ def on_close(reason, code) # :nodoc:
+ send_async :handle_close
end
protected
@@ -139,10 +154,19 @@ module ActionCable
attr_reader :message_buffer
private
- def on_open
+ def encode(cable_message)
+ @coder.encode cable_message
+ end
+
+ def decode(websocket_message)
+ @coder.decode websocket_message
+ end
+
+ def handle_open
+ @protocol = websocket.protocol
connect if respond_to?(:connect)
subscribe_to_internal_channel
- beat
+ send_welcome_message
message_buffer.process!
server.add_connection(self)
@@ -150,11 +174,7 @@ module ActionCable
respond_to_invalid_request
end
- def on_message(message)
- message_buffer.append message
- end
-
- def on_close
+ def handle_close
logger.info finished_request_message
server.remove_connection(self)
@@ -165,6 +185,13 @@ module ActionCable
disconnect if respond_to?(:disconnect)
end
+ def send_welcome_message
+ # Send welcome message to the internal connection monitor channel.
+ # This ensures the connection monitor state is reset after a successful
+ # websocket connection.
+ transmit type: ActionCable::INTERNAL[:message_types][:welcome]
+ end
+
def allow_request_origin?
return true if server.config.disable_request_forgery_protection
@@ -177,12 +204,14 @@ module ActionCable
end
def respond_to_successful_request
+ logger.info successful_request_message
websocket.rack_response
end
def respond_to_invalid_request
close if websocket.alive?
+ logger.error invalid_request_message
logger.info finished_request_message
[ 404, { 'Content-Type' => 'text/plain' }, [ 'Page not found' ] ]
end
@@ -197,7 +226,7 @@ module ActionCable
'Started %s "%s"%s for %s at %s' % [
request.request_method,
request.filtered_path,
- websocket.possible? ? ' [WebSocket]' : '',
+ websocket.possible? ? ' [WebSocket]' : '[non-WebSocket]',
request.ip,
Time.now.to_s ]
end
@@ -205,10 +234,22 @@ module ActionCable
def finished_request_message
'Finished "%s"%s for %s at %s' % [
request.filtered_path,
- websocket.possible? ? ' [WebSocket]' : '',
+ websocket.possible? ? ' [WebSocket]' : '[non-WebSocket]',
request.ip,
Time.now.to_s ]
end
+
+ def invalid_request_message
+ 'Failed to upgrade to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)' % [
+ env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"]
+ ]
+ end
+
+ def successful_request_message
+ 'Successfully upgraded to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)' % [
+ env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"]
+ ]
+ end
end
end
end
diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb
new file mode 100644
index 0000000000..6f29f32ea9
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/client_socket.rb
@@ -0,0 +1,155 @@
+require 'websocket/driver'
+
+module ActionCable
+ module Connection
+ #--
+ # This class is heavily based on faye-websocket-ruby
+ #
+ # Copyright (c) 2010-2015 James Coglan
+ class ClientSocket # :nodoc:
+ def self.determine_url(env)
+ scheme = secure_request?(env) ? 'wss:' : 'ws:'
+ "#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }"
+ end
+
+ def self.secure_request?(env)
+ return true if env['HTTPS'] == 'on'
+ return true if env['HTTP_X_FORWARDED_SSL'] == 'on'
+ return true if env['HTTP_X_FORWARDED_SCHEME'] == 'https'
+ return true if env['HTTP_X_FORWARDED_PROTO'] == 'https'
+ return true if env['rack.url_scheme'] == 'https'
+
+ return false
+ end
+
+ CONNECTING = 0
+ OPEN = 1
+ CLOSING = 2
+ CLOSED = 3
+
+ attr_reader :env, :url
+
+ def initialize(env, event_target, event_loop, protocols)
+ @env = env
+ @event_target = event_target
+ @event_loop = event_loop
+
+ @url = ClientSocket.determine_url(@env)
+
+ @driver = @driver_started = nil
+ @close_params = ['', 1006]
+
+ @ready_state = CONNECTING
+
+ # The driver calls +env+, +url+, and +write+
+ @driver = ::WebSocket::Driver.rack(self, protocols: protocols)
+
+ @driver.on(:open) { |e| open }
+ @driver.on(:message) { |e| receive_message(e.data) }
+ @driver.on(:close) { |e| begin_close(e.reason, e.code) }
+ @driver.on(:error) { |e| emit_error(e.message) }
+
+ @stream = ActionCable::Connection::Stream.new(@event_loop, self)
+ end
+
+ def start_driver
+ return if @driver.nil? || @driver_started
+ @stream.hijack_rack_socket
+
+ if callback = @env['async.callback']
+ callback.call([101, {}, @stream])
+ end
+
+ @driver_started = true
+ @driver.start
+ end
+
+ def rack_response
+ start_driver
+ [ -1, {}, [] ]
+ end
+
+ def write(data)
+ @stream.write(data)
+ rescue => e
+ emit_error e.message
+ end
+
+ def transmit(message)
+ return false if @ready_state > OPEN
+ case message
+ when Numeric then @driver.text(message.to_s)
+ when String then @driver.text(message)
+ when Array then @driver.binary(message)
+ else false
+ end
+ end
+
+ def close(code = nil, reason = nil)
+ code ||= 1000
+ reason ||= ''
+
+ unless code == 1000 or (code >= 3000 and code <= 4999)
+ raise ArgumentError, "Failed to execute 'close' on WebSocket: " +
+ "The code must be either 1000, or between 3000 and 4999. " +
+ "#{code} is neither."
+ end
+
+ @ready_state = CLOSING unless @ready_state == CLOSED
+ @driver.close(reason, code)
+ end
+
+ def parse(data)
+ @driver.parse(data)
+ end
+
+ def client_gone
+ finalize_close
+ end
+
+ def alive?
+ @ready_state == OPEN
+ end
+
+ def protocol
+ @driver.protocol
+ end
+
+ private
+ def open
+ return unless @ready_state == CONNECTING
+ @ready_state = OPEN
+
+ @event_target.on_open
+ end
+
+ def receive_message(data)
+ return unless @ready_state == OPEN
+
+ @event_target.on_message(data)
+ end
+
+ def emit_error(message)
+ return if @ready_state >= CLOSING
+
+ @event_target.on_error(message)
+ end
+
+ def begin_close(reason, code)
+ return if @ready_state == CLOSED
+ @ready_state = CLOSING
+ @close_params = [reason, code]
+
+ @stream.shutdown if @stream
+ finalize_close
+ end
+
+ def finalize_close
+ return if @ready_state == CLOSED
+ @ready_state = CLOSED
+
+ @event_target.on_close(*@close_params)
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/faye_client_socket.rb b/actioncable/lib/action_cable/connection/faye_client_socket.rb
new file mode 100644
index 0000000000..a4bfe7db17
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/faye_client_socket.rb
@@ -0,0 +1,48 @@
+require 'faye/websocket'
+
+module ActionCable
+ module Connection
+ class FayeClientSocket
+ def initialize(env, event_target, stream_event_loop, protocols)
+ @env = env
+ @event_target = event_target
+ @protocols = protocols
+
+ @faye = nil
+ end
+
+ def alive?
+ @faye && @faye.ready_state == Faye::WebSocket::API::OPEN
+ end
+
+ def transmit(data)
+ connect
+ @faye.send data
+ end
+
+ def close
+ @faye && @faye.close
+ end
+
+ def protocol
+ @faye && @faye.protocol
+ end
+
+ def rack_response
+ connect
+ @faye.rack_response
+ end
+
+ private
+ def connect
+ return if @faye
+ @faye = Faye::WebSocket.new(@env, @protocols)
+
+ @faye.on(:open) { |event| @event_target.on_open }
+ @faye.on(:message) { |event| @event_target.on_message(event.data) }
+ @faye.on(:close) { |event| @event_target.on_close(event.reason, event.code) }
+ @faye.on(:error) { |event| @event_target.on_error(event.message) }
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/faye_event_loop.rb b/actioncable/lib/action_cable/connection/faye_event_loop.rb
new file mode 100644
index 0000000000..9c44b38bc3
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/faye_event_loop.rb
@@ -0,0 +1,44 @@
+require 'thread'
+
+require 'eventmachine'
+EventMachine.epoll if EventMachine.epoll?
+EventMachine.kqueue if EventMachine.kqueue?
+
+module ActionCable
+ module Connection
+ class FayeEventLoop
+ @@mutex = Mutex.new
+
+ def timer(interval, &block)
+ ensure_reactor_running
+ EMTimer.new(::EM::PeriodicTimer.new(interval, &block))
+ end
+
+ def post(task = nil, &block)
+ task ||= block
+
+ ensure_reactor_running
+ ::EM.next_tick(&task)
+ end
+
+ private
+ def ensure_reactor_running
+ return if EventMachine.reactor_running?
+ @@mutex.synchronize do
+ Thread.new { EventMachine.run } unless EventMachine.reactor_running?
+ Thread.pass until EventMachine.reactor_running?
+ end
+ end
+
+ class EMTimer
+ def initialize(inner)
+ @inner = inner
+ end
+
+ def shutdown
+ @inner.cancel
+ end
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/identification.rb b/actioncable/lib/action_cable/connection/identification.rb
index 885ff3f102..4a54044aff 100644
--- a/actioncable/lib/action_cable/connection/identification.rb
+++ b/actioncable/lib/action_cable/connection/identification.rb
@@ -12,7 +12,7 @@ module ActionCable
class_methods do
# Mark a key as being a connection identifier index that can then be used to find the specific connection again later.
- # Common identifiers are current_user and current_account, but could be anything really.
+ # Common identifiers are current_user and current_account, but could be anything, really.
#
# Note that anything marked as an identifier will automatically create a delegate by the same name on any
# channel instances created off the connection.
diff --git a/actioncable/lib/action_cable/connection/internal_channel.rb b/actioncable/lib/action_cable/connection/internal_channel.rb
index 54ed7672d2..f70d52f99b 100644
--- a/actioncable/lib/action_cable/connection/internal_channel.rb
+++ b/actioncable/lib/action_cable/connection/internal_channel.rb
@@ -11,24 +11,22 @@ module ActionCable
def subscribe_to_internal_channel
if connection_identifier.present?
- callback = -> (message) { process_internal_message(message) }
+ callback = -> (message) { process_internal_message decode(message) }
@_internal_subscriptions ||= []
@_internal_subscriptions << [ internal_channel, callback ]
- EM.next_tick { pubsub.subscribe(internal_channel, callback) }
+ server.event_loop.post { pubsub.subscribe(internal_channel, callback) }
logger.info "Registered connection (#{connection_identifier})"
end
end
def unsubscribe_from_internal_channel
if @_internal_subscriptions.present?
- @_internal_subscriptions.each { |channel, callback| EM.next_tick { pubsub.unsubscribe(channel, callback) } }
+ @_internal_subscriptions.each { |channel, callback| server.event_loop.post { pubsub.unsubscribe(channel, callback) } }
end
end
def process_internal_message(message)
- message = ActiveSupport::JSON.decode(message)
-
case message['type']
when 'disconnect'
logger.info "Removing connection (#{connection_identifier})"
diff --git a/actioncable/lib/action_cable/connection/message_buffer.rb b/actioncable/lib/action_cable/connection/message_buffer.rb
index 2f65a1e84a..6a80770cae 100644
--- a/actioncable/lib/action_cable/connection/message_buffer.rb
+++ b/actioncable/lib/action_cable/connection/message_buffer.rb
@@ -1,8 +1,7 @@
module ActionCable
module Connection
- # Allows us to buffer messages received from the WebSocket before the Connection has been fully initialized and is ready to receive them.
- # Entirely internal operation and should not be used directly by the user.
- class MessageBuffer
+ # Allows us to buffer messages received from the WebSocket before the Connection has been fully initialized, and is ready to receive them.
+ class MessageBuffer # :nodoc:
def initialize(connection)
@connection = connection
@buffered_messages = []
@@ -31,7 +30,7 @@ module ActionCable
protected
attr_reader :connection
- attr_accessor :buffered_messages
+ attr_reader :buffered_messages
private
def valid?(message)
@@ -39,7 +38,7 @@ module ActionCable
end
def receive(message)
- connection.send_async :receive, message
+ connection.receive message
end
def buffer(message)
diff --git a/actioncable/lib/action_cable/connection/stream.rb b/actioncable/lib/action_cable/connection/stream.rb
new file mode 100644
index 0000000000..0cf59091bc
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/stream.rb
@@ -0,0 +1,57 @@
+module ActionCable
+ module Connection
+ #--
+ # This class is heavily based on faye-websocket-ruby
+ #
+ # Copyright (c) 2010-2015 James Coglan
+ class Stream # :nodoc:
+ def initialize(event_loop, socket)
+ @event_loop = event_loop
+ @socket_object = socket
+ @stream_send = socket.env['stream.send']
+
+ @rack_hijack_io = nil
+ end
+
+ def each(&callback)
+ @stream_send ||= callback
+ end
+
+ def close
+ shutdown
+ @socket_object.client_gone
+ end
+
+ def shutdown
+ clean_rack_hijack
+ end
+
+ def write(data)
+ return @rack_hijack_io.write(data) if @rack_hijack_io
+ return @stream_send.call(data) if @stream_send
+ rescue EOFError, Errno::ECONNRESET
+ @socket_object.client_gone
+ end
+
+ def receive(data)
+ @socket_object.parse(data)
+ end
+
+ def hijack_rack_socket
+ return unless @socket_object.env['rack.hijack']
+
+ @socket_object.env['rack.hijack'].call
+ @rack_hijack_io = @socket_object.env['rack.hijack_io']
+
+ @event_loop.attach(@rack_hijack_io, self)
+ end
+
+ private
+ def clean_rack_hijack
+ return unless @rack_hijack_io
+ @event_loop.detach(@rack_hijack_io, self)
+ @rack_hijack_io = nil
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/stream_event_loop.rb b/actioncable/lib/action_cable/connection/stream_event_loop.rb
new file mode 100644
index 0000000000..2abad09c03
--- /dev/null
+++ b/actioncable/lib/action_cable/connection/stream_event_loop.rb
@@ -0,0 +1,103 @@
+require 'nio'
+require 'thread'
+
+module ActionCable
+ module Connection
+ class StreamEventLoop
+ def initialize
+ @nio = @thread = nil
+ @map = {}
+ @stopping = false
+ @todo = Queue.new
+
+ @spawn_mutex = Mutex.new
+ end
+
+ def timer(interval, &block)
+ Concurrent::TimerTask.new(execution_interval: interval, &block).tap(&:execute)
+ end
+
+ def post(task = nil, &block)
+ task ||= block
+
+ Concurrent.global_io_executor << task
+ end
+
+ def attach(io, stream)
+ @todo << lambda do
+ @map[io] = stream
+ @nio.register(io, :r)
+ end
+ wakeup
+ end
+
+ def detach(io, stream)
+ @todo << lambda do
+ @nio.deregister io
+ @map.delete io
+ end
+ wakeup
+ end
+
+ def stop
+ @stopping = true
+ wakeup if @nio
+ end
+
+ private
+ def spawn
+ return if @thread && @thread.status
+
+ @spawn_mutex.synchronize do
+ return if @thread && @thread.status
+
+ @nio ||= NIO::Selector.new
+ @thread = Thread.new { run }
+
+ return true
+ end
+ end
+
+ def wakeup
+ spawn || @nio.wakeup
+ end
+
+ def run
+ loop do
+ if @stopping
+ @nio.close
+ break
+ end
+
+ until @todo.empty?
+ @todo.pop(true).call
+ end
+
+ next unless monitors = @nio.select
+
+ monitors.each do |monitor|
+ io = monitor.io
+ stream = @map[io]
+
+ begin
+ stream.receive io.read_nonblock(4096)
+ rescue IO::WaitReadable
+ next
+ rescue
+ # We expect one of EOFError or Errno::ECONNRESET in
+ # normal operation (when the client goes away). But if
+ # anything else goes wrong, this is still the best way
+ # to handle it.
+ begin
+ stream.close
+ rescue
+ @nio.deregister io
+ @map.delete io
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb
index d7f95e6a62..6051818bfb 100644
--- a/actioncable/lib/action_cable/connection/subscriptions.rb
+++ b/actioncable/lib/action_cable/connection/subscriptions.rb
@@ -3,8 +3,8 @@ require 'active_support/core_ext/hash/indifferent_access'
module ActionCable
module Connection
# Collection class for all the channel subscriptions established on a given connection. Responsible for routing incoming commands that arrive on
- # the connection to the proper channel. Should not be used directly by the user.
- class Subscriptions
+ # the connection to the proper channel.
+ class Subscriptions # :nodoc:
def initialize(connection)
@connection = connection
@subscriptions = {}
@@ -26,12 +26,12 @@ module ActionCable
id_key = data['identifier']
id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
- subscription_klass = connection.server.channel_classes[id_options[:channel]]
+ subscription_klass = id_options[:channel].safe_constantize
- if subscription_klass
+ if subscription_klass && ActionCable::Channel::Base >= subscription_klass
subscriptions[id_key] ||= subscription_klass.new(connection, id_key, id_options)
else
- logger.error "Subscription class not found (#{data.inspect})"
+ logger.error "Subscription class not found: #{id_options[:channel].inspect}"
end
end
@@ -54,7 +54,7 @@ module ActionCable
end
def unsubscribe_from_all
- subscriptions.each { |id, channel| channel.unsubscribe_from_channel }
+ subscriptions.each { |id, channel| remove_subscription(channel) }
end
protected
diff --git a/actioncable/lib/action_cable/connection/web_socket.rb b/actioncable/lib/action_cable/connection/web_socket.rb
index 670d5690ae..11f28c37e8 100644
--- a/actioncable/lib/action_cable/connection/web_socket.rb
+++ b/actioncable/lib/action_cable/connection/web_socket.rb
@@ -1,13 +1,11 @@
-require 'faye/websocket'
+require 'websocket/driver'
module ActionCable
module Connection
- # Decorate the Faye::WebSocket with helpers we need.
+ # Wrap the real socket to minimize the externally-presented API
class WebSocket
- delegate :rack_response, :close, :on, to: :websocket
-
- def initialize(env)
- @websocket = Faye::WebSocket.websocket?(env) ? Faye::WebSocket.new(env) : nil
+ def initialize(env, event_target, event_loop, client_socket_class, protocols: ActionCable::INTERNAL[:protocols])
+ @websocket = ::WebSocket::Driver.websocket?(env) ? client_socket_class.new(env, event_target, event_loop, protocols) : nil
end
def possible?
@@ -15,11 +13,23 @@ module ActionCable
end
def alive?
- websocket && websocket.ready_state == Faye::WebSocket::API::OPEN
+ websocket && websocket.alive?
end
def transmit(data)
- websocket.send data
+ websocket.transmit data
+ end
+
+ def close
+ websocket.close
+ end
+
+ def protocol
+ websocket.protocol
+ end
+
+ def rack_response
+ websocket.rack_response
end
protected
diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb
index f5e233e091..96176e0014 100644
--- a/actioncable/lib/action_cable/engine.rb
+++ b/actioncable/lib/action_cable/engine.rb
@@ -4,9 +4,9 @@ require "action_cable/helpers/action_cable_helper"
require "active_support/core_ext/hash/indifferent_access"
module ActionCable
- class Railtie < Rails::Engine # :nodoc:
+ class Engine < Rails::Engine # :nodoc:
config.action_cable = ActiveSupport::OrderedOptions.new
- config.action_cable.url = '/cable'
+ config.action_cable.mount_path = ActionCable::INTERNAL[:default_mount_path]
config.eager_load_namespaces << ActionCable
@@ -31,8 +31,47 @@ module ActionCable
self.cable = Rails.application.config_for(config_path).with_indifferent_access
end
+ previous_connection_class = self.connection_class
+ self.connection_class = -> { 'ApplicationCable::Connection'.safe_constantize || previous_connection_class.call }
+
options.each { |k,v| send("#{k}=", v) }
end
end
+
+ initializer "action_cable.routes" do
+ config.after_initialize do |app|
+ config = app.config
+ unless config.action_cable.mount_path.nil?
+ app.routes.prepend do
+ mount ActionCable.server => config.action_cable.mount_path, internal: true
+ end
+ end
+ end
+ end
+
+ initializer "action_cable.set_work_hooks" do |app|
+ ActiveSupport.on_load(:action_cable) do
+ ActionCable::Server::Worker.set_callback :work, :around, prepend: true do |_, inner|
+ app.executor.wrap do
+ # If we took a while to get the lock, we may have been halted
+ # in the meantime. As we haven't started doing any real work
+ # yet, we should pretend that we never made it off the queue.
+ unless stopping?
+ inner.call
+ end
+ end
+ end
+
+ wrap = lambda do |_, inner|
+ app.executor.wrap(&inner)
+ end
+ ActionCable::Channel::Base.set_callback :subscribe, :around, prepend: true, &wrap
+ ActionCable::Channel::Base.set_callback :unsubscribe, :around, prepend: true, &wrap
+
+ app.reloader.before_class_unload do
+ ActionCable.server.restart
+ end
+ end
+ end
end
end
diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb
index c652fb91ae..8ba0230d47 100644
--- a/actioncable/lib/action_cable/gem_version.rb
+++ b/actioncable/lib/action_cable/gem_version.rb
@@ -6,9 +6,9 @@ module ActionCable
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta1.1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actioncable/lib/action_cable/helpers/action_cable_helper.rb b/actioncable/lib/action_cable/helpers/action_cable_helper.rb
index b82751468a..2081a37db6 100644
--- a/actioncable/lib/action_cable/helpers/action_cable_helper.rb
+++ b/actioncable/lib/action_cable/helpers/action_cable_helper.rb
@@ -1,28 +1,39 @@
module ActionCable
module Helpers
module ActionCableHelper
- # Returns an "action-cable-url" meta tag with the value of the url specified in your
- # configuration. Ensure this is above your javascript tag:
+ # Returns an "action-cable-url" meta tag with the value of the URL specified in your
+ # configuration. Ensure this is above your JavaScript tag:
#
# <head>
# <%= action_cable_meta_tag %>
# <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
# </head>
#
- # This is then used by ActionCable to determine the url of your websocket server.
+ # This is then used by Action Cable to determine the URL of your WebSocket server.
# Your CoffeeScript can then connect to the server without needing to specify the
- # url directly:
+ # URL directly:
#
# #= require cable
# @App = {}
# App.cable = Cable.createConsumer()
#
- # Make sure to specify the correct server location in each of your environments
- # config file:
+ # Make sure to specify the correct server location in each of your environment
+ # config files:
+ #
+ # config.action_cable.mount_path = "/cable123"
+ # <%= action_cable_meta_tag %> would render:
+ # => <meta name="action-cable-url" content="/cable123" />
+ #
+ # config.action_cable.url = "ws://actioncable.com"
+ # <%= action_cable_meta_tag %> would render:
+ # => <meta name="action-cable-url" content="ws://actioncable.com" />
#
- # config.action_cable.url = "ws://example.com:28080"
def action_cable_meta_tag
- tag "meta", name: "action-cable-url", content: Rails.application.config.action_cable.url
+ tag "meta", name: "action-cable-url", content: (
+ ActionCable.server.config.url ||
+ ActionCable.server.config.mount_path ||
+ raise("No Action Cable URL configured -- please configure this at config.action_cable.url")
+ )
end
end
end
diff --git a/actioncable/lib/action_cable/process/logging.rb b/actioncable/lib/action_cable/process/logging.rb
deleted file mode 100644
index dce637b3ca..0000000000
--- a/actioncable/lib/action_cable/process/logging.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'action_cable/server'
-require 'eventmachine'
-
-EM.error_handler do |e|
- puts "Error raised inside the event loop: #{e.message}"
- puts e.backtrace.join("\n")
-end
diff --git a/actioncable/lib/action_cable/remote_connections.rb b/actioncable/lib/action_cable/remote_connections.rb
index aa2fc95d2f..a528024427 100644
--- a/actioncable/lib/action_cable/remote_connections.rb
+++ b/actioncable/lib/action_cable/remote_connections.rb
@@ -1,6 +1,7 @@
module ActionCable
- # If you need to disconnect a given connection, you go through the RemoteConnections. You find the connections you're looking for by
- # searching the identifier declared on the connection. Example:
+ # If you need to disconnect a given connection, you can go through the
+ # RemoteConnections. You can find the connections you're looking for by
+ # searching for the identifier declared on the connection. For example:
#
# module ApplicationCable
# class Connection < ActionCable::Connection::Base
@@ -11,8 +12,9 @@ module ActionCable
#
# ActionCable.server.remote_connections.where(current_user: User.find(1)).disconnect
#
- # That will disconnect all the connections established for User.find(1) across all servers running on all machines (because it uses
- # the internal channel that all these servers are subscribed to).
+ # This will disconnect all the connections established for
+ # <tt>User.find(1)</tt>, across all servers running on all machines, because
+ # it uses the internal channel that all of these servers are subscribed to.
class RemoteConnections
attr_reader :server
@@ -25,8 +27,8 @@ module ActionCable
end
private
- # Represents a single remote connection found via ActionCable.server.remote_connections.where(*).
- # Exists for the solely for the purpose of calling #disconnect on that connection.
+ # Represents a single remote connection found via <tt>ActionCable.server.remote_connections.where(*)</tt>.
+ # Exists solely for the purpose of calling #disconnect on that connection.
class RemoteConnection
class InvalidIdentifiersError < StandardError; end
diff --git a/actioncable/lib/action_cable/server.rb b/actioncable/lib/action_cable/server.rb
index a2a89d5f1e..bd6a3826a3 100644
--- a/actioncable/lib/action_cable/server.rb
+++ b/actioncable/lib/action_cable/server.rb
@@ -1,7 +1,3 @@
-require 'eventmachine'
-EventMachine.epoll if EventMachine.epoll?
-EventMachine.kqueue if EventMachine.kqueue?
-
module ActionCable
module Server
extend ActiveSupport::Autoload
diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb
index 3385a4c9f3..0ad1e408a9 100644
--- a/actioncable/lib/action_cable/server/base.rb
+++ b/actioncable/lib/action_cable/server/base.rb
@@ -1,9 +1,11 @@
+require 'monitor'
+
module ActionCable
module Server
- # A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the rack process that starts the cable server, but
- # also by the user to reach the RemoteConnections instead for finding and disconnecting connections across all servers.
+ # A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the Rack process that starts the Action Cable server, but
+ # is also used by the user to reach the RemoteConnections object, which is used for finding and disconnecting connections across all servers.
#
- # Also, this is the server instance used for broadcasting. See Broadcasting for details.
+ # Also, this is the server instance used for broadcasting. See Broadcasting for more information.
class Base
include ActionCable::Server::Broadcasting
include ActionCable::Server::Connections
@@ -13,13 +15,17 @@ module ActionCable
def self.logger; config.logger; end
delegate :logger, to: :config
+ attr_reader :mutex
+
def initialize
+ @mutex = Monitor.new
+ @remote_connections = @event_loop = @worker_pool = @pubsub = nil
end
- # Called by rack to setup the server.
+ # Called by Rack to setup the server.
def call(env)
setup_heartbeat_timer
- config.connection_class.new(self, env).process
+ config.connection_class.call.new(self, env).process
end
# Disconnect all the connections identified by `identifiers` on this server or any others via RemoteConnections.
@@ -27,32 +33,48 @@ module ActionCable
remote_connections.where(identifiers).disconnect
end
+ def restart
+ connections.each(&:close)
+
+ @mutex.synchronize do
+ worker_pool.halt if @worker_pool
+
+ @worker_pool = nil
+ end
+ end
+
# Gateway to RemoteConnections. See that class for details.
def remote_connections
- @remote_connections ||= RemoteConnections.new(self)
+ @remote_connections || @mutex.synchronize { @remote_connections ||= RemoteConnections.new(self) }
end
- # The thread worker pool for handling all the connection work on this server. Default size is set by config.worker_pool_size.
- def worker_pool
- @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size)
+ def event_loop
+ @event_loop || @mutex.synchronize { @event_loop ||= config.event_loop_class.new }
end
- # Requires and returns a hash of all the channel class constants keyed by name.
- def channel_classes
- @channel_classes ||= begin
- config.channel_paths.each { |channel_path| require channel_path }
- config.channel_class_names.each_with_object({}) { |name, hash| hash[name] = name.constantize }
- end
+ # The worker pool is where we run connection callbacks and channel actions. We do as little as possible on the server's main thread.
+ # The worker pool is an executor service that's backed by a pool of threads working from a task queue. The thread pool size maxes out
+ # at 4 worker threads by default. Tune the size yourself with `config.action_cable.worker_pool_size`.
+ #
+ # Using Active Record, Redis, etc within your channel actions means you'll get a separate connection from each thread in the worker pool.
+ # Plan your deployment accordingly: 5 servers each running 5 Puma workers each running an 8-thread worker pool means at least 200 database
+ # connections.
+ #
+ # Also, ensure that your database connection pool size is as least as large as your worker pool size. Otherwise, workers may oversubscribe
+ # the database connection pool and block while they wait for other workers to release their connections. Use a smaller worker pool or a larger
+ # database connection pool instead.
+ def worker_pool
+ @worker_pool || @mutex.synchronize { @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) }
end
# Adapter used for all streams/broadcasting.
def pubsub
- @pubsub ||= config.pubsub_adapter.new(self)
+ @pubsub || @mutex.synchronize { @pubsub ||= config.pubsub_adapter.new(self) }
end
- # All the identifiers applied to the connection class associated with this server.
+ # All of the identifiers applied to the connection class associated with this server.
def connection_identifiers
- config.connection_class.identifiers
+ config.connection_class.call.identifiers
end
end
diff --git a/actioncable/lib/action_cable/server/broadcasting.rb b/actioncable/lib/action_cable/server/broadcasting.rb
index 4a26ed9269..8f93564113 100644
--- a/actioncable/lib/action_cable/server/broadcasting.rb
+++ b/actioncable/lib/action_cable/server/broadcasting.rb
@@ -1,45 +1,46 @@
module ActionCable
module Server
- # Broadcasting is how other parts of your application can send messages to the channel subscribers. As explained in Channel, most of the time, these
+ # Broadcasting is how other parts of your application can send messages to a channel's subscribers. As explained in Channel, most of the time, these
# broadcastings are streamed directly to the clients subscribed to the named broadcasting. Let's explain with a full-stack example:
#
# class WebNotificationsChannel < ApplicationCable::Channel
- # def subscribed
- # stream_from "web_notifications_#{current_user.id}"
- # end
- # end
+ # def subscribed
+ # stream_from "web_notifications_#{current_user.id}"
+ # end
+ # end
#
- # # Somewhere in your app this is called, perhaps from a NewCommentJob
- # ActionCable.server.broadcast \
- # "web_notifications_1", { title: 'New things!', body: 'All shit fit for print' }
+ # # Somewhere in your app this is called, perhaps from a NewCommentJob:
+ # ActionCable.server.broadcast \
+ # "web_notifications_1", { title: "New things!", body: "All that's fit for print" }
#
- # # Client-side coffescript, which assumes you've already requested the right to send web notifications
- # App.cable.subscriptions.create "WebNotificationsChannel",
- # received: (data) ->
- # new Notification data['title'], body: data['body']
+ # # Client-side CoffeeScript, which assumes you've already requested the right to send web notifications:
+ # App.cable.subscriptions.create "WebNotificationsChannel",
+ # received: (data) ->
+ # new Notification data['title'], body: data['body']
module Broadcasting
- # Broadcast a hash directly to a named <tt>broadcasting</tt>. It'll automatically be JSON encoded.
- def broadcast(broadcasting, message)
- broadcaster_for(broadcasting).broadcast(message)
+ # Broadcast a hash directly to a named <tt>broadcasting</tt>. This will later be JSON encoded.
+ def broadcast(broadcasting, message, coder: ActiveSupport::JSON)
+ broadcaster_for(broadcasting, coder: coder).broadcast(message)
end
- # Returns a broadcaster for a named <tt>broadcasting</tt> that can be reused. Useful when you have a object that
+ # Returns a broadcaster for a named <tt>broadcasting</tt> that can be reused. Useful when you have an object that
# may need multiple spots to transmit to a specific broadcasting over and over.
- def broadcaster_for(broadcasting)
- Broadcaster.new(self, broadcasting)
+ def broadcaster_for(broadcasting, coder: ActiveSupport::JSON)
+ Broadcaster.new(self, String(broadcasting), coder: coder)
end
private
class Broadcaster
- attr_reader :server, :broadcasting
+ attr_reader :server, :broadcasting, :coder
- def initialize(server, broadcasting)
- @server, @broadcasting = server, broadcasting
+ def initialize(server, broadcasting, coder:)
+ @server, @broadcasting, @coder = server, broadcasting, coder
end
def broadcast(message)
- server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message}"
- server.pubsub.broadcast broadcasting, ActiveSupport::JSON.encode(message)
+ server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}"
+ encoded = coder ? coder.encode(message) : message
+ server.pubsub.broadcast broadcasting, encoded
end
end
end
diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb
index 9a248933c4..ada1ac22cc 100644
--- a/actioncable/lib/action_cable/server/configuration.rb
+++ b/actioncable/lib/action_cable/server/configuration.rb
@@ -1,37 +1,22 @@
module ActionCable
module Server
- # An instance of this configuration object is available via ActionCable.server.config, which allows you to tweak the configuration points
+ # An instance of this configuration object is available via ActionCable.server.config, which allows you to tweak Action Cable configuration
# in a Rails config initializer.
class Configuration
attr_accessor :logger, :log_tags
- attr_accessor :connection_class, :worker_pool_size
- attr_accessor :channel_load_paths
+ attr_accessor :use_faye, :connection_class, :worker_pool_size
attr_accessor :disable_request_forgery_protection, :allowed_request_origins
- attr_accessor :cable, :url
+ attr_accessor :cable, :url, :mount_path
def initialize
@log_tags = []
- @connection_class = ApplicationCable::Connection
- @worker_pool_size = 100
-
- @channel_load_paths = [Rails.root.join('app/channels')]
+ @connection_class = -> { ActionCable::Connection::Base }
+ @worker_pool_size = 4
@disable_request_forgery_protection = false
end
- def channel_paths
- @channel_paths ||= channel_load_paths.flat_map do |path|
- Dir["#{path}/**/*_channel.rb"]
- end
- end
-
- def channel_class_names
- @channel_class_names ||= channel_paths.collect do |channel_path|
- Pathname.new(channel_path).basename.to_s.split('.').first.camelize
- end
- end
-
# Returns constant of subscription adapter specified in config/cable.yml.
# If the adapter cannot be found, this will default to the Redis adapter.
# Also makes sure proper dependencies are required.
@@ -50,6 +35,22 @@ module ActionCable
adapter = 'PostgreSQL' if adapter == 'Postgresql'
"ActionCable::SubscriptionAdapter::#{adapter}".constantize
end
+
+ def event_loop_class
+ if use_faye
+ ActionCable::Connection::FayeEventLoop
+ else
+ ActionCable::Connection::StreamEventLoop
+ end
+ end
+
+ def client_socket_class
+ if use_faye
+ ActionCable::Connection::FayeClientSocket
+ else
+ ActionCable::Connection::ClientSocket
+ end
+ end
end
end
end
diff --git a/actioncable/lib/action_cable/server/connections.rb b/actioncable/lib/action_cable/server/connections.rb
index 47dcea8c20..5e61b4e335 100644
--- a/actioncable/lib/action_cable/server/connections.rb
+++ b/actioncable/lib/action_cable/server/connections.rb
@@ -1,9 +1,8 @@
module ActionCable
module Server
- # Collection class for all the connections that's been established on this specific server. Remember, usually you'll run many cable servers, so
- # you can't use this collection as an full list of all the connections established against your application. Use RemoteConnections for that.
- # As such, this is primarily for internal use.
- module Connections
+ # Collection class for all the connections that have been established on this specific server. Remember, usually you'll run many Action Cable servers, so
+ # you can't use this collection as a full list of all of the connections established against your application. Instead, use RemoteConnections for that.
+ module Connections # :nodoc:
BEAT_INTERVAL = 3
def connections
@@ -19,13 +18,11 @@ module ActionCable
end
# WebSocket connection implementations differ on when they'll mark a connection as stale. We basically never want a connection to go stale, as you
- # then can't rely on being able to receive and send to it. So there's a 3 second heartbeat running on all connections. If the beat fails, we automatically
+ # then can't rely on being able to communicate with the connection. To solve this, a 3 second heartbeat runs on all connections. If the beat fails, we automatically
# disconnect.
def setup_heartbeat_timer
- EM.next_tick do
- @heartbeat_timer ||= EventMachine.add_periodic_timer(BEAT_INTERVAL) do
- EM.next_tick { connections.map(&:beat) }
- end
+ @heartbeat_timer ||= event_loop.timer(BEAT_INTERVAL) do
+ event_loop.post { connections.map(&:beat) }
end
end
diff --git a/actioncable/lib/action_cable/server/worker.rb b/actioncable/lib/action_cable/server/worker.rb
index 3b6c6d44a1..a638ff72e7 100644
--- a/actioncable/lib/action_cable/server/worker.rb
+++ b/actioncable/lib/action_cable/server/worker.rb
@@ -4,60 +4,60 @@ require 'concurrent'
module ActionCable
module Server
- # Worker used by Server.send_async to do connection work in threads. Only for internal use.
- class Worker
+ # Worker used by Server.send_async to do connection work in threads.
+ class Worker # :nodoc:
include ActiveSupport::Callbacks
thread_mattr_accessor :connection
define_callbacks :work
include ActiveRecordConnectionManagement
+ attr_reader :executor
+
def initialize(max_size: 5)
- @pool = Concurrent::ThreadPoolExecutor.new(
+ @executor = Concurrent::ThreadPoolExecutor.new(
min_threads: 1,
max_threads: max_size,
max_queue: 0,
)
end
- def async_invoke(receiver, method, *args)
- @pool.post do
- invoke(receiver, method, *args)
- end
+ # Stop processing work: any work that has not already started
+ # running will be discarded from the queue
+ def halt
+ @executor.kill
end
- def invoke(receiver, method, *args)
- begin
- self.connection = receiver
+ def stopping?
+ @executor.shuttingdown?
+ end
- run_callbacks :work do
- receiver.send method, *args
- end
- rescue Exception => e
- logger.error "There was an exception - #{e.class}(#{e.message})"
- logger.error e.backtrace.join("\n")
+ def work(connection)
+ self.connection = connection
- receiver.handle_exception if receiver.respond_to?(:handle_exception)
- ensure
- self.connection = nil
+ run_callbacks :work do
+ yield
end
+ ensure
+ self.connection = nil
end
- def async_run_periodic_timer(channel, callback)
- @pool.post do
- run_periodic_timer(channel, callback)
+ def async_invoke(receiver, method, *args, connection: receiver)
+ @executor.post do
+ invoke(receiver, method, *args, connection: connection)
end
end
- def run_periodic_timer(channel, callback)
- begin
- self.connection = channel.connection
+ def invoke(receiver, method, *args, connection:)
+ work(connection) do
+ begin
+ receiver.send method, *args
+ rescue Exception => e
+ logger.error "There was an exception - #{e.class}(#{e.message})"
+ logger.error e.backtrace.join("\n")
- run_callbacks :work do
- callback.respond_to?(:call) ? channel.instance_exec(&callback) : channel.send(callback)
+ receiver.handle_exception if receiver.respond_to?(:handle_exception)
end
- ensure
- self.connection = nil
end
end
diff --git a/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb b/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb
index ecece4e270..c1e4aa8103 100644
--- a/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb
+++ b/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb
@@ -1,7 +1,6 @@
module ActionCable
module Server
class Worker
- # Clear active connections between units of work so the long-running channel or connection processes do not hoard connections.
module ActiveRecordConnectionManagement
extend ActiveSupport::Concern
@@ -13,10 +12,8 @@ module ActionCable
def with_database_connections
connection.logger.tag(ActiveRecord::Base.logger) { yield }
- ensure
- ActiveRecord::Base.clear_active_connections!
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/actioncable/lib/action_cable/subscription_adapter/async.rb b/actioncable/lib/action_cable/subscription_adapter/async.rb
index 85d4892e4c..10b3ac8cd8 100644
--- a/actioncable/lib/action_cable/subscription_adapter/async.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/async.rb
@@ -4,17 +4,22 @@ module ActionCable
module SubscriptionAdapter
class Async < Inline # :nodoc:
private
- def subscriber_map
- @subscriber_map ||= AsyncSubscriberMap.new
+ def new_subscriber_map
+ AsyncSubscriberMap.new(server.event_loop)
end
class AsyncSubscriberMap < SubscriberMap
+ def initialize(event_loop)
+ @event_loop = event_loop
+ super()
+ end
+
def add_subscriber(*)
- ::EM.next_tick { super }
+ @event_loop.post { super }
end
def invoke_callback(*)
- ::EM.next_tick { super }
+ @event_loop.post { super }
end
end
end
diff --git a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb
new file mode 100644
index 0000000000..4735a4bfa8
--- /dev/null
+++ b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb
@@ -0,0 +1,79 @@
+require 'thread'
+
+gem 'em-hiredis', '~> 0.3.0'
+gem 'redis', '~> 3.0'
+require 'em-hiredis'
+require 'redis'
+
+EventMachine.epoll if EventMachine.epoll?
+EventMachine.kqueue if EventMachine.kqueue?
+
+module ActionCable
+ module SubscriptionAdapter
+ class EventedRedis < Base # :nodoc:
+ @@mutex = Mutex.new
+
+ # Overwrite this factory method for EventMachine Redis connections if you want to use a different Redis connection library than EM::Hiredis.
+ # This is needed, for example, when using Makara proxies for distributed Redis.
+ cattr_accessor(:em_redis_connector) { ->(config) { EM::Hiredis.connect(config[:url]) } }
+
+ # Overwrite this factory method for Redis connections if you want to use a different Redis connection library than Redis.
+ # This is needed, for example, when using Makara proxies for distributed Redis.
+ cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } }
+
+ def initialize(*)
+ super
+ @redis_connection_for_broadcasts = @redis_connection_for_subscriptions = nil
+ end
+
+ def broadcast(channel, payload)
+ redis_connection_for_broadcasts.publish(channel, payload)
+ end
+
+ def subscribe(channel, message_callback, success_callback = nil)
+ redis_connection_for_subscriptions.pubsub.subscribe(channel, &message_callback).tap do |result|
+ result.callback { |reply| success_callback.call } if success_callback
+ end
+ end
+
+ def unsubscribe(channel, message_callback)
+ redis_connection_for_subscriptions.pubsub.unsubscribe_proc(channel, message_callback)
+ end
+
+ def shutdown
+ redis_connection_for_subscriptions.pubsub.close_connection
+ @redis_connection_for_subscriptions = nil
+ end
+
+ private
+ def redis_connection_for_subscriptions
+ ensure_reactor_running
+ @redis_connection_for_subscriptions || @server.mutex.synchronize do
+ @redis_connection_for_subscriptions ||= self.class.em_redis_connector.call(@server.config.cable).tap do |redis|
+ redis.on(:reconnect_failed) do
+ @logger.error "[ActionCable] Redis reconnect failed."
+ end
+
+ redis.on(:failed) do
+ @logger.error "[ActionCable] Redis connection has failed."
+ end
+ end
+ end
+ end
+
+ def redis_connection_for_broadcasts
+ @redis_connection_for_broadcasts || @server.mutex.synchronize do
+ @redis_connection_for_broadcasts ||= self.class.redis_connector.call(@server.config.cable)
+ end
+ end
+
+ def ensure_reactor_running
+ return if EventMachine.reactor_running?
+ @@mutex.synchronize do
+ Thread.new { EventMachine.run } unless EventMachine.reactor_running?
+ Thread.pass until EventMachine.reactor_running?
+ end
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/subscription_adapter/inline.rb b/actioncable/lib/action_cable/subscription_adapter/inline.rb
index 4a2a8d23a2..81357faead 100644
--- a/actioncable/lib/action_cable/subscription_adapter/inline.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/inline.rb
@@ -1,6 +1,11 @@
module ActionCable
module SubscriptionAdapter
class Inline < Base # :nodoc:
+ def initialize(*)
+ super
+ @subscriber_map = nil
+ end
+
def broadcast(channel, payload)
subscriber_map.broadcast(channel, payload)
end
@@ -19,7 +24,11 @@ module ActionCable
private
def subscriber_map
- @subscriber_map ||= SubscriberMap.new
+ @subscriber_map || @server.mutex.synchronize { @subscriber_map ||= new_subscriber_map }
+ end
+
+ def new_subscriber_map
+ SubscriberMap.new
end
end
end
diff --git a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb
index 78f8aeb599..66c7852f6e 100644
--- a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb
@@ -5,6 +5,11 @@ require 'thread'
module ActionCable
module SubscriptionAdapter
class PostgreSQL < Base # :nodoc:
+ def initialize(*)
+ super
+ @listener = nil
+ end
+
def broadcast(channel, payload)
with_connection do |pg_conn|
pg_conn.exec("NOTIFY #{pg_conn.escape_identifier(channel)}, '#{pg_conn.escape_string(payload)}'")
@@ -37,14 +42,15 @@ module ActionCable
private
def listener
- @listener ||= Listener.new(self)
+ @listener || @server.mutex.synchronize { @listener ||= Listener.new(self, @server.event_loop) }
end
class Listener < SubscriberMap
- def initialize(adapter)
+ def initialize(adapter, event_loop)
super()
@adapter = adapter
+ @event_loop = event_loop
@queue = Queue.new
@thread = Thread.new do
@@ -63,7 +69,7 @@ module ActionCable
case action
when :listen
pg_conn.exec("LISTEN #{pg_conn.escape_identifier channel}")
- ::EM.next_tick(&callback) if callback
+ @event_loop.post(&callback) if callback
when :unlisten
pg_conn.exec("UNLISTEN #{pg_conn.escape_identifier channel}")
when :shutdown
@@ -93,7 +99,7 @@ module ActionCable
end
def invoke_callback(*)
- ::EM.next_tick { super }
+ @event_loop.post { super }
end
end
end
diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb
index 3b86354621..65434f7107 100644
--- a/actioncable/lib/action_cable/subscription_adapter/redis.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb
@@ -1,41 +1,171 @@
-gem 'em-hiredis', '~> 0.3.0'
+require 'thread'
+
gem 'redis', '~> 3.0'
-require 'em-hiredis'
require 'redis'
module ActionCable
module SubscriptionAdapter
class Redis < Base # :nodoc:
+ # Overwrite this factory method for redis connections if you want to use a different Redis library than Redis.
+ # This is needed, for example, when using Makara proxies for distributed Redis.
+ cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } }
+
+ def initialize(*)
+ super
+ @listener = nil
+ @redis_connection_for_broadcasts = nil
+ end
+
def broadcast(channel, payload)
redis_connection_for_broadcasts.publish(channel, payload)
end
- def subscribe(channel, message_callback, success_callback = nil)
- redis_connection_for_subscriptions.pubsub.subscribe(channel, &message_callback).tap do |result|
- result.callback { |reply| success_callback.call } if success_callback
- end
+ def subscribe(channel, callback, success_callback = nil)
+ listener.add_subscriber(channel, callback, success_callback)
end
- def unsubscribe(channel, message_callback)
- redis_connection_for_subscriptions.pubsub.unsubscribe_proc(channel, message_callback)
+ def unsubscribe(channel, callback)
+ listener.remove_subscriber(channel, callback)
end
def shutdown
- redis_connection_for_subscriptions.pubsub.close_connection
- @redis_connection_for_subscriptions = nil
+ @listener.shutdown if @listener
+ end
+
+ def redis_connection_for_subscriptions
+ redis_connection
end
private
- def redis_connection_for_subscriptions
- @redis_connection_for_subscriptions ||= EM::Hiredis.connect(@server.config.cable[:url]).tap do |redis|
- redis.on(:reconnect_failed) do
- @logger.info "[ActionCable] Redis reconnect failed."
- end
- end
+ def listener
+ @listener || @server.mutex.synchronize { @listener ||= Listener.new(self, @server.event_loop) }
end
def redis_connection_for_broadcasts
- @redis_connection_for_broadcasts ||= ::Redis.new(@server.config.cable)
+ @redis_connection_for_broadcasts || @server.mutex.synchronize do
+ @redis_connection_for_broadcasts ||= redis_connection
+ end
+ end
+
+ def redis_connection
+ self.class.redis_connector.call(@server.config.cable)
+ end
+
+ class Listener < SubscriberMap
+ def initialize(adapter, event_loop)
+ super()
+
+ @adapter = adapter
+ @event_loop = event_loop
+
+ @subscribe_callbacks = Hash.new { |h, k| h[k] = [] }
+ @subscription_lock = Mutex.new
+
+ @raw_client = nil
+
+ @when_connected = []
+
+ @thread = nil
+ end
+
+ def listen(conn)
+ conn.without_reconnect do
+ original_client = conn.client
+
+ conn.subscribe('_action_cable_internal') do |on|
+ on.subscribe do |chan, count|
+ @subscription_lock.synchronize do
+ if count == 1
+ @raw_client = original_client
+
+ until @when_connected.empty?
+ @when_connected.shift.call
+ end
+ end
+
+ if callbacks = @subscribe_callbacks[chan]
+ next_callback = callbacks.shift
+ @event_loop.post(&next_callback) if next_callback
+ @subscribe_callbacks.delete(chan) if callbacks.empty?
+ end
+ end
+ end
+
+ on.message do |chan, message|
+ broadcast(chan, message)
+ end
+
+ on.unsubscribe do |chan, count|
+ if count == 0
+ @subscription_lock.synchronize do
+ @raw_client = nil
+ end
+ end
+ end
+ end
+ end
+ end
+
+ def shutdown
+ @subscription_lock.synchronize do
+ return if @thread.nil?
+
+ when_connected do
+ send_command('unsubscribe')
+ @raw_client = nil
+ end
+ end
+
+ Thread.pass while @thread.alive?
+ end
+
+ def add_channel(channel, on_success)
+ @subscription_lock.synchronize do
+ ensure_listener_running
+ @subscribe_callbacks[channel] << on_success
+ when_connected { send_command('subscribe', channel) }
+ end
+ end
+
+ def remove_channel(channel)
+ @subscription_lock.synchronize do
+ when_connected { send_command('unsubscribe', channel) }
+ end
+ end
+
+ def invoke_callback(*)
+ @event_loop.post { super }
+ end
+
+ private
+ def ensure_listener_running
+ @thread ||= Thread.new do
+ Thread.current.abort_on_exception = true
+
+ conn = @adapter.redis_connection_for_subscriptions
+ listen conn
+ end
+ end
+
+ def when_connected(&block)
+ if @raw_client
+ block.call
+ else
+ @when_connected << block
+ end
+ end
+
+ def send_command(*command)
+ @raw_client.write(command)
+
+ very_raw_connection =
+ @raw_client.connection.instance_variable_defined?(:@connection) &&
+ @raw_client.connection.instance_variable_get(:@connection)
+
+ if very_raw_connection && very_raw_connection.respond_to?(:flush)
+ very_raw_connection.flush
+ end
+ end
end
end
end
diff --git a/actioncable/lib/assets/javascripts/action_cable/connection.coffee b/actioncable/lib/assets/javascripts/action_cable/connection.coffee
deleted file mode 100644
index fbd7dbd35b..0000000000
--- a/actioncable/lib/assets/javascripts/action_cable/connection.coffee
+++ /dev/null
@@ -1,81 +0,0 @@
-# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation.
-
-{message_types} = ActionCable.INTERNAL
-
-class ActionCable.Connection
- @reopenDelay: 500
-
- constructor: (@consumer) ->
- @open()
-
- send: (data) ->
- if @isOpen()
- @webSocket.send(JSON.stringify(data))
- true
- else
- false
-
- open: =>
- if @webSocket and not @isState("closed")
- throw new Error("Existing connection must be closed before opening")
- else
- @webSocket = new WebSocket(@consumer.url)
- @installEventHandlers()
- true
-
- close: ->
- @webSocket?.close()
-
- reopen: ->
- if @isState("closed")
- @open()
- else
- try
- @close()
- finally
- setTimeout(@open, @constructor.reopenDelay)
-
- isOpen: ->
- @isState("open")
-
- # Private
-
- isState: (states...) ->
- @getState() in states
-
- getState: ->
- return state.toLowerCase() for state, value of WebSocket when value is @webSocket?.readyState
- null
-
- installEventHandlers: ->
- for eventName of @events
- handler = @events[eventName].bind(this)
- @webSocket["on#{eventName}"] = handler
- return
-
- events:
- message: (event) ->
- {identifier, message, type} = JSON.parse(event.data)
-
- switch type
- when message_types.confirmation
- @consumer.subscriptions.notify(identifier, "connected")
- when message_types.rejection
- @consumer.subscriptions.reject(identifier)
- else
- @consumer.subscriptions.notify(identifier, "received", message)
-
- open: ->
- @disconnected = false
- @consumer.subscriptions.reload()
-
- close: ->
- @disconnect()
-
- error: ->
- @disconnect()
-
- disconnect: ->
- return if @disconnected
- @disconnected = true
- @consumer.subscriptions.notifyAll("disconnected")
diff --git a/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee b/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee
deleted file mode 100644
index 99b9a1c6d5..0000000000
--- a/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee
+++ /dev/null
@@ -1,79 +0,0 @@
-# Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting
-# revival reconnections if things go astray. Internal class, not intended for direct user manipulation.
-class ActionCable.ConnectionMonitor
- @pollInterval:
- min: 3
- max: 30
-
- @staleThreshold: 6 # Server::Connections::BEAT_INTERVAL * 2 (missed two pings)
-
- identifier: ActionCable.INTERNAL.identifiers.ping
-
- constructor: (@consumer) ->
- @consumer.subscriptions.add(this)
- @start()
-
- connected: ->
- @reset()
- @pingedAt = now()
- delete @disconnectedAt
-
- disconnected: ->
- @disconnectedAt = now()
-
- received: ->
- @pingedAt = now()
-
- reset: ->
- @reconnectAttempts = 0
-
- start: ->
- @reset()
- delete @stoppedAt
- @startedAt = now()
- @poll()
- document.addEventListener("visibilitychange", @visibilityDidChange)
-
- stop: ->
- @stoppedAt = now()
- document.removeEventListener("visibilitychange", @visibilityDidChange)
-
- poll: ->
- setTimeout =>
- unless @stoppedAt
- @reconnectIfStale()
- @poll()
- , @getInterval()
-
- getInterval: ->
- {min, max} = @constructor.pollInterval
- interval = 5 * Math.log(@reconnectAttempts + 1)
- clamp(interval, min, max) * 1000
-
- reconnectIfStale: ->
- if @connectionIsStale()
- @reconnectAttempts++
- unless @disconnectedRecently()
- @consumer.connection.reopen()
-
- connectionIsStale: ->
- secondsSince(@pingedAt ? @startedAt) > @constructor.staleThreshold
-
- disconnectedRecently: ->
- @disconnectedAt and secondsSince(@disconnectedAt) < @constructor.staleThreshold
-
- visibilityDidChange: =>
- if document.visibilityState is "visible"
- setTimeout =>
- if @connectionIsStale() or not @consumer.connection.isOpen()
- @consumer.connection.reopen()
- , 200
-
- now = ->
- new Date().getTime()
-
- secondsSince = (time) ->
- (now() - time) / 1000
-
- clamp = (number, min, max) ->
- Math.max(min, Math.min(max, number))
diff --git a/actioncable/lib/rails/generators/channel/USAGE b/actioncable/lib/rails/generators/channel/USAGE
index 27a934c689..6249553c22 100644
--- a/actioncable/lib/rails/generators/channel/USAGE
+++ b/actioncable/lib/rails/generators/channel/USAGE
@@ -3,7 +3,7 @@ Description:
Stubs out a new cable channel for the server (in Ruby) and client (in CoffeeScript).
Pass the channel name, either CamelCased or under_scored, and an optional list of channel actions as arguments.
- Note: Turn on the cable connection in app/assets/javascript/cable.coffee after generating any channels.
+ Note: Turn on the cable connection in app/assets/javascript/cable.js after generating any channels.
Example:
========
diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb
index c5d398810a..47232252e3 100644
--- a/actioncable/lib/rails/generators/channel/channel_generator.rb
+++ b/actioncable/lib/rails/generators/channel/channel_generator.rb
@@ -13,13 +13,34 @@ module Rails
template "channel.rb", File.join('app/channels', class_path, "#{file_name}_channel.rb")
if options[:assets]
- template "assets/channel.coffee", File.join('app/assets/javascripts/channels', class_path, "#{file_name}.coffee")
+ if self.behavior == :invoke
+ template "assets/cable.js", "app/assets/javascripts/cable.js"
+ end
+
+ js_template "assets/channel", File.join('app/assets/javascripts/channels', class_path, "#{file_name}")
end
+
+ generate_application_cable_files
end
protected
def file_name
- @_file_name ||= super.gsub(/\_channel/i, '')
+ @_file_name ||= super.gsub(/_channel/i, '')
+ end
+
+ # FIXME: Change these files to symlinks once RubyGems 2.5.0 is required.
+ def generate_application_cable_files
+ return if self.behavior != :invoke
+
+ files = [
+ 'application_cable/channel.rb',
+ 'application_cable/connection.rb'
+ ]
+
+ files.each do |name|
+ path = File.join('app/channels/', name)
+ template(name, path) if !File.exist?(path)
+ end
end
end
end
diff --git a/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb b/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb
new file mode 100644
index 0000000000..17a85f60f9
--- /dev/null
+++ b/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb
@@ -0,0 +1,6 @@
+# Be sure to restart your server when you modify this file. Action Cable runs in
+# a loop that does not support auto reloading.
+module ApplicationCable
+ class Channel < ActionCable::Channel::Base
+ end
+end
diff --git a/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb b/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb
new file mode 100644
index 0000000000..93f28c4306
--- /dev/null
+++ b/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb
@@ -0,0 +1,6 @@
+# Be sure to restart your server when you modify this file. Action Cable runs in
+# a loop that does not support auto reloading.
+module ApplicationCable
+ class Connection < ActionCable::Connection::Base
+ end
+end
diff --git a/actioncable/lib/rails/generators/channel/templates/assets/cable.js b/actioncable/lib/rails/generators/channel/templates/assets/cable.js
new file mode 100644
index 0000000000..71ee1e66de
--- /dev/null
+++ b/actioncable/lib/rails/generators/channel/templates/assets/cable.js
@@ -0,0 +1,13 @@
+// Action Cable provides the framework to deal with WebSockets in Rails.
+// You can generate new channels where WebSocket features live using the rails generate channel command.
+//
+//= require action_cable
+//= require_self
+//= require_tree ./channels
+
+(function() {
+ this.App || (this.App = {});
+
+ App.cable = ActionCable.createConsumer();
+
+}).call(this);
diff --git a/actioncable/lib/rails/generators/channel/templates/assets/channel.js b/actioncable/lib/rails/generators/channel/templates/assets/channel.js
new file mode 100644
index 0000000000..ab0e68b11a
--- /dev/null
+++ b/actioncable/lib/rails/generators/channel/templates/assets/channel.js
@@ -0,0 +1,18 @@
+App.<%= class_name.underscore %> = App.cable.subscriptions.create("<%= class_name %>Channel", {
+ connected: function() {
+ // Called when the subscription is ready for use on the server
+ },
+
+ disconnected: function() {
+ // Called when the subscription has been terminated by the server
+ },
+
+ received: function(data) {
+ // Called when there's incoming data on the websocket for this channel
+ }<%= actions.any? ? ",\n" : '' %>
+<% actions.each do |action| -%>
+ <%=action %>: function() {
+ return this.perform('<%= action %>');
+ }<%= action == actions[-1] ? '' : ",\n" %>
+<% end -%>
+});
diff --git a/actioncable/lib/rails/generators/channel/templates/channel.rb b/actioncable/lib/rails/generators/channel/templates/channel.rb
index 6cf04ee61f..7bff3341c1 100644
--- a/actioncable/lib/rails/generators/channel/templates/channel.rb
+++ b/actioncable/lib/rails/generators/channel/templates/channel.rb
@@ -1,4 +1,4 @@
-# Be sure to restart your server when you modify this file. Action Cable runs in an EventMachine loop that does not support auto reloading.
+# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
<% module_namespacing do -%>
class <%= class_name %>Channel < ApplicationCable::Channel
def subscribed
diff --git a/actioncable/package.json b/actioncable/package.json
new file mode 100644
index 0000000000..37f82fa1ea
--- /dev/null
+++ b/actioncable/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "actioncable",
+ "version": "5.0.0-rc1",
+ "description": "WebSocket framework for Ruby on Rails.",
+ "main": "lib/assets/compiled/action_cable.js",
+ "files": [
+ "lib/assets/compiled/*.js"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "rails/rails"
+ },
+ "keywords": [
+ "websockets",
+ "actioncable",
+ "rails"
+ ],
+ "author": "David Heinemeier Hansson <david@loudthinking.com>",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/rails/rails/issues"
+ },
+ "homepage": "http://rubyonrails.org/"
+}
diff --git a/actioncable/test/channel/base_test.rb b/actioncable/test/channel/base_test.rb
index d41bf3064b..daa782eeb3 100644
--- a/actioncable/test/channel/base_test.rb
+++ b/actioncable/test/channel/base_test.rb
@@ -146,12 +146,12 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase
test "transmitting data" do
@channel.perform_action 'action' => :get_latest
- expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "message" => { "data" => "latest" }
+ expected = { "identifier" => "{id: 1}", "message" => { "data" => "latest" }}
assert_equal expected, @connection.last_transmission
end
test "subscription confirmation" do
- expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription"
+ expected = { "identifier" => "{id: 1}", "type" => "confirm_subscription" }
assert_equal expected, @connection.last_transmission
end
@@ -166,6 +166,81 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase
end
end
+ test "notification for perform_action" do
+ begin
+ events = []
+ ActiveSupport::Notifications.subscribe "perform_action.action_cable" do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ data = {'action' => :speak, 'content' => 'hello'}
+ @channel.perform_action data
+
+ assert_equal 1, events.length
+ assert_equal 'perform_action.action_cable', events[0].name
+ assert_equal 'ActionCable::Channel::BaseTest::ChatChannel', events[0].payload[:channel_class]
+ assert_equal :speak, events[0].payload[:action]
+ assert_equal data, events[0].payload[:data]
+ ensure
+ ActiveSupport::Notifications.unsubscribe "perform_action.action_cable"
+ end
+ end
+
+ test "notification for transmit" do
+ begin
+ events = []
+ ActiveSupport::Notifications.subscribe 'transmit.action_cable' do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ @channel.perform_action 'action' => :get_latest
+ expected_data = {data: 'latest'}
+
+ assert_equal 1, events.length
+ assert_equal 'transmit.action_cable', events[0].name
+ assert_equal 'ActionCable::Channel::BaseTest::ChatChannel', events[0].payload[:channel_class]
+ assert_equal expected_data, events[0].payload[:data]
+ assert_nil events[0].payload[:via]
+ ensure
+ ActiveSupport::Notifications.unsubscribe 'transmit.action_cable'
+ end
+ end
+
+ test "notification for transmit_subscription_confirmation" do
+ begin
+ events = []
+ ActiveSupport::Notifications.subscribe 'transmit_subscription_confirmation.action_cable' do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ @channel.stubs(:subscription_confirmation_sent?).returns(false)
+ @channel.send(:transmit_subscription_confirmation)
+
+ assert_equal 1, events.length
+ assert_equal 'transmit_subscription_confirmation.action_cable', events[0].name
+ assert_equal 'ActionCable::Channel::BaseTest::ChatChannel', events[0].payload[:channel_class]
+ ensure
+ ActiveSupport::Notifications.unsubscribe 'transmit_subscription_confirmation.action_cable'
+ end
+ end
+
+ test "notification for transmit_subscription_rejection" do
+ begin
+ events = []
+ ActiveSupport::Notifications.subscribe 'transmit_subscription_rejection.action_cable' do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ @channel.send(:transmit_subscription_rejection)
+
+ assert_equal 1, events.length
+ assert_equal 'transmit_subscription_rejection.action_cable', events[0].name
+ assert_equal 'ActionCable::Channel::BaseTest::ChatChannel', events[0].payload[:channel_class]
+ ensure
+ ActiveSupport::Notifications.unsubscribe 'transmit_subscription_rejection.action_cable'
+ end
+ end
+
private
def assert_logged(message)
old_logger = @connection.logger
diff --git a/actioncable/test/channel/periodic_timers_test.rb b/actioncable/test/channel/periodic_timers_test.rb
index 1590a12f09..03464003cf 100644
--- a/actioncable/test/channel/periodic_timers_test.rb
+++ b/actioncable/test/channel/periodic_timers_test.rb
@@ -1,12 +1,21 @@
require 'test_helper'
require 'stubs/test_connection'
require 'stubs/room'
+require 'active_support/time'
class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase
class ChatChannel < ActionCable::Channel::Base
- periodically -> { ping }, every: 5
+ # Method name arg
periodically :send_updates, every: 1
+ # Proc arg
+ periodically -> { ping }, every: 2
+
+ # Block arg
+ periodically every: 3 do
+ ping
+ end
+
private
def ping
end
@@ -19,22 +28,41 @@ class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase
test "periodic timers definition" do
timers = ChatChannel.periodic_timers
- assert_equal 2, timers.size
+ assert_equal 3, timers.size
- first_timer = timers[0]
- assert_kind_of Proc, first_timer[0]
- assert_equal 5, first_timer[1][:every]
+ timers.each_with_index do |timer, i|
+ assert_kind_of Proc, timer[0]
+ assert_equal i+1, timer[1][:every]
+ end
+ end
- second_timer = timers[1]
- assert_equal :send_updates, second_timer[0]
- assert_equal 1, second_timer[1][:every]
+ test 'disallow negative and zero periods' do
+ [ 0, 0.0, 0.seconds, -1, -1.seconds, 'foo', :foo, Object.new ].each do |invalid|
+ assert_raise ArgumentError, /Expected every:/ do
+ ChatChannel.periodically :send_updates, every: invalid
+ end
+ end
+ end
+
+ test 'disallow block and arg together' do
+ assert_raise ArgumentError, /not both/ do
+ ChatChannel.periodically(:send_updates, every: 1) { ping }
+ end
+ end
+
+ test 'disallow unknown args' do
+ [ 'send_updates', Object.new, nil ].each do |invalid|
+ assert_raise ArgumentError, /Expected a Symbol/ do
+ ChatChannel.periodically invalid, every: 1
+ end
+ end
end
test "timer start and stop" do
- EventMachine::PeriodicTimer.expects(:new).times(2).returns(true)
+ @connection.server.event_loop.expects(:timer).times(3).returns(stub(shutdown: nil))
channel = ChatChannel.new @connection, "{id: 1}", { id: 1 }
- channel.expects(:stop_periodic_timers).once
channel.unsubscribe_from_channel
+ assert_equal [], channel.send(:active_periodic_timers)
end
end
diff --git a/actioncable/test/channel/rejection_test.rb b/actioncable/test/channel/rejection_test.rb
index aa93396d44..15db57d6ba 100644
--- a/actioncable/test/channel/rejection_test.rb
+++ b/actioncable/test/channel/rejection_test.rb
@@ -18,7 +18,7 @@ class ActionCable::Channel::RejectionTest < ActiveSupport::TestCase
@connection.expects(:subscriptions).returns mock().tap { |m| m.expects(:remove_subscription).with instance_of(SecretChannel) }
@channel = SecretChannel.new @connection, "{id: 1}", { id: 1 }
- expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "reject_subscription"
+ expected = { "identifier" => "{id: 1}", "type" => "reject_subscription" }
assert_equal expected, @connection.last_transmission
end
diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb
index 3fa2b291b7..38543920d3 100644
--- a/actioncable/test/channel/stream_test.rb
+++ b/actioncable/test/channel/stream_test.rb
@@ -2,12 +2,20 @@ require 'test_helper'
require 'stubs/test_connection'
require 'stubs/room'
-class ActionCable::Channel::StreamTest < ActionCable::TestCase
+module ActionCable::StreamTests
+ class Connection < ActionCable::Connection::Base
+ attr_reader :websocket
+
+ def send_async(method, *args)
+ send method, *args
+ end
+ end
+
class ChatChannel < ActionCable::Channel::Base
def subscribed
if params[:id]
@room = Room.new params[:id]
- stream_from "test_room_#{@room.id}"
+ stream_from "test_room_#{@room.id}", coder: pick_coder(params[:coder])
end
end
@@ -15,66 +23,157 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase
transmit_subscription_confirmation
end
+ private def pick_coder(coder)
+ case coder
+ when nil, 'json'
+ ActiveSupport::JSON
+ when 'custom'
+ DummyEncoder
+ when 'none'
+ nil
+ end
+ end
end
- test "streaming start and stop" do
- run_in_eventmachine do
- connection = TestConnection.new
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
- channel = ChatChannel.new connection, "{id: 1}", { id: 1 }
+ module DummyEncoder
+ extend self
+ def encode(*) '{ "foo": "encoded" }' end
+ def decode(*) { foo: 'decoded' } end
+ end
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) }
- channel.unsubscribe_from_channel
+ class SymbolChannel < ActionCable::Channel::Base
+ def subscribed
+ stream_from :channel
end
end
- test "stream_for" do
- run_in_eventmachine do
- connection = TestConnection.new
- EM.next_tick do
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
+ class StreamTest < ActionCable::TestCase
+ test "streaming start and stop" do
+ run_in_eventmachine do
+ connection = TestConnection.new
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
+ channel = ChatChannel.new connection, "{id: 1}", { id: 1 }
+
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) }
+ channel.unsubscribe_from_channel
+ end
+ end
+
+ test "stream from non-string channel" do
+ run_in_eventmachine do
+ connection = TestConnection.new
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("channel", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
+ channel = SymbolChannel.new connection, ""
+
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) }
+ channel.unsubscribe_from_channel
end
+ end
- channel = ChatChannel.new connection, ""
- channel.stream_for Room.new(1)
+ test "stream_for" do
+ run_in_eventmachine do
+ connection = TestConnection.new
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:stream_tests:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
+
+ channel = ChatChannel.new connection, ""
+ channel.stream_for Room.new(1)
+ end
end
- end
- test "stream_from subscription confirmation" do
- EM.run do
- connection = TestConnection.new
+ test "stream_from subscription confirmation" do
+ run_in_eventmachine do
+ connection = TestConnection.new
- ChatChannel.new connection, "{id: 1}", { id: 1 }
- assert_nil connection.last_transmission
+ ChatChannel.new connection, "{id: 1}", { id: 1 }
+ assert_nil connection.last_transmission
- EM::Timer.new(0.1) do
- expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription"
- connection.transmit(expected)
+ wait_for_async
- assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation within 0.1s"
+ confirmation = { "identifier" => "{id: 1}", "type" => "confirm_subscription" }
+ connection.transmit(confirmation)
- EM.run_deferred_callbacks
- EM.stop
+ assert_equal confirmation, connection.last_transmission, "Did not receive subscription confirmation within 0.1s"
end
end
- end
- test "subscription confirmation should only be sent out once" do
- EM.run do
- connection = TestConnection.new
+ test "subscription confirmation should only be sent out once" do
+ run_in_eventmachine do
+ connection = TestConnection.new
- channel = ChatChannel.new connection, "test_channel"
- channel.send_confirmation
- channel.send_confirmation
+ channel = ChatChannel.new connection, "test_channel"
+ channel.send_confirmation
+ channel.send_confirmation
- EM.run_deferred_callbacks
+ wait_for_async
- expected = ActiveSupport::JSON.encode "identifier" => "test_channel", "type" => "confirm_subscription"
- assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation"
+ expected = { "identifier" => "test_channel", "type" => "confirm_subscription" }
+ assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation"
- assert_equal 1, connection.transmissions.size
- EM.stop
+ assert_equal 1, connection.transmissions.size
+ end
end
end
+ require 'action_cable/subscription_adapter/inline'
+
+ class UserCallbackChannel < ActionCable::Channel::Base
+ def subscribed
+ stream_from :channel do
+ Thread.current[:ran_callback] = true
+ end
+ end
+ end
+
+ class StreamEncodingTest < ActionCable::TestCase
+ setup do
+ @server = TestServer.new(subscription_adapter: ActionCable::SubscriptionAdapter::Inline)
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ end
+
+ test 'custom encoder' do
+ run_in_eventmachine do
+ connection = open_connection
+ subscribe_to connection, identifiers: { id: 1 }
+
+ connection.websocket.expects(:transmit)
+ @server.broadcast 'test_room_1', { foo: 'bar' }, coder: DummyEncoder
+ wait_for_async
+ wait_for_executor connection.server.worker_pool.executor
+ end
+ end
+
+ test "user supplied callbacks are run through the worker pool" do
+ run_in_eventmachine do
+ connection = open_connection
+ receive(connection, command: 'subscribe', channel: UserCallbackChannel.name, identifiers: { id: 1 })
+
+ @server.broadcast 'channel', {}
+ wait_for_async
+ refute Thread.current[:ran_callback], "User callback was not run through the worker pool"
+ end
+ end
+
+ private
+ def subscribe_to(connection, identifiers:)
+ receive connection, command: 'subscribe', identifiers: identifiers
+ end
+
+ def open_connection
+ env = Rack::MockRequest.env_for '/test', 'HTTP_HOST' => 'localhost', 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket', 'HTTP_ORIGIN' => 'http://rubyonrails.com'
+
+ Connection.new(@server, env).tap do |connection|
+ connection.process
+ assert connection.websocket.possible?
+
+ wait_for_async
+ assert connection.websocket.alive?
+ end
+ end
+
+ def receive(connection, command:, identifiers:, channel: 'ActionCable::StreamTests::ChatChannel')
+ identifier = JSON.generate(channel: channel, **identifiers)
+ connection.dispatch_websocket_message JSON.generate(command: command, identifier: identifier)
+ wait_for_async
+ end
+ end
end
diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb
new file mode 100644
index 0000000000..f4b4a53aea
--- /dev/null
+++ b/actioncable/test/client_test.rb
@@ -0,0 +1,275 @@
+require 'test_helper'
+require 'concurrent'
+
+require 'active_support/core_ext/hash/indifferent_access'
+require 'pathname'
+
+require 'faye/websocket'
+require 'json'
+
+class ClientTest < ActionCable::TestCase
+ WAIT_WHEN_EXPECTING_EVENT = 8
+ WAIT_WHEN_NOT_EXPECTING_EVENT = 0.5
+
+ class EchoChannel < ActionCable::Channel::Base
+ def subscribed
+ stream_from "global"
+ end
+
+ def unsubscribed
+ 'Goodbye from EchoChannel!'
+ end
+
+ def ding(data)
+ transmit(dong: data['message'])
+ end
+
+ def delay(data)
+ sleep 1
+ transmit(dong: data['message'])
+ end
+
+ def bulk(data)
+ ActionCable.server.broadcast "global", wide: data['message']
+ end
+ end
+
+ def setup
+ ActionCable.instance_variable_set(:@server, nil)
+ server = ActionCable.server
+ server.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
+
+ server.config.cable = { adapter: 'async' }.with_indifferent_access
+ server.config.use_faye = ENV['FAYE'].present?
+
+ # and now the "real" setup for our test:
+ server.config.disable_request_forgery_protection = true
+
+ Thread.new { EventMachine.run } unless EventMachine.reactor_running?
+ Thread.pass until EventMachine.reactor_running?
+
+ # faye-websocket is warning-rich
+ @previous_verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ def teardown
+ $VERBOSE = @previous_verbose
+ end
+
+ def with_puma_server(rack_app = ActionCable.server, port = 3099)
+ server = ::Puma::Server.new(rack_app, ::Puma::Events.strings)
+ server.add_tcp_listener '127.0.0.1', port
+ server.min_threads = 1
+ server.max_threads = 4
+
+ t = Thread.new { server.run.join }
+ yield port
+
+ ensure
+ server.stop(true) if server
+ t.join if t
+ end
+
+ class SyncClient
+ attr_reader :pings
+
+ def initialize(port)
+ @ws = Faye::WebSocket::Client.new("ws://127.0.0.1:#{port}/")
+ @messages = Queue.new
+ @closed = Concurrent::Event.new
+ @has_messages = Concurrent::Semaphore.new(0)
+ @pings = 0
+
+ open = Concurrent::Event.new
+ error = nil
+
+ @ws.on(:error) do |event|
+ if open.set?
+ @messages << RuntimeError.new(event.message)
+ else
+ error = event.message
+ open.set
+ end
+ end
+
+ @ws.on(:open) do |event|
+ open.set
+ end
+
+ @ws.on(:message) do |event|
+ message = JSON.parse(event.data)
+ if message['type'] == 'ping'
+ @pings += 1
+ else
+ @messages << message
+ @has_messages.release
+ end
+ end
+
+ @ws.on(:close) do |event|
+ @closed.set
+ end
+
+ open.wait(WAIT_WHEN_EXPECTING_EVENT)
+ raise error if error
+ end
+
+ def read_message
+ @has_messages.try_acquire(1, WAIT_WHEN_EXPECTING_EVENT)
+
+ msg = @messages.pop(true)
+ raise msg if msg.is_a?(Exception)
+
+ msg
+ end
+
+ def read_messages(expected_size = 0)
+ list = []
+ loop do
+ if @has_messages.try_acquire(1, list.size < expected_size ? WAIT_WHEN_EXPECTING_EVENT : WAIT_WHEN_NOT_EXPECTING_EVENT)
+ msg = @messages.pop(true)
+ raise msg if msg.is_a?(Exception)
+
+ list << msg
+ else
+ break
+ end
+ end
+ list
+ end
+
+ def send_message(message)
+ @ws.send(JSON.generate(message))
+ end
+
+ def close
+ sleep WAIT_WHEN_NOT_EXPECTING_EVENT
+
+ unless @messages.empty?
+ raise "#{@messages.size} messages unprocessed"
+ end
+
+ @ws.close
+ wait_for_close
+ end
+
+ def wait_for_close
+ @closed.wait(WAIT_WHEN_EXPECTING_EVENT)
+ end
+
+ def closed?
+ @closed.set?
+ end
+ end
+
+ def faye_client(port)
+ SyncClient.new(port)
+ end
+
+ def test_single_client
+ with_puma_server do |port|
+ c = faye_client(port)
+ assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
+ c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'ClientTest::EchoChannel')
+ assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
+ c.send_message command: 'message', identifier: JSON.generate(channel: 'ClientTest::EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello')
+ assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "message"=>{"dong"=>"hello"}}, c.read_message)
+ c.close
+ end
+ end
+
+ def test_interacting_clients
+ with_puma_server do |port|
+ clients = 10.times.map { faye_client(port) }
+
+ barrier_1 = Concurrent::CyclicBarrier.new(clients.size)
+ barrier_2 = Concurrent::CyclicBarrier.new(clients.size)
+
+ clients.map {|c| Concurrent::Future.execute {
+ assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
+ c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'ClientTest::EchoChannel')
+ assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message)
+ c.send_message command: 'message', identifier: JSON.generate(channel: 'ClientTest::EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello')
+ assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message)
+ barrier_1.wait WAIT_WHEN_EXPECTING_EVENT
+ c.send_message command: 'message', identifier: JSON.generate(channel: 'ClientTest::EchoChannel'), data: JSON.generate(action: 'bulk', message: 'hello')
+ barrier_2.wait WAIT_WHEN_EXPECTING_EVENT
+ assert_equal clients.size, c.read_messages(clients.size).size
+ } }.each(&:wait!)
+
+ clients.map {|c| Concurrent::Future.execute { c.close } }.each(&:wait!)
+ end
+ end
+
+ def test_many_clients
+ with_puma_server do |port|
+ clients = 100.times.map { faye_client(port) }
+
+ clients.map {|c| Concurrent::Future.execute {
+ assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
+ c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'ClientTest::EchoChannel')
+ assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message)
+ c.send_message command: 'message', identifier: JSON.generate(channel: 'ClientTest::EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello')
+ assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message)
+ } }.each(&:wait!)
+
+ clients.map {|c| Concurrent::Future.execute { c.close } }.each(&:wait!)
+ end
+ end
+
+ def test_disappearing_client
+ with_puma_server do |port|
+ c = faye_client(port)
+ assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
+ c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'ClientTest::EchoChannel')
+ assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
+ c.send_message command: 'message', identifier: JSON.generate(channel: 'ClientTest::EchoChannel'), data: JSON.generate(action: 'delay', message: 'hello')
+ c.close # disappear before write
+
+ c = faye_client(port)
+ assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
+ c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'ClientTest::EchoChannel')
+ assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
+ c.send_message command: 'message', identifier: JSON.generate(channel: 'ClientTest::EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello')
+ assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message)
+ c.close # disappear before read
+ end
+ end
+
+ def test_unsubscribe_client
+ with_puma_server do |port|
+ app = ActionCable.server
+ identifier = JSON.generate(channel: 'ClientTest::EchoChannel')
+
+ c = faye_client(port)
+ assert_equal({"type" => "welcome"}, c.read_message)
+ c.send_message command: 'subscribe', identifier: identifier
+ assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
+ assert_equal(1, app.connections.count)
+ assert(app.remote_connections.where(identifier: identifier))
+
+ subscriptions = app.connections.first.subscriptions.send(:subscriptions)
+ assert_not_equal 0, subscriptions.size, 'Missing EchoChannel subscription'
+ channel = subscriptions.first[1]
+ channel.expects(:unsubscribed)
+ c.close
+ sleep 0.1 # Data takes a moment to process
+
+ # All data is removed: No more connection or subscription information!
+ assert_equal(0, app.connections.count)
+ end
+ end
+
+ def test_server_restart
+ with_puma_server do |port|
+ c = faye_client(port)
+ assert_equal({"type" => "welcome"}, c.read_message)
+ c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'ClientTest::EchoChannel')
+ assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
+
+ ActionCable.server.restart
+ c.wait_for_close
+ assert c.closed?
+ end
+ end
+end
diff --git a/actioncable/test/connection/authorization_test.rb b/actioncable/test/connection/authorization_test.rb
index 87d0e79ef3..a0506cb9c0 100644
--- a/actioncable/test/connection/authorization_test.rb
+++ b/actioncable/test/connection/authorization_test.rb
@@ -20,7 +20,7 @@ class ActionCable::Connection::AuthorizationTest < ActionCable::TestCase
server.config.allowed_request_origins = %w( http://rubyonrails.com )
env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket',
- 'HTTP_ORIGIN' => 'http://rubyonrails.com'
+ 'HTTP_HOST' => 'localhost', 'HTTP_ORIGIN' => 'http://rubyonrails.com'
connection = Connection.new(server, env)
connection.websocket.expects(:close)
diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb
index 182562db82..d7e1041e68 100644
--- a/actioncable/test/connection/base_test.rb
+++ b/actioncable/test/connection/base_test.rb
@@ -1,5 +1,6 @@
require 'test_helper'
require 'stubs/test_server'
+require 'active_support/core_ext/object/json'
class ActionCable::Connection::BaseTest < ActionCable::TestCase
class Connection < ActionCable::Connection::Base
@@ -37,6 +38,8 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase
connection.process
assert connection.websocket.possible?
+
+ wait_for_async
assert connection.websocket.alive?
end
end
@@ -53,16 +56,15 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase
test "on connection open" do
run_in_eventmachine do
connection = open_connection
- connection.process
- connection.websocket.expects(:transmit).with(regexp_matches(/\_ping/))
+ connection.websocket.expects(:transmit).with({ type: "welcome" }.to_json)
connection.message_buffer.expects(:process!)
- # Allow EM to run on_open callback
- EM.next_tick do
- assert_equal [ connection ], @server.connections
- assert connection.connected
- end
+ connection.process
+ wait_for_async
+
+ assert_equal [ connection ], @server.connections
+ assert connection.connected
end
end
@@ -72,12 +74,12 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase
connection.process
# Setup the connection
- EventMachine.stubs(:add_periodic_timer).returns(true)
- connection.send :on_open
+ connection.server.stubs(:timer).returns(true)
+ connection.send :handle_open
assert connection.connected
connection.subscriptions.expects(:unsubscribe_from_all)
- connection.send :on_close
+ connection.send :handle_close
assert ! connection.connected
assert_equal [], @server.connections
@@ -107,10 +109,30 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase
end
end
+ test "rejecting a connection causes a 404" do
+ run_in_eventmachine do
+ class CallMeMaybe
+ def call(*)
+ raise 'Do not call me!'
+ end
+ end
+
+ env = Rack::MockRequest.env_for(
+ "/test",
+ { 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket',
+ 'HTTP_HOST' => 'localhost', 'HTTP_ORIGIN' => 'http://rubyonrails.org', 'rack.hijack' => CallMeMaybe.new }
+ )
+
+ connection = ActionCable::Connection::Base.new(@server, env)
+ response = connection.process
+ assert_equal 404, response[0]
+ end
+ end
+
private
def open_connection
env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket',
- 'HTTP_ORIGIN' => 'http://rubyonrails.com'
+ 'HTTP_HOST' => 'localhost', 'HTTP_ORIGIN' => 'http://rubyonrails.com'
Connection.new(@server, env)
end
diff --git a/actioncable/test/connection/client_socket_test.rb b/actioncable/test/connection/client_socket_test.rb
new file mode 100644
index 0000000000..4af071b4da
--- /dev/null
+++ b/actioncable/test/connection/client_socket_test.rb
@@ -0,0 +1,64 @@
+require 'test_helper'
+require 'stubs/test_server'
+
+class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ attr_reader :connected, :websocket, :errors
+
+ def initialize(*)
+ super
+ @errors = []
+ end
+
+ def connect
+ @connected = true
+ end
+
+ def disconnect
+ @connected = false
+ end
+
+ def send_async(method, *args)
+ send method, *args
+ end
+
+ def on_error(message)
+ @errors << message
+ end
+ end
+
+ setup do
+ @server = TestServer.new
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ end
+
+ test 'delegate socket errors to on_error handler' do
+ skip if ENV['FAYE'].present?
+
+ run_in_eventmachine do
+ connection = open_connection
+
+ # Internal hax = :(
+ client = connection.websocket.send(:websocket)
+ client.instance_variable_get('@stream').expects(:write).raises('foo')
+ client.expects(:client_gone).never
+
+ client.write('boo')
+ assert_equal %w[ foo ], connection.errors
+ end
+ end
+
+ private
+ def open_connection
+ env = Rack::MockRequest.env_for '/test',
+ 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket',
+ 'HTTP_HOST' => 'localhost', 'HTTP_ORIGIN' => 'http://rubyonrails.com'
+ env['rack.hijack'] = -> { env['rack.hijack_io'] = StringIO.new }
+
+ Connection.new(@server, env).tap do |connection|
+ connection.process
+ connection.send :handle_open
+ assert connection.connected
+ end
+ end
+end
diff --git a/actioncable/test/connection/cross_site_forgery_test.rb b/actioncable/test/connection/cross_site_forgery_test.rb
index a29f65fb97..2d516b0533 100644
--- a/actioncable/test/connection/cross_site_forgery_test.rb
+++ b/actioncable/test/connection/cross_site_forgery_test.rb
@@ -76,6 +76,6 @@ class ActionCable::Connection::CrossSiteForgeryTest < ActionCable::TestCase
def env_for_origin(origin)
Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket', 'SERVER_NAME' => HOST,
- 'HTTP_ORIGIN' => origin
+ 'HTTP_HOST' => HOST, 'HTTP_ORIGIN' => origin
end
end
diff --git a/actioncable/test/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb
index a110dfdee0..b48d9af809 100644
--- a/actioncable/test/connection/identifier_test.rb
+++ b/actioncable/test/connection/identifier_test.rb
@@ -40,8 +40,7 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
open_connection_with_stubbed_pubsub
@connection.websocket.expects(:close)
- message = ActiveSupport::JSON.encode('type' => 'disconnect')
- @connection.process_internal_message message
+ @connection.process_internal_message 'type' => 'disconnect'
end
end
@@ -50,8 +49,7 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
open_connection_with_stubbed_pubsub
@connection.websocket.expects(:close).never
- message = ActiveSupport::JSON.encode('type' => 'unknown')
- @connection.process_internal_message message
+ @connection.process_internal_message 'type' => 'unknown'
end
end
@@ -64,14 +62,14 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
end
def open_connection(server:)
- env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
+ env = Rack::MockRequest.env_for "/test", 'HTTP_HOST' => 'localhost', 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
@connection = Connection.new(server, env)
@connection.process
- @connection.send :on_open
+ @connection.send :handle_open
end
def close_connection
- @connection.send :on_close
+ @connection.send :handle_close
end
end
diff --git a/actioncable/test/connection/multiple_identifiers_test.rb b/actioncable/test/connection/multiple_identifiers_test.rb
index 55a9f96cb3..484e73bb30 100644
--- a/actioncable/test/connection/multiple_identifiers_test.rb
+++ b/actioncable/test/connection/multiple_identifiers_test.rb
@@ -28,14 +28,14 @@ class ActionCable::Connection::MultipleIdentifiersTest < ActionCable::TestCase
end
def open_connection(server:)
- env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
+ env = Rack::MockRequest.env_for "/test", 'HTTP_HOST' => 'localhost', 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
@connection = Connection.new(server, env)
@connection.process
- @connection.send :on_open
+ @connection.send :handle_open
end
def close_connection
- @connection.send :on_close
+ @connection.send :handle_close
end
end
diff --git a/actioncable/test/connection/stream_test.rb b/actioncable/test/connection/stream_test.rb
new file mode 100644
index 0000000000..a7a61d8d6f
--- /dev/null
+++ b/actioncable/test/connection/stream_test.rb
@@ -0,0 +1,66 @@
+require 'test_helper'
+require 'stubs/test_server'
+
+class ActionCable::Connection::StreamTest < ActionCable::TestCase
+ class Connection < ActionCable::Connection::Base
+ attr_reader :connected, :websocket, :errors
+
+ def initialize(*)
+ super
+ @errors = []
+ end
+
+ def connect
+ @connected = true
+ end
+
+ def disconnect
+ @connected = false
+ end
+
+ def send_async(method, *args)
+ send method, *args
+ end
+
+ def on_error(message)
+ @errors << message
+ end
+ end
+
+ setup do
+ @server = TestServer.new
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ end
+
+ [ EOFError, Errno::ECONNRESET ].each do |closed_exception|
+ test "closes socket on #{closed_exception}" do
+ skip if ENV['FAYE'].present?
+
+ run_in_eventmachine do
+ connection = open_connection
+
+ # Internal hax = :(
+ client = connection.websocket.send(:websocket)
+ client.instance_variable_get('@stream').instance_variable_get('@rack_hijack_io').expects(:write).raises(closed_exception, 'foo')
+ client.expects(:client_gone)
+
+ client.write('boo')
+ assert_equal [], connection.errors
+ end
+ end
+ end
+
+ private
+ def open_connection
+ env = Rack::MockRequest.env_for '/test',
+ 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket',
+ 'HTTP_HOST' => 'localhost', 'HTTP_ORIGIN' => 'http://rubyonrails.com'
+ env['rack.hijack'] = -> { env['rack.hijack_io'] = StringIO.new }
+
+ Connection.new(@server, env).tap do |connection|
+ connection.process
+ connection.send :handle_open
+ assert connection.connected
+ end
+ end
+end
diff --git a/actioncable/test/connection/string_identifier_test.rb b/actioncable/test/connection/string_identifier_test.rb
index 9d0bda83ef..eca0c31060 100644
--- a/actioncable/test/connection/string_identifier_test.rb
+++ b/actioncable/test/connection/string_identifier_test.rb
@@ -30,7 +30,7 @@ class ActionCable::Connection::StringIdentifierTest < ActionCable::TestCase
end
def open_connection
- env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
+ env = Rack::MockRequest.env_for "/test", 'HTTP_HOST' => 'localhost', 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
@connection = Connection.new(@server, env)
@connection.process
diff --git a/actioncable/test/connection/subscriptions_test.rb b/actioncable/test/connection/subscriptions_test.rb
index 62e41484fe..a5b1e5dcf3 100644
--- a/actioncable/test/connection/subscriptions_test.rb
+++ b/actioncable/test/connection/subscriptions_test.rb
@@ -24,7 +24,6 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
setup do
@server = TestServer.new
- @server.stubs(:channel_classes).returns(ChatChannel.name => ChatChannel)
@chat_identifier = ActiveSupport::JSON.encode(id: 1, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel')
end
@@ -82,7 +81,7 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
end
end
- test "unsubscrib from all" do
+ test "unsubscribe from all" do
run_in_eventmachine do
setup_connection
@@ -107,7 +106,7 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
end
def setup_connection
- env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
+ env = Rack::MockRequest.env_for "/test", 'HTTP_HOST' => 'localhost', 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
@connection = Connection.new(@server, env)
@subscriptions = ActionCable::Connection::Subscriptions.new(@connection)
diff --git a/actioncable/test/javascript/src/test.coffee b/actioncable/test/javascript/src/test.coffee
new file mode 100644
index 0000000000..eb95fb2604
--- /dev/null
+++ b/actioncable/test/javascript/src/test.coffee
@@ -0,0 +1,3 @@
+#= require action_cable
+#= require ./test_helpers
+#= require_tree ./unit
diff --git a/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee b/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee
new file mode 100644
index 0000000000..6b145dede8
--- /dev/null
+++ b/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee
@@ -0,0 +1,37 @@
+#= require mock-socket
+
+{TestHelpers} = ActionCable
+
+TestHelpers.consumerTest = (name, options = {}, callback) ->
+ unless callback?
+ callback = options
+ options = {}
+
+ options.url ?= TestHelpers.testURL
+
+ QUnit.test name, (assert) ->
+ doneAsync = assert.async()
+
+ ActionCable.WebSocket = MockWebSocket
+ server = new MockServer options.url
+ consumer = ActionCable.createConsumer(options.url)
+
+ server.on "connection", ->
+ clients = server.clients()
+ assert.equal clients.length, 1
+ assert.equal clients[0].readyState, WebSocket.OPEN
+
+ done = ->
+ consumer.disconnect()
+ server.close()
+ doneAsync()
+
+ testData = {assert, consumer, server, done}
+
+ if options.connect is false
+ callback(testData)
+ else
+ server.on "connection", ->
+ testData.client = server.clients()[0]
+ callback(testData)
+ consumer.connect()
diff --git a/actioncable/test/javascript/src/test_helpers/index.coffee b/actioncable/test/javascript/src/test_helpers/index.coffee
new file mode 100644
index 0000000000..d36524d9cc
--- /dev/null
+++ b/actioncable/test/javascript/src/test_helpers/index.coffee
@@ -0,0 +1,8 @@
+#= require_self
+#= require_tree .
+
+ActionCable.TestHelpers =
+ testURL: "ws://cable.example.com/"
+
+originalWebSocket = ActionCable.WebSocket
+QUnit.testDone -> ActionCable.WebSocket = originalWebSocket
diff --git a/actioncable/test/javascript/src/unit/action_cable_test.coffee b/actioncable/test/javascript/src/unit/action_cable_test.coffee
new file mode 100644
index 0000000000..3944f3a7f6
--- /dev/null
+++ b/actioncable/test/javascript/src/unit/action_cable_test.coffee
@@ -0,0 +1,41 @@
+{module, test} = QUnit
+{testURL} = ActionCable.TestHelpers
+
+module "ActionCable", ->
+ module "Adapters", ->
+ module "WebSocket", ->
+ test "default is window.WebSocket", (assert) ->
+ assert.equal ActionCable.WebSocket, window.WebSocket
+
+ test "configurable", (assert) ->
+ ActionCable.WebSocket = ""
+ assert.equal ActionCable.WebSocket, ""
+
+ module "logger", ->
+ test "default is window.console", (assert) ->
+ assert.equal ActionCable.logger, window.console
+
+ test "configurable", (assert) ->
+ ActionCable.logger = ""
+ assert.equal ActionCable.logger, ""
+
+ module "#createConsumer", ->
+ test "uses specified URL", (assert) ->
+ consumer = ActionCable.createConsumer(testURL)
+ assert.equal consumer.url, testURL
+
+ test "uses default URL", (assert) ->
+ pattern = ///#{ActionCable.INTERNAL.default_mount_path}$///
+ consumer = ActionCable.createConsumer()
+ assert.ok pattern.test(consumer.url), "Expected #{consumer.url} to match #{pattern}"
+
+ test "uses URL from meta tag", (assert) ->
+ element = document.createElement("meta")
+ element.setAttribute("name", "action-cable-url")
+ element.setAttribute("content", testURL)
+
+ document.head.appendChild(element)
+ consumer = ActionCable.createConsumer()
+ document.head.removeChild(element)
+
+ assert.equal consumer.url, testURL
diff --git a/actioncable/test/javascript/src/unit/consumer_test.coffee b/actioncable/test/javascript/src/unit/consumer_test.coffee
new file mode 100644
index 0000000000..cf8a592255
--- /dev/null
+++ b/actioncable/test/javascript/src/unit/consumer_test.coffee
@@ -0,0 +1,11 @@
+{module, test} = QUnit
+{consumerTest} = ActionCable.TestHelpers
+
+module "ActionCable.Consumer", ->
+ consumerTest "#connect", connect: false, ({consumer, server, done}) ->
+ server.on("connection", done)
+ consumer.connect()
+
+ consumerTest "#disconnect", ({consumer, client, done}) ->
+ client.addEventListener("close", done)
+ consumer.disconnect()
diff --git a/actioncable/test/javascript/vendor/mock-socket.js b/actioncable/test/javascript/vendor/mock-socket.js
new file mode 100644
index 0000000000..b465c8b53f
--- /dev/null
+++ b/actioncable/test/javascript/vendor/mock-socket.js
@@ -0,0 +1,4533 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/*!
+ * URI.js - Mutating URLs
+ * IPv6 Support
+ *
+ * Version: 1.17.0
+ *
+ * Author: Rodney Rehm
+ * Web: http://medialize.github.io/URI.js/
+ *
+ * Licensed under
+ * MIT License http://www.opensource.org/licenses/mit-license
+ * GPL v3 http://opensource.org/licenses/GPL-3.0
+ *
+ */
+
+(function (root, factory) {
+ 'use strict';
+ // https://github.com/umdjs/umd/blob/master/returnExports.js
+ if (typeof exports === 'object') {
+ // Node
+ module.exports = factory();
+ } else if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(factory);
+ } else {
+ // Browser globals (root is window)
+ root.IPv6 = factory(root);
+ }
+}(this, function (root) {
+ 'use strict';
+
+ /*
+ var _in = "fe80:0000:0000:0000:0204:61ff:fe9d:f156";
+ var _out = IPv6.best(_in);
+ var _expected = "fe80::204:61ff:fe9d:f156";
+
+ console.log(_in, _out, _expected, _out === _expected);
+ */
+
+ // save current IPv6 variable, if any
+ var _IPv6 = root && root.IPv6;
+
+ function bestPresentation(address) {
+ // based on:
+ // Javascript to test an IPv6 address for proper format, and to
+ // present the "best text representation" according to IETF Draft RFC at
+ // http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04
+ // 8 Feb 2010 Rich Brown, Dartware, LLC
+ // Please feel free to use this code as long as you provide a link to
+ // http://www.intermapper.com
+ // http://intermapper.com/support/tools/IPV6-Validator.aspx
+ // http://download.dartware.com/thirdparty/ipv6validator.js
+
+ var _address = address.toLowerCase();
+ var segments = _address.split(':');
+ var length = segments.length;
+ var total = 8;
+
+ // trim colons (:: or ::a:b:c… or …a:b:c::)
+ if (segments[0] === '' && segments[1] === '' && segments[2] === '') {
+ // must have been ::
+ // remove first two items
+ segments.shift();
+ segments.shift();
+ } else if (segments[0] === '' && segments[1] === '') {
+ // must have been ::xxxx
+ // remove the first item
+ segments.shift();
+ } else if (segments[length - 1] === '' && segments[length - 2] === '') {
+ // must have been xxxx::
+ segments.pop();
+ }
+
+ length = segments.length;
+
+ // adjust total segments for IPv4 trailer
+ if (segments[length - 1].indexOf('.') !== -1) {
+ // found a "." which means IPv4
+ total = 7;
+ }
+
+ // fill empty segments them with "0000"
+ var pos;
+ for (pos = 0; pos < length; pos++) {
+ if (segments[pos] === '') {
+ break;
+ }
+ }
+
+ if (pos < total) {
+ segments.splice(pos, 1, '0000');
+ while (segments.length < total) {
+ segments.splice(pos, 0, '0000');
+ }
+
+ length = segments.length;
+ }
+
+ // strip leading zeros
+ var _segments;
+ for (var i = 0; i < total; i++) {
+ _segments = segments[i].split('');
+ for (var j = 0; j < 3 ; j++) {
+ if (_segments[0] === '0' && _segments.length > 1) {
+ _segments.splice(0,1);
+ } else {
+ break;
+ }
+ }
+
+ segments[i] = _segments.join('');
+ }
+
+ // find longest sequence of zeroes and coalesce them into one segment
+ var best = -1;
+ var _best = 0;
+ var _current = 0;
+ var current = -1;
+ var inzeroes = false;
+ // i; already declared
+
+ for (i = 0; i < total; i++) {
+ if (inzeroes) {
+ if (segments[i] === '0') {
+ _current += 1;
+ } else {
+ inzeroes = false;
+ if (_current > _best) {
+ best = current;
+ _best = _current;
+ }
+ }
+ } else {
+ if (segments[i] === '0') {
+ inzeroes = true;
+ current = i;
+ _current = 1;
+ }
+ }
+ }
+
+ if (_current > _best) {
+ best = current;
+ _best = _current;
+ }
+
+ if (_best > 1) {
+ segments.splice(best, _best, '');
+ }
+
+ length = segments.length;
+
+ // assemble remaining segments
+ var result = '';
+ if (segments[0] === '') {
+ result = ':';
+ }
+
+ for (i = 0; i < length; i++) {
+ result += segments[i];
+ if (i === length - 1) {
+ break;
+ }
+
+ result += ':';
+ }
+
+ if (segments[length - 1] === '') {
+ result += ':';
+ }
+
+ return result;
+ }
+
+ function noConflict() {
+ /*jshint validthis: true */
+ if (root.IPv6 === this) {
+ root.IPv6 = _IPv6;
+ }
+
+ return this;
+ }
+
+ return {
+ best: bestPresentation,
+ noConflict: noConflict
+ };
+}));
+
+},{}],2:[function(require,module,exports){
+/*!
+ * URI.js - Mutating URLs
+ * Second Level Domain (SLD) Support
+ *
+ * Version: 1.17.0
+ *
+ * Author: Rodney Rehm
+ * Web: http://medialize.github.io/URI.js/
+ *
+ * Licensed under
+ * MIT License http://www.opensource.org/licenses/mit-license
+ * GPL v3 http://opensource.org/licenses/GPL-3.0
+ *
+ */
+
+(function (root, factory) {
+ 'use strict';
+ // https://github.com/umdjs/umd/blob/master/returnExports.js
+ if (typeof exports === 'object') {
+ // Node
+ module.exports = factory();
+ } else if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(factory);
+ } else {
+ // Browser globals (root is window)
+ root.SecondLevelDomains = factory(root);
+ }
+}(this, function (root) {
+ 'use strict';
+
+ // save current SecondLevelDomains variable, if any
+ var _SecondLevelDomains = root && root.SecondLevelDomains;
+
+ var SLD = {
+ // list of known Second Level Domains
+ // converted list of SLDs from https://github.com/gavingmiller/second-level-domains
+ // ----
+ // publicsuffix.org is more current and actually used by a couple of browsers internally.
+ // downside is it also contains domains like "dyndns.org" - which is fine for the security
+ // issues browser have to deal with (SOP for cookies, etc) - but is way overboard for URI.js
+ // ----
+ list: {
+ 'ac':' com gov mil net org ',
+ 'ae':' ac co gov mil name net org pro sch ',
+ 'af':' com edu gov net org ',
+ 'al':' com edu gov mil net org ',
+ 'ao':' co ed gv it og pb ',
+ 'ar':' com edu gob gov int mil net org tur ',
+ 'at':' ac co gv or ',
+ 'au':' asn com csiro edu gov id net org ',
+ 'ba':' co com edu gov mil net org rs unbi unmo unsa untz unze ',
+ 'bb':' biz co com edu gov info net org store tv ',
+ 'bh':' biz cc com edu gov info net org ',
+ 'bn':' com edu gov net org ',
+ 'bo':' com edu gob gov int mil net org tv ',
+ 'br':' adm adv agr am arq art ato b bio blog bmd cim cng cnt com coop ecn edu eng esp etc eti far flog fm fnd fot fst g12 ggf gov imb ind inf jor jus lel mat med mil mus net nom not ntr odo org ppg pro psc psi qsl rec slg srv tmp trd tur tv vet vlog wiki zlg ',
+ 'bs':' com edu gov net org ',
+ 'bz':' du et om ov rg ',
+ 'ca':' ab bc mb nb nf nl ns nt nu on pe qc sk yk ',
+ 'ck':' biz co edu gen gov info net org ',
+ 'cn':' ac ah bj com cq edu fj gd gov gs gx gz ha hb he hi hl hn jl js jx ln mil net nm nx org qh sc sd sh sn sx tj tw xj xz yn zj ',
+ 'co':' com edu gov mil net nom org ',
+ 'cr':' ac c co ed fi go or sa ',
+ 'cy':' ac biz com ekloges gov ltd name net org parliament press pro tm ',
+ 'do':' art com edu gob gov mil net org sld web ',
+ 'dz':' art asso com edu gov net org pol ',
+ 'ec':' com edu fin gov info med mil net org pro ',
+ 'eg':' com edu eun gov mil name net org sci ',
+ 'er':' com edu gov ind mil net org rochest w ',
+ 'es':' com edu gob nom org ',
+ 'et':' biz com edu gov info name net org ',
+ 'fj':' ac biz com info mil name net org pro ',
+ 'fk':' ac co gov net nom org ',
+ 'fr':' asso com f gouv nom prd presse tm ',
+ 'gg':' co net org ',
+ 'gh':' com edu gov mil org ',
+ 'gn':' ac com gov net org ',
+ 'gr':' com edu gov mil net org ',
+ 'gt':' com edu gob ind mil net org ',
+ 'gu':' com edu gov net org ',
+ 'hk':' com edu gov idv net org ',
+ 'hu':' 2000 agrar bolt casino city co erotica erotika film forum games hotel info ingatlan jogasz konyvelo lakas media news org priv reklam sex shop sport suli szex tm tozsde utazas video ',
+ 'id':' ac co go mil net or sch web ',
+ 'il':' ac co gov idf k12 muni net org ',
+ 'in':' ac co edu ernet firm gen gov i ind mil net nic org res ',
+ 'iq':' com edu gov i mil net org ',
+ 'ir':' ac co dnssec gov i id net org sch ',
+ 'it':' edu gov ',
+ 'je':' co net org ',
+ 'jo':' com edu gov mil name net org sch ',
+ 'jp':' ac ad co ed go gr lg ne or ',
+ 'ke':' ac co go info me mobi ne or sc ',
+ 'kh':' com edu gov mil net org per ',
+ 'ki':' biz com de edu gov info mob net org tel ',
+ 'km':' asso com coop edu gouv k medecin mil nom notaires pharmaciens presse tm veterinaire ',
+ 'kn':' edu gov net org ',
+ 'kr':' ac busan chungbuk chungnam co daegu daejeon es gangwon go gwangju gyeongbuk gyeonggi gyeongnam hs incheon jeju jeonbuk jeonnam k kg mil ms ne or pe re sc seoul ulsan ',
+ 'kw':' com edu gov net org ',
+ 'ky':' com edu gov net org ',
+ 'kz':' com edu gov mil net org ',
+ 'lb':' com edu gov net org ',
+ 'lk':' assn com edu gov grp hotel int ltd net ngo org sch soc web ',
+ 'lr':' com edu gov net org ',
+ 'lv':' asn com conf edu gov id mil net org ',
+ 'ly':' com edu gov id med net org plc sch ',
+ 'ma':' ac co gov m net org press ',
+ 'mc':' asso tm ',
+ 'me':' ac co edu gov its net org priv ',
+ 'mg':' com edu gov mil nom org prd tm ',
+ 'mk':' com edu gov inf name net org pro ',
+ 'ml':' com edu gov net org presse ',
+ 'mn':' edu gov org ',
+ 'mo':' com edu gov net org ',
+ 'mt':' com edu gov net org ',
+ 'mv':' aero biz com coop edu gov info int mil museum name net org pro ',
+ 'mw':' ac co com coop edu gov int museum net org ',
+ 'mx':' com edu gob net org ',
+ 'my':' com edu gov mil name net org sch ',
+ 'nf':' arts com firm info net other per rec store web ',
+ 'ng':' biz com edu gov mil mobi name net org sch ',
+ 'ni':' ac co com edu gob mil net nom org ',
+ 'np':' com edu gov mil net org ',
+ 'nr':' biz com edu gov info net org ',
+ 'om':' ac biz co com edu gov med mil museum net org pro sch ',
+ 'pe':' com edu gob mil net nom org sld ',
+ 'ph':' com edu gov i mil net ngo org ',
+ 'pk':' biz com edu fam gob gok gon gop gos gov net org web ',
+ 'pl':' art bialystok biz com edu gda gdansk gorzow gov info katowice krakow lodz lublin mil net ngo olsztyn org poznan pwr radom slupsk szczecin torun warszawa waw wroc wroclaw zgora ',
+ 'pr':' ac biz com edu est gov info isla name net org pro prof ',
+ 'ps':' com edu gov net org plo sec ',
+ 'pw':' belau co ed go ne or ',
+ 'ro':' arts com firm info nom nt org rec store tm www ',
+ 'rs':' ac co edu gov in org ',
+ 'sb':' com edu gov net org ',
+ 'sc':' com edu gov net org ',
+ 'sh':' co com edu gov net nom org ',
+ 'sl':' com edu gov net org ',
+ 'st':' co com consulado edu embaixada gov mil net org principe saotome store ',
+ 'sv':' com edu gob org red ',
+ 'sz':' ac co org ',
+ 'tr':' av bbs bel biz com dr edu gen gov info k12 name net org pol tel tsk tv web ',
+ 'tt':' aero biz cat co com coop edu gov info int jobs mil mobi museum name net org pro tel travel ',
+ 'tw':' club com ebiz edu game gov idv mil net org ',
+ 'mu':' ac co com gov net or org ',
+ 'mz':' ac co edu gov org ',
+ 'na':' co com ',
+ 'nz':' ac co cri geek gen govt health iwi maori mil net org parliament school ',
+ 'pa':' abo ac com edu gob ing med net nom org sld ',
+ 'pt':' com edu gov int net nome org publ ',
+ 'py':' com edu gov mil net org ',
+ 'qa':' com edu gov mil net org ',
+ 're':' asso com nom ',
+ 'ru':' ac adygeya altai amur arkhangelsk astrakhan bashkiria belgorod bir bryansk buryatia cbg chel chelyabinsk chita chukotka chuvashia com dagestan e-burg edu gov grozny int irkutsk ivanovo izhevsk jar joshkar-ola kalmykia kaluga kamchatka karelia kazan kchr kemerovo khabarovsk khakassia khv kirov koenig komi kostroma kranoyarsk kuban kurgan kursk lipetsk magadan mari mari-el marine mil mordovia mosreg msk murmansk nalchik net nnov nov novosibirsk nsk omsk orenburg org oryol penza perm pp pskov ptz rnd ryazan sakhalin samara saratov simbirsk smolensk spb stavropol stv surgut tambov tatarstan tom tomsk tsaritsyn tsk tula tuva tver tyumen udm udmurtia ulan-ude vladikavkaz vladimir vladivostok volgograd vologda voronezh vrn vyatka yakutia yamal yekaterinburg yuzhno-sakhalinsk ',
+ 'rw':' ac co com edu gouv gov int mil net ',
+ 'sa':' com edu gov med net org pub sch ',
+ 'sd':' com edu gov info med net org tv ',
+ 'se':' a ac b bd c d e f g h i k l m n o org p parti pp press r s t tm u w x y z ',
+ 'sg':' com edu gov idn net org per ',
+ 'sn':' art com edu gouv org perso univ ',
+ 'sy':' com edu gov mil net news org ',
+ 'th':' ac co go in mi net or ',
+ 'tj':' ac biz co com edu go gov info int mil name net nic org test web ',
+ 'tn':' agrinet com defense edunet ens fin gov ind info intl mincom nat net org perso rnrt rns rnu tourism ',
+ 'tz':' ac co go ne or ',
+ 'ua':' biz cherkassy chernigov chernovtsy ck cn co com crimea cv dn dnepropetrovsk donetsk dp edu gov if in ivano-frankivsk kh kharkov kherson khmelnitskiy kiev kirovograd km kr ks kv lg lugansk lutsk lviv me mk net nikolaev od odessa org pl poltava pp rovno rv sebastopol sumy te ternopil uzhgorod vinnica vn zaporizhzhe zhitomir zp zt ',
+ 'ug':' ac co go ne or org sc ',
+ 'uk':' ac bl british-library co cym gov govt icnet jet lea ltd me mil mod national-library-scotland nel net nhs nic nls org orgn parliament plc police sch scot soc ',
+ 'us':' dni fed isa kids nsn ',
+ 'uy':' com edu gub mil net org ',
+ 've':' co com edu gob info mil net org web ',
+ 'vi':' co com k12 net org ',
+ 'vn':' ac biz com edu gov health info int name net org pro ',
+ 'ye':' co com gov ltd me net org plc ',
+ 'yu':' ac co edu gov org ',
+ 'za':' ac agric alt bourse city co cybernet db edu gov grondar iaccess imt inca landesign law mil net ngo nis nom olivetti org pix school tm web ',
+ 'zm':' ac co com edu gov net org sch '
+ },
+ // gorhill 2013-10-25: Using indexOf() instead Regexp(). Significant boost
+ // in both performance and memory footprint. No initialization required.
+ // http://jsperf.com/uri-js-sld-regex-vs-binary-search/4
+ // Following methods use lastIndexOf() rather than array.split() in order
+ // to avoid any memory allocations.
+ has: function(domain) {
+ var tldOffset = domain.lastIndexOf('.');
+ if (tldOffset <= 0 || tldOffset >= (domain.length-1)) {
+ return false;
+ }
+ var sldOffset = domain.lastIndexOf('.', tldOffset-1);
+ if (sldOffset <= 0 || sldOffset >= (tldOffset-1)) {
+ return false;
+ }
+ var sldList = SLD.list[domain.slice(tldOffset+1)];
+ if (!sldList) {
+ return false;
+ }
+ return sldList.indexOf(' ' + domain.slice(sldOffset+1, tldOffset) + ' ') >= 0;
+ },
+ is: function(domain) {
+ var tldOffset = domain.lastIndexOf('.');
+ if (tldOffset <= 0 || tldOffset >= (domain.length-1)) {
+ return false;
+ }
+ var sldOffset = domain.lastIndexOf('.', tldOffset-1);
+ if (sldOffset >= 0) {
+ return false;
+ }
+ var sldList = SLD.list[domain.slice(tldOffset+1)];
+ if (!sldList) {
+ return false;
+ }
+ return sldList.indexOf(' ' + domain.slice(0, tldOffset) + ' ') >= 0;
+ },
+ get: function(domain) {
+ var tldOffset = domain.lastIndexOf('.');
+ if (tldOffset <= 0 || tldOffset >= (domain.length-1)) {
+ return null;
+ }
+ var sldOffset = domain.lastIndexOf('.', tldOffset-1);
+ if (sldOffset <= 0 || sldOffset >= (tldOffset-1)) {
+ return null;
+ }
+ var sldList = SLD.list[domain.slice(tldOffset+1)];
+ if (!sldList) {
+ return null;
+ }
+ if (sldList.indexOf(' ' + domain.slice(sldOffset+1, tldOffset) + ' ') < 0) {
+ return null;
+ }
+ return domain.slice(sldOffset+1);
+ },
+ noConflict: function(){
+ if (root.SecondLevelDomains === this) {
+ root.SecondLevelDomains = _SecondLevelDomains;
+ }
+ return this;
+ }
+ };
+
+ return SLD;
+}));
+
+},{}],3:[function(require,module,exports){
+/*!
+ * URI.js - Mutating URLs
+ *
+ * Version: 1.17.0
+ *
+ * Author: Rodney Rehm
+ * Web: http://medialize.github.io/URI.js/
+ *
+ * Licensed under
+ * MIT License http://www.opensource.org/licenses/mit-license
+ * GPL v3 http://opensource.org/licenses/GPL-3.0
+ *
+ */
+(function (root, factory) {
+ 'use strict';
+ // https://github.com/umdjs/umd/blob/master/returnExports.js
+ if (typeof exports === 'object') {
+ // Node
+ module.exports = factory(require('./punycode'), require('./IPv6'), require('./SecondLevelDomains'));
+ } else if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['./punycode', './IPv6', './SecondLevelDomains'], factory);
+ } else {
+ // Browser globals (root is window)
+ root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains, root);
+ }
+}(this, function (punycode, IPv6, SLD, root) {
+ 'use strict';
+ /*global location, escape, unescape */
+ // FIXME: v2.0.0 renamce non-camelCase properties to uppercase
+ /*jshint camelcase: false */
+
+ // save current URI variable, if any
+ var _URI = root && root.URI;
+
+ function URI(url, base) {
+ var _urlSupplied = arguments.length >= 1;
+ var _baseSupplied = arguments.length >= 2;
+
+ // Allow instantiation without the 'new' keyword
+ if (!(this instanceof URI)) {
+ if (_urlSupplied) {
+ if (_baseSupplied) {
+ return new URI(url, base);
+ }
+
+ return new URI(url);
+ }
+
+ return new URI();
+ }
+
+ if (url === undefined) {
+ if (_urlSupplied) {
+ throw new TypeError('undefined is not a valid argument for URI');
+ }
+
+ if (typeof location !== 'undefined') {
+ url = location.href + '';
+ } else {
+ url = '';
+ }
+ }
+
+ this.href(url);
+
+ // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor
+ if (base !== undefined) {
+ return this.absoluteTo(base);
+ }
+
+ return this;
+ }
+
+ URI.version = '1.17.0';
+
+ var p = URI.prototype;
+ var hasOwn = Object.prototype.hasOwnProperty;
+
+ function escapeRegEx(string) {
+ // https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963
+ return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+ }
+
+ function getType(value) {
+ // IE8 doesn't return [Object Undefined] but [Object Object] for undefined value
+ if (value === undefined) {
+ return 'Undefined';
+ }
+
+ return String(Object.prototype.toString.call(value)).slice(8, -1);
+ }
+
+ function isArray(obj) {
+ return getType(obj) === 'Array';
+ }
+
+ function filterArrayValues(data, value) {
+ var lookup = {};
+ var i, length;
+
+ if (getType(value) === 'RegExp') {
+ lookup = null;
+ } else if (isArray(value)) {
+ for (i = 0, length = value.length; i < length; i++) {
+ lookup[value[i]] = true;
+ }
+ } else {
+ lookup[value] = true;
+ }
+
+ for (i = 0, length = data.length; i < length; i++) {
+ /*jshint laxbreak: true */
+ var _match = lookup && lookup[data[i]] !== undefined
+ || !lookup && value.test(data[i]);
+ /*jshint laxbreak: false */
+ if (_match) {
+ data.splice(i, 1);
+ length--;
+ i--;
+ }
+ }
+
+ return data;
+ }
+
+ function arrayContains(list, value) {
+ var i, length;
+
+ // value may be string, number, array, regexp
+ if (isArray(value)) {
+ // Note: this can be optimized to O(n) (instead of current O(m * n))
+ for (i = 0, length = value.length; i < length; i++) {
+ if (!arrayContains(list, value[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ var _type = getType(value);
+ for (i = 0, length = list.length; i < length; i++) {
+ if (_type === 'RegExp') {
+ if (typeof list[i] === 'string' && list[i].match(value)) {
+ return true;
+ }
+ } else if (list[i] === value) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function arraysEqual(one, two) {
+ if (!isArray(one) || !isArray(two)) {
+ return false;
+ }
+
+ // arrays can't be equal if they have different amount of content
+ if (one.length !== two.length) {
+ return false;
+ }
+
+ one.sort();
+ two.sort();
+
+ for (var i = 0, l = one.length; i < l; i++) {
+ if (one[i] !== two[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ function trimSlashes(text) {
+ var trim_expression = /^\/+|\/+$/g;
+ return text.replace(trim_expression, '');
+ }
+
+ URI._parts = function() {
+ return {
+ protocol: null,
+ username: null,
+ password: null,
+ hostname: null,
+ urn: null,
+ port: null,
+ path: null,
+ query: null,
+ fragment: null,
+ // state
+ duplicateQueryParameters: URI.duplicateQueryParameters,
+ escapeQuerySpace: URI.escapeQuerySpace
+ };
+ };
+ // state: allow duplicate query parameters (a=1&a=1)
+ URI.duplicateQueryParameters = false;
+ // state: replaces + with %20 (space in query strings)
+ URI.escapeQuerySpace = true;
+ // static properties
+ URI.protocol_expression = /^[a-z][a-z0-9.+-]*$/i;
+ URI.idn_expression = /[^a-z0-9\.-]/i;
+ URI.punycode_expression = /(xn--)/i;
+ // well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care?
+ URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
+ // credits to Rich Brown
+ // source: http://forums.intermapper.com/viewtopic.php?p=1096#1096
+ // specification: http://www.ietf.org/rfc/rfc4291.txt
+ URI.ip6_expression = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/;
+ // expression used is "gruber revised" (@gruber v2) determined to be the
+ // best solution in a regex-golf we did a couple of ages ago at
+ // * http://mathiasbynens.be/demo/url-regex
+ // * http://rodneyrehm.de/t/url-regex.html
+ URI.find_uri_expression = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig;
+ URI.findUri = {
+ // valid "scheme://" or "www."
+ start: /\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi,
+ // everything up to the next whitespace
+ end: /[\s\r\n]|$/,
+ // trim trailing punctuation captured by end RegExp
+ trim: /[`!()\[\]{};:'".,<>?«»“”„‘’]+$/
+ };
+ // http://www.iana.org/assignments/uri-schemes.html
+ // http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports
+ URI.defaultPorts = {
+ http: '80',
+ https: '443',
+ ftp: '21',
+ gopher: '70',
+ ws: '80',
+ wss: '443'
+ };
+ // allowed hostname characters according to RFC 3986
+ // ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded
+ // I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . -
+ URI.invalid_hostname_characters = /[^a-zA-Z0-9\.-]/;
+ // map DOM Elements to their URI attribute
+ URI.domAttributes = {
+ 'a': 'href',
+ 'blockquote': 'cite',
+ 'link': 'href',
+ 'base': 'href',
+ 'script': 'src',
+ 'form': 'action',
+ 'img': 'src',
+ 'area': 'href',
+ 'iframe': 'src',
+ 'embed': 'src',
+ 'source': 'src',
+ 'track': 'src',
+ 'input': 'src', // but only if type="image"
+ 'audio': 'src',
+ 'video': 'src'
+ };
+ URI.getDomAttribute = function(node) {
+ if (!node || !node.nodeName) {
+ return undefined;
+ }
+
+ var nodeName = node.nodeName.toLowerCase();
+ // <input> should only expose src for type="image"
+ if (nodeName === 'input' && node.type !== 'image') {
+ return undefined;
+ }
+
+ return URI.domAttributes[nodeName];
+ };
+
+ function escapeForDumbFirefox36(value) {
+ // https://github.com/medialize/URI.js/issues/91
+ return escape(value);
+ }
+
+ // encoding / decoding according to RFC3986
+ function strictEncodeURIComponent(string) {
+ // see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent
+ return encodeURIComponent(string)
+ .replace(/[!'()*]/g, escapeForDumbFirefox36)
+ .replace(/\*/g, '%2A');
+ }
+ URI.encode = strictEncodeURIComponent;
+ URI.decode = decodeURIComponent;
+ URI.iso8859 = function() {
+ URI.encode = escape;
+ URI.decode = unescape;
+ };
+ URI.unicode = function() {
+ URI.encode = strictEncodeURIComponent;
+ URI.decode = decodeURIComponent;
+ };
+ URI.characters = {
+ pathname: {
+ encode: {
+ // RFC3986 2.1: For consistency, URI producers and normalizers should
+ // use uppercase hexadecimal digits for all percent-encodings.
+ expression: /%(24|26|2B|2C|3B|3D|3A|40)/ig,
+ map: {
+ // -._~!'()*
+ '%24': '$',
+ '%26': '&',
+ '%2B': '+',
+ '%2C': ',',
+ '%3B': ';',
+ '%3D': '=',
+ '%3A': ':',
+ '%40': '@'
+ }
+ },
+ decode: {
+ expression: /[\/\?#]/g,
+ map: {
+ '/': '%2F',
+ '?': '%3F',
+ '#': '%23'
+ }
+ }
+ },
+ reserved: {
+ encode: {
+ // RFC3986 2.1: For consistency, URI producers and normalizers should
+ // use uppercase hexadecimal digits for all percent-encodings.
+ expression: /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,
+ map: {
+ // gen-delims
+ '%3A': ':',
+ '%2F': '/',
+ '%3F': '?',
+ '%23': '#',
+ '%5B': '[',
+ '%5D': ']',
+ '%40': '@',
+ // sub-delims
+ '%21': '!',
+ '%24': '$',
+ '%26': '&',
+ '%27': '\'',
+ '%28': '(',
+ '%29': ')',
+ '%2A': '*',
+ '%2B': '+',
+ '%2C': ',',
+ '%3B': ';',
+ '%3D': '='
+ }
+ }
+ },
+ urnpath: {
+ // The characters under `encode` are the characters called out by RFC 2141 as being acceptable
+ // for usage in a URN. RFC2141 also calls out "-", ".", and "_" as acceptable characters, but
+ // these aren't encoded by encodeURIComponent, so we don't have to call them out here. Also
+ // note that the colon character is not featured in the encoding map; this is because URI.js
+ // gives the colons in URNs semantic meaning as the delimiters of path segements, and so it
+ // should not appear unencoded in a segment itself.
+ // See also the note above about RFC3986 and capitalalized hex digits.
+ encode: {
+ expression: /%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig,
+ map: {
+ '%21': '!',
+ '%24': '$',
+ '%27': '\'',
+ '%28': '(',
+ '%29': ')',
+ '%2A': '*',
+ '%2B': '+',
+ '%2C': ',',
+ '%3B': ';',
+ '%3D': '=',
+ '%40': '@'
+ }
+ },
+ // These characters are the characters called out by RFC2141 as "reserved" characters that
+ // should never appear in a URN, plus the colon character (see note above).
+ decode: {
+ expression: /[\/\?#:]/g,
+ map: {
+ '/': '%2F',
+ '?': '%3F',
+ '#': '%23',
+ ':': '%3A'
+ }
+ }
+ }
+ };
+ URI.encodeQuery = function(string, escapeQuerySpace) {
+ var escaped = URI.encode(string + '');
+ if (escapeQuerySpace === undefined) {
+ escapeQuerySpace = URI.escapeQuerySpace;
+ }
+
+ return escapeQuerySpace ? escaped.replace(/%20/g, '+') : escaped;
+ };
+ URI.decodeQuery = function(string, escapeQuerySpace) {
+ string += '';
+ if (escapeQuerySpace === undefined) {
+ escapeQuerySpace = URI.escapeQuerySpace;
+ }
+
+ try {
+ return URI.decode(escapeQuerySpace ? string.replace(/\+/g, '%20') : string);
+ } catch(e) {
+ // we're not going to mess with weird encodings,
+ // give up and return the undecoded original string
+ // see https://github.com/medialize/URI.js/issues/87
+ // see https://github.com/medialize/URI.js/issues/92
+ return string;
+ }
+ };
+ // generate encode/decode path functions
+ var _parts = {'encode':'encode', 'decode':'decode'};
+ var _part;
+ var generateAccessor = function(_group, _part) {
+ return function(string) {
+ try {
+ return URI[_part](string + '').replace(URI.characters[_group][_part].expression, function(c) {
+ return URI.characters[_group][_part].map[c];
+ });
+ } catch (e) {
+ // we're not going to mess with weird encodings,
+ // give up and return the undecoded original string
+ // see https://github.com/medialize/URI.js/issues/87
+ // see https://github.com/medialize/URI.js/issues/92
+ return string;
+ }
+ };
+ };
+
+ for (_part in _parts) {
+ URI[_part + 'PathSegment'] = generateAccessor('pathname', _parts[_part]);
+ URI[_part + 'UrnPathSegment'] = generateAccessor('urnpath', _parts[_part]);
+ }
+
+ var generateSegmentedPathFunction = function(_sep, _codingFuncName, _innerCodingFuncName) {
+ return function(string) {
+ // Why pass in names of functions, rather than the function objects themselves? The
+ // definitions of some functions (but in particular, URI.decode) will occasionally change due
+ // to URI.js having ISO8859 and Unicode modes. Passing in the name and getting it will ensure
+ // that the functions we use here are "fresh".
+ var actualCodingFunc;
+ if (!_innerCodingFuncName) {
+ actualCodingFunc = URI[_codingFuncName];
+ } else {
+ actualCodingFunc = function(string) {
+ return URI[_codingFuncName](URI[_innerCodingFuncName](string));
+ };
+ }
+
+ var segments = (string + '').split(_sep);
+
+ for (var i = 0, length = segments.length; i < length; i++) {
+ segments[i] = actualCodingFunc(segments[i]);
+ }
+
+ return segments.join(_sep);
+ };
+ };
+
+ // This takes place outside the above loop because we don't want, e.g., encodeUrnPath functions.
+ URI.decodePath = generateSegmentedPathFunction('/', 'decodePathSegment');
+ URI.decodeUrnPath = generateSegmentedPathFunction(':', 'decodeUrnPathSegment');
+ URI.recodePath = generateSegmentedPathFunction('/', 'encodePathSegment', 'decode');
+ URI.recodeUrnPath = generateSegmentedPathFunction(':', 'encodeUrnPathSegment', 'decode');
+
+ URI.encodeReserved = generateAccessor('reserved', 'encode');
+
+ URI.parse = function(string, parts) {
+ var pos;
+ if (!parts) {
+ parts = {};
+ }
+ // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment]
+
+ // extract fragment
+ pos = string.indexOf('#');
+ if (pos > -1) {
+ // escaping?
+ parts.fragment = string.substring(pos + 1) || null;
+ string = string.substring(0, pos);
+ }
+
+ // extract query
+ pos = string.indexOf('?');
+ if (pos > -1) {
+ // escaping?
+ parts.query = string.substring(pos + 1) || null;
+ string = string.substring(0, pos);
+ }
+
+ // extract protocol
+ if (string.substring(0, 2) === '//') {
+ // relative-scheme
+ parts.protocol = null;
+ string = string.substring(2);
+ // extract "user:pass@host:port"
+ string = URI.parseAuthority(string, parts);
+ } else {
+ pos = string.indexOf(':');
+ if (pos > -1) {
+ parts.protocol = string.substring(0, pos) || null;
+ if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) {
+ // : may be within the path
+ parts.protocol = undefined;
+ } else if (string.substring(pos + 1, pos + 3) === '//') {
+ string = string.substring(pos + 3);
+
+ // extract "user:pass@host:port"
+ string = URI.parseAuthority(string, parts);
+ } else {
+ string = string.substring(pos + 1);
+ parts.urn = true;
+ }
+ }
+ }
+
+ // what's left must be the path
+ parts.path = string;
+
+ // and we're done
+ return parts;
+ };
+ URI.parseHost = function(string, parts) {
+ // Copy chrome, IE, opera backslash-handling behavior.
+ // Back slashes before the query string get converted to forward slashes
+ // See: https://github.com/joyent/node/blob/386fd24f49b0e9d1a8a076592a404168faeecc34/lib/url.js#L115-L124
+ // See: https://code.google.com/p/chromium/issues/detail?id=25916
+ // https://github.com/medialize/URI.js/pull/233
+ string = string.replace(/\\/g, '/');
+
+ // extract host:port
+ var pos = string.indexOf('/');
+ var bracketPos;
+ var t;
+
+ if (pos === -1) {
+ pos = string.length;
+ }
+
+ if (string.charAt(0) === '[') {
+ // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6
+ // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts
+ // IPv6+port in the format [2001:db8::1]:80 (for the time being)
+ bracketPos = string.indexOf(']');
+ parts.hostname = string.substring(1, bracketPos) || null;
+ parts.port = string.substring(bracketPos + 2, pos) || null;
+ if (parts.port === '/') {
+ parts.port = null;
+ }
+ } else {
+ var firstColon = string.indexOf(':');
+ var firstSlash = string.indexOf('/');
+ var nextColon = string.indexOf(':', firstColon + 1);
+ if (nextColon !== -1 && (firstSlash === -1 || nextColon < firstSlash)) {
+ // IPv6 host contains multiple colons - but no port
+ // this notation is actually not allowed by RFC 3986, but we're a liberal parser
+ parts.hostname = string.substring(0, pos) || null;
+ parts.port = null;
+ } else {
+ t = string.substring(0, pos).split(':');
+ parts.hostname = t[0] || null;
+ parts.port = t[1] || null;
+ }
+ }
+
+ if (parts.hostname && string.substring(pos).charAt(0) !== '/') {
+ pos++;
+ string = '/' + string;
+ }
+
+ return string.substring(pos) || '/';
+ };
+ URI.parseAuthority = function(string, parts) {
+ string = URI.parseUserinfo(string, parts);
+ return URI.parseHost(string, parts);
+ };
+ URI.parseUserinfo = function(string, parts) {
+ // extract username:password
+ var firstSlash = string.indexOf('/');
+ var pos = string.lastIndexOf('@', firstSlash > -1 ? firstSlash : string.length - 1);
+ var t;
+
+ // authority@ must come before /path
+ if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) {
+ t = string.substring(0, pos).split(':');
+ parts.username = t[0] ? URI.decode(t[0]) : null;
+ t.shift();
+ parts.password = t[0] ? URI.decode(t.join(':')) : null;
+ string = string.substring(pos + 1);
+ } else {
+ parts.username = null;
+ parts.password = null;
+ }
+
+ return string;
+ };
+ URI.parseQuery = function(string, escapeQuerySpace) {
+ if (!string) {
+ return {};
+ }
+
+ // throw out the funky business - "?"[name"="value"&"]+
+ string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, '');
+
+ if (!string) {
+ return {};
+ }
+
+ var items = {};
+ var splits = string.split('&');
+ var length = splits.length;
+ var v, name, value;
+
+ for (var i = 0; i < length; i++) {
+ v = splits[i].split('=');
+ name = URI.decodeQuery(v.shift(), escapeQuerySpace);
+ // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters
+ value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null;
+
+ if (hasOwn.call(items, name)) {
+ if (typeof items[name] === 'string' || items[name] === null) {
+ items[name] = [items[name]];
+ }
+
+ items[name].push(value);
+ } else {
+ items[name] = value;
+ }
+ }
+
+ return items;
+ };
+
+ URI.build = function(parts) {
+ var t = '';
+
+ if (parts.protocol) {
+ t += parts.protocol + ':';
+ }
+
+ if (!parts.urn && (t || parts.hostname)) {
+ t += '//';
+ }
+
+ t += (URI.buildAuthority(parts) || '');
+
+ if (typeof parts.path === 'string') {
+ if (parts.path.charAt(0) !== '/' && typeof parts.hostname === 'string') {
+ t += '/';
+ }
+
+ t += parts.path;
+ }
+
+ if (typeof parts.query === 'string' && parts.query) {
+ t += '?' + parts.query;
+ }
+
+ if (typeof parts.fragment === 'string' && parts.fragment) {
+ t += '#' + parts.fragment;
+ }
+ return t;
+ };
+ URI.buildHost = function(parts) {
+ var t = '';
+
+ if (!parts.hostname) {
+ return '';
+ } else if (URI.ip6_expression.test(parts.hostname)) {
+ t += '[' + parts.hostname + ']';
+ } else {
+ t += parts.hostname;
+ }
+
+ if (parts.port) {
+ t += ':' + parts.port;
+ }
+
+ return t;
+ };
+ URI.buildAuthority = function(parts) {
+ return URI.buildUserinfo(parts) + URI.buildHost(parts);
+ };
+ URI.buildUserinfo = function(parts) {
+ var t = '';
+
+ if (parts.username) {
+ t += URI.encode(parts.username);
+
+ if (parts.password) {
+ t += ':' + URI.encode(parts.password);
+ }
+
+ t += '@';
+ }
+
+ return t;
+ };
+ URI.buildQuery = function(data, duplicateQueryParameters, escapeQuerySpace) {
+ // according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html
+ // being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed
+ // the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax!
+ // URI.js treats the query string as being application/x-www-form-urlencoded
+ // see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
+
+ var t = '';
+ var unique, key, i, length;
+ for (key in data) {
+ if (hasOwn.call(data, key) && key) {
+ if (isArray(data[key])) {
+ unique = {};
+ for (i = 0, length = data[key].length; i < length; i++) {
+ if (data[key][i] !== undefined && unique[data[key][i] + ''] === undefined) {
+ t += '&' + URI.buildQueryParameter(key, data[key][i], escapeQuerySpace);
+ if (duplicateQueryParameters !== true) {
+ unique[data[key][i] + ''] = true;
+ }
+ }
+ }
+ } else if (data[key] !== undefined) {
+ t += '&' + URI.buildQueryParameter(key, data[key], escapeQuerySpace);
+ }
+ }
+ }
+
+ return t.substring(1);
+ };
+ URI.buildQueryParameter = function(name, value, escapeQuerySpace) {
+ // http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded
+ // don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization
+ return URI.encodeQuery(name, escapeQuerySpace) + (value !== null ? '=' + URI.encodeQuery(value, escapeQuerySpace) : '');
+ };
+
+ URI.addQuery = function(data, name, value) {
+ if (typeof name === 'object') {
+ for (var key in name) {
+ if (hasOwn.call(name, key)) {
+ URI.addQuery(data, key, name[key]);
+ }
+ }
+ } else if (typeof name === 'string') {
+ if (data[name] === undefined) {
+ data[name] = value;
+ return;
+ } else if (typeof data[name] === 'string') {
+ data[name] = [data[name]];
+ }
+
+ if (!isArray(value)) {
+ value = [value];
+ }
+
+ data[name] = (data[name] || []).concat(value);
+ } else {
+ throw new TypeError('URI.addQuery() accepts an object, string as the name parameter');
+ }
+ };
+ URI.removeQuery = function(data, name, value) {
+ var i, length, key;
+
+ if (isArray(name)) {
+ for (i = 0, length = name.length; i < length; i++) {
+ data[name[i]] = undefined;
+ }
+ } else if (getType(name) === 'RegExp') {
+ for (key in data) {
+ if (name.test(key)) {
+ data[key] = undefined;
+ }
+ }
+ } else if (typeof name === 'object') {
+ for (key in name) {
+ if (hasOwn.call(name, key)) {
+ URI.removeQuery(data, key, name[key]);
+ }
+ }
+ } else if (typeof name === 'string') {
+ if (value !== undefined) {
+ if (getType(value) === 'RegExp') {
+ if (!isArray(data[name]) && value.test(data[name])) {
+ data[name] = undefined;
+ } else {
+ data[name] = filterArrayValues(data[name], value);
+ }
+ } else if (data[name] === String(value) && (!isArray(value) || value.length === 1)) {
+ data[name] = undefined;
+ } else if (isArray(data[name])) {
+ data[name] = filterArrayValues(data[name], value);
+ }
+ } else {
+ data[name] = undefined;
+ }
+ } else {
+ throw new TypeError('URI.removeQuery() accepts an object, string, RegExp as the first parameter');
+ }
+ };
+ URI.hasQuery = function(data, name, value, withinArray) {
+ if (typeof name === 'object') {
+ for (var key in name) {
+ if (hasOwn.call(name, key)) {
+ if (!URI.hasQuery(data, key, name[key])) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ } else if (typeof name !== 'string') {
+ throw new TypeError('URI.hasQuery() accepts an object, string as the name parameter');
+ }
+
+ switch (getType(value)) {
+ case 'Undefined':
+ // true if exists (but may be empty)
+ return name in data; // data[name] !== undefined;
+
+ case 'Boolean':
+ // true if exists and non-empty
+ var _booly = Boolean(isArray(data[name]) ? data[name].length : data[name]);
+ return value === _booly;
+
+ case 'Function':
+ // allow complex comparison
+ return !!value(data[name], name, data);
+
+ case 'Array':
+ if (!isArray(data[name])) {
+ return false;
+ }
+
+ var op = withinArray ? arrayContains : arraysEqual;
+ return op(data[name], value);
+
+ case 'RegExp':
+ if (!isArray(data[name])) {
+ return Boolean(data[name] && data[name].match(value));
+ }
+
+ if (!withinArray) {
+ return false;
+ }
+
+ return arrayContains(data[name], value);
+
+ case 'Number':
+ value = String(value);
+ /* falls through */
+ case 'String':
+ if (!isArray(data[name])) {
+ return data[name] === value;
+ }
+
+ if (!withinArray) {
+ return false;
+ }
+
+ return arrayContains(data[name], value);
+
+ default:
+ throw new TypeError('URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter');
+ }
+ };
+
+
+ URI.commonPath = function(one, two) {
+ var length = Math.min(one.length, two.length);
+ var pos;
+
+ // find first non-matching character
+ for (pos = 0; pos < length; pos++) {
+ if (one.charAt(pos) !== two.charAt(pos)) {
+ pos--;
+ break;
+ }
+ }
+
+ if (pos < 1) {
+ return one.charAt(0) === two.charAt(0) && one.charAt(0) === '/' ? '/' : '';
+ }
+
+ // revert to last /
+ if (one.charAt(pos) !== '/' || two.charAt(pos) !== '/') {
+ pos = one.substring(0, pos).lastIndexOf('/');
+ }
+
+ return one.substring(0, pos + 1);
+ };
+
+ URI.withinString = function(string, callback, options) {
+ options || (options = {});
+ var _start = options.start || URI.findUri.start;
+ var _end = options.end || URI.findUri.end;
+ var _trim = options.trim || URI.findUri.trim;
+ var _attributeOpen = /[a-z0-9-]=["']?$/i;
+
+ _start.lastIndex = 0;
+ while (true) {
+ var match = _start.exec(string);
+ if (!match) {
+ break;
+ }
+
+ var start = match.index;
+ if (options.ignoreHtml) {
+ // attribut(e=["']?$)
+ var attributeOpen = string.slice(Math.max(start - 3, 0), start);
+ if (attributeOpen && _attributeOpen.test(attributeOpen)) {
+ continue;
+ }
+ }
+
+ var end = start + string.slice(start).search(_end);
+ var slice = string.slice(start, end).replace(_trim, '');
+ if (options.ignore && options.ignore.test(slice)) {
+ continue;
+ }
+
+ end = start + slice.length;
+ var result = callback(slice, start, end, string);
+ string = string.slice(0, start) + result + string.slice(end);
+ _start.lastIndex = start + result.length;
+ }
+
+ _start.lastIndex = 0;
+ return string;
+ };
+
+ URI.ensureValidHostname = function(v) {
+ // Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986)
+ // they are not part of DNS and therefore ignored by URI.js
+
+ if (v.match(URI.invalid_hostname_characters)) {
+ // test punycode
+ if (!punycode) {
+ throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-] and Punycode.js is not available');
+ }
+
+ if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) {
+ throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
+ }
+ }
+ };
+
+ // noConflict
+ URI.noConflict = function(removeAll) {
+ if (removeAll) {
+ var unconflicted = {
+ URI: this.noConflict()
+ };
+
+ if (root.URITemplate && typeof root.URITemplate.noConflict === 'function') {
+ unconflicted.URITemplate = root.URITemplate.noConflict();
+ }
+
+ if (root.IPv6 && typeof root.IPv6.noConflict === 'function') {
+ unconflicted.IPv6 = root.IPv6.noConflict();
+ }
+
+ if (root.SecondLevelDomains && typeof root.SecondLevelDomains.noConflict === 'function') {
+ unconflicted.SecondLevelDomains = root.SecondLevelDomains.noConflict();
+ }
+
+ return unconflicted;
+ } else if (root.URI === this) {
+ root.URI = _URI;
+ }
+
+ return this;
+ };
+
+ p.build = function(deferBuild) {
+ if (deferBuild === true) {
+ this._deferred_build = true;
+ } else if (deferBuild === undefined || this._deferred_build) {
+ this._string = URI.build(this._parts);
+ this._deferred_build = false;
+ }
+
+ return this;
+ };
+
+ p.clone = function() {
+ return new URI(this);
+ };
+
+ p.valueOf = p.toString = function() {
+ return this.build(false)._string;
+ };
+
+
+ function generateSimpleAccessor(_part){
+ return function(v, build) {
+ if (v === undefined) {
+ return this._parts[_part] || '';
+ } else {
+ this._parts[_part] = v || null;
+ this.build(!build);
+ return this;
+ }
+ };
+ }
+
+ function generatePrefixAccessor(_part, _key){
+ return function(v, build) {
+ if (v === undefined) {
+ return this._parts[_part] || '';
+ } else {
+ if (v !== null) {
+ v = v + '';
+ if (v.charAt(0) === _key) {
+ v = v.substring(1);
+ }
+ }
+
+ this._parts[_part] = v;
+ this.build(!build);
+ return this;
+ }
+ };
+ }
+
+ p.protocol = generateSimpleAccessor('protocol');
+ p.username = generateSimpleAccessor('username');
+ p.password = generateSimpleAccessor('password');
+ p.hostname = generateSimpleAccessor('hostname');
+ p.port = generateSimpleAccessor('port');
+ p.query = generatePrefixAccessor('query', '?');
+ p.fragment = generatePrefixAccessor('fragment', '#');
+
+ p.search = function(v, build) {
+ var t = this.query(v, build);
+ return typeof t === 'string' && t.length ? ('?' + t) : t;
+ };
+ p.hash = function(v, build) {
+ var t = this.fragment(v, build);
+ return typeof t === 'string' && t.length ? ('#' + t) : t;
+ };
+
+ p.pathname = function(v, build) {
+ if (v === undefined || v === true) {
+ var res = this._parts.path || (this._parts.hostname ? '/' : '');
+ return v ? (this._parts.urn ? URI.decodeUrnPath : URI.decodePath)(res) : res;
+ } else {
+ if (this._parts.urn) {
+ this._parts.path = v ? URI.recodeUrnPath(v) : '';
+ } else {
+ this._parts.path = v ? URI.recodePath(v) : '/';
+ }
+ this.build(!build);
+ return this;
+ }
+ };
+ p.path = p.pathname;
+ p.href = function(href, build) {
+ var key;
+
+ if (href === undefined) {
+ return this.toString();
+ }
+
+ this._string = '';
+ this._parts = URI._parts();
+
+ var _URI = href instanceof URI;
+ var _object = typeof href === 'object' && (href.hostname || href.path || href.pathname);
+ if (href.nodeName) {
+ var attribute = URI.getDomAttribute(href);
+ href = href[attribute] || '';
+ _object = false;
+ }
+
+ // window.location is reported to be an object, but it's not the sort
+ // of object we're looking for:
+ // * location.protocol ends with a colon
+ // * location.query != object.search
+ // * location.hash != object.fragment
+ // simply serializing the unknown object should do the trick
+ // (for location, not for everything...)
+ if (!_URI && _object && href.pathname !== undefined) {
+ href = href.toString();
+ }
+
+ if (typeof href === 'string' || href instanceof String) {
+ this._parts = URI.parse(String(href), this._parts);
+ } else if (_URI || _object) {
+ var src = _URI ? href._parts : href;
+ for (key in src) {
+ if (hasOwn.call(this._parts, key)) {
+ this._parts[key] = src[key];
+ }
+ }
+ } else {
+ throw new TypeError('invalid input');
+ }
+
+ this.build(!build);
+ return this;
+ };
+
+ // identification accessors
+ p.is = function(what) {
+ var ip = false;
+ var ip4 = false;
+ var ip6 = false;
+ var name = false;
+ var sld = false;
+ var idn = false;
+ var punycode = false;
+ var relative = !this._parts.urn;
+
+ if (this._parts.hostname) {
+ relative = false;
+ ip4 = URI.ip4_expression.test(this._parts.hostname);
+ ip6 = URI.ip6_expression.test(this._parts.hostname);
+ ip = ip4 || ip6;
+ name = !ip;
+ sld = name && SLD && SLD.has(this._parts.hostname);
+ idn = name && URI.idn_expression.test(this._parts.hostname);
+ punycode = name && URI.punycode_expression.test(this._parts.hostname);
+ }
+
+ switch (what.toLowerCase()) {
+ case 'relative':
+ return relative;
+
+ case 'absolute':
+ return !relative;
+
+ // hostname identification
+ case 'domain':
+ case 'name':
+ return name;
+
+ case 'sld':
+ return sld;
+
+ case 'ip':
+ return ip;
+
+ case 'ip4':
+ case 'ipv4':
+ case 'inet4':
+ return ip4;
+
+ case 'ip6':
+ case 'ipv6':
+ case 'inet6':
+ return ip6;
+
+ case 'idn':
+ return idn;
+
+ case 'url':
+ return !this._parts.urn;
+
+ case 'urn':
+ return !!this._parts.urn;
+
+ case 'punycode':
+ return punycode;
+ }
+
+ return null;
+ };
+
+ // component specific input validation
+ var _protocol = p.protocol;
+ var _port = p.port;
+ var _hostname = p.hostname;
+
+ p.protocol = function(v, build) {
+ if (v !== undefined) {
+ if (v) {
+ // accept trailing ://
+ v = v.replace(/:(\/\/)?$/, '');
+
+ if (!v.match(URI.protocol_expression)) {
+ throw new TypeError('Protocol "' + v + '" contains characters other than [A-Z0-9.+-] or doesn\'t start with [A-Z]');
+ }
+ }
+ }
+ return _protocol.call(this, v, build);
+ };
+ p.scheme = p.protocol;
+ p.port = function(v, build) {
+ if (this._parts.urn) {
+ return v === undefined ? '' : this;
+ }
+
+ if (v !== undefined) {
+ if (v === 0) {
+ v = null;
+ }
+
+ if (v) {
+ v += '';
+ if (v.charAt(0) === ':') {
+ v = v.substring(1);
+ }
+
+ if (v.match(/[^0-9]/)) {
+ throw new TypeError('Port "' + v + '" contains characters other than [0-9]');
+ }
+ }
+ }
+ return _port.call(this, v, build);
+ };
+ p.hostname = function(v, build) {
+ if (this._parts.urn) {
+ return v === undefined ? '' : this;
+ }
+
+ if (v !== undefined) {
+ var x = {};
+ var res = URI.parseHost(v, x);
+ if (res !== '/') {
+ throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
+ }
+
+ v = x.hostname;
+ }
+ return _hostname.call(this, v, build);
+ };
+
+ // compound accessors
+ p.origin = function(v, build) {
+ var parts;
+
+ if (this._parts.urn) {
+ return v === undefined ? '' : this;
+ }
+
+ if (v === undefined) {
+ var protocol = this.protocol();
+ var authority = this.authority();
+ if (!authority) return '';
+ return (protocol ? protocol + '://' : '') + this.authority();
+ } else {
+ var origin = URI(v);
+ this
+ .protocol(origin.protocol())
+ .authority(origin.authority())
+ .build(!build);
+ return this;
+ }
+ };
+ p.host = function(v, build) {
+ if (this._parts.urn) {
+ return v === undefined ? '' : this;
+ }
+
+ if (v === undefined) {
+ return this._parts.hostname ? URI.buildHost(this._parts) : '';
+ } else {
+ var res = URI.parseHost(v, this._parts);
+ if (res !== '/') {
+ throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
+ }
+
+ this.build(!build);
+ return this;
+ }
+ };
+ p.authority = function(v, build) {
+ if (this._parts.urn) {
+ return v === undefined ? '' : this;
+ }
+
+ if (v === undefined) {
+ return this._parts.hostname ? URI.buildAuthority(this._parts) : '';
+ } else {
+ var res = URI.parseAuthority(v, this._parts);
+ if (res !== '/') {
+ throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
+ }
+
+ this.build(!build);
+ return this;
+ }
+ };
+ p.userinfo = function(v, build) {
+ if (this._parts.urn) {
+ return v === undefined ? '' : this;
+ }
+
+ if (v === undefined) {
+ if (!this._parts.username) {
+ return '';
+ }
+
+ var t = URI.buildUserinfo(this._parts);
+ return t.substring(0, t.length -1);
+ } else {
+ if (v[v.length-1] !== '@') {
+ v += '@';
+ }
+
+ URI.parseUserinfo(v, this._parts);
+ this.build(!build);
+ return this;
+ }
+ };
+ p.resource = function(v, build) {
+ var parts;
+
+ if (v === undefined) {
+ return this.path() + this.search() + this.hash();
+ }
+
+ parts = URI.parse(v);
+ this._parts.path = parts.path;
+ this._parts.query = parts.query;
+ this._parts.fragment = parts.fragment;
+ this.build(!build);
+ return this;
+ };
+
+ // fraction accessors
+ p.subdomain = function(v, build) {
+ if (this._parts.urn) {
+ return v === undefined ? '' : this;
+ }
+
+ // convenience, return "www" from "www.example.org"
+ if (v === undefined) {
+ if (!this._parts.hostname || this.is('IP')) {
+ return '';
+ }
+
+ // grab domain and add another segment
+ var end = this._parts.hostname.length - this.domain().length - 1;
+ return this._parts.hostname.substring(0, end) || '';
+ } else {
+ var e = this._parts.hostname.length - this.domain().length;
+ var sub = this._parts.hostname.substring(0, e);
+ var replace = new RegExp('^' + escapeRegEx(sub));
+
+ if (v && v.charAt(v.length - 1) !== '.') {
+ v += '.';
+ }
+
+ if (v) {
+ URI.ensureValidHostname(v);
+ }
+
+ this._parts.hostname = this._parts.hostname.replace(replace, v);
+ this.build(!build);
+ return this;
+ }
+ };
+ p.domain = function(v, build) {
+ if (this._parts.urn) {
+ return v === undefined ? '' : this;
+ }
+
+ if (typeof v === 'boolean') {
+ build = v;
+ v = undefined;
+ }
+
+ // convenience, return "example.org" from "www.example.org"
+ if (v === undefined) {
+ if (!this._parts.hostname || this.is('IP')) {
+ return '';
+ }
+
+ // if hostname consists of 1 or 2 segments, it must be the domain
+ var t = this._parts.hostname.match(/\./g);
+ if (t && t.length < 2) {
+ return this._parts.hostname;
+ }
+
+ // grab tld and add another segment
+ var end = this._parts.hostname.length - this.tld(build).length - 1;
+ end = this._parts.hostname.lastIndexOf('.', end -1) + 1;
+ return this._parts.hostname.substring(end) || '';
+ } else {
+ if (!v) {
+ throw new TypeError('cannot set domain empty');
+ }
+
+ URI.ensureValidHostname(v);
+
+ if (!this._parts.hostname || this.is('IP')) {
+ this._parts.hostname = v;
+ } else {
+ var replace = new RegExp(escapeRegEx(this.domain()) + '$');
+ this._parts.hostname = this._parts.hostname.replace(replace, v);
+ }
+
+ this.build(!build);
+ return this;
+ }
+ };
+ p.tld = function(v, build) {
+ if (this._parts.urn) {
+ return v === undefined ? '' : this;
+ }
+
+ if (typeof v === 'boolean') {
+ build = v;
+ v = undefined;
+ }
+
+ // return "org" from "www.example.org"
+ if (v === undefined) {
+ if (!this._parts.hostname || this.is('IP')) {
+ return '';
+ }
+
+ var pos = this._parts.hostname.lastIndexOf('.');
+ var tld = this._parts.hostname.substring(pos + 1);
+
+ if (build !== true && SLD && SLD.list[tld.toLowerCase()]) {
+ return SLD.get(this._parts.hostname) || tld;
+ }
+
+ return tld;
+ } else {
+ var replace;
+
+ if (!v) {
+ throw new TypeError('cannot set TLD empty');
+ } else if (v.match(/[^a-zA-Z0-9-]/)) {
+ if (SLD && SLD.is(v)) {
+ replace = new RegExp(escapeRegEx(this.tld()) + '$');
+ this._parts.hostname = this._parts.hostname.replace(replace, v);
+ } else {
+ throw new TypeError('TLD "' + v + '" contains characters other than [A-Z0-9]');
+ }
+ } else if (!this._parts.hostname || this.is('IP')) {
+ throw new ReferenceError('cannot set TLD on non-domain host');
+ } else {
+ replace = new RegExp(escapeRegEx(this.tld()) + '$');
+ this._parts.hostname = this._parts.hostname.replace(replace, v);
+ }
+
+ this.build(!build);
+ return this;
+ }
+ };
+ p.directory = function(v, build) {
+ if (this._parts.urn) {
+ return v === undefined ? '' : this;
+ }
+
+ if (v === undefined || v === true) {
+ if (!this._parts.path && !this._parts.hostname) {
+ return '';
+ }
+
+ if (this._parts.path === '/') {
+ return '/';
+ }
+
+ var end = this._parts.path.length - this.filename().length - 1;
+ var res = this._parts.path.substring(0, end) || (this._parts.hostname ? '/' : '');
+
+ return v ? URI.decodePath(res) : res;
+
+ } else {
+ var e = this._parts.path.length - this.filename().length;
+ var directory = this._parts.path.substring(0, e);
+ var replace = new RegExp('^' + escapeRegEx(directory));
+
+ // fully qualifier directories begin with a slash
+ if (!this.is('relative')) {
+ if (!v) {
+ v = '/';
+ }
+
+ if (v.charAt(0) !== '/') {
+ v = '/' + v;
+ }
+ }
+
+ // directories always end with a slash
+ if (v && v.charAt(v.length - 1) !== '/') {
+ v += '/';
+ }
+
+ v = URI.recodePath(v);
+ this._parts.path = this._parts.path.replace(replace, v);
+ this.build(!build);
+ return this;
+ }
+ };
+ p.filename = function(v, build) {
+ if (this._parts.urn) {
+ return v === undefined ? '' : this;
+ }
+
+ if (v === undefined || v === true) {
+ if (!this._parts.path || this._parts.path === '/') {
+ return '';
+ }
+
+ var pos = this._parts.path.lastIndexOf('/');
+ var res = this._parts.path.substring(pos+1);
+
+ return v ? URI.decodePathSegment(res) : res;
+ } else {
+ var mutatedDirectory = false;
+
+ if (v.charAt(0) === '/') {
+ v = v.substring(1);
+ }
+
+ if (v.match(/\.?\//)) {
+ mutatedDirectory = true;
+ }
+
+ var replace = new RegExp(escapeRegEx(this.filename()) + '$');
+ v = URI.recodePath(v);
+ this._parts.path = this._parts.path.replace(replace, v);
+
+ if (mutatedDirectory) {
+ this.normalizePath(build);
+ } else {
+ this.build(!build);
+ }
+
+ return this;
+ }
+ };
+ p.suffix = function(v, build) {
+ if (this._parts.urn) {
+ return v === undefined ? '' : this;
+ }
+
+ if (v === undefined || v === true) {
+ if (!this._parts.path || this._parts.path === '/') {
+ return '';
+ }
+
+ var filename = this.filename();
+ var pos = filename.lastIndexOf('.');
+ var s, res;
+
+ if (pos === -1) {
+ return '';
+ }
+
+ // suffix may only contain alnum characters (yup, I made this up.)
+ s = filename.substring(pos+1);
+ res = (/^[a-z0-9%]+$/i).test(s) ? s : '';
+ return v ? URI.decodePathSegment(res) : res;
+ } else {
+ if (v.charAt(0) === '.') {
+ v = v.substring(1);
+ }
+
+ var suffix = this.suffix();
+ var replace;
+
+ if (!suffix) {
+ if (!v) {
+ return this;
+ }
+
+ this._parts.path += '.' + URI.recodePath(v);
+ } else if (!v) {
+ replace = new RegExp(escapeRegEx('.' + suffix) + '$');
+ } else {
+ replace = new RegExp(escapeRegEx(suffix) + '$');
+ }
+
+ if (replace) {
+ v = URI.recodePath(v);
+ this._parts.path = this._parts.path.replace(replace, v);
+ }
+
+ this.build(!build);
+ return this;
+ }
+ };
+ p.segment = function(segment, v, build) {
+ var separator = this._parts.urn ? ':' : '/';
+ var path = this.path();
+ var absolute = path.substring(0, 1) === '/';
+ var segments = path.split(separator);
+
+ if (segment !== undefined && typeof segment !== 'number') {
+ build = v;
+ v = segment;
+ segment = undefined;
+ }
+
+ if (segment !== undefined && typeof segment !== 'number') {
+ throw new Error('Bad segment "' + segment + '", must be 0-based integer');
+ }
+
+ if (absolute) {
+ segments.shift();
+ }
+
+ if (segment < 0) {
+ // allow negative indexes to address from the end
+ segment = Math.max(segments.length + segment, 0);
+ }
+
+ if (v === undefined) {
+ /*jshint laxbreak: true */
+ return segment === undefined
+ ? segments
+ : segments[segment];
+ /*jshint laxbreak: false */
+ } else if (segment === null || segments[segment] === undefined) {
+ if (isArray(v)) {
+ segments = [];
+ // collapse empty elements within array
+ for (var i=0, l=v.length; i < l; i++) {
+ if (!v[i].length && (!segments.length || !segments[segments.length -1].length)) {
+ continue;
+ }
+
+ if (segments.length && !segments[segments.length -1].length) {
+ segments.pop();
+ }
+
+ segments.push(trimSlashes(v[i]));
+ }
+ } else if (v || typeof v === 'string') {
+ v = trimSlashes(v);
+ if (segments[segments.length -1] === '') {
+ // empty trailing elements have to be overwritten
+ // to prevent results such as /foo//bar
+ segments[segments.length -1] = v;
+ } else {
+ segments.push(v);
+ }
+ }
+ } else {
+ if (v) {
+ segments[segment] = trimSlashes(v);
+ } else {
+ segments.splice(segment, 1);
+ }
+ }
+
+ if (absolute) {
+ segments.unshift('');
+ }
+
+ return this.path(segments.join(separator), build);
+ };
+ p.segmentCoded = function(segment, v, build) {
+ var segments, i, l;
+
+ if (typeof segment !== 'number') {
+ build = v;
+ v = segment;
+ segment = undefined;
+ }
+
+ if (v === undefined) {
+ segments = this.segment(segment, v, build);
+ if (!isArray(segments)) {
+ segments = segments !== undefined ? URI.decode(segments) : undefined;
+ } else {
+ for (i = 0, l = segments.length; i < l; i++) {
+ segments[i] = URI.decode(segments[i]);
+ }
+ }
+
+ return segments;
+ }
+
+ if (!isArray(v)) {
+ v = (typeof v === 'string' || v instanceof String) ? URI.encode(v) : v;
+ } else {
+ for (i = 0, l = v.length; i < l; i++) {
+ v[i] = URI.encode(v[i]);
+ }
+ }
+
+ return this.segment(segment, v, build);
+ };
+
+ // mutating query string
+ var q = p.query;
+ p.query = function(v, build) {
+ if (v === true) {
+ return URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
+ } else if (typeof v === 'function') {
+ var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
+ var result = v.call(this, data);
+ this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
+ this.build(!build);
+ return this;
+ } else if (v !== undefined && typeof v !== 'string') {
+ this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
+ this.build(!build);
+ return this;
+ } else {
+ return q.call(this, v, build);
+ }
+ };
+ p.setQuery = function(name, value, build) {
+ var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
+
+ if (typeof name === 'string' || name instanceof String) {
+ data[name] = value !== undefined ? value : null;
+ } else if (typeof name === 'object') {
+ for (var key in name) {
+ if (hasOwn.call(name, key)) {
+ data[key] = name[key];
+ }
+ }
+ } else {
+ throw new TypeError('URI.addQuery() accepts an object, string as the name parameter');
+ }
+
+ this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
+ if (typeof name !== 'string') {
+ build = value;
+ }
+
+ this.build(!build);
+ return this;
+ };
+ p.addQuery = function(name, value, build) {
+ var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
+ URI.addQuery(data, name, value === undefined ? null : value);
+ this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
+ if (typeof name !== 'string') {
+ build = value;
+ }
+
+ this.build(!build);
+ return this;
+ };
+ p.removeQuery = function(name, value, build) {
+ var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
+ URI.removeQuery(data, name, value);
+ this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
+ if (typeof name !== 'string') {
+ build = value;
+ }
+
+ this.build(!build);
+ return this;
+ };
+ p.hasQuery = function(name, value, withinArray) {
+ var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
+ return URI.hasQuery(data, name, value, withinArray);
+ };
+ p.setSearch = p.setQuery;
+ p.addSearch = p.addQuery;
+ p.removeSearch = p.removeQuery;
+ p.hasSearch = p.hasQuery;
+
+ // sanitizing URLs
+ p.normalize = function() {
+ if (this._parts.urn) {
+ return this
+ .normalizeProtocol(false)
+ .normalizePath(false)
+ .normalizeQuery(false)
+ .normalizeFragment(false)
+ .build();
+ }
+
+ return this
+ .normalizeProtocol(false)
+ .normalizeHostname(false)
+ .normalizePort(false)
+ .normalizePath(false)
+ .normalizeQuery(false)
+ .normalizeFragment(false)
+ .build();
+ };
+ p.normalizeProtocol = function(build) {
+ if (typeof this._parts.protocol === 'string') {
+ this._parts.protocol = this._parts.protocol.toLowerCase();
+ this.build(!build);
+ }
+
+ return this;
+ };
+ p.normalizeHostname = function(build) {
+ if (this._parts.hostname) {
+ if (this.is('IDN') && punycode) {
+ this._parts.hostname = punycode.toASCII(this._parts.hostname);
+ } else if (this.is('IPv6') && IPv6) {
+ this._parts.hostname = IPv6.best(this._parts.hostname);
+ }
+
+ this._parts.hostname = this._parts.hostname.toLowerCase();
+ this.build(!build);
+ }
+
+ return this;
+ };
+ p.normalizePort = function(build) {
+ // remove port of it's the protocol's default
+ if (typeof this._parts.protocol === 'string' && this._parts.port === URI.defaultPorts[this._parts.protocol]) {
+ this._parts.port = null;
+ this.build(!build);
+ }
+
+ return this;
+ };
+ p.normalizePath = function(build) {
+ var _path = this._parts.path;
+ if (!_path) {
+ return this;
+ }
+
+ if (this._parts.urn) {
+ this._parts.path = URI.recodeUrnPath(this._parts.path);
+ this.build(!build);
+ return this;
+ }
+
+ if (this._parts.path === '/') {
+ return this;
+ }
+
+ var _was_relative;
+ var _leadingParents = '';
+ var _parent, _pos;
+
+ // handle relative paths
+ if (_path.charAt(0) !== '/') {
+ _was_relative = true;
+ _path = '/' + _path;
+ }
+
+ // handle relative files (as opposed to directories)
+ if (_path.slice(-3) === '/..' || _path.slice(-2) === '/.') {
+ _path += '/';
+ }
+
+ // resolve simples
+ _path = _path
+ .replace(/(\/(\.\/)+)|(\/\.$)/g, '/')
+ .replace(/\/{2,}/g, '/');
+
+ // remember leading parents
+ if (_was_relative) {
+ _leadingParents = _path.substring(1).match(/^(\.\.\/)+/) || '';
+ if (_leadingParents) {
+ _leadingParents = _leadingParents[0];
+ }
+ }
+
+ // resolve parents
+ while (true) {
+ _parent = _path.indexOf('/..');
+ if (_parent === -1) {
+ // no more ../ to resolve
+ break;
+ } else if (_parent === 0) {
+ // top level cannot be relative, skip it
+ _path = _path.substring(3);
+ continue;
+ }
+
+ _pos = _path.substring(0, _parent).lastIndexOf('/');
+ if (_pos === -1) {
+ _pos = _parent;
+ }
+ _path = _path.substring(0, _pos) + _path.substring(_parent + 3);
+ }
+
+ // revert to relative
+ if (_was_relative && this.is('relative')) {
+ _path = _leadingParents + _path.substring(1);
+ }
+
+ _path = URI.recodePath(_path);
+ this._parts.path = _path;
+ this.build(!build);
+ return this;
+ };
+ p.normalizePathname = p.normalizePath;
+ p.normalizeQuery = function(build) {
+ if (typeof this._parts.query === 'string') {
+ if (!this._parts.query.length) {
+ this._parts.query = null;
+ } else {
+ this.query(URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace));
+ }
+
+ this.build(!build);
+ }
+
+ return this;
+ };
+ p.normalizeFragment = function(build) {
+ if (!this._parts.fragment) {
+ this._parts.fragment = null;
+ this.build(!build);
+ }
+
+ return this;
+ };
+ p.normalizeSearch = p.normalizeQuery;
+ p.normalizeHash = p.normalizeFragment;
+
+ p.iso8859 = function() {
+ // expect unicode input, iso8859 output
+ var e = URI.encode;
+ var d = URI.decode;
+
+ URI.encode = escape;
+ URI.decode = decodeURIComponent;
+ try {
+ this.normalize();
+ } finally {
+ URI.encode = e;
+ URI.decode = d;
+ }
+ return this;
+ };
+
+ p.unicode = function() {
+ // expect iso8859 input, unicode output
+ var e = URI.encode;
+ var d = URI.decode;
+
+ URI.encode = strictEncodeURIComponent;
+ URI.decode = unescape;
+ try {
+ this.normalize();
+ } finally {
+ URI.encode = e;
+ URI.decode = d;
+ }
+ return this;
+ };
+
+ p.readable = function() {
+ var uri = this.clone();
+ // removing username, password, because they shouldn't be displayed according to RFC 3986
+ uri.username('').password('').normalize();
+ var t = '';
+ if (uri._parts.protocol) {
+ t += uri._parts.protocol + '://';
+ }
+
+ if (uri._parts.hostname) {
+ if (uri.is('punycode') && punycode) {
+ t += punycode.toUnicode(uri._parts.hostname);
+ if (uri._parts.port) {
+ t += ':' + uri._parts.port;
+ }
+ } else {
+ t += uri.host();
+ }
+ }
+
+ if (uri._parts.hostname && uri._parts.path && uri._parts.path.charAt(0) !== '/') {
+ t += '/';
+ }
+
+ t += uri.path(true);
+ if (uri._parts.query) {
+ var q = '';
+ for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) {
+ var kv = (qp[i] || '').split('=');
+ q += '&' + URI.decodeQuery(kv[0], this._parts.escapeQuerySpace)
+ .replace(/&/g, '%26');
+
+ if (kv[1] !== undefined) {
+ q += '=' + URI.decodeQuery(kv[1], this._parts.escapeQuerySpace)
+ .replace(/&/g, '%26');
+ }
+ }
+ t += '?' + q.substring(1);
+ }
+
+ t += URI.decodeQuery(uri.hash(), true);
+ return t;
+ };
+
+ // resolving relative and absolute URLs
+ p.absoluteTo = function(base) {
+ var resolved = this.clone();
+ var properties = ['protocol', 'username', 'password', 'hostname', 'port'];
+ var basedir, i, p;
+
+ if (this._parts.urn) {
+ throw new Error('URNs do not have any generally defined hierarchical components');
+ }
+
+ if (!(base instanceof URI)) {
+ base = new URI(base);
+ }
+
+ if (!resolved._parts.protocol) {
+ resolved._parts.protocol = base._parts.protocol;
+ }
+
+ if (this._parts.hostname) {
+ return resolved;
+ }
+
+ for (i = 0; (p = properties[i]); i++) {
+ resolved._parts[p] = base._parts[p];
+ }
+
+ if (!resolved._parts.path) {
+ resolved._parts.path = base._parts.path;
+ if (!resolved._parts.query) {
+ resolved._parts.query = base._parts.query;
+ }
+ } else if (resolved._parts.path.substring(-2) === '..') {
+ resolved._parts.path += '/';
+ }
+
+ if (resolved.path().charAt(0) !== '/') {
+ basedir = base.directory();
+ basedir = basedir ? basedir : base.path().indexOf('/') === 0 ? '/' : '';
+ resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path;
+ resolved.normalizePath();
+ }
+
+ resolved.build();
+ return resolved;
+ };
+ p.relativeTo = function(base) {
+ var relative = this.clone().normalize();
+ var relativeParts, baseParts, common, relativePath, basePath;
+
+ if (relative._parts.urn) {
+ throw new Error('URNs do not have any generally defined hierarchical components');
+ }
+
+ base = new URI(base).normalize();
+ relativeParts = relative._parts;
+ baseParts = base._parts;
+ relativePath = relative.path();
+ basePath = base.path();
+
+ if (relativePath.charAt(0) !== '/') {
+ throw new Error('URI is already relative');
+ }
+
+ if (basePath.charAt(0) !== '/') {
+ throw new Error('Cannot calculate a URI relative to another relative URI');
+ }
+
+ if (relativeParts.protocol === baseParts.protocol) {
+ relativeParts.protocol = null;
+ }
+
+ if (relativeParts.username !== baseParts.username || relativeParts.password !== baseParts.password) {
+ return relative.build();
+ }
+
+ if (relativeParts.protocol !== null || relativeParts.username !== null || relativeParts.password !== null) {
+ return relative.build();
+ }
+
+ if (relativeParts.hostname === baseParts.hostname && relativeParts.port === baseParts.port) {
+ relativeParts.hostname = null;
+ relativeParts.port = null;
+ } else {
+ return relative.build();
+ }
+
+ if (relativePath === basePath) {
+ relativeParts.path = '';
+ return relative.build();
+ }
+
+ // determine common sub path
+ common = URI.commonPath(relativePath, basePath);
+
+ // If the paths have nothing in common, return a relative URL with the absolute path.
+ if (!common) {
+ return relative.build();
+ }
+
+ var parents = baseParts.path
+ .substring(common.length)
+ .replace(/[^\/]*$/, '')
+ .replace(/.*?\//g, '../');
+
+ relativeParts.path = (parents + relativeParts.path.substring(common.length)) || './';
+
+ return relative.build();
+ };
+
+ // comparing URIs
+ p.equals = function(uri) {
+ var one = this.clone();
+ var two = new URI(uri);
+ var one_map = {};
+ var two_map = {};
+ var checked = {};
+ var one_query, two_query, key;
+
+ one.normalize();
+ two.normalize();
+
+ // exact match
+ if (one.toString() === two.toString()) {
+ return true;
+ }
+
+ // extract query string
+ one_query = one.query();
+ two_query = two.query();
+ one.query('');
+ two.query('');
+
+ // definitely not equal if not even non-query parts match
+ if (one.toString() !== two.toString()) {
+ return false;
+ }
+
+ // query parameters have the same length, even if they're permuted
+ if (one_query.length !== two_query.length) {
+ return false;
+ }
+
+ one_map = URI.parseQuery(one_query, this._parts.escapeQuerySpace);
+ two_map = URI.parseQuery(two_query, this._parts.escapeQuerySpace);
+
+ for (key in one_map) {
+ if (hasOwn.call(one_map, key)) {
+ if (!isArray(one_map[key])) {
+ if (one_map[key] !== two_map[key]) {
+ return false;
+ }
+ } else if (!arraysEqual(one_map[key], two_map[key])) {
+ return false;
+ }
+
+ checked[key] = true;
+ }
+ }
+
+ for (key in two_map) {
+ if (hasOwn.call(two_map, key)) {
+ if (!checked[key]) {
+ // two contains a parameter not present in one
+ return false;
+ }
+ }
+ }
+
+ return true;
+ };
+
+ // state
+ p.duplicateQueryParameters = function(v) {
+ this._parts.duplicateQueryParameters = !!v;
+ return this;
+ };
+
+ p.escapeQuerySpace = function(v) {
+ this._parts.escapeQuerySpace = !!v;
+ return this;
+ };
+
+ return URI;
+}));
+
+},{"./IPv6":1,"./SecondLevelDomains":2,"./punycode":4}],4:[function(require,module,exports){
+(function (global){
+/*! http://mths.be/punycode v1.2.3 by @mathias */
+;(function(root) {
+
+ /** Detect free variables */
+ var freeExports = typeof exports == 'object' && exports;
+ var freeModule = typeof module == 'object' && module &&
+ module.exports == freeExports && module;
+ var freeGlobal = typeof global == 'object' && global;
+ if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+ root = freeGlobal;
+ }
+
+ /**
+ * The `punycode` object.
+ * @name punycode
+ * @type Object
+ */
+ var punycode,
+
+ /** Highest positive signed 32-bit float value */
+ maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
+
+ /** Bootstring parameters */
+ base = 36,
+ tMin = 1,
+ tMax = 26,
+ skew = 38,
+ damp = 700,
+ initialBias = 72,
+ initialN = 128, // 0x80
+ delimiter = '-', // '\x2D'
+
+ /** Regular expressions */
+ regexPunycode = /^xn--/,
+ regexNonASCII = /[^ -~]/, // unprintable ASCII chars + non-ASCII chars
+ regexSeparators = /\x2E|\u3002|\uFF0E|\uFF61/g, // RFC 3490 separators
+
+ /** Error messages */
+ errors = {
+ 'overflow': 'Overflow: input needs wider integers to process',
+ 'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
+ 'invalid-input': 'Invalid input'
+ },
+
+ /** Convenience shortcuts */
+ baseMinusTMin = base - tMin,
+ floor = Math.floor,
+ stringFromCharCode = String.fromCharCode,
+
+ /** Temporary variable */
+ key;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * A generic error utility function.
+ * @private
+ * @param {String} type The error type.
+ * @returns {Error} Throws a `RangeError` with the applicable error message.
+ */
+ function error(type) {
+ throw RangeError(errors[type]);
+ }
+
+ /**
+ * A generic `Array#map` utility function.
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} callback The function that gets called for every array
+ * item.
+ * @returns {Array} A new array of values returned by the callback function.
+ */
+ function map(array, fn) {
+ var length = array.length;
+ while (length--) {
+ array[length] = fn(array[length]);
+ }
+ return array;
+ }
+
+ /**
+ * A simple `Array#map`-like wrapper to work with domain name strings.
+ * @private
+ * @param {String} domain The domain name.
+ * @param {Function} callback The function that gets called for every
+ * character.
+ * @returns {Array} A new string of characters returned by the callback
+ * function.
+ */
+ function mapDomain(string, fn) {
+ return map(string.split(regexSeparators), fn).join('.');
+ }
+
+ /**
+ * Creates an array containing the numeric code points of each Unicode
+ * character in the string. While JavaScript uses UCS-2 internally,
+ * this function will convert a pair of surrogate halves (each of which
+ * UCS-2 exposes as separate characters) into a single code point,
+ * matching UTF-16.
+ * @see `punycode.ucs2.encode`
+ * @see <http://mathiasbynens.be/notes/javascript-encoding>
+ * @memberOf punycode.ucs2
+ * @name decode
+ * @param {String} string The Unicode input string (UCS-2).
+ * @returns {Array} The new array of code points.
+ */
+ function ucs2decode(string) {
+ var output = [],
+ counter = 0,
+ length = string.length,
+ value,
+ extra;
+ while (counter < length) {
+ value = string.charCodeAt(counter++);
+ if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+ // high surrogate, and there is a next character
+ extra = string.charCodeAt(counter++);
+ if ((extra & 0xFC00) == 0xDC00) { // low surrogate
+ output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+ } else {
+ // unmatched surrogate; only append this code unit, in case the next
+ // code unit is the high surrogate of a surrogate pair
+ output.push(value);
+ counter--;
+ }
+ } else {
+ output.push(value);
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Creates a string based on an array of numeric code points.
+ * @see `punycode.ucs2.decode`
+ * @memberOf punycode.ucs2
+ * @name encode
+ * @param {Array} codePoints The array of numeric code points.
+ * @returns {String} The new Unicode string (UCS-2).
+ */
+ function ucs2encode(array) {
+ return map(array, function(value) {
+ var output = '';
+ if (value > 0xFFFF) {
+ value -= 0x10000;
+ output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+ value = 0xDC00 | value & 0x3FF;
+ }
+ output += stringFromCharCode(value);
+ return output;
+ }).join('');
+ }
+
+ /**
+ * Converts a basic code point into a digit/integer.
+ * @see `digitToBasic()`
+ * @private
+ * @param {Number} codePoint The basic numeric code point value.
+ * @returns {Number} The numeric value of a basic code point (for use in
+ * representing integers) in the range `0` to `base - 1`, or `base` if
+ * the code point does not represent a value.
+ */
+ function basicToDigit(codePoint) {
+ if (codePoint - 48 < 10) {
+ return codePoint - 22;
+ }
+ if (codePoint - 65 < 26) {
+ return codePoint - 65;
+ }
+ if (codePoint - 97 < 26) {
+ return codePoint - 97;
+ }
+ return base;
+ }
+
+ /**
+ * Converts a digit/integer into a basic code point.
+ * @see `basicToDigit()`
+ * @private
+ * @param {Number} digit The numeric value of a basic code point.
+ * @returns {Number} The basic code point whose value (when used for
+ * representing integers) is `digit`, which needs to be in the range
+ * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
+ * used; else, the lowercase form is used. The behavior is undefined
+ * if `flag` is non-zero and `digit` has no uppercase form.
+ */
+ function digitToBasic(digit, flag) {
+ // 0..25 map to ASCII a..z or A..Z
+ // 26..35 map to ASCII 0..9
+ return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
+ }
+
+ /**
+ * Bias adaptation function as per section 3.4 of RFC 3492.
+ * http://tools.ietf.org/html/rfc3492#section-3.4
+ * @private
+ */
+ function adapt(delta, numPoints, firstTime) {
+ var k = 0;
+ delta = firstTime ? floor(delta / damp) : delta >> 1;
+ delta += floor(delta / numPoints);
+ for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
+ delta = floor(delta / baseMinusTMin);
+ }
+ return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
+ }
+
+ /**
+ * Converts a Punycode string of ASCII-only symbols to a string of Unicode
+ * symbols.
+ * @memberOf punycode
+ * @param {String} input The Punycode string of ASCII-only symbols.
+ * @returns {String} The resulting string of Unicode symbols.
+ */
+ function decode(input) {
+ // Don't use UCS-2
+ var output = [],
+ inputLength = input.length,
+ out,
+ i = 0,
+ n = initialN,
+ bias = initialBias,
+ basic,
+ j,
+ index,
+ oldi,
+ w,
+ k,
+ digit,
+ t,
+ length,
+ /** Cached calculation results */
+ baseMinusT;
+
+ // Handle the basic code points: let `basic` be the number of input code
+ // points before the last delimiter, or `0` if there is none, then copy
+ // the first basic code points to the output.
+
+ basic = input.lastIndexOf(delimiter);
+ if (basic < 0) {
+ basic = 0;
+ }
+
+ for (j = 0; j < basic; ++j) {
+ // if it's not a basic code point
+ if (input.charCodeAt(j) >= 0x80) {
+ error('not-basic');
+ }
+ output.push(input.charCodeAt(j));
+ }
+
+ // Main decoding loop: start just after the last delimiter if any basic code
+ // points were copied; start at the beginning otherwise.
+
+ for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
+
+ // `index` is the index of the next character to be consumed.
+ // Decode a generalized variable-length integer into `delta`,
+ // which gets added to `i`. The overflow checking is easier
+ // if we increase `i` as we go, then subtract off its starting
+ // value at the end to obtain `delta`.
+ for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
+
+ if (index >= inputLength) {
+ error('invalid-input');
+ }
+
+ digit = basicToDigit(input.charCodeAt(index++));
+
+ if (digit >= base || digit > floor((maxInt - i) / w)) {
+ error('overflow');
+ }
+
+ i += digit * w;
+ t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+
+ if (digit < t) {
+ break;
+ }
+
+ baseMinusT = base - t;
+ if (w > floor(maxInt / baseMinusT)) {
+ error('overflow');
+ }
+
+ w *= baseMinusT;
+
+ }
+
+ out = output.length + 1;
+ bias = adapt(i - oldi, out, oldi == 0);
+
+ // `i` was supposed to wrap around from `out` to `0`,
+ // incrementing `n` each time, so we'll fix that now:
+ if (floor(i / out) > maxInt - n) {
+ error('overflow');
+ }
+
+ n += floor(i / out);
+ i %= out;
+
+ // Insert `n` at position `i` of the output
+ output.splice(i++, 0, n);
+
+ }
+
+ return ucs2encode(output);
+ }
+
+ /**
+ * Converts a string of Unicode symbols to a Punycode string of ASCII-only
+ * symbols.
+ * @memberOf punycode
+ * @param {String} input The string of Unicode symbols.
+ * @returns {String} The resulting Punycode string of ASCII-only symbols.
+ */
+ function encode(input) {
+ var n,
+ delta,
+ handledCPCount,
+ basicLength,
+ bias,
+ j,
+ m,
+ q,
+ k,
+ t,
+ currentValue,
+ output = [],
+ /** `inputLength` will hold the number of code points in `input`. */
+ inputLength,
+ /** Cached calculation results */
+ handledCPCountPlusOne,
+ baseMinusT,
+ qMinusT;
+
+ // Convert the input in UCS-2 to Unicode
+ input = ucs2decode(input);
+
+ // Cache the length
+ inputLength = input.length;
+
+ // Initialize the state
+ n = initialN;
+ delta = 0;
+ bias = initialBias;
+
+ // Handle the basic code points
+ for (j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+ if (currentValue < 0x80) {
+ output.push(stringFromCharCode(currentValue));
+ }
+ }
+
+ handledCPCount = basicLength = output.length;
+
+ // `handledCPCount` is the number of code points that have been handled;
+ // `basicLength` is the number of basic code points.
+
+ // Finish the basic string - if it is not empty - with a delimiter
+ if (basicLength) {
+ output.push(delimiter);
+ }
+
+ // Main encoding loop:
+ while (handledCPCount < inputLength) {
+
+ // All non-basic code points < n have been handled already. Find the next
+ // larger one:
+ for (m = maxInt, j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+ if (currentValue >= n && currentValue < m) {
+ m = currentValue;
+ }
+ }
+
+ // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
+ // but guard against overflow
+ handledCPCountPlusOne = handledCPCount + 1;
+ if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
+ error('overflow');
+ }
+
+ delta += (m - n) * handledCPCountPlusOne;
+ n = m;
+
+ for (j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+
+ if (currentValue < n && ++delta > maxInt) {
+ error('overflow');
+ }
+
+ if (currentValue == n) {
+ // Represent delta as a generalized variable-length integer
+ for (q = delta, k = base; /* no condition */; k += base) {
+ t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+ if (q < t) {
+ break;
+ }
+ qMinusT = q - t;
+ baseMinusT = base - t;
+ output.push(
+ stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
+ );
+ q = floor(qMinusT / baseMinusT);
+ }
+
+ output.push(stringFromCharCode(digitToBasic(q, 0)));
+ bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
+ delta = 0;
+ ++handledCPCount;
+ }
+ }
+
+ ++delta;
+ ++n;
+
+ }
+ return output.join('');
+ }
+
+ /**
+ * Converts a Punycode string representing a domain name to Unicode. Only the
+ * Punycoded parts of the domain name will be converted, i.e. it doesn't
+ * matter if you call it on a string that has already been converted to
+ * Unicode.
+ * @memberOf punycode
+ * @param {String} domain The Punycode domain name to convert to Unicode.
+ * @returns {String} The Unicode representation of the given Punycode
+ * string.
+ */
+ function toUnicode(domain) {
+ return mapDomain(domain, function(string) {
+ return regexPunycode.test(string)
+ ? decode(string.slice(4).toLowerCase())
+ : string;
+ });
+ }
+
+ /**
+ * Converts a Unicode string representing a domain name to Punycode. Only the
+ * non-ASCII parts of the domain name will be converted, i.e. it doesn't
+ * matter if you call it with a domain that's already in ASCII.
+ * @memberOf punycode
+ * @param {String} domain The domain name to convert, as a Unicode string.
+ * @returns {String} The Punycode representation of the given domain name.
+ */
+ function toASCII(domain) {
+ return mapDomain(domain, function(string) {
+ return regexNonASCII.test(string)
+ ? 'xn--' + encode(string)
+ : string;
+ });
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /** Define the public API */
+ punycode = {
+ /**
+ * A string representing the current Punycode.js version number.
+ * @memberOf punycode
+ * @type String
+ */
+ 'version': '1.2.3',
+ /**
+ * An object of methods to convert from JavaScript's internal character
+ * representation (UCS-2) to Unicode code points, and back.
+ * @see <http://mathiasbynens.be/notes/javascript-encoding>
+ * @memberOf punycode
+ * @type Object
+ */
+ 'ucs2': {
+ 'decode': ucs2decode,
+ 'encode': ucs2encode
+ },
+ 'decode': decode,
+ 'encode': encode,
+ 'toASCII': toASCII,
+ 'toUnicode': toUnicode
+ };
+
+ /** Expose `punycode` */
+ // Some AMD build optimizers, like r.js, check for specific condition patterns
+ // like the following:
+ if (
+ typeof define == 'function' &&
+ typeof define.amd == 'object' &&
+ define.amd
+ ) {
+ define(function() {
+ return punycode;
+ });
+ } else if (freeExports && !freeExports.nodeType) {
+ if (freeModule) { // in Node.js or RingoJS v0.8.0+
+ freeModule.exports = punycode;
+ } else { // in Narwhal or RingoJS v0.7.0-
+ for (key in punycode) {
+ punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
+ }
+ }
+ } else { // in Rhino or a web browser
+ root.punycode = punycode;
+ }
+
+}(this));
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{}],5:[function(require,module,exports){
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _helpersEvent = require('./helpers/event');
+
+var _helpersEvent2 = _interopRequireDefault(_helpersEvent);
+
+var _helpersMessageEvent = require('./helpers/message-event');
+
+var _helpersMessageEvent2 = _interopRequireDefault(_helpersMessageEvent);
+
+var _helpersCloseEvent = require('./helpers/close-event');
+
+var _helpersCloseEvent2 = _interopRequireDefault(_helpersCloseEvent);
+
+/*
+* Creates an Event object and extends it to allow full modification of
+* its properties.
+*
+* @param {object} config - within config you will need to pass type and optionally target
+*/
+function createEvent(config) {
+ var type = config.type;
+ var target = config.target;
+
+ var eventObject = new _helpersEvent2['default'](type);
+
+ if (target) {
+ eventObject.target = target;
+ eventObject.srcElement = target;
+ eventObject.currentTarget = target;
+ }
+
+ return eventObject;
+}
+
+/*
+* Creates a MessageEvent object and extends it to allow full modification of
+* its properties.
+*
+* @param {object} config - within config you will need to pass type, origin, data and optionally target
+*/
+function createMessageEvent(config) {
+ var type = config.type;
+ var origin = config.origin;
+ var data = config.data;
+ var target = config.target;
+
+ var messageEvent = new _helpersMessageEvent2['default'](type, {
+ data: data,
+ origin: origin
+ });
+
+ if (target) {
+ messageEvent.target = target;
+ messageEvent.srcElement = target;
+ messageEvent.currentTarget = target;
+ }
+
+ return messageEvent;
+}
+
+/*
+* Creates a CloseEvent object and extends it to allow full modification of
+* its properties.
+*
+* @param {object} config - within config you will need to pass type and optionally target, code, and reason
+*/
+function createCloseEvent(config) {
+ var code = config.code;
+ var reason = config.reason;
+ var type = config.type;
+ var target = config.target;
+ var wasClean = config.wasClean;
+
+ if (!wasClean) {
+ wasClean = code === 1000;
+ }
+
+ var closeEvent = new _helpersCloseEvent2['default'](type, {
+ code: code,
+ reason: reason,
+ wasClean: wasClean
+ });
+
+ if (target) {
+ closeEvent.target = target;
+ closeEvent.srcElement = target;
+ closeEvent.currentTarget = target;
+ }
+
+ return closeEvent;
+}
+
+exports.createEvent = createEvent;
+exports.createMessageEvent = createMessageEvent;
+exports.createCloseEvent = createCloseEvent;
+},{"./helpers/close-event":9,"./helpers/event":12,"./helpers/message-event":13}],6:[function(require,module,exports){
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var _helpersArrayHelpers = require('./helpers/array-helpers');
+
+/*
+* EventTarget is an interface implemented by objects that can
+* receive events and may have listeners for them.
+*
+* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
+*/
+
+var EventTarget = (function () {
+ function EventTarget() {
+ _classCallCheck(this, EventTarget);
+
+ this.listeners = {};
+ }
+
+ /*
+ * Ties a listener function to a event type which can later be invoked via the
+ * dispatchEvent method.
+ *
+ * @param {string} type - the type of event (ie: 'open', 'message', etc.)
+ * @param {function} listener - the callback function to invoke whenever a event is dispatched matching the given type
+ * @param {boolean} useCapture - N/A TODO: implement useCapture functionality
+ */
+
+ _createClass(EventTarget, [{
+ key: 'addEventListener',
+ value: function addEventListener(type, listener /* , useCapture */) {
+ if (typeof listener === 'function') {
+ if (!Array.isArray(this.listeners[type])) {
+ this.listeners[type] = [];
+ }
+
+ // Only add the same function once
+ if ((0, _helpersArrayHelpers.filter)(this.listeners[type], function (item) {
+ return item === listener;
+ }).length === 0) {
+ this.listeners[type].push(listener);
+ }
+ }
+ }
+
+ /*
+ * Removes the listener so it will no longer be invoked via the dispatchEvent method.
+ *
+ * @param {string} type - the type of event (ie: 'open', 'message', etc.)
+ * @param {function} listener - the callback function to invoke whenever a event is dispatched matching the given type
+ * @param {boolean} useCapture - N/A TODO: implement useCapture functionality
+ */
+ }, {
+ key: 'removeEventListener',
+ value: function removeEventListener(type, removingListener /* , useCapture */) {
+ var arrayOfListeners = this.listeners[type];
+ this.listeners[type] = (0, _helpersArrayHelpers.reject)(arrayOfListeners, function (listener) {
+ return listener === removingListener;
+ });
+ }
+
+ /*
+ * Invokes all listener functions that are listening to the given event.type property. Each
+ * listener will be passed the event as the first argument.
+ *
+ * @param {object} event - event object which will be passed to all listeners of the event.type property
+ */
+ }, {
+ key: 'dispatchEvent',
+ value: function dispatchEvent(event) {
+ var _this = this;
+
+ for (var _len = arguments.length, customArguments = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+ customArguments[_key - 1] = arguments[_key];
+ }
+
+ var eventName = event.type;
+ var listeners = this.listeners[eventName];
+
+ if (!Array.isArray(listeners)) {
+ return false;
+ }
+
+ listeners.forEach(function (listener) {
+ if (customArguments.length > 0) {
+ listener.apply(_this, customArguments);
+ } else {
+ listener.call(_this, event);
+ }
+ });
+ }
+ }]);
+
+ return EventTarget;
+})();
+
+exports['default'] = EventTarget;
+module.exports = exports['default'];
+},{"./helpers/array-helpers":7}],7:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.reject = reject;
+exports.filter = filter;
+
+function reject(array, callback) {
+ var results = [];
+ array.forEach(function (itemInArray) {
+ if (!callback(itemInArray)) {
+ results.push(itemInArray);
+ }
+ });
+
+ return results;
+}
+
+function filter(array, callback) {
+ var results = [];
+ array.forEach(function (itemInArray) {
+ if (callback(itemInArray)) {
+ results.push(itemInArray);
+ }
+ });
+
+ return results;
+}
+},{}],8:[function(require,module,exports){
+/*
+* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
+*/
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var codes = {
+ CLOSE_NORMAL: 1000,
+ CLOSE_GOING_AWAY: 1001,
+ CLOSE_PROTOCOL_ERROR: 1002,
+ CLOSE_UNSUPPORTED: 1003,
+ CLOSE_NO_STATUS: 1005,
+ CLOSE_ABNORMAL: 1006,
+ CLOSE_TOO_LARGE: 1009
+};
+
+exports["default"] = codes;
+module.exports = exports["default"];
+},{}],9:[function(require,module,exports){
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _eventPrototype = require('./event-prototype');
+
+var _eventPrototype2 = _interopRequireDefault(_eventPrototype);
+
+var CloseEvent = (function (_EventPrototype) {
+ _inherits(CloseEvent, _EventPrototype);
+
+ function CloseEvent(type) {
+ var eventInitConfig = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+ _classCallCheck(this, CloseEvent);
+
+ _get(Object.getPrototypeOf(CloseEvent.prototype), 'constructor', this).call(this);
+
+ if (!type) {
+ throw new TypeError('Failed to construct \'CloseEvent\': 1 argument required, but only 0 present.');
+ }
+
+ if (typeof eventInitConfig !== 'object') {
+ throw new TypeError('Failed to construct \'CloseEvent\': parameter 2 (\'eventInitDict\') is not an object');
+ }
+
+ var bubbles = eventInitConfig.bubbles;
+ var cancelable = eventInitConfig.cancelable;
+ var code = eventInitConfig.code;
+ var reason = eventInitConfig.reason;
+ var wasClean = eventInitConfig.wasClean;
+
+ this.type = String(type);
+ this.timeStamp = Date.now();
+ this.target = null;
+ this.srcElement = null;
+ this.returnValue = true;
+ this.isTrusted = false;
+ this.eventPhase = 0;
+ this.defaultPrevented = false;
+ this.currentTarget = null;
+ this.cancelable = cancelable ? Boolean(cancelable) : false;
+ this.canncelBubble = false;
+ this.bubbles = bubbles ? Boolean(bubbles) : false;
+ this.code = typeof code === 'number' ? Number(code) : 0;
+ this.reason = reason ? String(reason) : '';
+ this.wasClean = wasClean ? Boolean(wasClean) : false;
+ }
+
+ return CloseEvent;
+})(_eventPrototype2['default']);
+
+exports['default'] = CloseEvent;
+module.exports = exports['default'];
+},{"./event-prototype":11}],10:[function(require,module,exports){
+/*
+* This delay allows the thread to finish assigning its on* methods
+* before invoking the delay callback. This is purely a timing hack.
+* http://geekabyte.blogspot.com/2014/01/javascript-effect-of-setting-settimeout.html
+*
+* @param {callback: function} the callback which will be invoked after the timeout
+* @parma {context: object} the context in which to invoke the function
+*/
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+function delay(callback, context) {
+ setTimeout(function timeout(timeoutContext) {
+ callback.call(timeoutContext);
+ }, 4, context);
+}
+
+exports["default"] = delay;
+module.exports = exports["default"];
+},{}],11:[function(require,module,exports){
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var EventPrototype = (function () {
+ function EventPrototype() {
+ _classCallCheck(this, EventPrototype);
+ }
+
+ _createClass(EventPrototype, [{
+ key: 'stopPropagation',
+
+ // Noops
+ value: function stopPropagation() {}
+ }, {
+ key: 'stopImmediatePropagation',
+ value: function stopImmediatePropagation() {}
+
+ // if no arguments are passed then the type is set to "undefined" on
+ // chrome and safari.
+ }, {
+ key: 'initEvent',
+ value: function initEvent() {
+ var type = arguments.length <= 0 || arguments[0] === undefined ? 'undefined' : arguments[0];
+ var bubbles = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
+ var cancelable = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
+
+ Object.assign(this, {
+ type: String(type),
+ bubbles: Boolean(bubbles),
+ cancelable: Boolean(cancelable)
+ });
+ }
+ }]);
+
+ return EventPrototype;
+})();
+
+exports['default'] = EventPrototype;
+module.exports = exports['default'];
+},{}],12:[function(require,module,exports){
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _eventPrototype = require('./event-prototype');
+
+var _eventPrototype2 = _interopRequireDefault(_eventPrototype);
+
+var Event = (function (_EventPrototype) {
+ _inherits(Event, _EventPrototype);
+
+ function Event(type) {
+ var eventInitConfig = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+ _classCallCheck(this, Event);
+
+ _get(Object.getPrototypeOf(Event.prototype), 'constructor', this).call(this);
+
+ if (!type) {
+ throw new TypeError('Failed to construct \'Event\': 1 argument required, but only 0 present.');
+ }
+
+ if (typeof eventInitConfig !== 'object') {
+ throw new TypeError('Failed to construct \'Event\': parameter 2 (\'eventInitDict\') is not an object');
+ }
+
+ var bubbles = eventInitConfig.bubbles;
+ var cancelable = eventInitConfig.cancelable;
+
+ this.type = String(type);
+ this.timeStamp = Date.now();
+ this.target = null;
+ this.srcElement = null;
+ this.returnValue = true;
+ this.isTrusted = false;
+ this.eventPhase = 0;
+ this.defaultPrevented = false;
+ this.currentTarget = null;
+ this.cancelable = cancelable ? Boolean(cancelable) : false;
+ this.canncelBubble = false;
+ this.bubbles = bubbles ? Boolean(bubbles) : false;
+ }
+
+ return Event;
+})(_eventPrototype2['default']);
+
+exports['default'] = Event;
+module.exports = exports['default'];
+},{"./event-prototype":11}],13:[function(require,module,exports){
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _eventPrototype = require('./event-prototype');
+
+var _eventPrototype2 = _interopRequireDefault(_eventPrototype);
+
+var MessageEvent = (function (_EventPrototype) {
+ _inherits(MessageEvent, _EventPrototype);
+
+ function MessageEvent(type) {
+ var eventInitConfig = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+ _classCallCheck(this, MessageEvent);
+
+ _get(Object.getPrototypeOf(MessageEvent.prototype), 'constructor', this).call(this);
+
+ if (!type) {
+ throw new TypeError('Failed to construct \'MessageEvent\': 1 argument required, but only 0 present.');
+ }
+
+ if (typeof eventInitConfig !== 'object') {
+ throw new TypeError('Failed to construct \'MessageEvent\': parameter 2 (\'eventInitDict\') is not an object');
+ }
+
+ var bubbles = eventInitConfig.bubbles;
+ var cancelable = eventInitConfig.cancelable;
+ var data = eventInitConfig.data;
+ var origin = eventInitConfig.origin;
+ var lastEventId = eventInitConfig.lastEventId;
+ var ports = eventInitConfig.ports;
+
+ this.type = String(type);
+ this.timeStamp = Date.now();
+ this.target = null;
+ this.srcElement = null;
+ this.returnValue = true;
+ this.isTrusted = false;
+ this.eventPhase = 0;
+ this.defaultPrevented = false;
+ this.currentTarget = null;
+ this.cancelable = cancelable ? Boolean(cancelable) : false;
+ this.canncelBubble = false;
+ this.bubbles = bubbles ? Boolean(bubbles) : false;
+ this.origin = origin ? String(origin) : '';
+ this.ports = typeof ports === 'undefined' ? null : ports;
+ this.data = typeof data === 'undefined' ? null : data;
+ this.lastEventId = lastEventId ? String(lastEventId) : '';
+ }
+
+ return MessageEvent;
+})(_eventPrototype2['default']);
+
+exports['default'] = MessageEvent;
+module.exports = exports['default'];
+},{"./event-prototype":11}],14:[function(require,module,exports){
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _server = require('./server');
+
+var _server2 = _interopRequireDefault(_server);
+
+var _socketIo = require('./socket-io');
+
+var _socketIo2 = _interopRequireDefault(_socketIo);
+
+var _websocket = require('./websocket');
+
+var _websocket2 = _interopRequireDefault(_websocket);
+
+if (typeof window !== 'undefined') {
+ window.MockServer = _server2['default'];
+ window.MockWebSocket = _websocket2['default'];
+ window.MockSocketIO = _socketIo2['default'];
+}
+
+var Server = _server2['default'];
+exports.Server = Server;
+var WebSocket = _websocket2['default'];
+exports.WebSocket = WebSocket;
+var SocketIO = _socketIo2['default'];
+exports.SocketIO = SocketIO;
+},{"./server":16,"./socket-io":17,"./websocket":18}],15:[function(require,module,exports){
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var _helpersArrayHelpers = require('./helpers/array-helpers');
+
+/*
+* The network bridge is a way for the mock websocket object to 'communicate' with
+* all avalible servers. This is a singleton object so it is important that you
+* clean up urlMap whenever you are finished.
+*/
+
+var NetworkBridge = (function () {
+ function NetworkBridge() {
+ _classCallCheck(this, NetworkBridge);
+
+ this.urlMap = {};
+ }
+
+ /*
+ * Attaches a websocket object to the urlMap hash so that it can find the server
+ * it is connected to and the server in turn can find it.
+ *
+ * @param {object} websocket - websocket object to add to the urlMap hash
+ * @param {string} url
+ */
+
+ _createClass(NetworkBridge, [{
+ key: 'attachWebSocket',
+ value: function attachWebSocket(websocket, url) {
+ var connectionLookup = this.urlMap[url];
+
+ if (connectionLookup && connectionLookup.server && connectionLookup.websockets.indexOf(websocket) === -1) {
+ connectionLookup.websockets.push(websocket);
+ return connectionLookup.server;
+ }
+ }
+
+ /*
+ * Attaches a websocket to a room
+ */
+ }, {
+ key: 'addMembershipToRoom',
+ value: function addMembershipToRoom(websocket, room) {
+ var connectionLookup = this.urlMap[websocket.url];
+
+ if (connectionLookup && connectionLookup.server && connectionLookup.websockets.indexOf(websocket) !== -1) {
+ if (!connectionLookup.roomMemberships[room]) {
+ connectionLookup.roomMemberships[room] = [];
+ }
+
+ connectionLookup.roomMemberships[room].push(websocket);
+ }
+ }
+
+ /*
+ * Attaches a server object to the urlMap hash so that it can find a websockets
+ * which are connected to it and so that websockets can in turn can find it.
+ *
+ * @param {object} server - server object to add to the urlMap hash
+ * @param {string} url
+ */
+ }, {
+ key: 'attachServer',
+ value: function attachServer(server, url) {
+ var connectionLookup = this.urlMap[url];
+
+ if (!connectionLookup) {
+ this.urlMap[url] = {
+ server: server,
+ websockets: [],
+ roomMemberships: {}
+ };
+
+ return server;
+ }
+ }
+
+ /*
+ * Finds the server which is 'running' on the given url.
+ *
+ * @param {string} url - the url to use to find which server is running on it
+ */
+ }, {
+ key: 'serverLookup',
+ value: function serverLookup(url) {
+ var connectionLookup = this.urlMap[url];
+
+ if (connectionLookup) {
+ return connectionLookup.server;
+ }
+ }
+
+ /*
+ * Finds all websockets which is 'listening' on the given url.
+ *
+ * @param {string} url - the url to use to find all websockets which are associated with it
+ * @param {string} room - if a room is provided, will only return sockets in this room
+ */
+ }, {
+ key: 'websocketsLookup',
+ value: function websocketsLookup(url, room) {
+ var connectionLookup = this.urlMap[url];
+
+ if (!connectionLookup) {
+ return [];
+ }
+
+ if (room) {
+ var members = connectionLookup.roomMemberships[room];
+ return members ? members : [];
+ }
+
+ return connectionLookup.websockets;
+ }
+
+ /*
+ * Removes the entry associated with the url.
+ *
+ * @param {string} url
+ */
+ }, {
+ key: 'removeServer',
+ value: function removeServer(url) {
+ delete this.urlMap[url];
+ }
+
+ /*
+ * Removes the individual websocket from the map of associated websockets.
+ *
+ * @param {object} websocket - websocket object to remove from the url map
+ * @param {string} url
+ */
+ }, {
+ key: 'removeWebSocket',
+ value: function removeWebSocket(websocket, url) {
+ var connectionLookup = this.urlMap[url];
+
+ if (connectionLookup) {
+ connectionLookup.websockets = (0, _helpersArrayHelpers.reject)(connectionLookup.websockets, function (socket) {
+ return socket === websocket;
+ });
+ }
+ }
+
+ /*
+ * Removes a websocket from a room
+ */
+ }, {
+ key: 'removeMembershipFromRoom',
+ value: function removeMembershipFromRoom(websocket, room) {
+ var connectionLookup = this.urlMap[websocket.url];
+ var memberships = connectionLookup.roomMemberships[room];
+
+ if (connectionLookup && memberships !== null) {
+ connectionLookup.roomMemberships[room] = (0, _helpersArrayHelpers.reject)(memberships, function (socket) {
+ return socket === websocket;
+ });
+ }
+ }
+ }]);
+
+ return NetworkBridge;
+})();
+
+exports['default'] = new NetworkBridge();
+// Note: this is a singleton
+module.exports = exports['default'];
+},{"./helpers/array-helpers":7}],16:[function(require,module,exports){
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x4, _x5, _x6) { var _again = true; _function: while (_again) { var object = _x4, property = _x5, receiver = _x6; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x4 = parent; _x5 = property; _x6 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _urijs = require('urijs');
+
+var _urijs2 = _interopRequireDefault(_urijs);
+
+var _websocket = require('./websocket');
+
+var _websocket2 = _interopRequireDefault(_websocket);
+
+var _eventTarget = require('./event-target');
+
+var _eventTarget2 = _interopRequireDefault(_eventTarget);
+
+var _networkBridge = require('./network-bridge');
+
+var _networkBridge2 = _interopRequireDefault(_networkBridge);
+
+var _helpersCloseCodes = require('./helpers/close-codes');
+
+var _helpersCloseCodes2 = _interopRequireDefault(_helpersCloseCodes);
+
+var _eventFactory = require('./event-factory');
+
+/*
+* https://github.com/websockets/ws#server-example
+*/
+
+var Server = (function (_EventTarget) {
+ _inherits(Server, _EventTarget);
+
+ /*
+ * @param {string} url
+ */
+
+ function Server(url) {
+ _classCallCheck(this, Server);
+
+ _get(Object.getPrototypeOf(Server.prototype), 'constructor', this).call(this);
+ this.url = (0, _urijs2['default'])(url).toString();
+ var server = _networkBridge2['default'].attachServer(this, this.url);
+
+ if (!server) {
+ this.dispatchEvent((0, _eventFactory.createEvent)({ type: 'error' }));
+ throw new Error('A mock server is already listening on this url');
+ }
+ }
+
+ /*
+ * Alternative constructor to support namespaces in socket.io
+ *
+ * http://socket.io/docs/rooms-and-namespaces/#custom-namespaces
+ */
+
+ /*
+ * This is the main function for the mock server to subscribe to the on events.
+ *
+ * ie: mockServer.on('connection', function() { console.log('a mock client connected'); });
+ *
+ * @param {string} type - The event key to subscribe to. Valid keys are: connection, message, and close.
+ * @param {function} callback - The callback which should be called when a certain event is fired.
+ */
+
+ _createClass(Server, [{
+ key: 'on',
+ value: function on(type, callback) {
+ this.addEventListener(type, callback);
+ }
+
+ /*
+ * This send function will notify all mock clients via their onmessage callbacks that the server
+ * has a message for them.
+ *
+ * @param {*} data - Any javascript object which will be crafted into a MessageObject.
+ */
+ }, {
+ key: 'send',
+ value: function send(data) {
+ var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+ this.emit('message', data, options);
+ }
+
+ /*
+ * Sends a generic message event to all mock clients.
+ */
+ }, {
+ key: 'emit',
+ value: function emit(event, data) {
+ var _this2 = this;
+
+ var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+ var websockets = options.websockets;
+
+ if (!websockets) {
+ websockets = _networkBridge2['default'].websocketsLookup(this.url);
+ }
+
+ websockets.forEach(function (socket) {
+ socket.dispatchEvent((0, _eventFactory.createMessageEvent)({
+ type: event,
+ data: data,
+ origin: _this2.url,
+ target: socket
+ }));
+ });
+ }
+
+ /*
+ * Closes the connection and triggers the onclose method of all listening
+ * websockets. After that it removes itself from the urlMap so another server
+ * could add itself to the url.
+ *
+ * @param {object} options
+ */
+ }, {
+ key: 'close',
+ value: function close() {
+ var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+ var code = options.code;
+ var reason = options.reason;
+ var wasClean = options.wasClean;
+
+ var listeners = _networkBridge2['default'].websocketsLookup(this.url);
+
+ listeners.forEach(function (socket) {
+ socket.readyState = _websocket2['default'].CLOSE;
+ socket.dispatchEvent((0, _eventFactory.createCloseEvent)({
+ type: 'close',
+ target: socket,
+ code: code || _helpersCloseCodes2['default'].CLOSE_NORMAL,
+ reason: reason || '',
+ wasClean: wasClean
+ }));
+ });
+
+ this.dispatchEvent((0, _eventFactory.createCloseEvent)({ type: 'close' }), this);
+ _networkBridge2['default'].removeServer(this.url);
+ }
+
+ /*
+ * Returns an array of websockets which are listening to this server
+ */
+ }, {
+ key: 'clients',
+ value: function clients() {
+ return _networkBridge2['default'].websocketsLookup(this.url);
+ }
+
+ /*
+ * Prepares a method to submit an event to members of the room
+ *
+ * e.g. server.to('my-room').emit('hi!');
+ */
+ }, {
+ key: 'to',
+ value: function to(room) {
+ var _this = this;
+ var websockets = _networkBridge2['default'].websocketsLookup(this.url, room);
+ return {
+ emit: function emit(event, data) {
+ _this.emit(event, data, { websockets: websockets });
+ }
+ };
+ }
+ }]);
+
+ return Server;
+})(_eventTarget2['default']);
+
+Server.of = function of(url) {
+ return new Server(url);
+};
+
+exports['default'] = Server;
+module.exports = exports['default'];
+},{"./event-factory":5,"./event-target":6,"./helpers/close-codes":8,"./network-bridge":15,"./websocket":18,"urijs":3}],17:[function(require,module,exports){
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x3, _x4, _x5) { var _again = true; _function: while (_again) { var object = _x3, property = _x4, receiver = _x5; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x3 = parent; _x4 = property; _x5 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _urijs = require('urijs');
+
+var _urijs2 = _interopRequireDefault(_urijs);
+
+var _helpersDelay = require('./helpers/delay');
+
+var _helpersDelay2 = _interopRequireDefault(_helpersDelay);
+
+var _eventTarget = require('./event-target');
+
+var _eventTarget2 = _interopRequireDefault(_eventTarget);
+
+var _networkBridge = require('./network-bridge');
+
+var _networkBridge2 = _interopRequireDefault(_networkBridge);
+
+var _helpersCloseCodes = require('./helpers/close-codes');
+
+var _helpersCloseCodes2 = _interopRequireDefault(_helpersCloseCodes);
+
+var _eventFactory = require('./event-factory');
+
+/*
+* The socket-io class is designed to mimick the real API as closely as possible.
+*
+* http://socket.io/docs/
+*/
+
+var SocketIO = (function (_EventTarget) {
+ _inherits(SocketIO, _EventTarget);
+
+ /*
+ * @param {string} url
+ */
+
+ function SocketIO() {
+ var _this = this;
+
+ var url = arguments.length <= 0 || arguments[0] === undefined ? 'socket.io' : arguments[0];
+ var protocol = arguments.length <= 1 || arguments[1] === undefined ? '' : arguments[1];
+
+ _classCallCheck(this, SocketIO);
+
+ _get(Object.getPrototypeOf(SocketIO.prototype), 'constructor', this).call(this);
+
+ this.binaryType = 'blob';
+ this.url = (0, _urijs2['default'])(url).toString();
+ this.readyState = SocketIO.CONNECTING;
+ this.protocol = '';
+
+ if (typeof protocol === 'string') {
+ this.protocol = protocol;
+ } else if (Array.isArray(protocol) && protocol.length > 0) {
+ this.protocol = protocol[0];
+ }
+
+ var server = _networkBridge2['default'].attachWebSocket(this, this.url);
+
+ /*
+ * Delay triggering the connection events so they can be defined in time.
+ */
+ (0, _helpersDelay2['default'])(function delayCallback() {
+ if (server) {
+ this.readyState = SocketIO.OPEN;
+ server.dispatchEvent((0, _eventFactory.createEvent)({ type: 'connection' }), server, this);
+ server.dispatchEvent((0, _eventFactory.createEvent)({ type: 'connect' }), server, this); // alias
+ this.dispatchEvent((0, _eventFactory.createEvent)({ type: 'connect', target: this }));
+ } else {
+ this.readyState = SocketIO.CLOSED;
+ this.dispatchEvent((0, _eventFactory.createEvent)({ type: 'error', target: this }));
+ this.dispatchEvent((0, _eventFactory.createCloseEvent)({
+ type: 'close',
+ target: this,
+ code: _helpersCloseCodes2['default'].CLOSE_NORMAL
+ }));
+
+ console.error('Socket.io connection to \'' + this.url + '\' failed');
+ }
+ }, this);
+
+ /**
+ Add an aliased event listener for close / disconnect
+ */
+ this.addEventListener('close', function (event) {
+ _this.dispatchEvent((0, _eventFactory.createCloseEvent)({
+ type: 'disconnect',
+ target: event.target,
+ code: event.code
+ }));
+ });
+ }
+
+ /*
+ * Closes the SocketIO connection or connection attempt, if any.
+ * If the connection is already CLOSED, this method does nothing.
+ */
+
+ _createClass(SocketIO, [{
+ key: 'close',
+ value: function close() {
+ if (this.readyState !== SocketIO.OPEN) {
+ return undefined;
+ }
+
+ var server = _networkBridge2['default'].serverLookup(this.url);
+ _networkBridge2['default'].removeWebSocket(this, this.url);
+
+ this.readyState = SocketIO.CLOSED;
+ this.dispatchEvent((0, _eventFactory.createCloseEvent)({
+ type: 'close',
+ target: this,
+ code: _helpersCloseCodes2['default'].CLOSE_NORMAL
+ }));
+
+ if (server) {
+ server.dispatchEvent((0, _eventFactory.createCloseEvent)({
+ type: 'disconnect',
+ target: this,
+ code: _helpersCloseCodes2['default'].CLOSE_NORMAL
+ }), server);
+ }
+ }
+
+ /*
+ * Alias for Socket#close
+ *
+ * https://github.com/socketio/socket.io-client/blob/master/lib/socket.js#L383
+ */
+ }, {
+ key: 'disconnect',
+ value: function disconnect() {
+ this.close();
+ }
+
+ /*
+ * Submits an event to the server with a payload
+ */
+ }, {
+ key: 'emit',
+ value: function emit(event, data) {
+ if (this.readyState !== SocketIO.OPEN) {
+ throw new Error('SocketIO is already in CLOSING or CLOSED state');
+ }
+
+ var messageEvent = (0, _eventFactory.createMessageEvent)({
+ type: event,
+ origin: this.url,
+ data: data
+ });
+
+ var server = _networkBridge2['default'].serverLookup(this.url);
+
+ if (server) {
+ server.dispatchEvent(messageEvent, data);
+ }
+ }
+
+ /*
+ * Submits a 'message' event to the server.
+ *
+ * Should behave exactly like WebSocket#send
+ *
+ * https://github.com/socketio/socket.io-client/blob/master/lib/socket.js#L113
+ */
+ }, {
+ key: 'send',
+ value: function send(data) {
+ this.emit('message', data);
+ }
+
+ /*
+ * For registering events to be received from the server
+ */
+ }, {
+ key: 'on',
+ value: function on(type, callback) {
+ this.addEventListener(type, callback);
+ }
+
+ /*
+ * Join a room on a server
+ *
+ * http://socket.io/docs/rooms-and-namespaces/#joining-and-leaving
+ */
+ }, {
+ key: 'join',
+ value: function join(room) {
+ _networkBridge2['default'].addMembershipToRoom(this, room);
+ }
+
+ /*
+ * Get the websocket to leave the room
+ *
+ * http://socket.io/docs/rooms-and-namespaces/#joining-and-leaving
+ */
+ }, {
+ key: 'leave',
+ value: function leave(room) {
+ _networkBridge2['default'].removeMembershipFromRoom(this, room);
+ }
+
+ /*
+ * Invokes all listener functions that are listening to the given event.type property. Each
+ * listener will be passed the event as the first argument.
+ *
+ * @param {object} event - event object which will be passed to all listeners of the event.type property
+ */
+ }, {
+ key: 'dispatchEvent',
+ value: function dispatchEvent(event) {
+ var _this2 = this;
+
+ for (var _len = arguments.length, customArguments = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+ customArguments[_key - 1] = arguments[_key];
+ }
+
+ var eventName = event.type;
+ var listeners = this.listeners[eventName];
+
+ if (!Array.isArray(listeners)) {
+ return false;
+ }
+
+ listeners.forEach(function (listener) {
+ if (customArguments.length > 0) {
+ listener.apply(_this2, customArguments);
+ } else {
+ // Regular WebSockets expect a MessageEvent but Socketio.io just wants raw data
+ // payload instanceof MessageEvent works, but you can't isntance of NodeEvent
+ // for now we detect if the output has data defined on it
+ listener.call(_this2, event.data ? event.data : event);
+ }
+ });
+ }
+ }]);
+
+ return SocketIO;
+})(_eventTarget2['default']);
+
+SocketIO.CONNECTING = 0;
+SocketIO.OPEN = 1;
+SocketIO.CLOSING = 2;
+SocketIO.CLOSED = 3;
+
+/*
+* Static constructor methods for the IO Socket
+*/
+var IO = function ioConstructor(url) {
+ return new SocketIO(url);
+};
+
+/*
+* Alias the raw IO() constructor
+*/
+IO.connect = function ioConnect(url) {
+ /* eslint-disable new-cap */
+ return IO(url);
+ /* eslint-enable new-cap */
+};
+
+exports['default'] = IO;
+module.exports = exports['default'];
+},{"./event-factory":5,"./event-target":6,"./helpers/close-codes":8,"./helpers/delay":10,"./network-bridge":15,"urijs":3}],18:[function(require,module,exports){
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _urijs = require('urijs');
+
+var _urijs2 = _interopRequireDefault(_urijs);
+
+var _helpersDelay = require('./helpers/delay');
+
+var _helpersDelay2 = _interopRequireDefault(_helpersDelay);
+
+var _eventTarget = require('./event-target');
+
+var _eventTarget2 = _interopRequireDefault(_eventTarget);
+
+var _networkBridge = require('./network-bridge');
+
+var _networkBridge2 = _interopRequireDefault(_networkBridge);
+
+var _helpersCloseCodes = require('./helpers/close-codes');
+
+var _helpersCloseCodes2 = _interopRequireDefault(_helpersCloseCodes);
+
+var _eventFactory = require('./event-factory');
+
+/*
+* The main websocket class which is designed to mimick the native WebSocket class as close
+* as possible.
+*
+* https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
+*/
+
+var WebSocket = (function (_EventTarget) {
+ _inherits(WebSocket, _EventTarget);
+
+ /*
+ * @param {string} url
+ */
+
+ function WebSocket(url) {
+ var protocol = arguments.length <= 1 || arguments[1] === undefined ? '' : arguments[1];
+
+ _classCallCheck(this, WebSocket);
+
+ _get(Object.getPrototypeOf(WebSocket.prototype), 'constructor', this).call(this);
+
+ if (!url) {
+ throw new TypeError('Failed to construct \'WebSocket\': 1 argument required, but only 0 present.');
+ }
+
+ this.binaryType = 'blob';
+ this.url = (0, _urijs2['default'])(url).toString();
+ this.readyState = WebSocket.CONNECTING;
+ this.protocol = '';
+
+ if (typeof protocol === 'string') {
+ this.protocol = protocol;
+ } else if (Array.isArray(protocol) && protocol.length > 0) {
+ this.protocol = protocol[0];
+ }
+
+ /*
+ * In order to capture the callback function we need to define custom setters.
+ * To illustrate:
+ * mySocket.onopen = function() { alert(true) };
+ *
+ * The only way to capture that function and hold onto it for later is with the
+ * below code:
+ */
+ Object.defineProperties(this, {
+ onopen: {
+ configurable: true,
+ enumerable: true,
+ get: function get() {
+ return this.listeners.open;
+ },
+ set: function set(listener) {
+ this.addEventListener('open', listener);
+ }
+ },
+ onmessage: {
+ configurable: true,
+ enumerable: true,
+ get: function get() {
+ return this.listeners.message;
+ },
+ set: function set(listener) {
+ this.addEventListener('message', listener);
+ }
+ },
+ onclose: {
+ configurable: true,
+ enumerable: true,
+ get: function get() {
+ return this.listeners.close;
+ },
+ set: function set(listener) {
+ this.addEventListener('close', listener);
+ }
+ },
+ onerror: {
+ configurable: true,
+ enumerable: true,
+ get: function get() {
+ return this.listeners.error;
+ },
+ set: function set(listener) {
+ this.addEventListener('error', listener);
+ }
+ }
+ });
+
+ var server = _networkBridge2['default'].attachWebSocket(this, this.url);
+
+ /*
+ * This delay is needed so that we dont trigger an event before the callbacks have been
+ * setup. For example:
+ *
+ * var socket = new WebSocket('ws://localhost');
+ *
+ * // If we dont have the delay then the event would be triggered right here and this is
+ * // before the onopen had a chance to register itself.
+ *
+ * socket.onopen = () => { // this would never be called };
+ *
+ * // and with the delay the event gets triggered here after all of the callbacks have been
+ * // registered :-)
+ */
+ (0, _helpersDelay2['default'])(function delayCallback() {
+ if (server) {
+ this.readyState = WebSocket.OPEN;
+ server.dispatchEvent((0, _eventFactory.createEvent)({ type: 'connection' }), server, this);
+ this.dispatchEvent((0, _eventFactory.createEvent)({ type: 'open', target: this }));
+ } else {
+ this.readyState = WebSocket.CLOSED;
+ this.dispatchEvent((0, _eventFactory.createEvent)({ type: 'error', target: this }));
+ this.dispatchEvent((0, _eventFactory.createCloseEvent)({ type: 'close', target: this, code: _helpersCloseCodes2['default'].CLOSE_NORMAL }));
+
+ console.error('WebSocket connection to \'' + this.url + '\' failed');
+ }
+ }, this);
+ }
+
+ /*
+ * Transmits data to the server over the WebSocket connection.
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#send()
+ */
+
+ _createClass(WebSocket, [{
+ key: 'send',
+ value: function send(data) {
+ if (this.readyState === WebSocket.CLOSING || this.readyState === WebSocket.CLOSED) {
+ throw new Error('WebSocket is already in CLOSING or CLOSED state');
+ }
+
+ var messageEvent = (0, _eventFactory.createMessageEvent)({
+ type: 'message',
+ origin: this.url,
+ data: data
+ });
+
+ var server = _networkBridge2['default'].serverLookup(this.url);
+
+ if (server) {
+ server.dispatchEvent(messageEvent, data);
+ }
+ }
+
+ /*
+ * Closes the WebSocket connection or connection attempt, if any.
+ * If the connection is already CLOSED, this method does nothing.
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#close()
+ */
+ }, {
+ key: 'close',
+ value: function close() {
+ if (this.readyState !== WebSocket.OPEN) {
+ return undefined;
+ }
+
+ var server = _networkBridge2['default'].serverLookup(this.url);
+ var closeEvent = (0, _eventFactory.createCloseEvent)({
+ type: 'close',
+ target: this,
+ code: _helpersCloseCodes2['default'].CLOSE_NORMAL
+ });
+
+ _networkBridge2['default'].removeWebSocket(this, this.url);
+
+ this.readyState = WebSocket.CLOSED;
+ this.dispatchEvent(closeEvent);
+
+ if (server) {
+ server.dispatchEvent(closeEvent, server);
+ }
+ }
+ }]);
+
+ return WebSocket;
+})(_eventTarget2['default']);
+
+WebSocket.CONNECTING = 0;
+WebSocket.OPEN = 1;
+WebSocket.CLOSING = 2;
+WebSocket.CLOSED = 3;
+
+exports['default'] = WebSocket;
+module.exports = exports['default'];
+},{"./event-factory":5,"./event-target":6,"./helpers/close-codes":8,"./helpers/delay":10,"./network-bridge":15,"urijs":3}]},{},[14])
+//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCIuLi8uLi9ub2RlX21vZHVsZXMvdXJpanMvc3JjL0lQdjYuanMiLCIuLi8uLi9ub2RlX21vZHVsZXMvdXJpanMvc3JjL1NlY29uZExldmVsRG9tYWlucy5qcyIsIi4uLy4uL25vZGVfbW9kdWxlcy91cmlqcy9zcmMvVVJJLmpzIiwiLi4vLi4vbm9kZV9tb2R1bGVzL3VyaWpzL3NyYy9wdW55Y29kZS5qcyIsImV2ZW50LWZhY3RvcnkuanMiLCJldmVudC10YXJnZXQuanMiLCJoZWxwZXJzL2FycmF5LWhlbHBlcnMuanMiLCJoZWxwZXJzL2Nsb3NlLWNvZGVzLmpzIiwiaGVscGVycy9jbG9zZS1ldmVudC5qcyIsImhlbHBlcnMvZGVsYXkuanMiLCJoZWxwZXJzL2V2ZW50LXByb3RvdHlwZS5qcyIsImhlbHBlcnMvZXZlbnQuanMiLCJoZWxwZXJzL21lc3NhZ2UtZXZlbnQuanMiLCJtYWluLmpzIiwibmV0d29yay1icmlkZ2UuanMiLCJzZXJ2ZXIuanMiLCJzb2NrZXQtaW8uanMiLCJ3ZWJzb2NrZXQuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNUxBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDalBBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztBQ2puRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7OztBQzVmQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDckdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN4R0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM1QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNuQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDL0RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNwQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN0NBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDakVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDL0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3S0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN0xBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDclJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsIi8qIVxuICogVVJJLmpzIC0gTXV0YXRpbmcgVVJMc1xuICogSVB2NiBTdXBwb3J0XG4gKlxuICogVmVyc2lvbjogMS4xNy4wXG4gKlxuICogQXV0aG9yOiBSb2RuZXkgUmVobVxuICogV2ViOiBodHRwOi8vbWVkaWFsaXplLmdpdGh1Yi5pby9VUkkuanMvXG4gKlxuICogTGljZW5zZWQgdW5kZXJcbiAqICAgTUlUIExpY2Vuc2UgaHR0cDovL3d3dy5vcGVuc291cmNlLm9yZy9saWNlbnNlcy9taXQtbGljZW5zZVxuICogICBHUEwgdjMgaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0dQTC0zLjBcbiAqXG4gKi9cblxuKGZ1bmN0aW9uIChyb290LCBmYWN0b3J5KSB7XG4gICd1c2Ugc3RyaWN0JztcbiAgLy8gaHR0cHM6Ly9naXRodWIuY29tL3VtZGpzL3VtZC9ibG9iL21hc3Rlci9yZXR1cm5FeHBvcnRzLmpzXG4gIGlmICh0eXBlb2YgZXhwb3J0cyA9PT0gJ29iamVjdCcpIHtcbiAgICAvLyBOb2RlXG4gICAgbW9kdWxlLmV4cG9ydHMgPSBmYWN0b3J5KCk7XG4gIH0gZWxzZSBpZiAodHlwZW9mIGRlZmluZSA9PT0gJ2Z1bmN0aW9uJyAmJiBkZWZpbmUuYW1kKSB7XG4gICAgLy8gQU1ELiBSZWdpc3RlciBhcyBhbiBhbm9ueW1vdXMgbW9kdWxlLlxuICAgIGRlZmluZShmYWN0b3J5KTtcbiAgfSBlbHNlIHtcbiAgICAvLyBCcm93c2VyIGdsb2JhbHMgKHJvb3QgaXMgd2luZG93KVxuICAgIHJvb3QuSVB2NiA9IGZhY3Rvcnkocm9vdCk7XG4gIH1cbn0odGhpcywgZnVuY3Rpb24gKHJvb3QpIHtcbiAgJ3VzZSBzdHJpY3QnO1xuXG4gIC8qXG4gIHZhciBfaW4gPSBcImZlODA6MDAwMDowMDAwOjAwMDA6MDIwNDo2MWZmOmZlOWQ6ZjE1NlwiO1xuICB2YXIgX291dCA9IElQdjYuYmVzdChfaW4pO1xuICB2YXIgX2V4cGVjdGVkID0gXCJmZTgwOjoyMDQ6NjFmZjpmZTlkOmYxNTZcIjtcblxuICBjb25zb2xlLmxvZyhfaW4sIF9vdXQsIF9leHBlY3RlZCwgX291dCA9PT0gX2V4cGVjdGVkKTtcbiAgKi9cblxuICAvLyBzYXZlIGN1cnJlbnQgSVB2NiB2YXJpYWJsZSwgaWYgYW55XG4gIHZhciBfSVB2NiA9IHJvb3QgJiYgcm9vdC5JUHY2O1xuXG4gIGZ1bmN0aW9uIGJlc3RQcmVzZW50YXRpb24oYWRkcmVzcykge1xuICAgIC8vIGJhc2VkIG9uOlxuICAgIC8vIEphdmFzY3JpcHQgdG8gdGVzdCBhbiBJUHY2IGFkZHJlc3MgZm9yIHByb3BlciBmb3JtYXQsIGFuZCB0b1xuICAgIC8vIHByZXNlbnQgdGhlIFwiYmVzdCB0ZXh0IHJlcHJlc2VudGF0aW9uXCIgYWNjb3JkaW5nIHRvIElFVEYgRHJhZnQgUkZDIGF0XG4gICAgLy8gaHR0cDovL3Rvb2xzLmlldGYub3JnL2h0bWwvZHJhZnQtaWV0Zi02bWFuLXRleHQtYWRkci1yZXByZXNlbnRhdGlvbi0wNFxuICAgIC8vIDggRmViIDIwMTAgUmljaCBCcm93biwgRGFydHdhcmUsIExMQ1xuICAgIC8vIFBsZWFzZSBmZWVsIGZyZWUgdG8gdXNlIHRoaXMgY29kZSBhcyBsb25nIGFzIHlvdSBwcm92aWRlIGEgbGluayB0b1xuICAgIC8vIGh0dHA6Ly93d3cuaW50ZXJtYXBwZXIuY29tXG4gICAgLy8gaHR0cDovL2ludGVybWFwcGVyLmNvbS9zdXBwb3J0L3Rvb2xzL0lQVjYtVmFsaWRhdG9yLmFzcHhcbiAgICAvLyBodHRwOi8vZG93bmxvYWQuZGFydHdhcmUuY29tL3RoaXJkcGFydHkvaXB2NnZhbGlkYXRvci5qc1xuXG4gICAgdmFyIF9hZGRyZXNzID0gYWRkcmVzcy50b0xvd2VyQ2FzZSgpO1xuICAgIHZhciBzZWdtZW50cyA9IF9hZGRyZXNzLnNwbGl0KCc6Jyk7XG4gICAgdmFyIGxlbmd0aCA9IHNlZ21lbnRzLmxlbmd0aDtcbiAgICB2YXIgdG90YWwgPSA4O1xuXG4gICAgLy8gdHJpbSBjb2xvbnMgKDo6IG9yIDo6YTpiOmPigKYgb3Ig4oCmYTpiOmM6OilcbiAgICBpZiAoc2VnbWVudHNbMF0gPT09ICcnICYmIHNlZ21lbnRzWzFdID09PSAnJyAmJiBzZWdtZW50c1syXSA9PT0gJycpIHtcbiAgICAgIC8vIG11c3QgaGF2ZSBiZWVuIDo6XG4gICAgICAvLyByZW1vdmUgZmlyc3QgdHdvIGl0ZW1zXG4gICAgICBzZWdtZW50cy5zaGlmdCgpO1xuICAgICAgc2VnbWVudHMuc2hpZnQoKTtcbiAgICB9IGVsc2UgaWYgKHNlZ21lbnRzWzBdID09PSAnJyAmJiBzZWdtZW50c1sxXSA9PT0gJycpIHtcbiAgICAgIC8vIG11c3QgaGF2ZSBiZWVuIDo6eHh4eFxuICAgICAgLy8gcmVtb3ZlIHRoZSBmaXJzdCBpdGVtXG4gICAgICBzZWdtZW50cy5zaGlmdCgpO1xuICAgIH0gZWxzZSBpZiAoc2VnbWVudHNbbGVuZ3RoIC0gMV0gPT09ICcnICYmIHNlZ21lbnRzW2xlbmd0aCAtIDJdID09PSAnJykge1xuICAgICAgLy8gbXVzdCBoYXZlIGJlZW4geHh4eDo6XG4gICAgICBzZWdtZW50cy5wb3AoKTtcbiAgICB9XG5cbiAgICBsZW5ndGggPSBzZWdtZW50cy5sZW5ndGg7XG5cbiAgICAvLyBhZGp1c3QgdG90YWwgc2VnbWVudHMgZm9yIElQdjQgdHJhaWxlclxuICAgIGlmIChzZWdtZW50c1tsZW5ndGggLSAxXS5pbmRleE9mKCcuJykgIT09IC0xKSB7XG4gICAgICAvLyBmb3VuZCBhIFwiLlwiIHdoaWNoIG1lYW5zIElQdjRcbiAgICAgIHRvdGFsID0gNztcbiAgICB9XG5cbiAgICAvLyBmaWxsIGVtcHR5IHNlZ21lbnRzIHRoZW0gd2l0aCBcIjAwMDBcIlxuICAgIHZhciBwb3M7XG4gICAgZm9yIChwb3MgPSAwOyBwb3MgPCBsZW5ndGg7IHBvcysrKSB7XG4gICAgICBpZiAoc2VnbWVudHNbcG9zXSA9PT0gJycpIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKHBvcyA8IHRvdGFsKSB7XG4gICAgICBzZWdtZW50cy5zcGxpY2UocG9zLCAxLCAnMDAwMCcpO1xuICAgICAgd2hpbGUgKHNlZ21lbnRzLmxlbmd0aCA8IHRvdGFsKSB7XG4gICAgICAgIHNlZ21lbnRzLnNwbGljZShwb3MsIDAsICcwMDAwJyk7XG4gICAgICB9XG5cbiAgICAgIGxlbmd0aCA9IHNlZ21lbnRzLmxlbmd0aDtcbiAgICB9XG5cbiAgICAvLyBzdHJpcCBsZWFkaW5nIHplcm9zXG4gICAgdmFyIF9zZWdtZW50cztcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRvdGFsOyBpKyspIHtcbiAgICAgIF9zZWdtZW50cyA9IHNlZ21lbnRzW2ldLnNwbGl0KCcnKTtcbiAgICAgIGZvciAodmFyIGogPSAwOyBqIDwgMyA7IGorKykge1xuICAgICAgICBpZiAoX3NlZ21lbnRzWzBdID09PSAnMCcgJiYgX3NlZ21lbnRzLmxlbmd0aCA+IDEpIHtcbiAgICAgICAgICBfc2VnbWVudHMuc3BsaWNlKDAsMSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgc2VnbWVudHNbaV0gPSBfc2VnbWVudHMuam9pbignJyk7XG4gICAgfVxuXG4gICAgLy8gZmluZCBsb25nZXN0IHNlcXVlbmNlIG9mIHplcm9lcyBhbmQgY29hbGVzY2UgdGhlbSBpbnRvIG9uZSBzZWdtZW50XG4gICAgdmFyIGJlc3QgPSAtMTtcbiAgICB2YXIgX2Jlc3QgPSAwO1xuICAgIHZhciBfY3VycmVudCA9IDA7XG4gICAgdmFyIGN1cnJlbnQgPSAtMTtcbiAgICB2YXIgaW56ZXJvZXMgPSBmYWxzZTtcbiAgICAvLyBpOyBhbHJlYWR5IGRlY2xhcmVkXG5cbiAgICBmb3IgKGkgPSAwOyBpIDwgdG90YWw7IGkrKykge1xuICAgICAgaWYgKGluemVyb2VzKSB7XG4gICAgICAgIGlmIChzZWdtZW50c1tpXSA9PT0gJzAnKSB7XG4gICAgICAgICAgX2N1cnJlbnQgKz0gMTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBpbnplcm9lcyA9IGZhbHNlO1xuICAgICAgICAgIGlmIChfY3VycmVudCA+IF9iZXN0KSB7XG4gICAgICAgICAgICBiZXN0ID0gY3VycmVudDtcbiAgICAgICAgICAgIF9iZXN0ID0gX2N1cnJlbnQ7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpZiAoc2VnbWVudHNbaV0gPT09ICcwJykge1xuICAgICAgICAgIGluemVyb2VzID0gdHJ1ZTtcbiAgICAgICAgICBjdXJyZW50ID0gaTtcbiAgICAgICAgICBfY3VycmVudCA9IDE7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoX2N1cnJlbnQgPiBfYmVzdCkge1xuICAgICAgYmVzdCA9IGN1cnJlbnQ7XG4gICAgICBfYmVzdCA9IF9jdXJyZW50O1xuICAgIH1cblxuICAgIGlmIChfYmVzdCA+IDEpIHtcbiAgICAgIHNlZ21lbnRzLnNwbGljZShiZXN0LCBfYmVzdCwgJycpO1xuICAgIH1cblxuICAgIGxlbmd0aCA9IHNlZ21lbnRzLmxlbmd0aDtcblxuICAgIC8vIGFzc2VtYmxlIHJlbWFpbmluZyBzZWdtZW50c1xuICAgIHZhciByZXN1bHQgPSAnJztcbiAgICBpZiAoc2VnbWVudHNbMF0gPT09ICcnKSAge1xuICAgICAgcmVzdWx0ID0gJzonO1xuICAgIH1cblxuICAgIGZvciAoaSA9IDA7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgcmVzdWx0ICs9IHNlZ21lbnRzW2ldO1xuICAgICAgaWYgKGkgPT09IGxlbmd0aCAtIDEpIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG5cbiAgICAgIHJlc3VsdCArPSAnOic7XG4gICAgfVxuXG4gICAgaWYgKHNlZ21lbnRzW2xlbmd0aCAtIDFdID09PSAnJykge1xuICAgICAgcmVzdWx0ICs9ICc6JztcbiAgICB9XG5cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gbm9Db25mbGljdCgpIHtcbiAgICAvKmpzaGludCB2YWxpZHRoaXM6IHRydWUgKi9cbiAgICBpZiAocm9vdC5JUHY2ID09PSB0aGlzKSB7XG4gICAgICByb290LklQdjYgPSBfSVB2NjtcbiAgICB9XG4gIFxuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgcmV0dXJuIHtcbiAgICBiZXN0OiBiZXN0UHJlc2VudGF0aW9uLFxuICAgIG5vQ29uZmxpY3Q6IG5vQ29uZmxpY3RcbiAgfTtcbn0pKTtcbiIsIi8qIVxuICogVVJJLmpzIC0gTXV0YXRpbmcgVVJMc1xuICogU2Vjb25kIExldmVsIERvbWFpbiAoU0xEKSBTdXBwb3J0XG4gKlxuICogVmVyc2lvbjogMS4xNy4wXG4gKlxuICogQXV0aG9yOiBSb2RuZXkgUmVobVxuICogV2ViOiBodHRwOi8vbWVkaWFsaXplLmdpdGh1Yi5pby9VUkkuanMvXG4gKlxuICogTGljZW5zZWQgdW5kZXJcbiAqICAgTUlUIExpY2Vuc2UgaHR0cDovL3d3dy5vcGVuc291cmNlLm9yZy9saWNlbnNlcy9taXQtbGljZW5zZVxuICogICBHUEwgdjMgaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0dQTC0zLjBcbiAqXG4gKi9cblxuKGZ1bmN0aW9uIChyb290LCBmYWN0b3J5KSB7XG4gICd1c2Ugc3RyaWN0JztcbiAgLy8gaHR0cHM6Ly9naXRodWIuY29tL3VtZGpzL3VtZC9ibG9iL21hc3Rlci9yZXR1cm5FeHBvcnRzLmpzXG4gIGlmICh0eXBlb2YgZXhwb3J0cyA9PT0gJ29iamVjdCcpIHtcbiAgICAvLyBOb2RlXG4gICAgbW9kdWxlLmV4cG9ydHMgPSBmYWN0b3J5KCk7XG4gIH0gZWxzZSBpZiAodHlwZW9mIGRlZmluZSA9PT0gJ2Z1bmN0aW9uJyAmJiBkZWZpbmUuYW1kKSB7XG4gICAgLy8gQU1ELiBSZWdpc3RlciBhcyBhbiBhbm9ueW1vdXMgbW9kdWxlLlxuICAgIGRlZmluZShmYWN0b3J5KTtcbiAgfSBlbHNlIHtcbiAgICAvLyBCcm93c2VyIGdsb2JhbHMgKHJvb3QgaXMgd2luZG93KVxuICAgIHJvb3QuU2Vjb25kTGV2ZWxEb21haW5zID0gZmFjdG9yeShyb290KTtcbiAgfVxufSh0aGlzLCBmdW5jdGlvbiAocm9vdCkge1xuICAndXNlIHN0cmljdCc7XG5cbiAgLy8gc2F2ZSBjdXJyZW50IFNlY29uZExldmVsRG9tYWlucyB2YXJpYWJsZSwgaWYgYW55XG4gIHZhciBfU2Vjb25kTGV2ZWxEb21haW5zID0gcm9vdCAmJiByb290LlNlY29uZExldmVsRG9tYWlucztcblxuICB2YXIgU0xEID0ge1xuICAgIC8vIGxpc3Qgb2Yga25vd24gU2Vjb25kIExldmVsIERvbWFpbnNcbiAgICAvLyBjb252ZXJ0ZWQgbGlzdCBvZiBTTERzIGZyb20gaHR0cHM6Ly9naXRodWIuY29tL2dhdmluZ21pbGxlci9zZWNvbmQtbGV2ZWwtZG9tYWluc1xuICAgIC8vIC0tLS1cbiAgICAvLyBwdWJsaWNzdWZmaXgub3JnIGlzIG1vcmUgY3VycmVudCBhbmQgYWN0dWFsbHkgdXNlZCBieSBhIGNvdXBsZSBvZiBicm93c2VycyBpbnRlcm5hbGx5LlxuICAgIC8vIGRvd25zaWRlIGlzIGl0IGFsc28gY29udGFpbnMgZG9tYWlucyBsaWtlIFwiZHluZG5zLm9yZ1wiIC0gd2hpY2ggaXMgZmluZSBmb3IgdGhlIHNlY3VyaXR5XG4gICAgLy8gaXNzdWVzIGJyb3dzZXIgaGF2ZSB0byBkZWFsIHdpdGggKFNPUCBmb3IgY29va2llcywgZXRjKSAtIGJ1dCBpcyB3YXkgb3ZlcmJvYXJkIGZvciBVUkkuanNcbiAgICAvLyAtLS0tXG4gICAgbGlzdDoge1xuICAgICAgJ2FjJzonIGNvbSBnb3YgbWlsIG5ldCBvcmcgJyxcbiAgICAgICdhZSc6JyBhYyBjbyBnb3YgbWlsIG5hbWUgbmV0IG9yZyBwcm8gc2NoICcsXG4gICAgICAnYWYnOicgY29tIGVkdSBnb3YgbmV0IG9yZyAnLFxuICAgICAgJ2FsJzonIGNvbSBlZHUgZ292IG1pbCBuZXQgb3JnICcsXG4gICAgICAnYW8nOicgY28gZWQgZ3YgaXQgb2cgcGIgJyxcbiAgICAgICdhcic6JyBjb20gZWR1IGdvYiBnb3YgaW50IG1pbCBuZXQgb3JnIHR1ciAnLFxuICAgICAgJ2F0JzonIGFjIGNvIGd2IG9yICcsXG4gICAgICAnYXUnOicgYXNuIGNvbSBjc2lybyBlZHUgZ292IGlkIG5ldCBvcmcgJyxcbiAgICAgICdiYSc6JyBjbyBjb20gZWR1IGdvdiBtaWwgbmV0IG9yZyBycyB1bmJpIHVubW8gdW5zYSB1bnR6IHVuemUgJyxcbiAgICAgICdiYic6JyBiaXogY28gY29tIGVkdSBnb3YgaW5mbyBuZXQgb3JnIHN0b3JlIHR2ICcsXG4gICAgICAnYmgnOicgYml6IGNjIGNvbSBlZHUgZ292IGluZm8gbmV0IG9yZyAnLFxuICAgICAgJ2JuJzonIGNvbSBlZHUgZ292IG5ldCBvcmcgJyxcbiAgICAgICdibyc6JyBjb20gZWR1IGdvYiBnb3YgaW50IG1pbCBuZXQgb3JnIHR2ICcsXG4gICAgICAnYnInOicgYWRtIGFkdiBhZ3IgYW0gYXJxIGFydCBhdG8gYiBiaW8gYmxvZyBibWQgY2ltIGNuZyBjbnQgY29tIGNvb3AgZWNuIGVkdSBlbmcgZXNwIGV0YyBldGkgZmFyIGZsb2cgZm0gZm5kIGZvdCBmc3QgZzEyIGdnZiBnb3YgaW1iIGluZCBpbmYgam9yIGp1cyBsZWwgbWF0IG1lZCBtaWwgbXVzIG5ldCBub20gbm90IG50ciBvZG8gb3JnIHBwZyBwcm8gcHNjIHBzaSBxc2wgcmVjIHNsZyBzcnYgdG1wIHRyZCB0dXIgdHYgdmV0IHZsb2cgd2lraSB6bGcgJyxcbiAgICAgICdicyc6JyBjb20gZWR1IGdvdiBuZXQgb3JnICcsXG4gICAgICAnYnonOicgZHUgZXQgb20gb3YgcmcgJyxcbiAgICAgICdjYSc6JyBhYiBiYyBtYiBuYiBuZiBubCBucyBudCBudSBvbiBwZSBxYyBzayB5ayAnLFxuICAgICAgJ2NrJzonIGJpeiBjbyBlZHUgZ2VuIGdvdiBpbmZvIG5ldCBvcmcgJyxcbiAgICAgICdjbic6JyBhYyBhaCBiaiBjb20gY3EgZWR1IGZqIGdkIGdvdiBncyBneCBneiBoYSBoYiBoZSBoaSBobCBobiBqbCBqcyBqeCBsbiBtaWwgbmV0IG5tIG54IG9yZyBxaCBzYyBzZCBzaCBzbiBzeCB0aiB0dyB4aiB4eiB5biB6aiAnLFxuICAgICAgJ2NvJzonIGNvbSBlZHUgZ292IG1pbCBuZXQgbm9tIG9yZyAnLFxuICAgICAgJ2NyJzonIGFjIGMgY28gZWQgZmkgZ28gb3Igc2EgJyxcbiAgICAgICdjeSc6JyBhYyBiaXogY29tIGVrbG9nZXMgZ292IGx0ZCBuYW1lIG5ldCBvcmcgcGFybGlhbWVudCBwcmVzcyBwcm8gdG0gJyxcbiAgICAgICdkbyc6JyBhcnQgY29tIGVkdSBnb2IgZ292IG1pbCBuZXQgb3JnIHNsZCB3ZWIgJyxcbiAgICAgICdkeic6JyBhcnQgYXNzbyBjb20gZWR1IGdvdiBuZXQgb3JnIHBvbCAnLFxuICAgICAgJ2VjJzonIGNvbSBlZHUgZmluIGdvdiBpbmZvIG1lZCBtaWwgbmV0IG9yZyBwcm8gJyxcbiAgICAgICdlZyc6JyBjb20gZWR1IGV1biBnb3YgbWlsIG5hbWUgbmV0IG9yZyBzY2kgJyxcbiAgICAgICdlcic6JyBjb20gZWR1IGdvdiBpbmQgbWlsIG5ldCBvcmcgcm9jaGVzdCB3ICcsXG4gICAgICAnZXMnOicgY29tIGVkdSBnb2Igbm9tIG9yZyAnLFxuICAgICAgJ2V0JzonIGJpeiBjb20gZWR1IGdvdiBpbmZvIG5hbWUgbmV0IG9yZyAnLFxuICAgICAgJ2ZqJzonIGFjIGJpeiBjb20gaW5mbyBtaWwgbmFtZSBuZXQgb3JnIHBybyAnLFxuICAgICAgJ2ZrJzonIGFjIGNvIGdvdiBuZXQgbm9tIG9yZyAnLFxuICAgICAgJ2ZyJzonIGFzc28gY29tIGYgZ291diBub20gcHJkIHByZXNzZSB0bSAnLFxuICAgICAgJ2dnJzonIGNvIG5ldCBvcmcgJyxcbiAgICAgICdnaCc6JyBjb20gZWR1IGdvdiBtaWwgb3JnICcsXG4gICAgICAnZ24nOicgYWMgY29tIGdvdiBuZXQgb3JnICcsXG4gICAgICAnZ3InOicgY29tIGVkdSBnb3YgbWlsIG5ldCBvcmcgJyxcbiAgICAgICdndCc6JyBjb20gZWR1IGdvYiBpbmQgbWlsIG5ldCBvcmcgJyxcbiAgICAgICdndSc6JyBjb20gZWR1IGdvdiBuZXQgb3JnICcsXG4gICAgICAnaGsnOicgY29tIGVkdSBnb3YgaWR2IG5ldCBvcmcgJyxcbiAgICAgICdodSc6JyAyMDAwIGFncmFyIGJvbHQgY2FzaW5vIGNpdHkgY28gZXJvdGljYSBlcm90aWthIGZpbG0gZm9ydW0gZ2FtZXMgaG90ZWwgaW5mbyBpbmdhdGxhbiBqb2dhc3oga29ueXZlbG8gbGFrYXMgbWVkaWEgbmV3cyBvcmcgcHJpdiByZWtsYW0gc2V4IHNob3Agc3BvcnQgc3VsaSBzemV4IHRtIHRvenNkZSB1dGF6YXMgdmlkZW8gJyxcbiAgICAgICdpZCc6JyBhYyBjbyBnbyBtaWwgbmV0IG9yIHNjaCB3ZWIgJyxcbiAgICAgICdpbCc6JyBhYyBjbyBnb3YgaWRmIGsxMiBtdW5pIG5ldCBvcmcgJyxcbiAgICAgICdpbic6JyBhYyBjbyBlZHUgZXJuZXQgZmlybSBnZW4gZ292IGkgaW5kIG1pbCBuZXQgbmljIG9yZyByZXMgJyxcbiAgICAgICdpcSc6JyBjb20gZWR1IGdvdiBpIG1pbCBuZXQgb3JnICcsXG4gICAgICAnaXInOicgYWMgY28gZG5zc2VjIGdvdiBpIGlkIG5ldCBvcmcgc2NoICcsXG4gICAgICAnaXQnOicgZWR1IGdvdiAnLFxuICAgICAgJ2plJzonIGNvIG5ldCBvcmcgJyxcbiAgICAgICdqbyc6JyBjb20gZWR1IGdvdiBtaWwgbmFtZSBuZXQgb3JnIHNjaCAnLFxuICAgICAgJ2pwJzonIGFjIGFkIGNvIGVkIGdvIGdyIGxnIG5lIG9yICcsXG4gICAgICAna2UnOicgYWMgY28gZ28gaW5mbyBtZSBtb2JpIG5lIG9yIHNjICcsXG4gICAgICAna2gnOicgY29tIGVkdSBnb3YgbWlsIG5ldCBvcmcgcGVyICcsXG4gICAgICAna2knOicgYml6IGNvbSBkZSBlZHUgZ292IGluZm8gbW9iIG5ldCBvcmcgdGVsICcsXG4gICAgICAna20nOicgYXNzbyBjb20gY29vcCBlZHUgZ291diBrIG1lZGVjaW4gbWlsIG5vbSBub3RhaXJlcyBwaGFybWFjaWVucyBwcmVzc2UgdG0gdmV0ZXJpbmFpcmUgJyxcbiAgICAgICdrbic6JyBlZHUgZ292IG5ldCBvcmcgJyxcbiAgICAgICdrcic6JyBhYyBidXNhbiBjaHVuZ2J1ayBjaHVuZ25hbSBjbyBkYWVndSBkYWVqZW9uIGVzIGdhbmd3b24gZ28gZ3dhbmdqdSBneWVvbmdidWsgZ3llb25nZ2kgZ3llb25nbmFtIGhzIGluY2hlb24gamVqdSBqZW9uYnVrIGplb25uYW0gayBrZyBtaWwgbXMgbmUgb3IgcGUgcmUgc2Mgc2VvdWwgdWxzYW4gJyxcbiAgICAgICdrdyc6JyBjb20gZWR1IGdvdiBuZXQgb3JnICcsXG4gICAgICAna3knOicgY29tIGVkdSBnb3YgbmV0IG9yZyAnLFxuICAgICAgJ2t6JzonIGNvbSBlZHUgZ292IG1pbCBuZXQgb3JnICcsXG4gICAgICAnbGInOicgY29tIGVkdSBnb3YgbmV0IG9yZyAnLFxuICAgICAgJ2xrJzonIGFzc24gY29tIGVkdSBnb3YgZ3JwIGhvdGVsIGludCBsdGQgbmV0IG5nbyBvcmcgc2NoIHNvYyB3ZWIgJyxcbiAgICAgICdscic6JyBjb20gZWR1IGdvdiBuZXQgb3JnICcsXG4gICAgICAnbHYnOicgYXNuIGNvbSBjb25mIGVkdSBnb3YgaWQgbWlsIG5ldCBvcmcgJyxcbiAgICAgICdseSc6JyBjb20gZWR1IGdvdiBpZCBtZWQgbmV0IG9yZyBwbGMgc2NoICcsXG4gICAgICAnbWEnOicgYWMgY28gZ292IG0gbmV0IG9yZyBwcmVzcyAnLFxuICAgICAgJ21jJzonIGFzc28gdG0gJyxcbiAgICAgICdtZSc6JyBhYyBjbyBlZHUgZ292IGl0cyBuZXQgb3JnIHByaXYgJyxcbiAgICAgICdtZyc6JyBjb20gZWR1IGdvdiBtaWwgbm9tIG9yZyBwcmQgdG0gJyxcbiAgICAgICdtayc6JyBjb20gZWR1IGdvdiBpbmYgbmFtZSBuZXQgb3JnIHBybyAnLFxuICAgICAgJ21sJzonIGNvbSBlZHUgZ292IG5ldCBvcmcgcHJlc3NlICcsXG4gICAgICAnbW4nOicgZWR1IGdvdiBvcmcgJyxcbiAgICAgICdtbyc6JyBjb20gZWR1IGdvdiBuZXQgb3JnICcsXG4gICAgICAnbXQnOicgY29tIGVkdSBnb3YgbmV0IG9yZyAnLFxuICAgICAgJ212JzonIGFlcm8gYml6IGNvbSBjb29wIGVkdSBnb3YgaW5mbyBpbnQgbWlsIG11c2V1bSBuYW1lIG5ldCBvcmcgcHJvICcsXG4gICAgICAnbXcnOicgYWMgY28gY29tIGNvb3AgZWR1IGdvdiBpbnQgbXVzZXVtIG5ldCBvcmcgJyxcbiAgICAgICdteCc6JyBjb20gZWR1IGdvYiBuZXQgb3JnICcsXG4gICAgICAnbXknOicgY29tIGVkdSBnb3YgbWlsIG5hbWUgbmV0IG9yZyBzY2ggJyxcbiAgICAgICduZic6JyBhcnRzIGNvbSBmaXJtIGluZm8gbmV0IG90aGVyIHBlciByZWMgc3RvcmUgd2ViICcsXG4gICAgICAnbmcnOicgYml6IGNvbSBlZHUgZ292IG1pbCBtb2JpIG5hbWUgbmV0IG9yZyBzY2ggJyxcbiAgICAgICduaSc6JyBhYyBjbyBjb20gZWR1IGdvYiBtaWwgbmV0IG5vbSBvcmcgJyxcbiAgICAgICducCc6JyBjb20gZWR1IGdvdiBtaWwgbmV0IG9yZyAnLFxuICAgICAgJ25yJzonIGJpeiBjb20gZWR1IGdvdiBpbmZvIG5ldCBvcmcgJyxcbiAgICAgICdvbSc6JyBhYyBiaXogY28gY29tIGVkdSBnb3YgbWVkIG1pbCBtdXNldW0gbmV0IG9yZyBwcm8gc2NoICcsXG4gICAgICAncGUnOicgY29tIGVkdSBnb2IgbWlsIG5ldCBub20gb3JnIHNsZCAnLFxuICAgICAgJ3BoJzonIGNvbSBlZHUgZ292IGkgbWlsIG5ldCBuZ28gb3JnICcsXG4gICAgICAncGsnOicgYml6IGNvbSBlZHUgZmFtIGdvYiBnb2sgZ29uIGdvcCBnb3MgZ292IG5ldCBvcmcgd2ViICcsXG4gICAgICAncGwnOicgYXJ0IGJpYWx5c3RvayBiaXogY29tIGVkdSBnZGEgZ2RhbnNrIGdvcnpvdyBnb3YgaW5mbyBrYXRvd2ljZSBrcmFrb3cgbG9keiBsdWJsaW4gbWlsIG5ldCBuZ28gb2xzenR5biBvcmcgcG96bmFuIHB3ciByYWRvbSBzbHVwc2sgc3pjemVjaW4gdG9ydW4gd2Fyc3phd2Egd2F3IHdyb2Mgd3JvY2xhdyB6Z29yYSAnLFxuICAgICAgJ3ByJzonIGFjIGJpeiBjb20gZWR1IGVzdCBnb3YgaW5mbyBpc2xhIG5hbWUgbmV0IG9yZyBwcm8gcHJvZiAnLFxuICAgICAgJ3BzJzonIGNvbSBlZHUgZ292IG5ldCBvcmcgcGxvIHNlYyAnLFxuICAgICAgJ3B3JzonIGJlbGF1IGNvIGVkIGdvIG5lIG9yICcsXG4gICAgICAncm8nOicgYXJ0cyBjb20gZmlybSBpbmZvIG5vbSBudCBvcmcgcmVjIHN0b3JlIHRtIHd3dyAnLFxuICAgICAgJ3JzJzonIGFjIGNvIGVkdSBnb3YgaW4gb3JnICcsXG4gICAgICAnc2InOicgY29tIGVkdSBnb3YgbmV0IG9yZyAnLFxuICAgICAgJ3NjJzonIGNvbSBlZHUgZ292IG5ldCBvcmcgJyxcbiAgICAgICdzaCc6JyBjbyBjb20gZWR1IGdvdiBuZXQgbm9tIG9yZyAnLFxuICAgICAgJ3NsJzonIGNvbSBlZHUgZ292IG5ldCBvcmcgJyxcbiAgICAgICdzdCc6JyBjbyBjb20gY29uc3VsYWRvIGVkdSBlbWJhaXhhZGEgZ292IG1pbCBuZXQgb3JnIHByaW5jaXBlIHNhb3RvbWUgc3RvcmUgJyxcbiAgICAgICdzdic6JyBjb20gZWR1IGdvYiBvcmcgcmVkICcsXG4gICAgICAnc3onOicgYWMgY28gb3JnICcsXG4gICAgICAndHInOicgYXYgYmJzIGJlbCBiaXogY29tIGRyIGVkdSBnZW4gZ292IGluZm8gazEyIG5hbWUgbmV0IG9yZyBwb2wgdGVsIHRzayB0diB3ZWIgJyxcbiAgICAgICd0dCc6JyBhZXJvIGJpeiBjYXQgY28gY29tIGNvb3AgZWR1IGdvdiBpbmZvIGludCBqb2JzIG1pbCBtb2JpIG11c2V1bSBuYW1lIG5ldCBvcmcgcHJvIHRlbCB0cmF2ZWwgJyxcbiAgICAgICd0dyc6JyBjbHViIGNvbSBlYml6IGVkdSBnYW1lIGdvdiBpZHYgbWlsIG5ldCBvcmcgJyxcbiAgICAgICdtdSc6JyBhYyBjbyBjb20gZ292IG5ldCBvciBvcmcgJyxcbiAgICAgICdteic6JyBhYyBjbyBlZHUgZ292IG9yZyAnLFxuICAgICAgJ25hJzonIGNvIGNvbSAnLFxuICAgICAgJ256JzonIGFjIGNvIGNyaSBnZWVrIGdlbiBnb3Z0IGhlYWx0aCBpd2kgbWFvcmkgbWlsIG5ldCBvcmcgcGFybGlhbWVudCBzY2hvb2wgJyxcbiAgICAgICdwYSc6JyBhYm8gYWMgY29tIGVkdSBnb2IgaW5nIG1lZCBuZXQgbm9tIG9yZyBzbGQgJyxcbiAgICAgICdwdCc6JyBjb20gZWR1IGdvdiBpbnQgbmV0IG5vbWUgb3JnIHB1YmwgJyxcbiAgICAgICdweSc6JyBjb20gZWR1IGdvdiBtaWwgbmV0IG9yZyAnLFxuICAgICAgJ3FhJzonIGNvbSBlZHUgZ292IG1pbCBuZXQgb3JnICcsXG4gICAgICAncmUnOicgYXNzbyBjb20gbm9tICcsXG4gICAgICAncnUnOicgYWMgYWR5Z2V5YSBhbHRhaSBhbXVyIGFya2hhbmdlbHNrIGFzdHJha2hhbiBiYXNoa2lyaWEgYmVsZ29yb2QgYmlyIGJyeWFuc2sgYnVyeWF0aWEgY2JnIGNoZWwgY2hlbHlhYmluc2sgY2hpdGEgY2h1a290a2EgY2h1dmFzaGlhIGNvbSBkYWdlc3RhbiBlLWJ1cmcgZWR1IGdvdiBncm96bnkgaW50IGlya3V0c2sgaXZhbm92byBpemhldnNrIGphciBqb3Noa2FyLW9sYSBrYWxteWtpYSBrYWx1Z2Ega2FtY2hhdGthIGthcmVsaWEga2F6YW4ga2NociBrZW1lcm92byBraGFiYXJvdnNrIGtoYWthc3NpYSBraHYga2lyb3Yga29lbmlnIGtvbWkga29zdHJvbWEga3Jhbm95YXJzayBrdWJhbiBrdXJnYW4ga3Vyc2sgbGlwZXRzayBtYWdhZGFuIG1hcmkgbWFyaS1lbCBtYXJpbmUgbWlsIG1vcmRvdmlhIG1vc3JlZyBtc2sgbXVybWFuc2sgbmFsY2hpayBuZXQgbm5vdiBub3Ygbm92b3NpYmlyc2sgbnNrIG9tc2sgb3JlbmJ1cmcgb3JnIG9yeW9sIHBlbnphIHBlcm0gcHAgcHNrb3YgcHR6IHJuZCByeWF6YW4gc2FraGFsaW4gc2FtYXJhIHNhcmF0b3Ygc2ltYmlyc2sgc21vbGVuc2sgc3BiIHN0YXZyb3BvbCBzdHYgc3VyZ3V0IHRhbWJvdiB0YXRhcnN0YW4gdG9tIHRvbXNrIHRzYXJpdHN5biB0c2sgdHVsYSB0dXZhIHR2ZXIgdHl1bWVuIHVkbSB1ZG11cnRpYSB1bGFuLXVkZSB2bGFkaWthdmtheiB2bGFkaW1pciB2bGFkaXZvc3RvayB2b2xnb2dyYWQgdm9sb2dkYSB2b3JvbmV6aCB2cm4gdnlhdGthIHlha3V0aWEgeWFtYWwgeWVrYXRlcmluYnVyZyB5dXpobm8tc2FraGFsaW5zayAnLFxuICAgICAgJ3J3JzonIGFjIGNvIGNvbSBlZHUgZ291diBnb3YgaW50IG1pbCBuZXQgJyxcbiAgICAgICdzYSc6JyBjb20gZWR1IGdvdiBtZWQgbmV0IG9yZyBwdWIgc2NoICcsXG4gICAgICAnc2QnOicgY29tIGVkdSBnb3YgaW5mbyBtZWQgbmV0IG9yZyB0diAnLFxuICAgICAgJ3NlJzonIGEgYWMgYiBiZCBjIGQgZSBmIGcgaCBpIGsgbCBtIG4gbyBvcmcgcCBwYXJ0aSBwcCBwcmVzcyByIHMgdCB0bSB1IHcgeCB5IHogJyxcbiAgICAgICdzZyc6JyBjb20gZWR1IGdvdiBpZG4gbmV0IG9yZyBwZXIgJyxcbiAgICAgICdzbic6JyBhcnQgY29tIGVkdSBnb3V2IG9yZyBwZXJzbyB1bml2ICcsXG4gICAgICAnc3knOicgY29tIGVkdSBnb3YgbWlsIG5ldCBuZXdzIG9yZyAnLFxuICAgICAgJ3RoJzonIGFjIGNvIGdvIGluIG1pIG5ldCBvciAnLFxuICAgICAgJ3RqJzonIGFjIGJpeiBjbyBjb20gZWR1IGdvIGdvdiBpbmZvIGludCBtaWwgbmFtZSBuZXQgbmljIG9yZyB0ZXN0IHdlYiAnLFxuICAgICAgJ3RuJzonIGFncmluZXQgY29tIGRlZmVuc2UgZWR1bmV0IGVucyBmaW4gZ292IGluZCBpbmZvIGludGwgbWluY29tIG5hdCBuZXQgb3JnIHBlcnNvIHJucnQgcm5zIHJudSB0b3VyaXNtICcsXG4gICAgICAndHonOicgYWMgY28gZ28gbmUgb3IgJyxcbiAgICAgICd1YSc6JyBiaXogY2hlcmthc3N5IGNoZXJuaWdvdiBjaGVybm92dHN5IGNrIGNuIGNvIGNvbSBjcmltZWEgY3YgZG4gZG5lcHJvcGV0cm92c2sgZG9uZXRzayBkcCBlZHUgZ292IGlmIGluIGl2YW5vLWZyYW5raXZzayBraCBraGFya292IGtoZXJzb24ga2htZWxuaXRza2l5IGtpZXYga2lyb3ZvZ3JhZCBrbSBrciBrcyBrdiBsZyBsdWdhbnNrIGx1dHNrIGx2aXYgbWUgbWsgbmV0IG5pa29sYWV2IG9kIG9kZXNzYSBvcmcgcGwgcG9sdGF2YSBwcCByb3ZubyBydiBzZWJhc3RvcG9sIHN1bXkgdGUgdGVybm9waWwgdXpoZ29yb2QgdmlubmljYSB2biB6YXBvcml6aHpoZSB6aGl0b21pciB6cCB6dCAnLFxuICAgICAgJ3VnJzonIGFjIGNvIGdvIG5lIG9yIG9yZyBzYyAnLFxuICAgICAgJ3VrJzonIGFjIGJsIGJyaXRpc2gtbGlicmFyeSBjbyBjeW0gZ292IGdvdnQgaWNuZXQgamV0IGxlYSBsdGQgbWUgbWlsIG1vZCBuYXRpb25hbC1saWJyYXJ5LXNjb3RsYW5kIG5lbCBuZXQgbmhzIG5pYyBubHMgb3JnIG9yZ24gcGFybGlhbWVudCBwbGMgcG9saWNlIHNjaCBzY290IHNvYyAnLFxuICAgICAgJ3VzJzonIGRuaSBmZWQgaXNhIGtpZHMgbnNuICcsXG4gICAgICAndXknOicgY29tIGVkdSBndWIgbWlsIG5ldCBvcmcgJyxcbiAgICAgICd2ZSc6JyBjbyBjb20gZWR1IGdvYiBpbmZvIG1pbCBuZXQgb3JnIHdlYiAnLFxuICAgICAgJ3ZpJzonIGNvIGNvbSBrMTIgbmV0IG9yZyAnLFxuICAgICAgJ3ZuJzonIGFjIGJpeiBjb20gZWR1IGdvdiBoZWFsdGggaW5mbyBpbnQgbmFtZSBuZXQgb3JnIHBybyAnLFxuICAgICAgJ3llJzonIGNvIGNvbSBnb3YgbHRkIG1lIG5ldCBvcmcgcGxjICcsXG4gICAgICAneXUnOicgYWMgY28gZWR1IGdvdiBvcmcgJyxcbiAgICAgICd6YSc6JyBhYyBhZ3JpYyBhbHQgYm91cnNlIGNpdHkgY28gY3liZXJuZXQgZGIgZWR1IGdvdiBncm9uZGFyIGlhY2Nlc3MgaW10IGluY2EgbGFuZGVzaWduIGxhdyBtaWwgbmV0IG5nbyBuaXMgbm9tIG9saXZldHRpIG9yZyBwaXggc2Nob29sIHRtIHdlYiAnLFxuICAgICAgJ3ptJzonIGFjIGNvIGNvbSBlZHUgZ292IG5ldCBvcmcgc2NoICdcbiAgICB9LFxuICAgIC8vIGdvcmhpbGwgMjAxMy0xMC0yNTogVXNpbmcgaW5kZXhPZigpIGluc3RlYWQgUmVnZXhwKCkuIFNpZ25pZmljYW50IGJvb3N0XG4gICAgLy8gaW4gYm90aCBwZXJmb3JtYW5jZSBhbmQgbWVtb3J5IGZvb3RwcmludC4gTm8gaW5pdGlhbGl6YXRpb24gcmVxdWlyZWQuXG4gICAgLy8gaHR0cDovL2pzcGVyZi5jb20vdXJpLWpzLXNsZC1yZWdleC12cy1iaW5hcnktc2VhcmNoLzRcbiAgICAvLyBGb2xsb3dpbmcgbWV0aG9kcyB1c2UgbGFzdEluZGV4T2YoKSByYXRoZXIgdGhhbiBhcnJheS5zcGxpdCgpIGluIG9yZGVyXG4gICAgLy8gdG8gYXZvaWQgYW55IG1lbW9yeSBhbGxvY2F0aW9ucy5cbiAgICBoYXM6IGZ1bmN0aW9uKGRvbWFpbikge1xuICAgICAgdmFyIHRsZE9mZnNldCA9IGRvbWFpbi5sYXN0SW5kZXhPZignLicpO1xuICAgICAgaWYgKHRsZE9mZnNldCA8PSAwIHx8IHRsZE9mZnNldCA+PSAoZG9tYWluLmxlbmd0aC0xKSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgICB2YXIgc2xkT2Zmc2V0ID0gZG9tYWluLmxhc3RJbmRleE9mKCcuJywgdGxkT2Zmc2V0LTEpO1xuICAgICAgaWYgKHNsZE9mZnNldCA8PSAwIHx8IHNsZE9mZnNldCA+PSAodGxkT2Zmc2V0LTEpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIHZhciBzbGRMaXN0ID0gU0xELmxpc3RbZG9tYWluLnNsaWNlKHRsZE9mZnNldCsxKV07XG4gICAgICBpZiAoIXNsZExpc3QpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHNsZExpc3QuaW5kZXhPZignICcgKyBkb21haW4uc2xpY2Uoc2xkT2Zmc2V0KzEsIHRsZE9mZnNldCkgKyAnICcpID49IDA7XG4gICAgfSxcbiAgICBpczogZnVuY3Rpb24oZG9tYWluKSB7XG4gICAgICB2YXIgdGxkT2Zmc2V0ID0gZG9tYWluLmxhc3RJbmRleE9mKCcuJyk7XG4gICAgICBpZiAodGxkT2Zmc2V0IDw9IDAgfHwgdGxkT2Zmc2V0ID49IChkb21haW4ubGVuZ3RoLTEpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIHZhciBzbGRPZmZzZXQgPSBkb21haW4ubGFzdEluZGV4T2YoJy4nLCB0bGRPZmZzZXQtMSk7XG4gICAgICBpZiAoc2xkT2Zmc2V0ID49IDApIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAgdmFyIHNsZExpc3QgPSBTTEQubGlzdFtkb21haW4uc2xpY2UodGxkT2Zmc2V0KzEpXTtcbiAgICAgIGlmICghc2xkTGlzdCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgICByZXR1cm4gc2xkTGlzdC5pbmRleE9mKCcgJyArIGRvbWFpbi5zbGljZSgwLCB0bGRPZmZzZXQpICsgJyAnKSA+PSAwO1xuICAgIH0sXG4gICAgZ2V0OiBmdW5jdGlvbihkb21haW4pIHtcbiAgICAgIHZhciB0bGRPZmZzZXQgPSBkb21haW4ubGFzdEluZGV4T2YoJy4nKTtcbiAgICAgIGlmICh0bGRPZmZzZXQgPD0gMCB8fCB0bGRPZmZzZXQgPj0gKGRvbWFpbi5sZW5ndGgtMSkpIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICB9XG4gICAgICB2YXIgc2xkT2Zmc2V0ID0gZG9tYWluLmxhc3RJbmRleE9mKCcuJywgdGxkT2Zmc2V0LTEpO1xuICAgICAgaWYgKHNsZE9mZnNldCA8PSAwIHx8IHNsZE9mZnNldCA+PSAodGxkT2Zmc2V0LTEpKSB7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgICAgfVxuICAgICAgdmFyIHNsZExpc3QgPSBTTEQubGlzdFtkb21haW4uc2xpY2UodGxkT2Zmc2V0KzEpXTtcbiAgICAgIGlmICghc2xkTGlzdCkge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICAgIGlmIChzbGRMaXN0LmluZGV4T2YoJyAnICsgZG9tYWluLnNsaWNlKHNsZE9mZnNldCsxLCB0bGRPZmZzZXQpICsgJyAnKSA8IDApIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICB9XG4gICAgICByZXR1cm4gZG9tYWluLnNsaWNlKHNsZE9mZnNldCsxKTtcbiAgICB9LFxuICAgIG5vQ29uZmxpY3Q6IGZ1bmN0aW9uKCl7XG4gICAgICBpZiAocm9vdC5TZWNvbmRMZXZlbERvbWFpbnMgPT09IHRoaXMpIHtcbiAgICAgICAgcm9vdC5TZWNvbmRMZXZlbERvbWFpbnMgPSBfU2Vjb25kTGV2ZWxEb21haW5zO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICB9O1xuXG4gIHJldHVybiBTTEQ7XG59KSk7XG4iLCIvKiFcbiAqIFVSSS5qcyAtIE11dGF0aW5nIFVSTHNcbiAqXG4gKiBWZXJzaW9uOiAxLjE3LjBcbiAqXG4gKiBBdXRob3I6IFJvZG5leSBSZWhtXG4gKiBXZWI6IGh0dHA6Ly9tZWRpYWxpemUuZ2l0aHViLmlvL1VSSS5qcy9cbiAqXG4gKiBMaWNlbnNlZCB1bmRlclxuICogICBNSVQgTGljZW5zZSBodHRwOi8vd3d3Lm9wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL21pdC1saWNlbnNlXG4gKiAgIEdQTCB2MyBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvR1BMLTMuMFxuICpcbiAqL1xuKGZ1bmN0aW9uIChyb290LCBmYWN0b3J5KSB7XG4gICd1c2Ugc3RyaWN0JztcbiAgLy8gaHR0cHM6Ly9naXRodWIuY29tL3VtZGpzL3VtZC9ibG9iL21hc3Rlci9yZXR1cm5FeHBvcnRzLmpzXG4gIGlmICh0eXBlb2YgZXhwb3J0cyA9PT0gJ29iamVjdCcpIHtcbiAgICAvLyBOb2RlXG4gICAgbW9kdWxlLmV4cG9ydHMgPSBmYWN0b3J5KHJlcXVpcmUoJy4vcHVueWNvZGUnKSwgcmVxdWlyZSgnLi9JUHY2JyksIHJlcXVpcmUoJy4vU2Vjb25kTGV2ZWxEb21haW5zJykpO1xuICB9IGVsc2UgaWYgKHR5cGVvZiBkZWZpbmUgPT09ICdmdW5jdGlvbicgJiYgZGVmaW5lLmFtZCkge1xuICAgIC8vIEFNRC4gUmVnaXN0ZXIgYXMgYW4gYW5vbnltb3VzIG1vZHVsZS5cbiAgICBkZWZpbmUoWycuL3B1bnljb2RlJywgJy4vSVB2NicsICcuL1NlY29uZExldmVsRG9tYWlucyddLCBmYWN0b3J5KTtcbiAgfSBlbHNlIHtcbiAgICAvLyBCcm93c2VyIGdsb2JhbHMgKHJvb3QgaXMgd2luZG93KVxuICAgIHJvb3QuVVJJID0gZmFjdG9yeShyb290LnB1bnljb2RlLCByb290LklQdjYsIHJvb3QuU2Vjb25kTGV2ZWxEb21haW5zLCByb290KTtcbiAgfVxufSh0aGlzLCBmdW5jdGlvbiAocHVueWNvZGUsIElQdjYsIFNMRCwgcm9vdCkge1xuICAndXNlIHN0cmljdCc7XG4gIC8qZ2xvYmFsIGxvY2F0aW9uLCBlc2NhcGUsIHVuZXNjYXBlICovXG4gIC8vIEZJWE1FOiB2Mi4wLjAgcmVuYW1jZSBub24tY2FtZWxDYXNlIHByb3BlcnRpZXMgdG8gdXBwZXJjYXNlXG4gIC8qanNoaW50IGNhbWVsY2FzZTogZmFsc2UgKi9cblxuICAvLyBzYXZlIGN1cnJlbnQgVVJJIHZhcmlhYmxlLCBpZiBhbnlcbiAgdmFyIF9VUkkgPSByb290ICYmIHJvb3QuVVJJO1xuXG4gIGZ1bmN0aW9uIFVSSSh1cmwsIGJhc2UpIHtcbiAgICB2YXIgX3VybFN1cHBsaWVkID0gYXJndW1lbnRzLmxlbmd0aCA+PSAxO1xuICAgIHZhciBfYmFzZVN1cHBsaWVkID0gYXJndW1lbnRzLmxlbmd0aCA+PSAyO1xuXG4gICAgLy8gQWxsb3cgaW5zdGFudGlhdGlvbiB3aXRob3V0IHRoZSAnbmV3JyBrZXl3b3JkXG4gICAgaWYgKCEodGhpcyBpbnN0YW5jZW9mIFVSSSkpIHtcbiAgICAgIGlmIChfdXJsU3VwcGxpZWQpIHtcbiAgICAgICAgaWYgKF9iYXNlU3VwcGxpZWQpIHtcbiAgICAgICAgICByZXR1cm4gbmV3IFVSSSh1cmwsIGJhc2UpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIG5ldyBVUkkodXJsKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIG5ldyBVUkkoKTtcbiAgICB9XG5cbiAgICBpZiAodXJsID09PSB1bmRlZmluZWQpIHtcbiAgICAgIGlmIChfdXJsU3VwcGxpZWQpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcigndW5kZWZpbmVkIGlzIG5vdCBhIHZhbGlkIGFyZ3VtZW50IGZvciBVUkknKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHR5cGVvZiBsb2NhdGlvbiAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgICAgdXJsID0gbG9jYXRpb24uaHJlZiArICcnO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdXJsID0gJyc7XG4gICAgICB9XG4gICAgfVxuXG4gICAgdGhpcy5ocmVmKHVybCk7XG5cbiAgICAvLyByZXNvbHZlIHRvIGJhc2UgYWNjb3JkaW5nIHRvIGh0dHA6Ly9kdmNzLnczLm9yZy9oZy91cmwvcmF3LWZpbGUvdGlwL092ZXJ2aWV3Lmh0bWwjY29uc3RydWN0b3JcbiAgICBpZiAoYmFzZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICByZXR1cm4gdGhpcy5hYnNvbHV0ZVRvKGJhc2UpO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgVVJJLnZlcnNpb24gPSAnMS4xNy4wJztcblxuICB2YXIgcCA9IFVSSS5wcm90b3R5cGU7XG4gIHZhciBoYXNPd24gPSBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5O1xuXG4gIGZ1bmN0aW9uIGVzY2FwZVJlZ0V4KHN0cmluZykge1xuICAgIC8vIGh0dHBzOi8vZ2l0aHViLmNvbS9tZWRpYWxpemUvVVJJLmpzL2NvbW1pdC84NWFjMjE3ODNjMTFmOGNjYWIwNjEwNmRiYTk3MzVhMzFhODY5MjRkI2NvbW1pdGNvbW1lbnQtODIxOTYzXG4gICAgcmV0dXJuIHN0cmluZy5yZXBsYWNlKC8oWy4qKz9ePSE6JHt9KCl8W1xcXVxcL1xcXFxdKS9nLCAnXFxcXCQxJyk7XG4gIH1cblxuICBmdW5jdGlvbiBnZXRUeXBlKHZhbHVlKSB7XG4gICAgLy8gSUU4IGRvZXNuJ3QgcmV0dXJuIFtPYmplY3QgVW5kZWZpbmVkXSBidXQgW09iamVjdCBPYmplY3RdIGZvciB1bmRlZmluZWQgdmFsdWVcbiAgICBpZiAodmFsdWUgPT09IHVuZGVmaW5lZCkge1xuICAgICAgcmV0dXJuICdVbmRlZmluZWQnO1xuICAgIH1cblxuICAgIHJldHVybiBTdHJpbmcoT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZy5jYWxsKHZhbHVlKSkuc2xpY2UoOCwgLTEpO1xuICB9XG5cbiAgZnVuY3Rpb24gaXNBcnJheShvYmopIHtcbiAgICByZXR1cm4gZ2V0VHlwZShvYmopID09PSAnQXJyYXknO1xuICB9XG5cbiAgZnVuY3Rpb24gZmlsdGVyQXJyYXlWYWx1ZXMoZGF0YSwgdmFsdWUpIHtcbiAgICB2YXIgbG9va3VwID0ge307XG4gICAgdmFyIGksIGxlbmd0aDtcblxuICAgIGlmIChnZXRUeXBlKHZhbHVlKSA9PT0gJ1JlZ0V4cCcpIHtcbiAgICAgIGxvb2t1cCA9IG51bGw7XG4gICAgfSBlbHNlIGlmIChpc0FycmF5KHZhbHVlKSkge1xuICAgICAgZm9yIChpID0gMCwgbGVuZ3RoID0gdmFsdWUubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgbG9va3VwW3ZhbHVlW2ldXSA9IHRydWU7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIGxvb2t1cFt2YWx1ZV0gPSB0cnVlO1xuICAgIH1cblxuICAgIGZvciAoaSA9IDAsIGxlbmd0aCA9IGRhdGEubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIC8qanNoaW50IGxheGJyZWFrOiB0cnVlICovXG4gICAgICB2YXIgX21hdGNoID0gbG9va3VwICYmIGxvb2t1cFtkYXRhW2ldXSAhPT0gdW5kZWZpbmVkXG4gICAgICAgIHx8ICFsb29rdXAgJiYgdmFsdWUudGVzdChkYXRhW2ldKTtcbiAgICAgIC8qanNoaW50IGxheGJyZWFrOiBmYWxzZSAqL1xuICAgICAgaWYgKF9tYXRjaCkge1xuICAgICAgICBkYXRhLnNwbGljZShpLCAxKTtcbiAgICAgICAgbGVuZ3RoLS07XG4gICAgICAgIGktLTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gZGF0YTtcbiAgfVxuXG4gIGZ1bmN0aW9uIGFycmF5Q29udGFpbnMobGlzdCwgdmFsdWUpIHtcbiAgICB2YXIgaSwgbGVuZ3RoO1xuXG4gICAgLy8gdmFsdWUgbWF5IGJlIHN0cmluZywgbnVtYmVyLCBhcnJheSwgcmVnZXhwXG4gICAgaWYgKGlzQXJyYXkodmFsdWUpKSB7XG4gICAgICAvLyBOb3RlOiB0aGlzIGNhbiBiZSBvcHRpbWl6ZWQgdG8gTyhuKSAoaW5zdGVhZCBvZiBjdXJyZW50IE8obSAqIG4pKVxuICAgICAgZm9yIChpID0gMCwgbGVuZ3RoID0gdmFsdWUubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgaWYgKCFhcnJheUNvbnRhaW5zKGxpc3QsIHZhbHVlW2ldKSkge1xuICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG5cbiAgICB2YXIgX3R5cGUgPSBnZXRUeXBlKHZhbHVlKTtcbiAgICBmb3IgKGkgPSAwLCBsZW5ndGggPSBsaXN0Lmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICBpZiAoX3R5cGUgPT09ICdSZWdFeHAnKSB7XG4gICAgICAgIGlmICh0eXBlb2YgbGlzdFtpXSA9PT0gJ3N0cmluZycgJiYgbGlzdFtpXS5tYXRjaCh2YWx1ZSkpIHtcbiAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIGlmIChsaXN0W2ldID09PSB2YWx1ZSkge1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICBmdW5jdGlvbiBhcnJheXNFcXVhbChvbmUsIHR3bykge1xuICAgIGlmICghaXNBcnJheShvbmUpIHx8ICFpc0FycmF5KHR3bykpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyBhcnJheXMgY2FuJ3QgYmUgZXF1YWwgaWYgdGhleSBoYXZlIGRpZmZlcmVudCBhbW91bnQgb2YgY29udGVudFxuICAgIGlmIChvbmUubGVuZ3RoICE9PSB0d28ubGVuZ3RoKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgb25lLnNvcnQoKTtcbiAgICB0d28uc29ydCgpO1xuXG4gICAgZm9yICh2YXIgaSA9IDAsIGwgPSBvbmUubGVuZ3RoOyBpIDwgbDsgaSsrKSB7XG4gICAgICBpZiAob25lW2ldICE9PSB0d29baV0pIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiB0cnVlO1xuICB9XG5cbiAgZnVuY3Rpb24gdHJpbVNsYXNoZXModGV4dCkge1xuICAgIHZhciB0cmltX2V4cHJlc3Npb24gPSAvXlxcLyt8XFwvKyQvZztcbiAgICByZXR1cm4gdGV4dC5yZXBsYWNlKHRyaW1fZXhwcmVzc2lvbiwgJycpO1xuICB9XG5cbiAgVVJJLl9wYXJ0cyA9IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiB7XG4gICAgICBwcm90b2NvbDogbnVsbCxcbiAgICAgIHVzZXJuYW1lOiBudWxsLFxuICAgICAgcGFzc3dvcmQ6IG51bGwsXG4gICAgICBob3N0bmFtZTogbnVsbCxcbiAgICAgIHVybjogbnVsbCxcbiAgICAgIHBvcnQ6IG51bGwsXG4gICAgICBwYXRoOiBudWxsLFxuICAgICAgcXVlcnk6IG51bGwsXG4gICAgICBmcmFnbWVudDogbnVsbCxcbiAgICAgIC8vIHN0YXRlXG4gICAgICBkdXBsaWNhdGVRdWVyeVBhcmFtZXRlcnM6IFVSSS5kdXBsaWNhdGVRdWVyeVBhcmFtZXRlcnMsXG4gICAgICBlc2NhcGVRdWVyeVNwYWNlOiBVUkkuZXNjYXBlUXVlcnlTcGFjZVxuICAgIH07XG4gIH07XG4gIC8vIHN0YXRlOiBhbGxvdyBkdXBsaWNhdGUgcXVlcnkgcGFyYW1ldGVycyAoYT0xJmE9MSlcbiAgVVJJLmR1cGxpY2F0ZVF1ZXJ5UGFyYW1ldGVycyA9IGZhbHNlO1xuICAvLyBzdGF0ZTogcmVwbGFjZXMgKyB3aXRoICUyMCAoc3BhY2UgaW4gcXVlcnkgc3RyaW5ncylcbiAgVVJJLmVzY2FwZVF1ZXJ5U3BhY2UgPSB0cnVlO1xuICAvLyBzdGF0aWMgcHJvcGVydGllc1xuICBVUkkucHJvdG9jb2xfZXhwcmVzc2lvbiA9IC9eW2Etel1bYS16MC05ListXSokL2k7XG4gIFVSSS5pZG5fZXhwcmVzc2lvbiA9IC9bXmEtejAtOVxcLi1dL2k7XG4gIFVSSS5wdW55Y29kZV9leHByZXNzaW9uID0gLyh4bi0tKS9pO1xuICAvLyB3ZWxsLCAzMzMuNDQ0LjU1NS42NjYgbWF0Y2hlcywgYnV0IGl0IHN1cmUgYWluJ3Qgbm8gSVB2NCAtIGRvIHdlIGNhcmU/XG4gIFVSSS5pcDRfZXhwcmVzc2lvbiA9IC9eXFxkezEsM31cXC5cXGR7MSwzfVxcLlxcZHsxLDN9XFwuXFxkezEsM30kLztcbiAgLy8gY3JlZGl0cyB0byBSaWNoIEJyb3duXG4gIC8vIHNvdXJjZTogaHR0cDovL2ZvcnVtcy5pbnRlcm1hcHBlci5jb20vdmlld3RvcGljLnBocD9wPTEwOTYjMTA5NlxuICAvLyBzcGVjaWZpY2F0aW9uOiBodHRwOi8vd3d3LmlldGYub3JnL3JmYy9yZmM0MjkxLnR4dFxuICBVUkkuaXA2X2V4cHJlc3Npb24gPSAvXlxccyooKChbMC05QS1GYS1mXXsxLDR9Oil7N30oWzAtOUEtRmEtZl17MSw0fXw6KSl8KChbMC05QS1GYS1mXXsxLDR9Oil7Nn0oOlswLTlBLUZhLWZdezEsNH18KCgyNVswLTVdfDJbMC00XVxcZHwxXFxkXFxkfFsxLTldP1xcZCkoXFwuKDI1WzAtNV18MlswLTRdXFxkfDFcXGRcXGR8WzEtOV0/XFxkKSl7M30pfDopKXwoKFswLTlBLUZhLWZdezEsNH06KXs1fSgoKDpbMC05QS1GYS1mXXsxLDR9KXsxLDJ9KXw6KCgyNVswLTVdfDJbMC00XVxcZHwxXFxkXFxkfFsxLTldP1xcZCkoXFwuKDI1WzAtNV18MlswLTRdXFxkfDFcXGRcXGR8WzEtOV0/XFxkKSl7M30pfDopKXwoKFswLTlBLUZhLWZdezEsNH06KXs0fSgoKDpbMC05QS1GYS1mXXsxLDR9KXsxLDN9KXwoKDpbMC05QS1GYS1mXXsxLDR9KT86KCgyNVswLTVdfDJbMC00XVxcZHwxXFxkXFxkfFsxLTldP1xcZCkoXFwuKDI1WzAtNV18MlswLTRdXFxkfDFcXGRcXGR8WzEtOV0/XFxkKSl7M30pKXw6KSl8KChbMC05QS1GYS1mXXsxLDR9Oil7M30oKCg6WzAtOUEtRmEtZl17MSw0fSl7MSw0fSl8KCg6WzAtOUEtRmEtZl17MSw0fSl7MCwyfTooKDI1WzAtNV18MlswLTRdXFxkfDFcXGRcXGR8WzEtOV0/XFxkKShcXC4oMjVbMC01XXwyWzAtNF1cXGR8MVxcZFxcZHxbMS05XT9cXGQpKXszfSkpfDopKXwoKFswLTlBLUZhLWZdezEsNH06KXsyfSgoKDpbMC05QS1GYS1mXXsxLDR9KXsxLDV9KXwoKDpbMC05QS1GYS1mXXsxLDR9KXswLDN9OigoMjVbMC01XXwyWzAtNF1cXGR8MVxcZFxcZHxbMS05XT9cXGQpKFxcLigyNVswLTVdfDJbMC00XVxcZHwxXFxkXFxkfFsxLTldP1xcZCkpezN9KSl8OikpfCgoWzAtOUEtRmEtZl17MSw0fTopezF9KCgoOlswLTlBLUZhLWZdezEsNH0pezEsNn0pfCgoOlswLTlBLUZhLWZdezEsNH0pezAsNH06KCgyNVswLTVdfDJbMC00XVxcZHwxXFxkXFxkfFsxLTldP1xcZCkoXFwuKDI1WzAtNV18MlswLTRdXFxkfDFcXGRcXGR8WzEtOV0/XFxkKSl7M30pKXw6KSl8KDooKCg6WzAtOUEtRmEtZl17MSw0fSl7MSw3fSl8KCg6WzAtOUEtRmEtZl17MSw0fSl7MCw1fTooKDI1WzAtNV18MlswLTRdXFxkfDFcXGRcXGR8WzEtOV0/XFxkKShcXC4oMjVbMC01XXwyWzAtNF1cXGR8MVxcZFxcZHxbMS05XT9cXGQpKXszfSkpfDopKSkoJS4rKT9cXHMqJC87XG4gIC8vIGV4cHJlc3Npb24gdXNlZCBpcyBcImdydWJlciByZXZpc2VkXCIgKEBncnViZXIgdjIpIGRldGVybWluZWQgdG8gYmUgdGhlXG4gIC8vIGJlc3Qgc29sdXRpb24gaW4gYSByZWdleC1nb2xmIHdlIGRpZCBhIGNvdXBsZSBvZiBhZ2VzIGFnbyBhdFxuICAvLyAqIGh0dHA6Ly9tYXRoaWFzYnluZW5zLmJlL2RlbW8vdXJsLXJlZ2V4XG4gIC8vICogaHR0cDovL3JvZG5leXJlaG0uZGUvdC91cmwtcmVnZXguaHRtbFxuICBVUkkuZmluZF91cmlfZXhwcmVzc2lvbiA9IC9cXGIoKD86W2Etel1bXFx3LV0rOig/OlxcL3sxLDN9fFthLXowLTklXSl8d3d3XFxkezAsM31bLl18W2EtejAtOS5cXC1dK1suXVthLXpdezIsNH1cXC8pKD86W15cXHMoKTw+XSt8XFwoKFteXFxzKCk8Pl0rfChcXChbXlxccygpPD5dK1xcKSkpKlxcKSkrKD86XFwoKFteXFxzKCk8Pl0rfChcXChbXlxccygpPD5dK1xcKSkpKlxcKXxbXlxcc2AhKClcXFtcXF17fTs6J1wiLiw8Pj/Cq8K74oCc4oCd4oCY4oCZXSkpL2lnO1xuICBVUkkuZmluZFVyaSA9IHtcbiAgICAvLyB2YWxpZCBcInNjaGVtZTovL1wiIG9yIFwid3d3LlwiXG4gICAgc3RhcnQ6IC9cXGIoPzooW2Etel1bYS16MC05ListXSo6XFwvXFwvKXx3d3dcXC4pL2dpLFxuICAgIC8vIGV2ZXJ5dGhpbmcgdXAgdG8gdGhlIG5leHQgd2hpdGVzcGFjZVxuICAgIGVuZDogL1tcXHNcXHJcXG5dfCQvLFxuICAgIC8vIHRyaW0gdHJhaWxpbmcgcHVuY3R1YXRpb24gY2FwdHVyZWQgYnkgZW5kIFJlZ0V4cFxuICAgIHRyaW06IC9bYCEoKVxcW1xcXXt9OzonXCIuLDw+P8KrwrvigJzigJ3igJ7igJjigJldKyQvXG4gIH07XG4gIC8vIGh0dHA6Ly93d3cuaWFuYS5vcmcvYXNzaWdubWVudHMvdXJpLXNjaGVtZXMuaHRtbFxuICAvLyBodHRwOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0xpc3Rfb2ZfVENQX2FuZF9VRFBfcG9ydF9udW1iZXJzI1dlbGwta25vd25fcG9ydHNcbiAgVVJJLmRlZmF1bHRQb3J0cyA9IHtcbiAgICBodHRwOiAnODAnLFxuICAgIGh0dHBzOiAnNDQzJyxcbiAgICBmdHA6ICcyMScsXG4gICAgZ29waGVyOiAnNzAnLFxuICAgIHdzOiAnODAnLFxuICAgIHdzczogJzQ0MydcbiAgfTtcbiAgLy8gYWxsb3dlZCBob3N0bmFtZSBjaGFyYWN0ZXJzIGFjY29yZGluZyB0byBSRkMgMzk4NlxuICAvLyBBTFBIQSBESUdJVCBcIi1cIiBcIi5cIiBcIl9cIiBcIn5cIiBcIiFcIiBcIiRcIiBcIiZcIiBcIidcIiBcIihcIiBcIilcIiBcIipcIiBcIitcIiBcIixcIiBcIjtcIiBcIj1cIiAlZW5jb2RlZFxuICAvLyBJJ3ZlIG5ldmVyIHNlZW4gYSAobm9uLUlETikgaG9zdG5hbWUgb3RoZXIgdGhhbjogQUxQSEEgRElHSVQgLiAtXG4gIFVSSS5pbnZhbGlkX2hvc3RuYW1lX2NoYXJhY3RlcnMgPSAvW15hLXpBLVowLTlcXC4tXS87XG4gIC8vIG1hcCBET00gRWxlbWVudHMgdG8gdGhlaXIgVVJJIGF0dHJpYnV0ZVxuICBVUkkuZG9tQXR0cmlidXRlcyA9IHtcbiAgICAnYSc6ICdocmVmJyxcbiAgICAnYmxvY2txdW90ZSc6ICdjaXRlJyxcbiAgICAnbGluayc6ICdocmVmJyxcbiAgICAnYmFzZSc6ICdocmVmJyxcbiAgICAnc2NyaXB0JzogJ3NyYycsXG4gICAgJ2Zvcm0nOiAnYWN0aW9uJyxcbiAgICAnaW1nJzogJ3NyYycsXG4gICAgJ2FyZWEnOiAnaHJlZicsXG4gICAgJ2lmcmFtZSc6ICdzcmMnLFxuICAgICdlbWJlZCc6ICdzcmMnLFxuICAgICdzb3VyY2UnOiAnc3JjJyxcbiAgICAndHJhY2snOiAnc3JjJyxcbiAgICAnaW5wdXQnOiAnc3JjJywgLy8gYnV0IG9ubHkgaWYgdHlwZT1cImltYWdlXCJcbiAgICAnYXVkaW8nOiAnc3JjJyxcbiAgICAndmlkZW8nOiAnc3JjJ1xuICB9O1xuICBVUkkuZ2V0RG9tQXR0cmlidXRlID0gZnVuY3Rpb24obm9kZSkge1xuICAgIGlmICghbm9kZSB8fCAhbm9kZS5ub2RlTmFtZSkge1xuICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG5cbiAgICB2YXIgbm9kZU5hbWUgPSBub2RlLm5vZGVOYW1lLnRvTG93ZXJDYXNlKCk7XG4gICAgLy8gPGlucHV0PiBzaG91bGQgb25seSBleHBvc2Ugc3JjIGZvciB0eXBlPVwiaW1hZ2VcIlxuICAgIGlmIChub2RlTmFtZSA9PT0gJ2lucHV0JyAmJiBub2RlLnR5cGUgIT09ICdpbWFnZScpIHtcbiAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgcmV0dXJuIFVSSS5kb21BdHRyaWJ1dGVzW25vZGVOYW1lXTtcbiAgfTtcblxuICBmdW5jdGlvbiBlc2NhcGVGb3JEdW1iRmlyZWZveDM2KHZhbHVlKSB7XG4gICAgLy8gaHR0cHM6Ly9naXRodWIuY29tL21lZGlhbGl6ZS9VUkkuanMvaXNzdWVzLzkxXG4gICAgcmV0dXJuIGVzY2FwZSh2YWx1ZSk7XG4gIH1cblxuICAvLyBlbmNvZGluZyAvIGRlY29kaW5nIGFjY29yZGluZyB0byBSRkMzOTg2XG4gIGZ1bmN0aW9uIHN0cmljdEVuY29kZVVSSUNvbXBvbmVudChzdHJpbmcpIHtcbiAgICAvLyBzZWUgaHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvZW4tVVMvZG9jcy9KYXZhU2NyaXB0L1JlZmVyZW5jZS9HbG9iYWxfT2JqZWN0cy9lbmNvZGVVUklDb21wb25lbnRcbiAgICByZXR1cm4gZW5jb2RlVVJJQ29tcG9uZW50KHN0cmluZylcbiAgICAgIC5yZXBsYWNlKC9bIScoKSpdL2csIGVzY2FwZUZvckR1bWJGaXJlZm94MzYpXG4gICAgICAucmVwbGFjZSgvXFwqL2csICclMkEnKTtcbiAgfVxuICBVUkkuZW5jb2RlID0gc3RyaWN0RW5jb2RlVVJJQ29tcG9uZW50O1xuICBVUkkuZGVjb2RlID0gZGVjb2RlVVJJQ29tcG9uZW50O1xuICBVUkkuaXNvODg1OSA9IGZ1bmN0aW9uKCkge1xuICAgIFVSSS5lbmNvZGUgPSBlc2NhcGU7XG4gICAgVVJJLmRlY29kZSA9IHVuZXNjYXBlO1xuICB9O1xuICBVUkkudW5pY29kZSA9IGZ1bmN0aW9uKCkge1xuICAgIFVSSS5lbmNvZGUgPSBzdHJpY3RFbmNvZGVVUklDb21wb25lbnQ7XG4gICAgVVJJLmRlY29kZSA9IGRlY29kZVVSSUNvbXBvbmVudDtcbiAgfTtcbiAgVVJJLmNoYXJhY3RlcnMgPSB7XG4gICAgcGF0aG5hbWU6IHtcbiAgICAgIGVuY29kZToge1xuICAgICAgICAvLyBSRkMzOTg2IDIuMTogRm9yIGNvbnNpc3RlbmN5LCBVUkkgcHJvZHVjZXJzIGFuZCBub3JtYWxpemVycyBzaG91bGRcbiAgICAgICAgLy8gdXNlIHVwcGVyY2FzZSBoZXhhZGVjaW1hbCBkaWdpdHMgZm9yIGFsbCBwZXJjZW50LWVuY29kaW5ncy5cbiAgICAgICAgZXhwcmVzc2lvbjogLyUoMjR8MjZ8MkJ8MkN8M0J8M0R8M0F8NDApL2lnLFxuICAgICAgICBtYXA6IHtcbiAgICAgICAgICAvLyAtLl9+IScoKSpcbiAgICAgICAgICAnJTI0JzogJyQnLFxuICAgICAgICAgICclMjYnOiAnJicsXG4gICAgICAgICAgJyUyQic6ICcrJyxcbiAgICAgICAgICAnJTJDJzogJywnLFxuICAgICAgICAgICclM0InOiAnOycsXG4gICAgICAgICAgJyUzRCc6ICc9JyxcbiAgICAgICAgICAnJTNBJzogJzonLFxuICAgICAgICAgICclNDAnOiAnQCdcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIGRlY29kZToge1xuICAgICAgICBleHByZXNzaW9uOiAvW1xcL1xcPyNdL2csXG4gICAgICAgIG1hcDoge1xuICAgICAgICAgICcvJzogJyUyRicsXG4gICAgICAgICAgJz8nOiAnJTNGJyxcbiAgICAgICAgICAnIyc6ICclMjMnXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9LFxuICAgIHJlc2VydmVkOiB7XG4gICAgICBlbmNvZGU6IHtcbiAgICAgICAgLy8gUkZDMzk4NiAyLjE6IEZvciBjb25zaXN0ZW5jeSwgVVJJIHByb2R1Y2VycyBhbmQgbm9ybWFsaXplcnMgc2hvdWxkXG4gICAgICAgIC8vIHVzZSB1cHBlcmNhc2UgaGV4YWRlY2ltYWwgZGlnaXRzIGZvciBhbGwgcGVyY2VudC1lbmNvZGluZ3MuXG4gICAgICAgIGV4cHJlc3Npb246IC8lKDIxfDIzfDI0fDI2fDI3fDI4fDI5fDJBfDJCfDJDfDJGfDNBfDNCfDNEfDNGfDQwfDVCfDVEKS9pZyxcbiAgICAgICAgbWFwOiB7XG4gICAgICAgICAgLy8gZ2VuLWRlbGltc1xuICAgICAgICAgICclM0EnOiAnOicsXG4gICAgICAgICAgJyUyRic6ICcvJyxcbiAgICAgICAgICAnJTNGJzogJz8nLFxuICAgICAgICAgICclMjMnOiAnIycsXG4gICAgICAgICAgJyU1Qic6ICdbJyxcbiAgICAgICAgICAnJTVEJzogJ10nLFxuICAgICAgICAgICclNDAnOiAnQCcsXG4gICAgICAgICAgLy8gc3ViLWRlbGltc1xuICAgICAgICAgICclMjEnOiAnIScsXG4gICAgICAgICAgJyUyNCc6ICckJyxcbiAgICAgICAgICAnJTI2JzogJyYnLFxuICAgICAgICAgICclMjcnOiAnXFwnJyxcbiAgICAgICAgICAnJTI4JzogJygnLFxuICAgICAgICAgICclMjknOiAnKScsXG4gICAgICAgICAgJyUyQSc6ICcqJyxcbiAgICAgICAgICAnJTJCJzogJysnLFxuICAgICAgICAgICclMkMnOiAnLCcsXG4gICAgICAgICAgJyUzQic6ICc7JyxcbiAgICAgICAgICAnJTNEJzogJz0nXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9LFxuICAgIHVybnBhdGg6IHtcbiAgICAgIC8vIFRoZSBjaGFyYWN0ZXJzIHVuZGVyIGBlbmNvZGVgIGFyZSB0aGUgY2hhcmFjdGVycyBjYWxsZWQgb3V0IGJ5IFJGQyAyMTQxIGFzIGJlaW5nIGFjY2VwdGFibGVcbiAgICAgIC8vIGZvciB1c2FnZSBpbiBhIFVSTi4gUkZDMjE0MSBhbHNvIGNhbGxzIG91dCBcIi1cIiwgXCIuXCIsIGFuZCBcIl9cIiBhcyBhY2NlcHRhYmxlIGNoYXJhY3RlcnMsIGJ1dFxuICAgICAgLy8gdGhlc2UgYXJlbid0IGVuY29kZWQgYnkgZW5jb2RlVVJJQ29tcG9uZW50LCBzbyB3ZSBkb24ndCBoYXZlIHRvIGNhbGwgdGhlbSBvdXQgaGVyZS4gQWxzb1xuICAgICAgLy8gbm90ZSB0aGF0IHRoZSBjb2xvbiBjaGFyYWN0ZXIgaXMgbm90IGZlYXR1cmVkIGluIHRoZSBlbmNvZGluZyBtYXA7IHRoaXMgaXMgYmVjYXVzZSBVUkkuanNcbiAgICAgIC8vIGdpdmVzIHRoZSBjb2xvbnMgaW4gVVJOcyBzZW1hbnRpYyBtZWFuaW5nIGFzIHRoZSBkZWxpbWl0ZXJzIG9mIHBhdGggc2VnZW1lbnRzLCBhbmQgc28gaXRcbiAgICAgIC8vIHNob3VsZCBub3QgYXBwZWFyIHVuZW5jb2RlZCBpbiBhIHNlZ21lbnQgaXRzZWxmLlxuICAgICAgLy8gU2VlIGFsc28gdGhlIG5vdGUgYWJvdmUgYWJvdXQgUkZDMzk4NiBhbmQgY2FwaXRhbGFsaXplZCBoZXggZGlnaXRzLlxuICAgICAgZW5jb2RlOiB7XG4gICAgICAgIGV4cHJlc3Npb246IC8lKDIxfDI0fDI3fDI4fDI5fDJBfDJCfDJDfDNCfDNEfDQwKS9pZyxcbiAgICAgICAgbWFwOiB7XG4gICAgICAgICAgJyUyMSc6ICchJyxcbiAgICAgICAgICAnJTI0JzogJyQnLFxuICAgICAgICAgICclMjcnOiAnXFwnJyxcbiAgICAgICAgICAnJTI4JzogJygnLFxuICAgICAgICAgICclMjknOiAnKScsXG4gICAgICAgICAgJyUyQSc6ICcqJyxcbiAgICAgICAgICAnJTJCJzogJysnLFxuICAgICAgICAgICclMkMnOiAnLCcsXG4gICAgICAgICAgJyUzQic6ICc7JyxcbiAgICAgICAgICAnJTNEJzogJz0nLFxuICAgICAgICAgICclNDAnOiAnQCdcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIC8vIFRoZXNlIGNoYXJhY3RlcnMgYXJlIHRoZSBjaGFyYWN0ZXJzIGNhbGxlZCBvdXQgYnkgUkZDMjE0MSBhcyBcInJlc2VydmVkXCIgY2hhcmFjdGVycyB0aGF0XG4gICAgICAvLyBzaG91bGQgbmV2ZXIgYXBwZWFyIGluIGEgVVJOLCBwbHVzIHRoZSBjb2xvbiBjaGFyYWN0ZXIgKHNlZSBub3RlIGFib3ZlKS5cbiAgICAgIGRlY29kZToge1xuICAgICAgICBleHByZXNzaW9uOiAvW1xcL1xcPyM6XS9nLFxuICAgICAgICBtYXA6IHtcbiAgICAgICAgICAnLyc6ICclMkYnLFxuICAgICAgICAgICc/JzogJyUzRicsXG4gICAgICAgICAgJyMnOiAnJTIzJyxcbiAgICAgICAgICAnOic6ICclM0EnXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH07XG4gIFVSSS5lbmNvZGVRdWVyeSA9IGZ1bmN0aW9uKHN0cmluZywgZXNjYXBlUXVlcnlTcGFjZSkge1xuICAgIHZhciBlc2NhcGVkID0gVVJJLmVuY29kZShzdHJpbmcgKyAnJyk7XG4gICAgaWYgKGVzY2FwZVF1ZXJ5U3BhY2UgPT09IHVuZGVmaW5lZCkge1xuICAgICAgZXNjYXBlUXVlcnlTcGFjZSA9IFVSSS5lc2NhcGVRdWVyeVNwYWNlO1xuICAgIH1cblxuICAgIHJldHVybiBlc2NhcGVRdWVyeVNwYWNlID8gZXNjYXBlZC5yZXBsYWNlKC8lMjAvZywgJysnKSA6IGVzY2FwZWQ7XG4gIH07XG4gIFVSSS5kZWNvZGVRdWVyeSA9IGZ1bmN0aW9uKHN0cmluZywgZXNjYXBlUXVlcnlTcGFjZSkge1xuICAgIHN0cmluZyArPSAnJztcbiAgICBpZiAoZXNjYXBlUXVlcnlTcGFjZSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICBlc2NhcGVRdWVyeVNwYWNlID0gVVJJLmVzY2FwZVF1ZXJ5U3BhY2U7XG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiBVUkkuZGVjb2RlKGVzY2FwZVF1ZXJ5U3BhY2UgPyBzdHJpbmcucmVwbGFjZSgvXFwrL2csICclMjAnKSA6IHN0cmluZyk7XG4gICAgfSBjYXRjaChlKSB7XG4gICAgICAvLyB3ZSdyZSBub3QgZ29pbmcgdG8gbWVzcyB3aXRoIHdlaXJkIGVuY29kaW5ncyxcbiAgICAgIC8vIGdpdmUgdXAgYW5kIHJldHVybiB0aGUgdW5kZWNvZGVkIG9yaWdpbmFsIHN0cmluZ1xuICAgICAgLy8gc2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9tZWRpYWxpemUvVVJJLmpzL2lzc3Vlcy84N1xuICAgICAgLy8gc2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9tZWRpYWxpemUvVVJJLmpzL2lzc3Vlcy85MlxuICAgICAgcmV0dXJuIHN0cmluZztcbiAgICB9XG4gIH07XG4gIC8vIGdlbmVyYXRlIGVuY29kZS9kZWNvZGUgcGF0aCBmdW5jdGlvbnNcbiAgdmFyIF9wYXJ0cyA9IHsnZW5jb2RlJzonZW5jb2RlJywgJ2RlY29kZSc6J2RlY29kZSd9O1xuICB2YXIgX3BhcnQ7XG4gIHZhciBnZW5lcmF0ZUFjY2Vzc29yID0gZnVuY3Rpb24oX2dyb3VwLCBfcGFydCkge1xuICAgIHJldHVybiBmdW5jdGlvbihzdHJpbmcpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBVUklbX3BhcnRdKHN0cmluZyArICcnKS5yZXBsYWNlKFVSSS5jaGFyYWN0ZXJzW19ncm91cF1bX3BhcnRdLmV4cHJlc3Npb24sIGZ1bmN0aW9uKGMpIHtcbiAgICAgICAgICByZXR1cm4gVVJJLmNoYXJhY3RlcnNbX2dyb3VwXVtfcGFydF0ubWFwW2NdO1xuICAgICAgICB9KTtcbiAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgLy8gd2UncmUgbm90IGdvaW5nIHRvIG1lc3Mgd2l0aCB3ZWlyZCBlbmNvZGluZ3MsXG4gICAgICAgIC8vIGdpdmUgdXAgYW5kIHJldHVybiB0aGUgdW5kZWNvZGVkIG9yaWdpbmFsIHN0cmluZ1xuICAgICAgICAvLyBzZWUgaHR0cHM6Ly9naXRodWIuY29tL21lZGlhbGl6ZS9VUkkuanMvaXNzdWVzLzg3XG4gICAgICAgIC8vIHNlZSBodHRwczovL2dpdGh1Yi5jb20vbWVkaWFsaXplL1VSSS5qcy9pc3N1ZXMvOTJcbiAgICAgICAgcmV0dXJuIHN0cmluZztcbiAgICAgIH1cbiAgICB9O1xuICB9O1xuXG4gIGZvciAoX3BhcnQgaW4gX3BhcnRzKSB7XG4gICAgVVJJW19wYXJ0ICsgJ1BhdGhTZWdtZW50J10gPSBnZW5lcmF0ZUFjY2Vzc29yKCdwYXRobmFtZScsIF9wYXJ0c1tfcGFydF0pO1xuICAgIFVSSVtfcGFydCArICdVcm5QYXRoU2VnbWVudCddID0gZ2VuZXJhdGVBY2Nlc3NvcigndXJucGF0aCcsIF9wYXJ0c1tfcGFydF0pO1xuICB9XG5cbiAgdmFyIGdlbmVyYXRlU2VnbWVudGVkUGF0aEZ1bmN0aW9uID0gZnVuY3Rpb24oX3NlcCwgX2NvZGluZ0Z1bmNOYW1lLCBfaW5uZXJDb2RpbmdGdW5jTmFtZSkge1xuICAgIHJldHVybiBmdW5jdGlvbihzdHJpbmcpIHtcbiAgICAgIC8vIFdoeSBwYXNzIGluIG5hbWVzIG9mIGZ1bmN0aW9ucywgcmF0aGVyIHRoYW4gdGhlIGZ1bmN0aW9uIG9iamVjdHMgdGhlbXNlbHZlcz8gVGhlXG4gICAgICAvLyBkZWZpbml0aW9ucyBvZiBzb21lIGZ1bmN0aW9ucyAoYnV0IGluIHBhcnRpY3VsYXIsIFVSSS5kZWNvZGUpIHdpbGwgb2NjYXNpb25hbGx5IGNoYW5nZSBkdWVcbiAgICAgIC8vIHRvIFVSSS5qcyBoYXZpbmcgSVNPODg1OSBhbmQgVW5pY29kZSBtb2Rlcy4gUGFzc2luZyBpbiB0aGUgbmFtZSBhbmQgZ2V0dGluZyBpdCB3aWxsIGVuc3VyZVxuICAgICAgLy8gdGhhdCB0aGUgZnVuY3Rpb25zIHdlIHVzZSBoZXJlIGFyZSBcImZyZXNoXCIuXG4gICAgICB2YXIgYWN0dWFsQ29kaW5nRnVuYztcbiAgICAgIGlmICghX2lubmVyQ29kaW5nRnVuY05hbWUpIHtcbiAgICAgICAgYWN0dWFsQ29kaW5nRnVuYyA9IFVSSVtfY29kaW5nRnVuY05hbWVdO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgYWN0dWFsQ29kaW5nRnVuYyA9IGZ1bmN0aW9uKHN0cmluZykge1xuICAgICAgICAgIHJldHVybiBVUklbX2NvZGluZ0Z1bmNOYW1lXShVUklbX2lubmVyQ29kaW5nRnVuY05hbWVdKHN0cmluZykpO1xuICAgICAgICB9O1xuICAgICAgfVxuXG4gICAgICB2YXIgc2VnbWVudHMgPSAoc3RyaW5nICsgJycpLnNwbGl0KF9zZXApO1xuXG4gICAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gc2VnbWVudHMubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgc2VnbWVudHNbaV0gPSBhY3R1YWxDb2RpbmdGdW5jKHNlZ21lbnRzW2ldKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHNlZ21lbnRzLmpvaW4oX3NlcCk7XG4gICAgfTtcbiAgfTtcblxuICAvLyBUaGlzIHRha2VzIHBsYWNlIG91dHNpZGUgdGhlIGFib3ZlIGxvb3AgYmVjYXVzZSB3ZSBkb24ndCB3YW50LCBlLmcuLCBlbmNvZGVVcm5QYXRoIGZ1bmN0aW9ucy5cbiAgVVJJLmRlY29kZVBhdGggPSBnZW5lcmF0ZVNlZ21lbnRlZFBhdGhGdW5jdGlvbignLycsICdkZWNvZGVQYXRoU2VnbWVudCcpO1xuICBVUkkuZGVjb2RlVXJuUGF0aCA9IGdlbmVyYXRlU2VnbWVudGVkUGF0aEZ1bmN0aW9uKCc6JywgJ2RlY29kZVVyblBhdGhTZWdtZW50Jyk7XG4gIFVSSS5yZWNvZGVQYXRoID0gZ2VuZXJhdGVTZWdtZW50ZWRQYXRoRnVuY3Rpb24oJy8nLCAnZW5jb2RlUGF0aFNlZ21lbnQnLCAnZGVjb2RlJyk7XG4gIFVSSS5yZWNvZGVVcm5QYXRoID0gZ2VuZXJhdGVTZWdtZW50ZWRQYXRoRnVuY3Rpb24oJzonLCAnZW5jb2RlVXJuUGF0aFNlZ21lbnQnLCAnZGVjb2RlJyk7XG5cbiAgVVJJLmVuY29kZVJlc2VydmVkID0gZ2VuZXJhdGVBY2Nlc3NvcigncmVzZXJ2ZWQnLCAnZW5jb2RlJyk7XG5cbiAgVVJJLnBhcnNlID0gZnVuY3Rpb24oc3RyaW5nLCBwYXJ0cykge1xuICAgIHZhciBwb3M7XG4gICAgaWYgKCFwYXJ0cykge1xuICAgICAgcGFydHMgPSB7fTtcbiAgICB9XG4gICAgLy8gW3Byb3RvY29sXCI6Ly9cIlt1c2VybmFtZVtcIjpcInBhc3N3b3JkXVwiQFwiXWhvc3RuYW1lW1wiOlwicG9ydF1cIi9cIj9dW3BhdGhdW1wiP1wicXVlcnlzdHJpbmddW1wiI1wiZnJhZ21lbnRdXG5cbiAgICAvLyBleHRyYWN0IGZyYWdtZW50XG4gICAgcG9zID0gc3RyaW5nLmluZGV4T2YoJyMnKTtcbiAgICBpZiAocG9zID4gLTEpIHtcbiAgICAgIC8vIGVzY2FwaW5nP1xuICAgICAgcGFydHMuZnJhZ21lbnQgPSBzdHJpbmcuc3Vic3RyaW5nKHBvcyArIDEpIHx8IG51bGw7XG4gICAgICBzdHJpbmcgPSBzdHJpbmcuc3Vic3RyaW5nKDAsIHBvcyk7XG4gICAgfVxuXG4gICAgLy8gZXh0cmFjdCBxdWVyeVxuICAgIHBvcyA9IHN0cmluZy5pbmRleE9mKCc/Jyk7XG4gICAgaWYgKHBvcyA+IC0xKSB7XG4gICAgICAvLyBlc2NhcGluZz9cbiAgICAgIHBhcnRzLnF1ZXJ5ID0gc3RyaW5nLnN1YnN0cmluZyhwb3MgKyAxKSB8fCBudWxsO1xuICAgICAgc3RyaW5nID0gc3RyaW5nLnN1YnN0cmluZygwLCBwb3MpO1xuICAgIH1cblxuICAgIC8vIGV4dHJhY3QgcHJvdG9jb2xcbiAgICBpZiAoc3RyaW5nLnN1YnN0cmluZygwLCAyKSA9PT0gJy8vJykge1xuICAgICAgLy8gcmVsYXRpdmUtc2NoZW1lXG4gICAgICBwYXJ0cy5wcm90b2NvbCA9IG51bGw7XG4gICAgICBzdHJpbmcgPSBzdHJpbmcuc3Vic3RyaW5nKDIpO1xuICAgICAgLy8gZXh0cmFjdCBcInVzZXI6cGFzc0Bob3N0OnBvcnRcIlxuICAgICAgc3RyaW5nID0gVVJJLnBhcnNlQXV0aG9yaXR5KHN0cmluZywgcGFydHMpO1xuICAgIH0gZWxzZSB7XG4gICAgICBwb3MgPSBzdHJpbmcuaW5kZXhPZignOicpO1xuICAgICAgaWYgKHBvcyA+IC0xKSB7XG4gICAgICAgIHBhcnRzLnByb3RvY29sID0gc3RyaW5nLnN1YnN0cmluZygwLCBwb3MpIHx8IG51bGw7XG4gICAgICAgIGlmIChwYXJ0cy5wcm90b2NvbCAmJiAhcGFydHMucHJvdG9jb2wubWF0Y2goVVJJLnByb3RvY29sX2V4cHJlc3Npb24pKSB7XG4gICAgICAgICAgLy8gOiBtYXkgYmUgd2l0aGluIHRoZSBwYXRoXG4gICAgICAgICAgcGFydHMucHJvdG9jb2wgPSB1bmRlZmluZWQ7XG4gICAgICAgIH0gZWxzZSBpZiAoc3RyaW5nLnN1YnN0cmluZyhwb3MgKyAxLCBwb3MgKyAzKSA9PT0gJy8vJykge1xuICAgICAgICAgIHN0cmluZyA9IHN0cmluZy5zdWJzdHJpbmcocG9zICsgMyk7XG5cbiAgICAgICAgICAvLyBleHRyYWN0IFwidXNlcjpwYXNzQGhvc3Q6cG9ydFwiXG4gICAgICAgICAgc3RyaW5nID0gVVJJLnBhcnNlQXV0aG9yaXR5KHN0cmluZywgcGFydHMpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHN0cmluZyA9IHN0cmluZy5zdWJzdHJpbmcocG9zICsgMSk7XG4gICAgICAgICAgcGFydHMudXJuID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIC8vIHdoYXQncyBsZWZ0IG11c3QgYmUgdGhlIHBhdGhcbiAgICBwYXJ0cy5wYXRoID0gc3RyaW5nO1xuXG4gICAgLy8gYW5kIHdlJ3JlIGRvbmVcbiAgICByZXR1cm4gcGFydHM7XG4gIH07XG4gIFVSSS5wYXJzZUhvc3QgPSBmdW5jdGlvbihzdHJpbmcsIHBhcnRzKSB7XG4gICAgLy8gQ29weSBjaHJvbWUsIElFLCBvcGVyYSBiYWNrc2xhc2gtaGFuZGxpbmcgYmVoYXZpb3IuXG4gICAgLy8gQmFjayBzbGFzaGVzIGJlZm9yZSB0aGUgcXVlcnkgc3RyaW5nIGdldCBjb252ZXJ0ZWQgdG8gZm9yd2FyZCBzbGFzaGVzXG4gICAgLy8gU2VlOiBodHRwczovL2dpdGh1Yi5jb20vam95ZW50L25vZGUvYmxvYi8zODZmZDI0ZjQ5YjBlOWQxYThhMDc2NTkyYTQwNDE2OGZhZWVjYzM0L2xpYi91cmwuanMjTDExNS1MMTI0XG4gICAgLy8gU2VlOiBodHRwczovL2NvZGUuZ29vZ2xlLmNvbS9wL2Nocm9taXVtL2lzc3Vlcy9kZXRhaWw/aWQ9MjU5MTZcbiAgICAvLyBodHRwczovL2dpdGh1Yi5jb20vbWVkaWFsaXplL1VSSS5qcy9wdWxsLzIzM1xuICAgIHN0cmluZyA9IHN0cmluZy5yZXBsYWNlKC9cXFxcL2csICcvJyk7XG5cbiAgICAvLyBleHRyYWN0IGhvc3Q6cG9ydFxuICAgIHZhciBwb3MgPSBzdHJpbmcuaW5kZXhPZignLycpO1xuICAgIHZhciBicmFja2V0UG9zO1xuICAgIHZhciB0O1xuXG4gICAgaWYgKHBvcyA9PT0gLTEpIHtcbiAgICAgIHBvcyA9IHN0cmluZy5sZW5ndGg7XG4gICAgfVxuXG4gICAgaWYgKHN0cmluZy5jaGFyQXQoMCkgPT09ICdbJykge1xuICAgICAgLy8gSVB2NiBob3N0IC0gaHR0cDovL3Rvb2xzLmlldGYub3JnL2h0bWwvZHJhZnQtaWV0Zi02bWFuLXRleHQtYWRkci1yZXByZXNlbnRhdGlvbi0wNCNzZWN0aW9uLTZcbiAgICAgIC8vIEkgY2xhaW0gbW9zdCBjbGllbnQgc29mdHdhcmUgYnJlYWtzIG9uIElQdjYgYW55d2F5cy4gVG8gc2ltcGxpZnkgdGhpbmdzLCBVUkkgb25seSBhY2NlcHRzXG4gICAgICAvLyBJUHY2K3BvcnQgaW4gdGhlIGZvcm1hdCBbMjAwMTpkYjg6OjFdOjgwIChmb3IgdGhlIHRpbWUgYmVpbmcpXG4gICAgICBicmFja2V0UG9zID0gc3RyaW5nLmluZGV4T2YoJ10nKTtcbiAgICAgIHBhcnRzLmhvc3RuYW1lID0gc3RyaW5nLnN1YnN0cmluZygxLCBicmFja2V0UG9zKSB8fCBudWxsO1xuICAgICAgcGFydHMucG9ydCA9IHN0cmluZy5zdWJzdHJpbmcoYnJhY2tldFBvcyArIDIsIHBvcykgfHwgbnVsbDtcbiAgICAgIGlmIChwYXJ0cy5wb3J0ID09PSAnLycpIHtcbiAgICAgICAgcGFydHMucG9ydCA9IG51bGw7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIHZhciBmaXJzdENvbG9uID0gc3RyaW5nLmluZGV4T2YoJzonKTtcbiAgICAgIHZhciBmaXJzdFNsYXNoID0gc3RyaW5nLmluZGV4T2YoJy8nKTtcbiAgICAgIHZhciBuZXh0Q29sb24gPSBzdHJpbmcuaW5kZXhPZignOicsIGZpcnN0Q29sb24gKyAxKTtcbiAgICAgIGlmIChuZXh0Q29sb24gIT09IC0xICYmIChmaXJzdFNsYXNoID09PSAtMSB8fCBuZXh0Q29sb24gPCBmaXJzdFNsYXNoKSkge1xuICAgICAgICAvLyBJUHY2IGhvc3QgY29udGFpbnMgbXVsdGlwbGUgY29sb25zIC0gYnV0IG5vIHBvcnRcbiAgICAgICAgLy8gdGhpcyBub3RhdGlvbiBpcyBhY3R1YWxseSBub3QgYWxsb3dlZCBieSBSRkMgMzk4NiwgYnV0IHdlJ3JlIGEgbGliZXJhbCBwYXJzZXJcbiAgICAgICAgcGFydHMuaG9zdG5hbWUgPSBzdHJpbmcuc3Vic3RyaW5nKDAsIHBvcykgfHwgbnVsbDtcbiAgICAgICAgcGFydHMucG9ydCA9IG51bGw7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0ID0gc3RyaW5nLnN1YnN0cmluZygwLCBwb3MpLnNwbGl0KCc6Jyk7XG4gICAgICAgIHBhcnRzLmhvc3RuYW1lID0gdFswXSB8fCBudWxsO1xuICAgICAgICBwYXJ0cy5wb3J0ID0gdFsxXSB8fCBudWxsO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChwYXJ0cy5ob3N0bmFtZSAmJiBzdHJpbmcuc3Vic3RyaW5nKHBvcykuY2hhckF0KDApICE9PSAnLycpIHtcbiAgICAgIHBvcysrO1xuICAgICAgc3RyaW5nID0gJy8nICsgc3RyaW5nO1xuICAgIH1cblxuICAgIHJldHVybiBzdHJpbmcuc3Vic3RyaW5nKHBvcykgfHwgJy8nO1xuICB9O1xuICBVUkkucGFyc2VBdXRob3JpdHkgPSBmdW5jdGlvbihzdHJpbmcsIHBhcnRzKSB7XG4gICAgc3RyaW5nID0gVVJJLnBhcnNlVXNlcmluZm8oc3RyaW5nLCBwYXJ0cyk7XG4gICAgcmV0dXJuIFVSSS5wYXJzZUhvc3Qoc3RyaW5nLCBwYXJ0cyk7XG4gIH07XG4gIFVSSS5wYXJzZVVzZXJpbmZvID0gZnVuY3Rpb24oc3RyaW5nLCBwYXJ0cykge1xuICAgIC8vIGV4dHJhY3QgdXNlcm5hbWU6cGFzc3dvcmRcbiAgICB2YXIgZmlyc3RTbGFzaCA9IHN0cmluZy5pbmRleE9mKCcvJyk7XG4gICAgdmFyIHBvcyA9IHN0cmluZy5sYXN0SW5kZXhPZignQCcsIGZpcnN0U2xhc2ggPiAtMSA/IGZpcnN0U2xhc2ggOiBzdHJpbmcubGVuZ3RoIC0gMSk7XG4gICAgdmFyIHQ7XG5cbiAgICAvLyBhdXRob3JpdHlAIG11c3QgY29tZSBiZWZvcmUgL3BhdGhcbiAgICBpZiAocG9zID4gLTEgJiYgKGZpcnN0U2xhc2ggPT09IC0xIHx8IHBvcyA8IGZpcnN0U2xhc2gpKSB7XG4gICAgICB0ID0gc3RyaW5nLnN1YnN0cmluZygwLCBwb3MpLnNwbGl0KCc6Jyk7XG4gICAgICBwYXJ0cy51c2VybmFtZSA9IHRbMF0gPyBVUkkuZGVjb2RlKHRbMF0pIDogbnVsbDtcbiAgICAgIHQuc2hpZnQoKTtcbiAgICAgIHBhcnRzLnBhc3N3b3JkID0gdFswXSA/IFVSSS5kZWNvZGUodC5qb2luKCc6JykpIDogbnVsbDtcbiAgICAgIHN0cmluZyA9IHN0cmluZy5zdWJzdHJpbmcocG9zICsgMSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHBhcnRzLnVzZXJuYW1lID0gbnVsbDtcbiAgICAgIHBhcnRzLnBhc3N3b3JkID0gbnVsbDtcbiAgICB9XG5cbiAgICByZXR1cm4gc3RyaW5nO1xuICB9O1xuICBVUkkucGFyc2VRdWVyeSA9IGZ1bmN0aW9uKHN0cmluZywgZXNjYXBlUXVlcnlTcGFjZSkge1xuICAgIGlmICghc3RyaW5nKSB7XG4gICAgICByZXR1cm4ge307XG4gICAgfVxuXG4gICAgLy8gdGhyb3cgb3V0IHRoZSBmdW5reSBidXNpbmVzcyAtIFwiP1wiW25hbWVcIj1cInZhbHVlXCImXCJdK1xuICAgIHN0cmluZyA9IHN0cmluZy5yZXBsYWNlKC8mKy9nLCAnJicpLnJlcGxhY2UoL15cXD8qJip8JiskL2csICcnKTtcblxuICAgIGlmICghc3RyaW5nKSB7XG4gICAgICByZXR1cm4ge307XG4gICAgfVxuXG4gICAgdmFyIGl0ZW1zID0ge307XG4gICAgdmFyIHNwbGl0cyA9IHN0cmluZy5zcGxpdCgnJicpO1xuICAgIHZhciBsZW5ndGggPSBzcGxpdHMubGVuZ3RoO1xuICAgIHZhciB2LCBuYW1lLCB2YWx1ZTtcblxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHYgPSBzcGxpdHNbaV0uc3BsaXQoJz0nKTtcbiAgICAgIG5hbWUgPSBVUkkuZGVjb2RlUXVlcnkodi5zaGlmdCgpLCBlc2NhcGVRdWVyeVNwYWNlKTtcbiAgICAgIC8vIG5vIFwiPVwiIGlzIG51bGwgYWNjb3JkaW5nIHRvIGh0dHA6Ly9kdmNzLnczLm9yZy9oZy91cmwvcmF3LWZpbGUvdGlwL092ZXJ2aWV3Lmh0bWwjY29sbGVjdC11cmwtcGFyYW1ldGVyc1xuICAgICAgdmFsdWUgPSB2Lmxlbmd0aCA/IFVSSS5kZWNvZGVRdWVyeSh2LmpvaW4oJz0nKSwgZXNjYXBlUXVlcnlTcGFjZSkgOiBudWxsO1xuXG4gICAgICBpZiAoaGFzT3duLmNhbGwoaXRlbXMsIG5hbWUpKSB7XG4gICAgICAgIGlmICh0eXBlb2YgaXRlbXNbbmFtZV0gPT09ICdzdHJpbmcnIHx8IGl0ZW1zW25hbWVdID09PSBudWxsKSB7XG4gICAgICAgICAgaXRlbXNbbmFtZV0gPSBbaXRlbXNbbmFtZV1dO1xuICAgICAgICB9XG5cbiAgICAgICAgaXRlbXNbbmFtZV0ucHVzaCh2YWx1ZSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpdGVtc1tuYW1lXSA9IHZhbHVlO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBpdGVtcztcbiAgfTtcblxuICBVUkkuYnVpbGQgPSBmdW5jdGlvbihwYXJ0cykge1xuICAgIHZhciB0ID0gJyc7XG5cbiAgICBpZiAocGFydHMucHJvdG9jb2wpIHtcbiAgICAgIHQgKz0gcGFydHMucHJvdG9jb2wgKyAnOic7XG4gICAgfVxuXG4gICAgaWYgKCFwYXJ0cy51cm4gJiYgKHQgfHwgcGFydHMuaG9zdG5hbWUpKSB7XG4gICAgICB0ICs9ICcvLyc7XG4gICAgfVxuXG4gICAgdCArPSAoVVJJLmJ1aWxkQXV0aG9yaXR5KHBhcnRzKSB8fCAnJyk7XG5cbiAgICBpZiAodHlwZW9mIHBhcnRzLnBhdGggPT09ICdzdHJpbmcnKSB7XG4gICAgICBpZiAocGFydHMucGF0aC5jaGFyQXQoMCkgIT09ICcvJyAmJiB0eXBlb2YgcGFydHMuaG9zdG5hbWUgPT09ICdzdHJpbmcnKSB7XG4gICAgICAgIHQgKz0gJy8nO1xuICAgICAgfVxuXG4gICAgICB0ICs9IHBhcnRzLnBhdGg7XG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiBwYXJ0cy5xdWVyeSA9PT0gJ3N0cmluZycgJiYgcGFydHMucXVlcnkpIHtcbiAgICAgIHQgKz0gJz8nICsgcGFydHMucXVlcnk7XG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiBwYXJ0cy5mcmFnbWVudCA9PT0gJ3N0cmluZycgJiYgcGFydHMuZnJhZ21lbnQpIHtcbiAgICAgIHQgKz0gJyMnICsgcGFydHMuZnJhZ21lbnQ7XG4gICAgfVxuICAgIHJldHVybiB0O1xuICB9O1xuICBVUkkuYnVpbGRIb3N0ID0gZnVuY3Rpb24ocGFydHMpIHtcbiAgICB2YXIgdCA9ICcnO1xuXG4gICAgaWYgKCFwYXJ0cy5ob3N0bmFtZSkge1xuICAgICAgcmV0dXJuICcnO1xuICAgIH0gZWxzZSBpZiAoVVJJLmlwNl9leHByZXNzaW9uLnRlc3QocGFydHMuaG9zdG5hbWUpKSB7XG4gICAgICB0ICs9ICdbJyArIHBhcnRzLmhvc3RuYW1lICsgJ10nO1xuICAgIH0gZWxzZSB7XG4gICAgICB0ICs9IHBhcnRzLmhvc3RuYW1lO1xuICAgIH1cblxuICAgIGlmIChwYXJ0cy5wb3J0KSB7XG4gICAgICB0ICs9ICc6JyArIHBhcnRzLnBvcnQ7XG4gICAgfVxuXG4gICAgcmV0dXJuIHQ7XG4gIH07XG4gIFVSSS5idWlsZEF1dGhvcml0eSA9IGZ1bmN0aW9uKHBhcnRzKSB7XG4gICAgcmV0dXJuIFVSSS5idWlsZFVzZXJpbmZvKHBhcnRzKSArIFVSSS5idWlsZEhvc3QocGFydHMpO1xuICB9O1xuICBVUkkuYnVpbGRVc2VyaW5mbyA9IGZ1bmN0aW9uKHBhcnRzKSB7XG4gICAgdmFyIHQgPSAnJztcblxuICAgIGlmIChwYXJ0cy51c2VybmFtZSkge1xuICAgICAgdCArPSBVUkkuZW5jb2RlKHBhcnRzLnVzZXJuYW1lKTtcblxuICAgICAgaWYgKHBhcnRzLnBhc3N3b3JkKSB7XG4gICAgICAgIHQgKz0gJzonICsgVVJJLmVuY29kZShwYXJ0cy5wYXNzd29yZCk7XG4gICAgICB9XG5cbiAgICAgIHQgKz0gJ0AnO1xuICAgIH1cblxuICAgIHJldHVybiB0O1xuICB9O1xuICBVUkkuYnVpbGRRdWVyeSA9IGZ1bmN0aW9uKGRhdGEsIGR1cGxpY2F0ZVF1ZXJ5UGFyYW1ldGVycywgZXNjYXBlUXVlcnlTcGFjZSkge1xuICAgIC8vIGFjY29yZGluZyB0byBodHRwOi8vdG9vbHMuaWV0Zi5vcmcvaHRtbC9yZmMzOTg2IG9yIGh0dHA6Ly9sYWJzLmFwYWNoZS5vcmcvd2ViYXJjaC91cmkvcmZjL3JmYzM5ODYuaHRtbFxuICAgIC8vIGJlaW5nIMK7LS5ffiEkJicoKSorLDs9OkAvP8KrICVIRVggYW5kIGFsbnVtIGFyZSBhbGxvd2VkXG4gICAgLy8gdGhlIFJGQyBleHBsaWNpdGx5IHN0YXRlcyA/L2ZvbyBiZWluZyBhIHZhbGlkIHVzZSBjYXNlLCBubyBtZW50aW9uIG9mIHBhcmFtZXRlciBzeW50YXghXG4gICAgLy8gVVJJLmpzIHRyZWF0cyB0aGUgcXVlcnkgc3RyaW5nIGFzIGJlaW5nIGFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZFxuICAgIC8vIHNlZSBodHRwOi8vd3d3LnczLm9yZy9UUi9SRUMtaHRtbDQwL2ludGVyYWN0L2Zvcm1zLmh0bWwjZm9ybS1jb250ZW50LXR5cGVcblxuICAgIHZhciB0ID0gJyc7XG4gICAgdmFyIHVuaXF1ZSwga2V5LCBpLCBsZW5ndGg7XG4gICAgZm9yIChrZXkgaW4gZGF0YSkge1xuICAgICAgaWYgKGhhc093bi5jYWxsKGRhdGEsIGtleSkgJiYga2V5KSB7XG4gICAgICAgIGlmIChpc0FycmF5KGRhdGFba2V5XSkpIHtcbiAgICAgICAgICB1bmlxdWUgPSB7fTtcbiAgICAgICAgICBmb3IgKGkgPSAwLCBsZW5ndGggPSBkYXRhW2tleV0ubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGlmIChkYXRhW2tleV1baV0gIT09IHVuZGVmaW5lZCAmJiB1bmlxdWVbZGF0YVtrZXldW2ldICsgJyddID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgdCArPSAnJicgKyBVUkkuYnVpbGRRdWVyeVBhcmFtZXRlcihrZXksIGRhdGFba2V5XVtpXSwgZXNjYXBlUXVlcnlTcGFjZSk7XG4gICAgICAgICAgICAgIGlmIChkdXBsaWNhdGVRdWVyeVBhcmFtZXRlcnMgIT09IHRydWUpIHtcbiAgICAgICAgICAgICAgICB1bmlxdWVbZGF0YVtrZXldW2ldICsgJyddID0gdHJ1ZTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmIChkYXRhW2tleV0gIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgIHQgKz0gJyYnICsgVVJJLmJ1aWxkUXVlcnlQYXJhbWV0ZXIoa2V5LCBkYXRhW2tleV0sIGVzY2FwZVF1ZXJ5U3BhY2UpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIHQuc3Vic3RyaW5nKDEpO1xuICB9O1xuICBVUkkuYnVpbGRRdWVyeVBhcmFtZXRlciA9IGZ1bmN0aW9uKG5hbWUsIHZhbHVlLCBlc2NhcGVRdWVyeVNwYWNlKSB7XG4gICAgLy8gaHR0cDovL3d3dy53My5vcmcvVFIvUkVDLWh0bWw0MC9pbnRlcmFjdC9mb3Jtcy5odG1sI2Zvcm0tY29udGVudC10eXBlIC0tIGFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZFxuICAgIC8vIGRvbid0IGFwcGVuZCBcIj1cIiBmb3IgbnVsbCB2YWx1ZXMsIGFjY29yZGluZyB0byBodHRwOi8vZHZjcy53My5vcmcvaGcvdXJsL3Jhdy1maWxlL3RpcC9PdmVydmlldy5odG1sI3VybC1wYXJhbWV0ZXItc2VyaWFsaXphdGlvblxuICAgIHJldHVybiBVUkkuZW5jb2RlUXVlcnkobmFtZSwgZXNjYXBlUXVlcnlTcGFjZSkgKyAodmFsdWUgIT09IG51bGwgPyAnPScgKyBVUkkuZW5jb2RlUXVlcnkodmFsdWUsIGVzY2FwZVF1ZXJ5U3BhY2UpIDogJycpO1xuICB9O1xuXG4gIFVSSS5hZGRRdWVyeSA9IGZ1bmN0aW9uKGRhdGEsIG5hbWUsIHZhbHVlKSB7XG4gICAgaWYgKHR5cGVvZiBuYW1lID09PSAnb2JqZWN0Jykge1xuICAgICAgZm9yICh2YXIga2V5IGluIG5hbWUpIHtcbiAgICAgICAgaWYgKGhhc093bi5jYWxsKG5hbWUsIGtleSkpIHtcbiAgICAgICAgICBVUkkuYWRkUXVlcnkoZGF0YSwga2V5LCBuYW1lW2tleV0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBlbHNlIGlmICh0eXBlb2YgbmFtZSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIGlmIChkYXRhW25hbWVdID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgZGF0YVtuYW1lXSA9IHZhbHVlO1xuICAgICAgICByZXR1cm47XG4gICAgICB9IGVsc2UgaWYgKHR5cGVvZiBkYXRhW25hbWVdID09PSAnc3RyaW5nJykge1xuICAgICAgICBkYXRhW25hbWVdID0gW2RhdGFbbmFtZV1dO1xuICAgICAgfVxuXG4gICAgICBpZiAoIWlzQXJyYXkodmFsdWUpKSB7XG4gICAgICAgIHZhbHVlID0gW3ZhbHVlXTtcbiAgICAgIH1cblxuICAgICAgZGF0YVtuYW1lXSA9IChkYXRhW25hbWVdIHx8IFtdKS5jb25jYXQodmFsdWUpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdVUkkuYWRkUXVlcnkoKSBhY2NlcHRzIGFuIG9iamVjdCwgc3RyaW5nIGFzIHRoZSBuYW1lIHBhcmFtZXRlcicpO1xuICAgIH1cbiAgfTtcbiAgVVJJLnJlbW92ZVF1ZXJ5ID0gZnVuY3Rpb24oZGF0YSwgbmFtZSwgdmFsdWUpIHtcbiAgICB2YXIgaSwgbGVuZ3RoLCBrZXk7XG5cbiAgICBpZiAoaXNBcnJheShuYW1lKSkge1xuICAgICAgZm9yIChpID0gMCwgbGVuZ3RoID0gbmFtZS5sZW5ndGg7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgICBkYXRhW25hbWVbaV1dID0gdW5kZWZpbmVkO1xuICAgICAgfVxuICAgIH0gZWxzZSBpZiAoZ2V0VHlwZShuYW1lKSA9PT0gJ1JlZ0V4cCcpIHtcbiAgICAgIGZvciAoa2V5IGluIGRhdGEpIHtcbiAgICAgICAgaWYgKG5hbWUudGVzdChrZXkpKSB7XG4gICAgICAgICAgZGF0YVtrZXldID0gdW5kZWZpbmVkO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBlbHNlIGlmICh0eXBlb2YgbmFtZSA9PT0gJ29iamVjdCcpIHtcbiAgICAgIGZvciAoa2V5IGluIG5hbWUpIHtcbiAgICAgICAgaWYgKGhhc093bi5jYWxsKG5hbWUsIGtleSkpIHtcbiAgICAgICAgICBVUkkucmVtb3ZlUXVlcnkoZGF0YSwga2V5LCBuYW1lW2tleV0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBlbHNlIGlmICh0eXBlb2YgbmFtZSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIGlmICh2YWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGlmIChnZXRUeXBlKHZhbHVlKSA9PT0gJ1JlZ0V4cCcpIHtcbiAgICAgICAgICBpZiAoIWlzQXJyYXkoZGF0YVtuYW1lXSkgJiYgdmFsdWUudGVzdChkYXRhW25hbWVdKSkge1xuICAgICAgICAgICAgZGF0YVtuYW1lXSA9IHVuZGVmaW5lZDtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgZGF0YVtuYW1lXSA9IGZpbHRlckFycmF5VmFsdWVzKGRhdGFbbmFtZV0sIHZhbHVlKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSBpZiAoZGF0YVtuYW1lXSA9PT0gU3RyaW5nKHZhbHVlKSAmJiAoIWlzQXJyYXkodmFsdWUpIHx8IHZhbHVlLmxlbmd0aCA9PT0gMSkpIHtcbiAgICAgICAgICBkYXRhW25hbWVdID0gdW5kZWZpbmVkO1xuICAgICAgICB9IGVsc2UgaWYgKGlzQXJyYXkoZGF0YVtuYW1lXSkpIHtcbiAgICAgICAgICBkYXRhW25hbWVdID0gZmlsdGVyQXJyYXlWYWx1ZXMoZGF0YVtuYW1lXSwgdmFsdWUpO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBkYXRhW25hbWVdID0gdW5kZWZpbmVkO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdVUkkucmVtb3ZlUXVlcnkoKSBhY2NlcHRzIGFuIG9iamVjdCwgc3RyaW5nLCBSZWdFeHAgYXMgdGhlIGZpcnN0IHBhcmFtZXRlcicpO1xuICAgIH1cbiAgfTtcbiAgVVJJLmhhc1F1ZXJ5ID0gZnVuY3Rpb24oZGF0YSwgbmFtZSwgdmFsdWUsIHdpdGhpbkFycmF5KSB7XG4gICAgaWYgKHR5cGVvZiBuYW1lID09PSAnb2JqZWN0Jykge1xuICAgICAgZm9yICh2YXIga2V5IGluIG5hbWUpIHtcbiAgICAgICAgaWYgKGhhc093bi5jYWxsKG5hbWUsIGtleSkpIHtcbiAgICAgICAgICBpZiAoIVVSSS5oYXNRdWVyeShkYXRhLCBrZXksIG5hbWVba2V5XSkpIHtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfSBlbHNlIGlmICh0eXBlb2YgbmFtZSAhPT0gJ3N0cmluZycpIHtcbiAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ1VSSS5oYXNRdWVyeSgpIGFjY2VwdHMgYW4gb2JqZWN0LCBzdHJpbmcgYXMgdGhlIG5hbWUgcGFyYW1ldGVyJyk7XG4gICAgfVxuXG4gICAgc3dpdGNoIChnZXRUeXBlKHZhbHVlKSkge1xuICAgICAgY2FzZSAnVW5kZWZpbmVkJzpcbiAgICAgICAgLy8gdHJ1ZSBpZiBleGlzdHMgKGJ1dCBtYXkgYmUgZW1wdHkpXG4gICAgICAgIHJldHVybiBuYW1lIGluIGRhdGE7IC8vIGRhdGFbbmFtZV0gIT09IHVuZGVmaW5lZDtcblxuICAgICAgY2FzZSAnQm9vbGVhbic6XG4gICAgICAgIC8vIHRydWUgaWYgZXhpc3RzIGFuZCBub24tZW1wdHlcbiAgICAgICAgdmFyIF9ib29seSA9IEJvb2xlYW4oaXNBcnJheShkYXRhW25hbWVdKSA/IGRhdGFbbmFtZV0ubGVuZ3RoIDogZGF0YVtuYW1lXSk7XG4gICAgICAgIHJldHVybiB2YWx1ZSA9PT0gX2Jvb2x5O1xuXG4gICAgICBjYXNlICdGdW5jdGlvbic6XG4gICAgICAgIC8vIGFsbG93IGNvbXBsZXggY29tcGFyaXNvblxuICAgICAgICByZXR1cm4gISF2YWx1ZShkYXRhW25hbWVdLCBuYW1lLCBkYXRhKTtcblxuICAgICAgY2FzZSAnQXJyYXknOlxuICAgICAgICBpZiAoIWlzQXJyYXkoZGF0YVtuYW1lXSkpIHtcbiAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgb3AgPSB3aXRoaW5BcnJheSA/IGFycmF5Q29udGFpbnMgOiBhcnJheXNFcXVhbDtcbiAgICAgICAgcmV0dXJuIG9wKGRhdGFbbmFtZV0sIHZhbHVlKTtcblxuICAgICAgY2FzZSAnUmVnRXhwJzpcbiAgICAgICAgaWYgKCFpc0FycmF5KGRhdGFbbmFtZV0pKSB7XG4gICAgICAgICAgcmV0dXJuIEJvb2xlYW4oZGF0YVtuYW1lXSAmJiBkYXRhW25hbWVdLm1hdGNoKHZhbHVlKSk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIXdpdGhpbkFycmF5KSB7XG4gICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGFycmF5Q29udGFpbnMoZGF0YVtuYW1lXSwgdmFsdWUpO1xuXG4gICAgICBjYXNlICdOdW1iZXInOlxuICAgICAgICB2YWx1ZSA9IFN0cmluZyh2YWx1ZSk7XG4gICAgICAgIC8qIGZhbGxzIHRocm91Z2ggKi9cbiAgICAgIGNhc2UgJ1N0cmluZyc6XG4gICAgICAgIGlmICghaXNBcnJheShkYXRhW25hbWVdKSkge1xuICAgICAgICAgIHJldHVybiBkYXRhW25hbWVdID09PSB2YWx1ZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghd2l0aGluQXJyYXkpIHtcbiAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gYXJyYXlDb250YWlucyhkYXRhW25hbWVdLCB2YWx1ZSk7XG5cbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ1VSSS5oYXNRdWVyeSgpIGFjY2VwdHMgdW5kZWZpbmVkLCBib29sZWFuLCBzdHJpbmcsIG51bWJlciwgUmVnRXhwLCBGdW5jdGlvbiBhcyB0aGUgdmFsdWUgcGFyYW1ldGVyJyk7XG4gICAgfVxuICB9O1xuXG5cbiAgVVJJLmNvbW1vblBhdGggPSBmdW5jdGlvbihvbmUsIHR3bykge1xuICAgIHZhciBsZW5ndGggPSBNYXRoLm1pbihvbmUubGVuZ3RoLCB0d28ubGVuZ3RoKTtcbiAgICB2YXIgcG9zO1xuXG4gICAgLy8gZmluZCBmaXJzdCBub24tbWF0Y2hpbmcgY2hhcmFjdGVyXG4gICAgZm9yIChwb3MgPSAwOyBwb3MgPCBsZW5ndGg7IHBvcysrKSB7XG4gICAgICBpZiAob25lLmNoYXJBdChwb3MpICE9PSB0d28uY2hhckF0KHBvcykpIHtcbiAgICAgICAgcG9zLS07XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChwb3MgPCAxKSB7XG4gICAgICByZXR1cm4gb25lLmNoYXJBdCgwKSA9PT0gdHdvLmNoYXJBdCgwKSAmJiBvbmUuY2hhckF0KDApID09PSAnLycgPyAnLycgOiAnJztcbiAgICB9XG5cbiAgICAvLyByZXZlcnQgdG8gbGFzdCAvXG4gICAgaWYgKG9uZS5jaGFyQXQocG9zKSAhPT0gJy8nIHx8IHR3by5jaGFyQXQocG9zKSAhPT0gJy8nKSB7XG4gICAgICBwb3MgPSBvbmUuc3Vic3RyaW5nKDAsIHBvcykubGFzdEluZGV4T2YoJy8nKTtcbiAgICB9XG5cbiAgICByZXR1cm4gb25lLnN1YnN0cmluZygwLCBwb3MgKyAxKTtcbiAgfTtcblxuICBVUkkud2l0aGluU3RyaW5nID0gZnVuY3Rpb24oc3RyaW5nLCBjYWxsYmFjaywgb3B0aW9ucykge1xuICAgIG9wdGlvbnMgfHwgKG9wdGlvbnMgPSB7fSk7XG4gICAgdmFyIF9zdGFydCA9IG9wdGlvbnMuc3RhcnQgfHwgVVJJLmZpbmRVcmkuc3RhcnQ7XG4gICAgdmFyIF9lbmQgPSBvcHRpb25zLmVuZCB8fCBVUkkuZmluZFVyaS5lbmQ7XG4gICAgdmFyIF90cmltID0gb3B0aW9ucy50cmltIHx8IFVSSS5maW5kVXJpLnRyaW07XG4gICAgdmFyIF9hdHRyaWJ1dGVPcGVuID0gL1thLXowLTktXT1bXCInXT8kL2k7XG5cbiAgICBfc3RhcnQubGFzdEluZGV4ID0gMDtcbiAgICB3aGlsZSAodHJ1ZSkge1xuICAgICAgdmFyIG1hdGNoID0gX3N0YXJ0LmV4ZWMoc3RyaW5nKTtcbiAgICAgIGlmICghbWF0Y2gpIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG5cbiAgICAgIHZhciBzdGFydCA9IG1hdGNoLmluZGV4O1xuICAgICAgaWYgKG9wdGlvbnMuaWdub3JlSHRtbCkge1xuICAgICAgICAvLyBhdHRyaWJ1dChlPVtcIiddPyQpXG4gICAgICAgIHZhciBhdHRyaWJ1dGVPcGVuID0gc3RyaW5nLnNsaWNlKE1hdGgubWF4KHN0YXJ0IC0gMywgMCksIHN0YXJ0KTtcbiAgICAgICAgaWYgKGF0dHJpYnV0ZU9wZW4gJiYgX2F0dHJpYnV0ZU9wZW4udGVzdChhdHRyaWJ1dGVPcGVuKSkge1xuICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHZhciBlbmQgPSBzdGFydCArIHN0cmluZy5zbGljZShzdGFydCkuc2VhcmNoKF9lbmQpO1xuICAgICAgdmFyIHNsaWNlID0gc3RyaW5nLnNsaWNlKHN0YXJ0LCBlbmQpLnJlcGxhY2UoX3RyaW0sICcnKTtcbiAgICAgIGlmIChvcHRpb25zLmlnbm9yZSAmJiBvcHRpb25zLmlnbm9yZS50ZXN0KHNsaWNlKSkge1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cblxuICAgICAgZW5kID0gc3RhcnQgKyBzbGljZS5sZW5ndGg7XG4gICAgICB2YXIgcmVzdWx0ID0gY2FsbGJhY2soc2xpY2UsIHN0YXJ0LCBlbmQsIHN0cmluZyk7XG4gICAgICBzdHJpbmcgPSBzdHJpbmcuc2xpY2UoMCwgc3RhcnQpICsgcmVzdWx0ICsgc3RyaW5nLnNsaWNlKGVuZCk7XG4gICAgICBfc3RhcnQubGFzdEluZGV4ID0gc3RhcnQgKyByZXN1bHQubGVuZ3RoO1xuICAgIH1cblxuICAgIF9zdGFydC5sYXN0SW5kZXggPSAwO1xuICAgIHJldHVybiBzdHJpbmc7XG4gIH07XG5cbiAgVVJJLmVuc3VyZVZhbGlkSG9zdG5hbWUgPSBmdW5jdGlvbih2KSB7XG4gICAgLy8gVGhlb3JldGljYWxseSBVUklzIGFsbG93IHBlcmNlbnQtZW5jb2RpbmcgaW4gSG9zdG5hbWVzIChhY2NvcmRpbmcgdG8gUkZDIDM5ODYpXG4gICAgLy8gdGhleSBhcmUgbm90IHBhcnQgb2YgRE5TIGFuZCB0aGVyZWZvcmUgaWdub3JlZCBieSBVUkkuanNcblxuICAgIGlmICh2Lm1hdGNoKFVSSS5pbnZhbGlkX2hvc3RuYW1lX2NoYXJhY3RlcnMpKSB7XG4gICAgICAvLyB0ZXN0IHB1bnljb2RlXG4gICAgICBpZiAoIXB1bnljb2RlKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0hvc3RuYW1lIFwiJyArIHYgKyAnXCIgY29udGFpbnMgY2hhcmFjdGVycyBvdGhlciB0aGFuIFtBLVowLTkuLV0gYW5kIFB1bnljb2RlLmpzIGlzIG5vdCBhdmFpbGFibGUnKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHB1bnljb2RlLnRvQVNDSUkodikubWF0Y2goVVJJLmludmFsaWRfaG9zdG5hbWVfY2hhcmFjdGVycykpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignSG9zdG5hbWUgXCInICsgdiArICdcIiBjb250YWlucyBjaGFyYWN0ZXJzIG90aGVyIHRoYW4gW0EtWjAtOS4tXScpO1xuICAgICAgfVxuICAgIH1cbiAgfTtcblxuICAvLyBub0NvbmZsaWN0XG4gIFVSSS5ub0NvbmZsaWN0ID0gZnVuY3Rpb24ocmVtb3ZlQWxsKSB7XG4gICAgaWYgKHJlbW92ZUFsbCkge1xuICAgICAgdmFyIHVuY29uZmxpY3RlZCA9IHtcbiAgICAgICAgVVJJOiB0aGlzLm5vQ29uZmxpY3QoKVxuICAgICAgfTtcblxuICAgICAgaWYgKHJvb3QuVVJJVGVtcGxhdGUgJiYgdHlwZW9mIHJvb3QuVVJJVGVtcGxhdGUubm9Db25mbGljdCA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICB1bmNvbmZsaWN0ZWQuVVJJVGVtcGxhdGUgPSByb290LlVSSVRlbXBsYXRlLm5vQ29uZmxpY3QoKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHJvb3QuSVB2NiAmJiB0eXBlb2Ygcm9vdC5JUHY2Lm5vQ29uZmxpY3QgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgdW5jb25mbGljdGVkLklQdjYgPSByb290LklQdjYubm9Db25mbGljdCgpO1xuICAgICAgfVxuXG4gICAgICBpZiAocm9vdC5TZWNvbmRMZXZlbERvbWFpbnMgJiYgdHlwZW9mIHJvb3QuU2Vjb25kTGV2ZWxEb21haW5zLm5vQ29uZmxpY3QgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgdW5jb25mbGljdGVkLlNlY29uZExldmVsRG9tYWlucyA9IHJvb3QuU2Vjb25kTGV2ZWxEb21haW5zLm5vQ29uZmxpY3QoKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHVuY29uZmxpY3RlZDtcbiAgICB9IGVsc2UgaWYgKHJvb3QuVVJJID09PSB0aGlzKSB7XG4gICAgICByb290LlVSSSA9IF9VUkk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgcC5idWlsZCA9IGZ1bmN0aW9uKGRlZmVyQnVpbGQpIHtcbiAgICBpZiAoZGVmZXJCdWlsZCA9PT0gdHJ1ZSkge1xuICAgICAgdGhpcy5fZGVmZXJyZWRfYnVpbGQgPSB0cnVlO1xuICAgIH0gZWxzZSBpZiAoZGVmZXJCdWlsZCA9PT0gdW5kZWZpbmVkIHx8IHRoaXMuX2RlZmVycmVkX2J1aWxkKSB7XG4gICAgICB0aGlzLl9zdHJpbmcgPSBVUkkuYnVpbGQodGhpcy5fcGFydHMpO1xuICAgICAgdGhpcy5fZGVmZXJyZWRfYnVpbGQgPSBmYWxzZTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcztcbiAgfTtcblxuICBwLmNsb25lID0gZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIG5ldyBVUkkodGhpcyk7XG4gIH07XG5cbiAgcC52YWx1ZU9mID0gcC50b1N0cmluZyA9IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiB0aGlzLmJ1aWxkKGZhbHNlKS5fc3RyaW5nO1xuICB9O1xuXG5cbiAgZnVuY3Rpb24gZ2VuZXJhdGVTaW1wbGVBY2Nlc3NvcihfcGFydCl7XG4gICAgcmV0dXJuIGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgICBpZiAodiA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9wYXJ0c1tfcGFydF0gfHwgJyc7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aGlzLl9wYXJ0c1tfcGFydF0gPSB2IHx8IG51bGw7XG4gICAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICB9XG4gICAgfTtcbiAgfVxuXG4gIGZ1bmN0aW9uIGdlbmVyYXRlUHJlZml4QWNjZXNzb3IoX3BhcnQsIF9rZXkpe1xuICAgIHJldHVybiBmdW5jdGlvbih2LCBidWlsZCkge1xuICAgICAgaWYgKHYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fcGFydHNbX3BhcnRdIHx8ICcnO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgaWYgKHYgIT09IG51bGwpIHtcbiAgICAgICAgICB2ID0gdiArICcnO1xuICAgICAgICAgIGlmICh2LmNoYXJBdCgwKSA9PT0gX2tleSkge1xuICAgICAgICAgICAgdiA9IHYuc3Vic3RyaW5nKDEpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuX3BhcnRzW19wYXJ0XSA9IHY7XG4gICAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICB9XG4gICAgfTtcbiAgfVxuXG4gIHAucHJvdG9jb2wgPSBnZW5lcmF0ZVNpbXBsZUFjY2Vzc29yKCdwcm90b2NvbCcpO1xuICBwLnVzZXJuYW1lID0gZ2VuZXJhdGVTaW1wbGVBY2Nlc3NvcigndXNlcm5hbWUnKTtcbiAgcC5wYXNzd29yZCA9IGdlbmVyYXRlU2ltcGxlQWNjZXNzb3IoJ3Bhc3N3b3JkJyk7XG4gIHAuaG9zdG5hbWUgPSBnZW5lcmF0ZVNpbXBsZUFjY2Vzc29yKCdob3N0bmFtZScpO1xuICBwLnBvcnQgPSBnZW5lcmF0ZVNpbXBsZUFjY2Vzc29yKCdwb3J0Jyk7XG4gIHAucXVlcnkgPSBnZW5lcmF0ZVByZWZpeEFjY2Vzc29yKCdxdWVyeScsICc/Jyk7XG4gIHAuZnJhZ21lbnQgPSBnZW5lcmF0ZVByZWZpeEFjY2Vzc29yKCdmcmFnbWVudCcsICcjJyk7XG5cbiAgcC5zZWFyY2ggPSBmdW5jdGlvbih2LCBidWlsZCkge1xuICAgIHZhciB0ID0gdGhpcy5xdWVyeSh2LCBidWlsZCk7XG4gICAgcmV0dXJuIHR5cGVvZiB0ID09PSAnc3RyaW5nJyAmJiB0Lmxlbmd0aCA/ICgnPycgKyB0KSA6IHQ7XG4gIH07XG4gIHAuaGFzaCA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgdmFyIHQgPSB0aGlzLmZyYWdtZW50KHYsIGJ1aWxkKTtcbiAgICByZXR1cm4gdHlwZW9mIHQgPT09ICdzdHJpbmcnICYmIHQubGVuZ3RoID8gKCcjJyArIHQpIDogdDtcbiAgfTtcblxuICBwLnBhdGhuYW1lID0gZnVuY3Rpb24odiwgYnVpbGQpIHtcbiAgICBpZiAodiA9PT0gdW5kZWZpbmVkIHx8IHYgPT09IHRydWUpIHtcbiAgICAgIHZhciByZXMgPSB0aGlzLl9wYXJ0cy5wYXRoIHx8ICh0aGlzLl9wYXJ0cy5ob3N0bmFtZSA/ICcvJyA6ICcnKTtcbiAgICAgIHJldHVybiB2ID8gKHRoaXMuX3BhcnRzLnVybiA/IFVSSS5kZWNvZGVVcm5QYXRoIDogVVJJLmRlY29kZVBhdGgpKHJlcykgOiByZXM7XG4gICAgfSBlbHNlIHtcbiAgICAgIGlmICh0aGlzLl9wYXJ0cy51cm4pIHtcbiAgICAgICAgdGhpcy5fcGFydHMucGF0aCA9IHYgPyBVUkkucmVjb2RlVXJuUGF0aCh2KSA6ICcnO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5fcGFydHMucGF0aCA9IHYgPyBVUkkucmVjb2RlUGF0aCh2KSA6ICcvJztcbiAgICAgIH1cbiAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbiAgfTtcbiAgcC5wYXRoID0gcC5wYXRobmFtZTtcbiAgcC5ocmVmID0gZnVuY3Rpb24oaHJlZiwgYnVpbGQpIHtcbiAgICB2YXIga2V5O1xuXG4gICAgaWYgKGhyZWYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgcmV0dXJuIHRoaXMudG9TdHJpbmcoKTtcbiAgICB9XG5cbiAgICB0aGlzLl9zdHJpbmcgPSAnJztcbiAgICB0aGlzLl9wYXJ0cyA9IFVSSS5fcGFydHMoKTtcblxuICAgIHZhciBfVVJJID0gaHJlZiBpbnN0YW5jZW9mIFVSSTtcbiAgICB2YXIgX29iamVjdCA9IHR5cGVvZiBocmVmID09PSAnb2JqZWN0JyAmJiAoaHJlZi5ob3N0bmFtZSB8fCBocmVmLnBhdGggfHwgaHJlZi5wYXRobmFtZSk7XG4gICAgaWYgKGhyZWYubm9kZU5hbWUpIHtcbiAgICAgIHZhciBhdHRyaWJ1dGUgPSBVUkkuZ2V0RG9tQXR0cmlidXRlKGhyZWYpO1xuICAgICAgaHJlZiA9IGhyZWZbYXR0cmlidXRlXSB8fCAnJztcbiAgICAgIF9vYmplY3QgPSBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyB3aW5kb3cubG9jYXRpb24gaXMgcmVwb3J0ZWQgdG8gYmUgYW4gb2JqZWN0LCBidXQgaXQncyBub3QgdGhlIHNvcnRcbiAgICAvLyBvZiBvYmplY3Qgd2UncmUgbG9va2luZyBmb3I6XG4gICAgLy8gKiBsb2NhdGlvbi5wcm90b2NvbCBlbmRzIHdpdGggYSBjb2xvblxuICAgIC8vICogbG9jYXRpb24ucXVlcnkgIT0gb2JqZWN0LnNlYXJjaFxuICAgIC8vICogbG9jYXRpb24uaGFzaCAhPSBvYmplY3QuZnJhZ21lbnRcbiAgICAvLyBzaW1wbHkgc2VyaWFsaXppbmcgdGhlIHVua25vd24gb2JqZWN0IHNob3VsZCBkbyB0aGUgdHJpY2tcbiAgICAvLyAoZm9yIGxvY2F0aW9uLCBub3QgZm9yIGV2ZXJ5dGhpbmcuLi4pXG4gICAgaWYgKCFfVVJJICYmIF9vYmplY3QgJiYgaHJlZi5wYXRobmFtZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBocmVmID0gaHJlZi50b1N0cmluZygpO1xuICAgIH1cblxuICAgIGlmICh0eXBlb2YgaHJlZiA9PT0gJ3N0cmluZycgfHwgaHJlZiBpbnN0YW5jZW9mIFN0cmluZykge1xuICAgICAgdGhpcy5fcGFydHMgPSBVUkkucGFyc2UoU3RyaW5nKGhyZWYpLCB0aGlzLl9wYXJ0cyk7XG4gICAgfSBlbHNlIGlmIChfVVJJIHx8IF9vYmplY3QpIHtcbiAgICAgIHZhciBzcmMgPSBfVVJJID8gaHJlZi5fcGFydHMgOiBocmVmO1xuICAgICAgZm9yIChrZXkgaW4gc3JjKSB7XG4gICAgICAgIGlmIChoYXNPd24uY2FsbCh0aGlzLl9wYXJ0cywga2V5KSkge1xuICAgICAgICAgIHRoaXMuX3BhcnRzW2tleV0gPSBzcmNba2V5XTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdpbnZhbGlkIGlucHV0Jyk7XG4gICAgfVxuXG4gICAgdGhpcy5idWlsZCghYnVpbGQpO1xuICAgIHJldHVybiB0aGlzO1xuICB9O1xuXG4gIC8vIGlkZW50aWZpY2F0aW9uIGFjY2Vzc29yc1xuICBwLmlzID0gZnVuY3Rpb24od2hhdCkge1xuICAgIHZhciBpcCA9IGZhbHNlO1xuICAgIHZhciBpcDQgPSBmYWxzZTtcbiAgICB2YXIgaXA2ID0gZmFsc2U7XG4gICAgdmFyIG5hbWUgPSBmYWxzZTtcbiAgICB2YXIgc2xkID0gZmFsc2U7XG4gICAgdmFyIGlkbiA9IGZhbHNlO1xuICAgIHZhciBwdW55Y29kZSA9IGZhbHNlO1xuICAgIHZhciByZWxhdGl2ZSA9ICF0aGlzLl9wYXJ0cy51cm47XG5cbiAgICBpZiAodGhpcy5fcGFydHMuaG9zdG5hbWUpIHtcbiAgICAgIHJlbGF0aXZlID0gZmFsc2U7XG4gICAgICBpcDQgPSBVUkkuaXA0X2V4cHJlc3Npb24udGVzdCh0aGlzLl9wYXJ0cy5ob3N0bmFtZSk7XG4gICAgICBpcDYgPSBVUkkuaXA2X2V4cHJlc3Npb24udGVzdCh0aGlzLl9wYXJ0cy5ob3N0bmFtZSk7XG4gICAgICBpcCA9IGlwNCB8fCBpcDY7XG4gICAgICBuYW1lID0gIWlwO1xuICAgICAgc2xkID0gbmFtZSAmJiBTTEQgJiYgU0xELmhhcyh0aGlzLl9wYXJ0cy5ob3N0bmFtZSk7XG4gICAgICBpZG4gPSBuYW1lICYmIFVSSS5pZG5fZXhwcmVzc2lvbi50ZXN0KHRoaXMuX3BhcnRzLmhvc3RuYW1lKTtcbiAgICAgIHB1bnljb2RlID0gbmFtZSAmJiBVUkkucHVueWNvZGVfZXhwcmVzc2lvbi50ZXN0KHRoaXMuX3BhcnRzLmhvc3RuYW1lKTtcbiAgICB9XG5cbiAgICBzd2l0Y2ggKHdoYXQudG9Mb3dlckNhc2UoKSkge1xuICAgICAgY2FzZSAncmVsYXRpdmUnOlxuICAgICAgICByZXR1cm4gcmVsYXRpdmU7XG5cbiAgICAgIGNhc2UgJ2Fic29sdXRlJzpcbiAgICAgICAgcmV0dXJuICFyZWxhdGl2ZTtcblxuICAgICAgLy8gaG9zdG5hbWUgaWRlbnRpZmljYXRpb25cbiAgICAgIGNhc2UgJ2RvbWFpbic6XG4gICAgICBjYXNlICduYW1lJzpcbiAgICAgICAgcmV0dXJuIG5hbWU7XG5cbiAgICAgIGNhc2UgJ3NsZCc6XG4gICAgICAgIHJldHVybiBzbGQ7XG5cbiAgICAgIGNhc2UgJ2lwJzpcbiAgICAgICAgcmV0dXJuIGlwO1xuXG4gICAgICBjYXNlICdpcDQnOlxuICAgICAgY2FzZSAnaXB2NCc6XG4gICAgICBjYXNlICdpbmV0NCc6XG4gICAgICAgIHJldHVybiBpcDQ7XG5cbiAgICAgIGNhc2UgJ2lwNic6XG4gICAgICBjYXNlICdpcHY2JzpcbiAgICAgIGNhc2UgJ2luZXQ2JzpcbiAgICAgICAgcmV0dXJuIGlwNjtcblxuICAgICAgY2FzZSAnaWRuJzpcbiAgICAgICAgcmV0dXJuIGlkbjtcblxuICAgICAgY2FzZSAndXJsJzpcbiAgICAgICAgcmV0dXJuICF0aGlzLl9wYXJ0cy51cm47XG5cbiAgICAgIGNhc2UgJ3Vybic6XG4gICAgICAgIHJldHVybiAhIXRoaXMuX3BhcnRzLnVybjtcblxuICAgICAgY2FzZSAncHVueWNvZGUnOlxuICAgICAgICByZXR1cm4gcHVueWNvZGU7XG4gICAgfVxuXG4gICAgcmV0dXJuIG51bGw7XG4gIH07XG5cbiAgLy8gY29tcG9uZW50IHNwZWNpZmljIGlucHV0IHZhbGlkYXRpb25cbiAgdmFyIF9wcm90b2NvbCA9IHAucHJvdG9jb2w7XG4gIHZhciBfcG9ydCA9IHAucG9ydDtcbiAgdmFyIF9ob3N0bmFtZSA9IHAuaG9zdG5hbWU7XG5cbiAgcC5wcm90b2NvbCA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgaWYgKHYgIT09IHVuZGVmaW5lZCkge1xuICAgICAgaWYgKHYpIHtcbiAgICAgICAgLy8gYWNjZXB0IHRyYWlsaW5nIDovL1xuICAgICAgICB2ID0gdi5yZXBsYWNlKC86KFxcL1xcLyk/JC8sICcnKTtcblxuICAgICAgICBpZiAoIXYubWF0Y2goVVJJLnByb3RvY29sX2V4cHJlc3Npb24pKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignUHJvdG9jb2wgXCInICsgdiArICdcIiBjb250YWlucyBjaGFyYWN0ZXJzIG90aGVyIHRoYW4gW0EtWjAtOS4rLV0gb3IgZG9lc25cXCd0IHN0YXJ0IHdpdGggW0EtWl0nKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gX3Byb3RvY29sLmNhbGwodGhpcywgdiwgYnVpbGQpO1xuICB9O1xuICBwLnNjaGVtZSA9IHAucHJvdG9jb2w7XG4gIHAucG9ydCA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgaWYgKHRoaXMuX3BhcnRzLnVybikge1xuICAgICAgcmV0dXJuIHYgPT09IHVuZGVmaW5lZCA/ICcnIDogdGhpcztcbiAgICB9XG5cbiAgICBpZiAodiAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBpZiAodiA9PT0gMCkge1xuICAgICAgICB2ID0gbnVsbDtcbiAgICAgIH1cblxuICAgICAgaWYgKHYpIHtcbiAgICAgICAgdiArPSAnJztcbiAgICAgICAgaWYgKHYuY2hhckF0KDApID09PSAnOicpIHtcbiAgICAgICAgICB2ID0gdi5zdWJzdHJpbmcoMSk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodi5tYXRjaCgvW14wLTldLykpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdQb3J0IFwiJyArIHYgKyAnXCIgY29udGFpbnMgY2hhcmFjdGVycyBvdGhlciB0aGFuIFswLTldJyk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIF9wb3J0LmNhbGwodGhpcywgdiwgYnVpbGQpO1xuICB9O1xuICBwLmhvc3RuYW1lID0gZnVuY3Rpb24odiwgYnVpbGQpIHtcbiAgICBpZiAodGhpcy5fcGFydHMudXJuKSB7XG4gICAgICByZXR1cm4gdiA9PT0gdW5kZWZpbmVkID8gJycgOiB0aGlzO1xuICAgIH1cblxuICAgIGlmICh2ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIHZhciB4ID0ge307XG4gICAgICB2YXIgcmVzID0gVVJJLnBhcnNlSG9zdCh2LCB4KTtcbiAgICAgIGlmIChyZXMgIT09ICcvJykge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdIb3N0bmFtZSBcIicgKyB2ICsgJ1wiIGNvbnRhaW5zIGNoYXJhY3RlcnMgb3RoZXIgdGhhbiBbQS1aMC05Li1dJyk7XG4gICAgICB9XG5cbiAgICAgIHYgPSB4Lmhvc3RuYW1lO1xuICAgIH1cbiAgICByZXR1cm4gX2hvc3RuYW1lLmNhbGwodGhpcywgdiwgYnVpbGQpO1xuICB9O1xuXG4gIC8vIGNvbXBvdW5kIGFjY2Vzc29yc1xuICBwLm9yaWdpbiA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgdmFyIHBhcnRzO1xuXG4gICAgaWYgKHRoaXMuX3BhcnRzLnVybikge1xuICAgICAgcmV0dXJuIHYgPT09IHVuZGVmaW5lZCA/ICcnIDogdGhpcztcbiAgICB9XG5cbiAgICBpZiAodiA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICB2YXIgcHJvdG9jb2wgPSB0aGlzLnByb3RvY29sKCk7XG4gICAgICB2YXIgYXV0aG9yaXR5ID0gdGhpcy5hdXRob3JpdHkoKTtcbiAgICAgIGlmICghYXV0aG9yaXR5KSByZXR1cm4gJyc7XG4gICAgICByZXR1cm4gKHByb3RvY29sID8gcHJvdG9jb2wgKyAnOi8vJyA6ICcnKSArIHRoaXMuYXV0aG9yaXR5KCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHZhciBvcmlnaW4gPSBVUkkodik7XG4gICAgICB0aGlzXG4gICAgICAgIC5wcm90b2NvbChvcmlnaW4ucHJvdG9jb2woKSlcbiAgICAgICAgLmF1dGhvcml0eShvcmlnaW4uYXV0aG9yaXR5KCkpXG4gICAgICAgIC5idWlsZCghYnVpbGQpO1xuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICB9O1xuICBwLmhvc3QgPSBmdW5jdGlvbih2LCBidWlsZCkge1xuICAgIGlmICh0aGlzLl9wYXJ0cy51cm4pIHtcbiAgICAgIHJldHVybiB2ID09PSB1bmRlZmluZWQgPyAnJyA6IHRoaXM7XG4gICAgfVxuXG4gICAgaWYgKHYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgcmV0dXJuIHRoaXMuX3BhcnRzLmhvc3RuYW1lID8gVVJJLmJ1aWxkSG9zdCh0aGlzLl9wYXJ0cykgOiAnJztcbiAgICB9IGVsc2Uge1xuICAgICAgdmFyIHJlcyA9IFVSSS5wYXJzZUhvc3QodiwgdGhpcy5fcGFydHMpO1xuICAgICAgaWYgKHJlcyAhPT0gJy8nKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0hvc3RuYW1lIFwiJyArIHYgKyAnXCIgY29udGFpbnMgY2hhcmFjdGVycyBvdGhlciB0aGFuIFtBLVowLTkuLV0nKTtcbiAgICAgIH1cblxuICAgICAgdGhpcy5idWlsZCghYnVpbGQpO1xuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICB9O1xuICBwLmF1dGhvcml0eSA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgaWYgKHRoaXMuX3BhcnRzLnVybikge1xuICAgICAgcmV0dXJuIHYgPT09IHVuZGVmaW5lZCA/ICcnIDogdGhpcztcbiAgICB9XG5cbiAgICBpZiAodiA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICByZXR1cm4gdGhpcy5fcGFydHMuaG9zdG5hbWUgPyBVUkkuYnVpbGRBdXRob3JpdHkodGhpcy5fcGFydHMpIDogJyc7XG4gICAgfSBlbHNlIHtcbiAgICAgIHZhciByZXMgPSBVUkkucGFyc2VBdXRob3JpdHkodiwgdGhpcy5fcGFydHMpO1xuICAgICAgaWYgKHJlcyAhPT0gJy8nKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0hvc3RuYW1lIFwiJyArIHYgKyAnXCIgY29udGFpbnMgY2hhcmFjdGVycyBvdGhlciB0aGFuIFtBLVowLTkuLV0nKTtcbiAgICAgIH1cblxuICAgICAgdGhpcy5idWlsZCghYnVpbGQpO1xuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICB9O1xuICBwLnVzZXJpbmZvID0gZnVuY3Rpb24odiwgYnVpbGQpIHtcbiAgICBpZiAodGhpcy5fcGFydHMudXJuKSB7XG4gICAgICByZXR1cm4gdiA9PT0gdW5kZWZpbmVkID8gJycgOiB0aGlzO1xuICAgIH1cblxuICAgIGlmICh2ID09PSB1bmRlZmluZWQpIHtcbiAgICAgIGlmICghdGhpcy5fcGFydHMudXNlcm5hbWUpIHtcbiAgICAgICAgcmV0dXJuICcnO1xuICAgICAgfVxuXG4gICAgICB2YXIgdCA9IFVSSS5idWlsZFVzZXJpbmZvKHRoaXMuX3BhcnRzKTtcbiAgICAgIHJldHVybiB0LnN1YnN0cmluZygwLCB0Lmxlbmd0aCAtMSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGlmICh2W3YubGVuZ3RoLTFdICE9PSAnQCcpIHtcbiAgICAgICAgdiArPSAnQCc7XG4gICAgICB9XG5cbiAgICAgIFVSSS5wYXJzZVVzZXJpbmZvKHYsIHRoaXMuX3BhcnRzKTtcbiAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbiAgfTtcbiAgcC5yZXNvdXJjZSA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgdmFyIHBhcnRzO1xuXG4gICAgaWYgKHYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgcmV0dXJuIHRoaXMucGF0aCgpICsgdGhpcy5zZWFyY2goKSArIHRoaXMuaGFzaCgpO1xuICAgIH1cblxuICAgIHBhcnRzID0gVVJJLnBhcnNlKHYpO1xuICAgIHRoaXMuX3BhcnRzLnBhdGggPSBwYXJ0cy5wYXRoO1xuICAgIHRoaXMuX3BhcnRzLnF1ZXJ5ID0gcGFydHMucXVlcnk7XG4gICAgdGhpcy5fcGFydHMuZnJhZ21lbnQgPSBwYXJ0cy5mcmFnbWVudDtcbiAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgLy8gZnJhY3Rpb24gYWNjZXNzb3JzXG4gIHAuc3ViZG9tYWluID0gZnVuY3Rpb24odiwgYnVpbGQpIHtcbiAgICBpZiAodGhpcy5fcGFydHMudXJuKSB7XG4gICAgICByZXR1cm4gdiA9PT0gdW5kZWZpbmVkID8gJycgOiB0aGlzO1xuICAgIH1cblxuICAgIC8vIGNvbnZlbmllbmNlLCByZXR1cm4gXCJ3d3dcIiBmcm9tIFwid3d3LmV4YW1wbGUub3JnXCJcbiAgICBpZiAodiA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICBpZiAoIXRoaXMuX3BhcnRzLmhvc3RuYW1lIHx8IHRoaXMuaXMoJ0lQJykpIHtcbiAgICAgICAgcmV0dXJuICcnO1xuICAgICAgfVxuXG4gICAgICAvLyBncmFiIGRvbWFpbiBhbmQgYWRkIGFub3RoZXIgc2VnbWVudFxuICAgICAgdmFyIGVuZCA9IHRoaXMuX3BhcnRzLmhvc3RuYW1lLmxlbmd0aCAtIHRoaXMuZG9tYWluKCkubGVuZ3RoIC0gMTtcbiAgICAgIHJldHVybiB0aGlzLl9wYXJ0cy5ob3N0bmFtZS5zdWJzdHJpbmcoMCwgZW5kKSB8fCAnJztcbiAgICB9IGVsc2Uge1xuICAgICAgdmFyIGUgPSB0aGlzLl9wYXJ0cy5ob3N0bmFtZS5sZW5ndGggLSB0aGlzLmRvbWFpbigpLmxlbmd0aDtcbiAgICAgIHZhciBzdWIgPSB0aGlzLl9wYXJ0cy5ob3N0bmFtZS5zdWJzdHJpbmcoMCwgZSk7XG4gICAgICB2YXIgcmVwbGFjZSA9IG5ldyBSZWdFeHAoJ14nICsgZXNjYXBlUmVnRXgoc3ViKSk7XG5cbiAgICAgIGlmICh2ICYmIHYuY2hhckF0KHYubGVuZ3RoIC0gMSkgIT09ICcuJykge1xuICAgICAgICB2ICs9ICcuJztcbiAgICAgIH1cblxuICAgICAgaWYgKHYpIHtcbiAgICAgICAgVVJJLmVuc3VyZVZhbGlkSG9zdG5hbWUodik7XG4gICAgICB9XG5cbiAgICAgIHRoaXMuX3BhcnRzLmhvc3RuYW1lID0gdGhpcy5fcGFydHMuaG9zdG5hbWUucmVwbGFjZShyZXBsYWNlLCB2KTtcbiAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbiAgfTtcbiAgcC5kb21haW4gPSBmdW5jdGlvbih2LCBidWlsZCkge1xuICAgIGlmICh0aGlzLl9wYXJ0cy51cm4pIHtcbiAgICAgIHJldHVybiB2ID09PSB1bmRlZmluZWQgPyAnJyA6IHRoaXM7XG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiB2ID09PSAnYm9vbGVhbicpIHtcbiAgICAgIGJ1aWxkID0gdjtcbiAgICAgIHYgPSB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgLy8gY29udmVuaWVuY2UsIHJldHVybiBcImV4YW1wbGUub3JnXCIgZnJvbSBcInd3dy5leGFtcGxlLm9yZ1wiXG4gICAgaWYgKHYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgaWYgKCF0aGlzLl9wYXJ0cy5ob3N0bmFtZSB8fCB0aGlzLmlzKCdJUCcpKSB7XG4gICAgICAgIHJldHVybiAnJztcbiAgICAgIH1cblxuICAgICAgLy8gaWYgaG9zdG5hbWUgY29uc2lzdHMgb2YgMSBvciAyIHNlZ21lbnRzLCBpdCBtdXN0IGJlIHRoZSBkb21haW5cbiAgICAgIHZhciB0ID0gdGhpcy5fcGFydHMuaG9zdG5hbWUubWF0Y2goL1xcLi9nKTtcbiAgICAgIGlmICh0ICYmIHQubGVuZ3RoIDwgMikge1xuICAgICAgICByZXR1cm4gdGhpcy5fcGFydHMuaG9zdG5hbWU7XG4gICAgICB9XG5cbiAgICAgIC8vIGdyYWIgdGxkIGFuZCBhZGQgYW5vdGhlciBzZWdtZW50XG4gICAgICB2YXIgZW5kID0gdGhpcy5fcGFydHMuaG9zdG5hbWUubGVuZ3RoIC0gdGhpcy50bGQoYnVpbGQpLmxlbmd0aCAtIDE7XG4gICAgICBlbmQgPSB0aGlzLl9wYXJ0cy5ob3N0bmFtZS5sYXN0SW5kZXhPZignLicsIGVuZCAtMSkgKyAxO1xuICAgICAgcmV0dXJuIHRoaXMuX3BhcnRzLmhvc3RuYW1lLnN1YnN0cmluZyhlbmQpIHx8ICcnO1xuICAgIH0gZWxzZSB7XG4gICAgICBpZiAoIXYpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignY2Fubm90IHNldCBkb21haW4gZW1wdHknKTtcbiAgICAgIH1cblxuICAgICAgVVJJLmVuc3VyZVZhbGlkSG9zdG5hbWUodik7XG5cbiAgICAgIGlmICghdGhpcy5fcGFydHMuaG9zdG5hbWUgfHwgdGhpcy5pcygnSVAnKSkge1xuICAgICAgICB0aGlzLl9wYXJ0cy5ob3N0bmFtZSA9IHY7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB2YXIgcmVwbGFjZSA9IG5ldyBSZWdFeHAoZXNjYXBlUmVnRXgodGhpcy5kb21haW4oKSkgKyAnJCcpO1xuICAgICAgICB0aGlzLl9wYXJ0cy5ob3N0bmFtZSA9IHRoaXMuX3BhcnRzLmhvc3RuYW1lLnJlcGxhY2UocmVwbGFjZSwgdik7XG4gICAgICB9XG5cbiAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cbiAgfTtcbiAgcC50bGQgPSBmdW5jdGlvbih2LCBidWlsZCkge1xuICAgIGlmICh0aGlzLl9wYXJ0cy51cm4pIHtcbiAgICAgIHJldHVybiB2ID09PSB1bmRlZmluZWQgPyAnJyA6IHRoaXM7XG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiB2ID09PSAnYm9vbGVhbicpIHtcbiAgICAgIGJ1aWxkID0gdjtcbiAgICAgIHYgPSB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgLy8gcmV0dXJuIFwib3JnXCIgZnJvbSBcInd3dy5leGFtcGxlLm9yZ1wiXG4gICAgaWYgKHYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgaWYgKCF0aGlzLl9wYXJ0cy5ob3N0bmFtZSB8fCB0aGlzLmlzKCdJUCcpKSB7XG4gICAgICAgIHJldHVybiAnJztcbiAgICAgIH1cblxuICAgICAgdmFyIHBvcyA9IHRoaXMuX3BhcnRzLmhvc3RuYW1lLmxhc3RJbmRleE9mKCcuJyk7XG4gICAgICB2YXIgdGxkID0gdGhpcy5fcGFydHMuaG9zdG5hbWUuc3Vic3RyaW5nKHBvcyArIDEpO1xuXG4gICAgICBpZiAoYnVpbGQgIT09IHRydWUgJiYgU0xEICYmIFNMRC5saXN0W3RsZC50b0xvd2VyQ2FzZSgpXSkge1xuICAgICAgICByZXR1cm4gU0xELmdldCh0aGlzLl9wYXJ0cy5ob3N0bmFtZSkgfHwgdGxkO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gdGxkO1xuICAgIH0gZWxzZSB7XG4gICAgICB2YXIgcmVwbGFjZTtcblxuICAgICAgaWYgKCF2KSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ2Nhbm5vdCBzZXQgVExEIGVtcHR5Jyk7XG4gICAgICB9IGVsc2UgaWYgKHYubWF0Y2goL1teYS16QS1aMC05LV0vKSkge1xuICAgICAgICBpZiAoU0xEICYmIFNMRC5pcyh2KSkge1xuICAgICAgICAgIHJlcGxhY2UgPSBuZXcgUmVnRXhwKGVzY2FwZVJlZ0V4KHRoaXMudGxkKCkpICsgJyQnKTtcbiAgICAgICAgICB0aGlzLl9wYXJ0cy5ob3N0bmFtZSA9IHRoaXMuX3BhcnRzLmhvc3RuYW1lLnJlcGxhY2UocmVwbGFjZSwgdik7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignVExEIFwiJyArIHYgKyAnXCIgY29udGFpbnMgY2hhcmFjdGVycyBvdGhlciB0aGFuIFtBLVowLTldJyk7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSBpZiAoIXRoaXMuX3BhcnRzLmhvc3RuYW1lIHx8IHRoaXMuaXMoJ0lQJykpIHtcbiAgICAgICAgdGhyb3cgbmV3IFJlZmVyZW5jZUVycm9yKCdjYW5ub3Qgc2V0IFRMRCBvbiBub24tZG9tYWluIGhvc3QnKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJlcGxhY2UgPSBuZXcgUmVnRXhwKGVzY2FwZVJlZ0V4KHRoaXMudGxkKCkpICsgJyQnKTtcbiAgICAgICAgdGhpcy5fcGFydHMuaG9zdG5hbWUgPSB0aGlzLl9wYXJ0cy5ob3N0bmFtZS5yZXBsYWNlKHJlcGxhY2UsIHYpO1xuICAgICAgfVxuXG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gIH07XG4gIHAuZGlyZWN0b3J5ID0gZnVuY3Rpb24odiwgYnVpbGQpIHtcbiAgICBpZiAodGhpcy5fcGFydHMudXJuKSB7XG4gICAgICByZXR1cm4gdiA9PT0gdW5kZWZpbmVkID8gJycgOiB0aGlzO1xuICAgIH1cblxuICAgIGlmICh2ID09PSB1bmRlZmluZWQgfHwgdiA9PT0gdHJ1ZSkge1xuICAgICAgaWYgKCF0aGlzLl9wYXJ0cy5wYXRoICYmICF0aGlzLl9wYXJ0cy5ob3N0bmFtZSkge1xuICAgICAgICByZXR1cm4gJyc7XG4gICAgICB9XG5cbiAgICAgIGlmICh0aGlzLl9wYXJ0cy5wYXRoID09PSAnLycpIHtcbiAgICAgICAgcmV0dXJuICcvJztcbiAgICAgIH1cblxuICAgICAgdmFyIGVuZCA9IHRoaXMuX3BhcnRzLnBhdGgubGVuZ3RoIC0gdGhpcy5maWxlbmFtZSgpLmxlbmd0aCAtIDE7XG4gICAgICB2YXIgcmVzID0gdGhpcy5fcGFydHMucGF0aC5zdWJzdHJpbmcoMCwgZW5kKSB8fCAodGhpcy5fcGFydHMuaG9zdG5hbWUgPyAnLycgOiAnJyk7XG5cbiAgICAgIHJldHVybiB2ID8gVVJJLmRlY29kZVBhdGgocmVzKSA6IHJlcztcblxuICAgIH0gZWxzZSB7XG4gICAgICB2YXIgZSA9IHRoaXMuX3BhcnRzLnBhdGgubGVuZ3RoIC0gdGhpcy5maWxlbmFtZSgpLmxlbmd0aDtcbiAgICAgIHZhciBkaXJlY3RvcnkgPSB0aGlzLl9wYXJ0cy5wYXRoLnN1YnN0cmluZygwLCBlKTtcbiAgICAgIHZhciByZXBsYWNlID0gbmV3IFJlZ0V4cCgnXicgKyBlc2NhcGVSZWdFeChkaXJlY3RvcnkpKTtcblxuICAgICAgLy8gZnVsbHkgcXVhbGlmaWVyIGRpcmVjdG9yaWVzIGJlZ2luIHdpdGggYSBzbGFzaFxuICAgICAgaWYgKCF0aGlzLmlzKCdyZWxhdGl2ZScpKSB7XG4gICAgICAgIGlmICghdikge1xuICAgICAgICAgIHYgPSAnLyc7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodi5jaGFyQXQoMCkgIT09ICcvJykge1xuICAgICAgICAgIHYgPSAnLycgKyB2O1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8vIGRpcmVjdG9yaWVzIGFsd2F5cyBlbmQgd2l0aCBhIHNsYXNoXG4gICAgICBpZiAodiAmJiB2LmNoYXJBdCh2Lmxlbmd0aCAtIDEpICE9PSAnLycpIHtcbiAgICAgICAgdiArPSAnLyc7XG4gICAgICB9XG5cbiAgICAgIHYgPSBVUkkucmVjb2RlUGF0aCh2KTtcbiAgICAgIHRoaXMuX3BhcnRzLnBhdGggPSB0aGlzLl9wYXJ0cy5wYXRoLnJlcGxhY2UocmVwbGFjZSwgdik7XG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gIH07XG4gIHAuZmlsZW5hbWUgPSBmdW5jdGlvbih2LCBidWlsZCkge1xuICAgIGlmICh0aGlzLl9wYXJ0cy51cm4pIHtcbiAgICAgIHJldHVybiB2ID09PSB1bmRlZmluZWQgPyAnJyA6IHRoaXM7XG4gICAgfVxuXG4gICAgaWYgKHYgPT09IHVuZGVmaW5lZCB8fCB2ID09PSB0cnVlKSB7XG4gICAgICBpZiAoIXRoaXMuX3BhcnRzLnBhdGggfHwgdGhpcy5fcGFydHMucGF0aCA9PT0gJy8nKSB7XG4gICAgICAgIHJldHVybiAnJztcbiAgICAgIH1cblxuICAgICAgdmFyIHBvcyA9IHRoaXMuX3BhcnRzLnBhdGgubGFzdEluZGV4T2YoJy8nKTtcbiAgICAgIHZhciByZXMgPSB0aGlzLl9wYXJ0cy5wYXRoLnN1YnN0cmluZyhwb3MrMSk7XG5cbiAgICAgIHJldHVybiB2ID8gVVJJLmRlY29kZVBhdGhTZWdtZW50KHJlcykgOiByZXM7XG4gICAgfSBlbHNlIHtcbiAgICAgIHZhciBtdXRhdGVkRGlyZWN0b3J5ID0gZmFsc2U7XG5cbiAgICAgIGlmICh2LmNoYXJBdCgwKSA9PT0gJy8nKSB7XG4gICAgICAgIHYgPSB2LnN1YnN0cmluZygxKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHYubWF0Y2goL1xcLj9cXC8vKSkge1xuICAgICAgICBtdXRhdGVkRGlyZWN0b3J5ID0gdHJ1ZTtcbiAgICAgIH1cblxuICAgICAgdmFyIHJlcGxhY2UgPSBuZXcgUmVnRXhwKGVzY2FwZVJlZ0V4KHRoaXMuZmlsZW5hbWUoKSkgKyAnJCcpO1xuICAgICAgdiA9IFVSSS5yZWNvZGVQYXRoKHYpO1xuICAgICAgdGhpcy5fcGFydHMucGF0aCA9IHRoaXMuX3BhcnRzLnBhdGgucmVwbGFjZShyZXBsYWNlLCB2KTtcblxuICAgICAgaWYgKG11dGF0ZWREaXJlY3RvcnkpIHtcbiAgICAgICAgdGhpcy5ub3JtYWxpemVQYXRoKGJ1aWxkKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICB9O1xuICBwLnN1ZmZpeCA9IGZ1bmN0aW9uKHYsIGJ1aWxkKSB7XG4gICAgaWYgKHRoaXMuX3BhcnRzLnVybikge1xuICAgICAgcmV0dXJuIHYgPT09IHVuZGVmaW5lZCA/ICcnIDogdGhpcztcbiAgICB9XG5cbiAgICBpZiAodiA9PT0gdW5kZWZpbmVkIHx8IHYgPT09IHRydWUpIHtcbiAgICAgIGlmICghdGhpcy5fcGFydHMucGF0aCB8fCB0aGlzLl9wYXJ0cy5wYXRoID09PSAnLycpIHtcbiAgICAgICAgcmV0dXJuICcnO1xuICAgICAgfVxuXG4gICAgICB2YXIgZmlsZW5hbWUgPSB0aGlzLmZpbGVuYW1lKCk7XG4gICAgICB2YXIgcG9zID0gZmlsZW5hbWUubGFzdEluZGV4T2YoJy4nKTtcbiAgICAgIHZhciBzLCByZXM7XG5cbiAgICAgIGlmIChwb3MgPT09IC0xKSB7XG4gICAgICAgIHJldHVybiAnJztcbiAgICAgIH1cblxuICAgICAgLy8gc3VmZml4IG1heSBvbmx5IGNvbnRhaW4gYWxudW0gY2hhcmFjdGVycyAoeXVwLCBJIG1hZGUgdGhpcyB1cC4pXG4gICAgICBzID0gZmlsZW5hbWUuc3Vic3RyaW5nKHBvcysxKTtcbiAgICAgIHJlcyA9ICgvXlthLXowLTklXSskL2kpLnRlc3QocykgPyBzIDogJyc7XG4gICAgICByZXR1cm4gdiA/IFVSSS5kZWNvZGVQYXRoU2VnbWVudChyZXMpIDogcmVzO1xuICAgIH0gZWxzZSB7XG4gICAgICBpZiAodi5jaGFyQXQoMCkgPT09ICcuJykge1xuICAgICAgICB2ID0gdi5zdWJzdHJpbmcoMSk7XG4gICAgICB9XG5cbiAgICAgIHZhciBzdWZmaXggPSB0aGlzLnN1ZmZpeCgpO1xuICAgICAgdmFyIHJlcGxhY2U7XG5cbiAgICAgIGlmICghc3VmZml4KSB7XG4gICAgICAgIGlmICghdikge1xuICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5fcGFydHMucGF0aCArPSAnLicgKyBVUkkucmVjb2RlUGF0aCh2KTtcbiAgICAgIH0gZWxzZSBpZiAoIXYpIHtcbiAgICAgICAgcmVwbGFjZSA9IG5ldyBSZWdFeHAoZXNjYXBlUmVnRXgoJy4nICsgc3VmZml4KSArICckJyk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXBsYWNlID0gbmV3IFJlZ0V4cChlc2NhcGVSZWdFeChzdWZmaXgpICsgJyQnKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHJlcGxhY2UpIHtcbiAgICAgICAgdiA9IFVSSS5yZWNvZGVQYXRoKHYpO1xuICAgICAgICB0aGlzLl9wYXJ0cy5wYXRoID0gdGhpcy5fcGFydHMucGF0aC5yZXBsYWNlKHJlcGxhY2UsIHYpO1xuICAgICAgfVxuXG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG4gIH07XG4gIHAuc2VnbWVudCA9IGZ1bmN0aW9uKHNlZ21lbnQsIHYsIGJ1aWxkKSB7XG4gICAgdmFyIHNlcGFyYXRvciA9IHRoaXMuX3BhcnRzLnVybiA/ICc6JyA6ICcvJztcbiAgICB2YXIgcGF0aCA9IHRoaXMucGF0aCgpO1xuICAgIHZhciBhYnNvbHV0ZSA9IHBhdGguc3Vic3RyaW5nKDAsIDEpID09PSAnLyc7XG4gICAgdmFyIHNlZ21lbnRzID0gcGF0aC5zcGxpdChzZXBhcmF0b3IpO1xuXG4gICAgaWYgKHNlZ21lbnQgIT09IHVuZGVmaW5lZCAmJiB0eXBlb2Ygc2VnbWVudCAhPT0gJ251bWJlcicpIHtcbiAgICAgIGJ1aWxkID0gdjtcbiAgICAgIHYgPSBzZWdtZW50O1xuICAgICAgc2VnbWVudCA9IHVuZGVmaW5lZDtcbiAgICB9XG5cbiAgICBpZiAoc2VnbWVudCAhPT0gdW5kZWZpbmVkICYmIHR5cGVvZiBzZWdtZW50ICE9PSAnbnVtYmVyJykge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdCYWQgc2VnbWVudCBcIicgKyBzZWdtZW50ICsgJ1wiLCBtdXN0IGJlIDAtYmFzZWQgaW50ZWdlcicpO1xuICAgIH1cblxuICAgIGlmIChhYnNvbHV0ZSkge1xuICAgICAgc2VnbWVudHMuc2hpZnQoKTtcbiAgICB9XG5cbiAgICBpZiAoc2VnbWVudCA8IDApIHtcbiAgICAgIC8vIGFsbG93IG5lZ2F0aXZlIGluZGV4ZXMgdG8gYWRkcmVzcyBmcm9tIHRoZSBlbmRcbiAgICAgIHNlZ21lbnQgPSBNYXRoLm1heChzZWdtZW50cy5sZW5ndGggKyBzZWdtZW50LCAwKTtcbiAgICB9XG5cbiAgICBpZiAodiA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAvKmpzaGludCBsYXhicmVhazogdHJ1ZSAqL1xuICAgICAgcmV0dXJuIHNlZ21lbnQgPT09IHVuZGVmaW5lZFxuICAgICAgICA/IHNlZ21lbnRzXG4gICAgICAgIDogc2VnbWVudHNbc2VnbWVudF07XG4gICAgICAvKmpzaGludCBsYXhicmVhazogZmFsc2UgKi9cbiAgICB9IGVsc2UgaWYgKHNlZ21lbnQgPT09IG51bGwgfHwgc2VnbWVudHNbc2VnbWVudF0gPT09IHVuZGVmaW5lZCkge1xuICAgICAgaWYgKGlzQXJyYXkodikpIHtcbiAgICAgICAgc2VnbWVudHMgPSBbXTtcbiAgICAgICAgLy8gY29sbGFwc2UgZW1wdHkgZWxlbWVudHMgd2l0aGluIGFycmF5XG4gICAgICAgIGZvciAodmFyIGk9MCwgbD12Lmxlbmd0aDsgaSA8IGw7IGkrKykge1xuICAgICAgICAgIGlmICghdltpXS5sZW5ndGggJiYgKCFzZWdtZW50cy5sZW5ndGggfHwgIXNlZ21lbnRzW3NlZ21lbnRzLmxlbmd0aCAtMV0ubGVuZ3RoKSkge1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgaWYgKHNlZ21lbnRzLmxlbmd0aCAmJiAhc2VnbWVudHNbc2VnbWVudHMubGVuZ3RoIC0xXS5sZW5ndGgpIHtcbiAgICAgICAgICAgIHNlZ21lbnRzLnBvcCgpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIHNlZ21lbnRzLnB1c2godHJpbVNsYXNoZXModltpXSkpO1xuICAgICAgICB9XG4gICAgICB9IGVsc2UgaWYgKHYgfHwgdHlwZW9mIHYgPT09ICdzdHJpbmcnKSB7XG4gICAgICAgIHYgPSB0cmltU2xhc2hlcyh2KTtcbiAgICAgICAgaWYgKHNlZ21lbnRzW3NlZ21lbnRzLmxlbmd0aCAtMV0gPT09ICcnKSB7XG4gICAgICAgICAgLy8gZW1wdHkgdHJhaWxpbmcgZWxlbWVudHMgaGF2ZSB0byBiZSBvdmVyd3JpdHRlblxuICAgICAgICAgIC8vIHRvIHByZXZlbnQgcmVzdWx0cyBzdWNoIGFzIC9mb28vL2JhclxuICAgICAgICAgIHNlZ21lbnRzW3NlZ21lbnRzLmxlbmd0aCAtMV0gPSB2O1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHNlZ21lbnRzLnB1c2godik7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgaWYgKHYpIHtcbiAgICAgICAgc2VnbWVudHNbc2VnbWVudF0gPSB0cmltU2xhc2hlcyh2KTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHNlZ21lbnRzLnNwbGljZShzZWdtZW50LCAxKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoYWJzb2x1dGUpIHtcbiAgICAgIHNlZ21lbnRzLnVuc2hpZnQoJycpO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzLnBhdGgoc2VnbWVudHMuam9pbihzZXBhcmF0b3IpLCBidWlsZCk7XG4gIH07XG4gIHAuc2VnbWVudENvZGVkID0gZnVuY3Rpb24oc2VnbWVudCwgdiwgYnVpbGQpIHtcbiAgICB2YXIgc2VnbWVudHMsIGksIGw7XG5cbiAgICBpZiAodHlwZW9mIHNlZ21lbnQgIT09ICdudW1iZXInKSB7XG4gICAgICBidWlsZCA9IHY7XG4gICAgICB2ID0gc2VnbWVudDtcbiAgICAgIHNlZ21lbnQgPSB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgaWYgKHYgPT09IHVuZGVmaW5lZCkge1xuICAgICAgc2VnbWVudHMgPSB0aGlzLnNlZ21lbnQoc2VnbWVudCwgdiwgYnVpbGQpO1xuICAgICAgaWYgKCFpc0FycmF5KHNlZ21lbnRzKSkge1xuICAgICAgICBzZWdtZW50cyA9IHNlZ21lbnRzICE9PSB1bmRlZmluZWQgPyBVUkkuZGVjb2RlKHNlZ21lbnRzKSA6IHVuZGVmaW5lZDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGZvciAoaSA9IDAsIGwgPSBzZWdtZW50cy5sZW5ndGg7IGkgPCBsOyBpKyspIHtcbiAgICAgICAgICBzZWdtZW50c1tpXSA9IFVSSS5kZWNvZGUoc2VnbWVudHNbaV0pO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBzZWdtZW50cztcbiAgICB9XG5cbiAgICBpZiAoIWlzQXJyYXkodikpIHtcbiAgICAgIHYgPSAodHlwZW9mIHYgPT09ICdzdHJpbmcnIHx8IHYgaW5zdGFuY2VvZiBTdHJpbmcpID8gVVJJLmVuY29kZSh2KSA6IHY7XG4gICAgfSBlbHNlIHtcbiAgICAgIGZvciAoaSA9IDAsIGwgPSB2Lmxlbmd0aDsgaSA8IGw7IGkrKykge1xuICAgICAgICB2W2ldID0gVVJJLmVuY29kZSh2W2ldKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcy5zZWdtZW50KHNlZ21lbnQsIHYsIGJ1aWxkKTtcbiAgfTtcblxuICAvLyBtdXRhdGluZyBxdWVyeSBzdHJpbmdcbiAgdmFyIHEgPSBwLnF1ZXJ5O1xuICBwLnF1ZXJ5ID0gZnVuY3Rpb24odiwgYnVpbGQpIHtcbiAgICBpZiAodiA9PT0gdHJ1ZSkge1xuICAgICAgcmV0dXJuIFVSSS5wYXJzZVF1ZXJ5KHRoaXMuX3BhcnRzLnF1ZXJ5LCB0aGlzLl9wYXJ0cy5lc2NhcGVRdWVyeVNwYWNlKTtcbiAgICB9IGVsc2UgaWYgKHR5cGVvZiB2ID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICB2YXIgZGF0YSA9IFVSSS5wYXJzZVF1ZXJ5KHRoaXMuX3BhcnRzLnF1ZXJ5LCB0aGlzLl9wYXJ0cy5lc2NhcGVRdWVyeVNwYWNlKTtcbiAgICAgIHZhciByZXN1bHQgPSB2LmNhbGwodGhpcywgZGF0YSk7XG4gICAgICB0aGlzLl9wYXJ0cy5xdWVyeSA9IFVSSS5idWlsZFF1ZXJ5KHJlc3VsdCB8fCBkYXRhLCB0aGlzLl9wYXJ0cy5kdXBsaWNhdGVRdWVyeVBhcmFtZXRlcnMsIHRoaXMuX3BhcnRzLmVzY2FwZVF1ZXJ5U3BhY2UpO1xuICAgICAgdGhpcy5idWlsZCghYnVpbGQpO1xuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfSBlbHNlIGlmICh2ICE9PSB1bmRlZmluZWQgJiYgdHlwZW9mIHYgIT09ICdzdHJpbmcnKSB7XG4gICAgICB0aGlzLl9wYXJ0cy5xdWVyeSA9IFVSSS5idWlsZFF1ZXJ5KHYsIHRoaXMuX3BhcnRzLmR1cGxpY2F0ZVF1ZXJ5UGFyYW1ldGVycywgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSk7XG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHEuY2FsbCh0aGlzLCB2LCBidWlsZCk7XG4gICAgfVxuICB9O1xuICBwLnNldFF1ZXJ5ID0gZnVuY3Rpb24obmFtZSwgdmFsdWUsIGJ1aWxkKSB7XG4gICAgdmFyIGRhdGEgPSBVUkkucGFyc2VRdWVyeSh0aGlzLl9wYXJ0cy5xdWVyeSwgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSk7XG5cbiAgICBpZiAodHlwZW9mIG5hbWUgPT09ICdzdHJpbmcnIHx8IG5hbWUgaW5zdGFuY2VvZiBTdHJpbmcpIHtcbiAgICAgIGRhdGFbbmFtZV0gPSB2YWx1ZSAhPT0gdW5kZWZpbmVkID8gdmFsdWUgOiBudWxsO1xuICAgIH0gZWxzZSBpZiAodHlwZW9mIG5hbWUgPT09ICdvYmplY3QnKSB7XG4gICAgICBmb3IgKHZhciBrZXkgaW4gbmFtZSkge1xuICAgICAgICBpZiAoaGFzT3duLmNhbGwobmFtZSwga2V5KSkge1xuICAgICAgICAgIGRhdGFba2V5XSA9IG5hbWVba2V5XTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdVUkkuYWRkUXVlcnkoKSBhY2NlcHRzIGFuIG9iamVjdCwgc3RyaW5nIGFzIHRoZSBuYW1lIHBhcmFtZXRlcicpO1xuICAgIH1cblxuICAgIHRoaXMuX3BhcnRzLnF1ZXJ5ID0gVVJJLmJ1aWxkUXVlcnkoZGF0YSwgdGhpcy5fcGFydHMuZHVwbGljYXRlUXVlcnlQYXJhbWV0ZXJzLCB0aGlzLl9wYXJ0cy5lc2NhcGVRdWVyeVNwYWNlKTtcbiAgICBpZiAodHlwZW9mIG5hbWUgIT09ICdzdHJpbmcnKSB7XG4gICAgICBidWlsZCA9IHZhbHVlO1xuICAgIH1cblxuICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICByZXR1cm4gdGhpcztcbiAgfTtcbiAgcC5hZGRRdWVyeSA9IGZ1bmN0aW9uKG5hbWUsIHZhbHVlLCBidWlsZCkge1xuICAgIHZhciBkYXRhID0gVVJJLnBhcnNlUXVlcnkodGhpcy5fcGFydHMucXVlcnksIHRoaXMuX3BhcnRzLmVzY2FwZVF1ZXJ5U3BhY2UpO1xuICAgIFVSSS5hZGRRdWVyeShkYXRhLCBuYW1lLCB2YWx1ZSA9PT0gdW5kZWZpbmVkID8gbnVsbCA6IHZhbHVlKTtcbiAgICB0aGlzLl9wYXJ0cy5xdWVyeSA9IFVSSS5idWlsZFF1ZXJ5KGRhdGEsIHRoaXMuX3BhcnRzLmR1cGxpY2F0ZVF1ZXJ5UGFyYW1ldGVycywgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSk7XG4gICAgaWYgKHR5cGVvZiBuYW1lICE9PSAnc3RyaW5nJykge1xuICAgICAgYnVpbGQgPSB2YWx1ZTtcbiAgICB9XG5cbiAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG4gIHAucmVtb3ZlUXVlcnkgPSBmdW5jdGlvbihuYW1lLCB2YWx1ZSwgYnVpbGQpIHtcbiAgICB2YXIgZGF0YSA9IFVSSS5wYXJzZVF1ZXJ5KHRoaXMuX3BhcnRzLnF1ZXJ5LCB0aGlzLl9wYXJ0cy5lc2NhcGVRdWVyeVNwYWNlKTtcbiAgICBVUkkucmVtb3ZlUXVlcnkoZGF0YSwgbmFtZSwgdmFsdWUpO1xuICAgIHRoaXMuX3BhcnRzLnF1ZXJ5ID0gVVJJLmJ1aWxkUXVlcnkoZGF0YSwgdGhpcy5fcGFydHMuZHVwbGljYXRlUXVlcnlQYXJhbWV0ZXJzLCB0aGlzLl9wYXJ0cy5lc2NhcGVRdWVyeVNwYWNlKTtcbiAgICBpZiAodHlwZW9mIG5hbWUgIT09ICdzdHJpbmcnKSB7XG4gICAgICBidWlsZCA9IHZhbHVlO1xuICAgIH1cblxuICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICByZXR1cm4gdGhpcztcbiAgfTtcbiAgcC5oYXNRdWVyeSA9IGZ1bmN0aW9uKG5hbWUsIHZhbHVlLCB3aXRoaW5BcnJheSkge1xuICAgIHZhciBkYXRhID0gVVJJLnBhcnNlUXVlcnkodGhpcy5fcGFydHMucXVlcnksIHRoaXMuX3BhcnRzLmVzY2FwZVF1ZXJ5U3BhY2UpO1xuICAgIHJldHVybiBVUkkuaGFzUXVlcnkoZGF0YSwgbmFtZSwgdmFsdWUsIHdpdGhpbkFycmF5KTtcbiAgfTtcbiAgcC5zZXRTZWFyY2ggPSBwLnNldFF1ZXJ5O1xuICBwLmFkZFNlYXJjaCA9IHAuYWRkUXVlcnk7XG4gIHAucmVtb3ZlU2VhcmNoID0gcC5yZW1vdmVRdWVyeTtcbiAgcC5oYXNTZWFyY2ggPSBwLmhhc1F1ZXJ5O1xuXG4gIC8vIHNhbml0aXppbmcgVVJMc1xuICBwLm5vcm1hbGl6ZSA9IGZ1bmN0aW9uKCkge1xuICAgIGlmICh0aGlzLl9wYXJ0cy51cm4pIHtcbiAgICAgIHJldHVybiB0aGlzXG4gICAgICAgIC5ub3JtYWxpemVQcm90b2NvbChmYWxzZSlcbiAgICAgICAgLm5vcm1hbGl6ZVBhdGgoZmFsc2UpXG4gICAgICAgIC5ub3JtYWxpemVRdWVyeShmYWxzZSlcbiAgICAgICAgLm5vcm1hbGl6ZUZyYWdtZW50KGZhbHNlKVxuICAgICAgICAuYnVpbGQoKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpc1xuICAgICAgLm5vcm1hbGl6ZVByb3RvY29sKGZhbHNlKVxuICAgICAgLm5vcm1hbGl6ZUhvc3RuYW1lKGZhbHNlKVxuICAgICAgLm5vcm1hbGl6ZVBvcnQoZmFsc2UpXG4gICAgICAubm9ybWFsaXplUGF0aChmYWxzZSlcbiAgICAgIC5ub3JtYWxpemVRdWVyeShmYWxzZSlcbiAgICAgIC5ub3JtYWxpemVGcmFnbWVudChmYWxzZSlcbiAgICAgIC5idWlsZCgpO1xuICB9O1xuICBwLm5vcm1hbGl6ZVByb3RvY29sID0gZnVuY3Rpb24oYnVpbGQpIHtcbiAgICBpZiAodHlwZW9mIHRoaXMuX3BhcnRzLnByb3RvY29sID09PSAnc3RyaW5nJykge1xuICAgICAgdGhpcy5fcGFydHMucHJvdG9jb2wgPSB0aGlzLl9wYXJ0cy5wcm90b2NvbC50b0xvd2VyQ2FzZSgpO1xuICAgICAgdGhpcy5idWlsZCghYnVpbGQpO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xuICB9O1xuICBwLm5vcm1hbGl6ZUhvc3RuYW1lID0gZnVuY3Rpb24oYnVpbGQpIHtcbiAgICBpZiAodGhpcy5fcGFydHMuaG9zdG5hbWUpIHtcbiAgICAgIGlmICh0aGlzLmlzKCdJRE4nKSAmJiBwdW55Y29kZSkge1xuICAgICAgICB0aGlzLl9wYXJ0cy5ob3N0bmFtZSA9IHB1bnljb2RlLnRvQVNDSUkodGhpcy5fcGFydHMuaG9zdG5hbWUpO1xuICAgICAgfSBlbHNlIGlmICh0aGlzLmlzKCdJUHY2JykgJiYgSVB2Nikge1xuICAgICAgICB0aGlzLl9wYXJ0cy5ob3N0bmFtZSA9IElQdjYuYmVzdCh0aGlzLl9wYXJ0cy5ob3N0bmFtZSk7XG4gICAgICB9XG5cbiAgICAgIHRoaXMuX3BhcnRzLmhvc3RuYW1lID0gdGhpcy5fcGFydHMuaG9zdG5hbWUudG9Mb3dlckNhc2UoKTtcbiAgICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcztcbiAgfTtcbiAgcC5ub3JtYWxpemVQb3J0ID0gZnVuY3Rpb24oYnVpbGQpIHtcbiAgICAvLyByZW1vdmUgcG9ydCBvZiBpdCdzIHRoZSBwcm90b2NvbCdzIGRlZmF1bHRcbiAgICBpZiAodHlwZW9mIHRoaXMuX3BhcnRzLnByb3RvY29sID09PSAnc3RyaW5nJyAmJiB0aGlzLl9wYXJ0cy5wb3J0ID09PSBVUkkuZGVmYXVsdFBvcnRzW3RoaXMuX3BhcnRzLnByb3RvY29sXSkge1xuICAgICAgdGhpcy5fcGFydHMucG9ydCA9IG51bGw7XG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG4gIHAubm9ybWFsaXplUGF0aCA9IGZ1bmN0aW9uKGJ1aWxkKSB7XG4gICAgdmFyIF9wYXRoID0gdGhpcy5fcGFydHMucGF0aDtcbiAgICBpZiAoIV9wYXRoKSB7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG5cbiAgICBpZiAodGhpcy5fcGFydHMudXJuKSB7XG4gICAgICB0aGlzLl9wYXJ0cy5wYXRoID0gVVJJLnJlY29kZVVyblBhdGgodGhpcy5fcGFydHMucGF0aCk7XG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG5cbiAgICBpZiAodGhpcy5fcGFydHMucGF0aCA9PT0gJy8nKSB7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG5cbiAgICB2YXIgX3dhc19yZWxhdGl2ZTtcbiAgICB2YXIgX2xlYWRpbmdQYXJlbnRzID0gJyc7XG4gICAgdmFyIF9wYXJlbnQsIF9wb3M7XG5cbiAgICAvLyBoYW5kbGUgcmVsYXRpdmUgcGF0aHNcbiAgICBpZiAoX3BhdGguY2hhckF0KDApICE9PSAnLycpIHtcbiAgICAgIF93YXNfcmVsYXRpdmUgPSB0cnVlO1xuICAgICAgX3BhdGggPSAnLycgKyBfcGF0aDtcbiAgICB9XG5cbiAgICAvLyBoYW5kbGUgcmVsYXRpdmUgZmlsZXMgKGFzIG9wcG9zZWQgdG8gZGlyZWN0b3JpZXMpXG4gICAgaWYgKF9wYXRoLnNsaWNlKC0zKSA9PT0gJy8uLicgfHwgX3BhdGguc2xpY2UoLTIpID09PSAnLy4nKSB7XG4gICAgICBfcGF0aCArPSAnLyc7XG4gICAgfVxuXG4gICAgLy8gcmVzb2x2ZSBzaW1wbGVzXG4gICAgX3BhdGggPSBfcGF0aFxuICAgICAgLnJlcGxhY2UoLyhcXC8oXFwuXFwvKSspfChcXC9cXC4kKS9nLCAnLycpXG4gICAgICAucmVwbGFjZSgvXFwvezIsfS9nLCAnLycpO1xuXG4gICAgLy8gcmVtZW1iZXIgbGVhZGluZyBwYXJlbnRzXG4gICAgaWYgKF93YXNfcmVsYXRpdmUpIHtcbiAgICAgIF9sZWFkaW5nUGFyZW50cyA9IF9wYXRoLnN1YnN0cmluZygxKS5tYXRjaCgvXihcXC5cXC5cXC8pKy8pIHx8ICcnO1xuICAgICAgaWYgKF9sZWFkaW5nUGFyZW50cykge1xuICAgICAgICBfbGVhZGluZ1BhcmVudHMgPSBfbGVhZGluZ1BhcmVudHNbMF07XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gcmVzb2x2ZSBwYXJlbnRzXG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIF9wYXJlbnQgPSBfcGF0aC5pbmRleE9mKCcvLi4nKTtcbiAgICAgIGlmIChfcGFyZW50ID09PSAtMSkge1xuICAgICAgICAvLyBubyBtb3JlIC4uLyB0byByZXNvbHZlXG4gICAgICAgIGJyZWFrO1xuICAgICAgfSBlbHNlIGlmIChfcGFyZW50ID09PSAwKSB7XG4gICAgICAgIC8vIHRvcCBsZXZlbCBjYW5ub3QgYmUgcmVsYXRpdmUsIHNraXAgaXRcbiAgICAgICAgX3BhdGggPSBfcGF0aC5zdWJzdHJpbmcoMyk7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuXG4gICAgICBfcG9zID0gX3BhdGguc3Vic3RyaW5nKDAsIF9wYXJlbnQpLmxhc3RJbmRleE9mKCcvJyk7XG4gICAgICBpZiAoX3BvcyA9PT0gLTEpIHtcbiAgICAgICAgX3BvcyA9IF9wYXJlbnQ7XG4gICAgICB9XG4gICAgICBfcGF0aCA9IF9wYXRoLnN1YnN0cmluZygwLCBfcG9zKSArIF9wYXRoLnN1YnN0cmluZyhfcGFyZW50ICsgMyk7XG4gICAgfVxuXG4gICAgLy8gcmV2ZXJ0IHRvIHJlbGF0aXZlXG4gICAgaWYgKF93YXNfcmVsYXRpdmUgJiYgdGhpcy5pcygncmVsYXRpdmUnKSkge1xuICAgICAgX3BhdGggPSBfbGVhZGluZ1BhcmVudHMgKyBfcGF0aC5zdWJzdHJpbmcoMSk7XG4gICAgfVxuXG4gICAgX3BhdGggPSBVUkkucmVjb2RlUGF0aChfcGF0aCk7XG4gICAgdGhpcy5fcGFydHMucGF0aCA9IF9wYXRoO1xuICAgIHRoaXMuYnVpbGQoIWJ1aWxkKTtcbiAgICByZXR1cm4gdGhpcztcbiAgfTtcbiAgcC5ub3JtYWxpemVQYXRobmFtZSA9IHAubm9ybWFsaXplUGF0aDtcbiAgcC5ub3JtYWxpemVRdWVyeSA9IGZ1bmN0aW9uKGJ1aWxkKSB7XG4gICAgaWYgKHR5cGVvZiB0aGlzLl9wYXJ0cy5xdWVyeSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIGlmICghdGhpcy5fcGFydHMucXVlcnkubGVuZ3RoKSB7XG4gICAgICAgIHRoaXMuX3BhcnRzLnF1ZXJ5ID0gbnVsbDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMucXVlcnkoVVJJLnBhcnNlUXVlcnkodGhpcy5fcGFydHMucXVlcnksIHRoaXMuX3BhcnRzLmVzY2FwZVF1ZXJ5U3BhY2UpKTtcbiAgICAgIH1cblxuICAgICAgdGhpcy5idWlsZCghYnVpbGQpO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xuICB9O1xuICBwLm5vcm1hbGl6ZUZyYWdtZW50ID0gZnVuY3Rpb24oYnVpbGQpIHtcbiAgICBpZiAoIXRoaXMuX3BhcnRzLmZyYWdtZW50KSB7XG4gICAgICB0aGlzLl9wYXJ0cy5mcmFnbWVudCA9IG51bGw7XG4gICAgICB0aGlzLmJ1aWxkKCFidWlsZCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG4gIHAubm9ybWFsaXplU2VhcmNoID0gcC5ub3JtYWxpemVRdWVyeTtcbiAgcC5ub3JtYWxpemVIYXNoID0gcC5ub3JtYWxpemVGcmFnbWVudDtcblxuICBwLmlzbzg4NTkgPSBmdW5jdGlvbigpIHtcbiAgICAvLyBleHBlY3QgdW5pY29kZSBpbnB1dCwgaXNvODg1OSBvdXRwdXRcbiAgICB2YXIgZSA9IFVSSS5lbmNvZGU7XG4gICAgdmFyIGQgPSBVUkkuZGVjb2RlO1xuXG4gICAgVVJJLmVuY29kZSA9IGVzY2FwZTtcbiAgICBVUkkuZGVjb2RlID0gZGVjb2RlVVJJQ29tcG9uZW50O1xuICAgIHRyeSB7XG4gICAgICB0aGlzLm5vcm1hbGl6ZSgpO1xuICAgIH0gZmluYWxseSB7XG4gICAgICBVUkkuZW5jb2RlID0gZTtcbiAgICAgIFVSSS5kZWNvZGUgPSBkO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcztcbiAgfTtcblxuICBwLnVuaWNvZGUgPSBmdW5jdGlvbigpIHtcbiAgICAvLyBleHBlY3QgaXNvODg1OSBpbnB1dCwgdW5pY29kZSBvdXRwdXRcbiAgICB2YXIgZSA9IFVSSS5lbmNvZGU7XG4gICAgdmFyIGQgPSBVUkkuZGVjb2RlO1xuXG4gICAgVVJJLmVuY29kZSA9IHN0cmljdEVuY29kZVVSSUNvbXBvbmVudDtcbiAgICBVUkkuZGVjb2RlID0gdW5lc2NhcGU7XG4gICAgdHJ5IHtcbiAgICAgIHRoaXMubm9ybWFsaXplKCk7XG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIFVSSS5lbmNvZGUgPSBlO1xuICAgICAgVVJJLmRlY29kZSA9IGQ7XG4gICAgfVxuICAgIHJldHVybiB0aGlzO1xuICB9O1xuXG4gIHAucmVhZGFibGUgPSBmdW5jdGlvbigpIHtcbiAgICB2YXIgdXJpID0gdGhpcy5jbG9uZSgpO1xuICAgIC8vIHJlbW92aW5nIHVzZXJuYW1lLCBwYXNzd29yZCwgYmVjYXVzZSB0aGV5IHNob3VsZG4ndCBiZSBkaXNwbGF5ZWQgYWNjb3JkaW5nIHRvIFJGQyAzOTg2XG4gICAgdXJpLnVzZXJuYW1lKCcnKS5wYXNzd29yZCgnJykubm9ybWFsaXplKCk7XG4gICAgdmFyIHQgPSAnJztcbiAgICBpZiAodXJpLl9wYXJ0cy5wcm90b2NvbCkge1xuICAgICAgdCArPSB1cmkuX3BhcnRzLnByb3RvY29sICsgJzovLyc7XG4gICAgfVxuXG4gICAgaWYgKHVyaS5fcGFydHMuaG9zdG5hbWUpIHtcbiAgICAgIGlmICh1cmkuaXMoJ3B1bnljb2RlJykgJiYgcHVueWNvZGUpIHtcbiAgICAgICAgdCArPSBwdW55Y29kZS50b1VuaWNvZGUodXJpLl9wYXJ0cy5ob3N0bmFtZSk7XG4gICAgICAgIGlmICh1cmkuX3BhcnRzLnBvcnQpIHtcbiAgICAgICAgICB0ICs9ICc6JyArIHVyaS5fcGFydHMucG9ydDtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdCArPSB1cmkuaG9zdCgpO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmICh1cmkuX3BhcnRzLmhvc3RuYW1lICYmIHVyaS5fcGFydHMucGF0aCAmJiB1cmkuX3BhcnRzLnBhdGguY2hhckF0KDApICE9PSAnLycpIHtcbiAgICAgIHQgKz0gJy8nO1xuICAgIH1cblxuICAgIHQgKz0gdXJpLnBhdGgodHJ1ZSk7XG4gICAgaWYgKHVyaS5fcGFydHMucXVlcnkpIHtcbiAgICAgIHZhciBxID0gJyc7XG4gICAgICBmb3IgKHZhciBpID0gMCwgcXAgPSB1cmkuX3BhcnRzLnF1ZXJ5LnNwbGl0KCcmJyksIGwgPSBxcC5sZW5ndGg7IGkgPCBsOyBpKyspIHtcbiAgICAgICAgdmFyIGt2ID0gKHFwW2ldIHx8ICcnKS5zcGxpdCgnPScpO1xuICAgICAgICBxICs9ICcmJyArIFVSSS5kZWNvZGVRdWVyeShrdlswXSwgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSlcbiAgICAgICAgICAucmVwbGFjZSgvJi9nLCAnJTI2Jyk7XG5cbiAgICAgICAgaWYgKGt2WzFdICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICBxICs9ICc9JyArIFVSSS5kZWNvZGVRdWVyeShrdlsxXSwgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSlcbiAgICAgICAgICAgIC5yZXBsYWNlKC8mL2csICclMjYnKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgdCArPSAnPycgKyBxLnN1YnN0cmluZygxKTtcbiAgICB9XG5cbiAgICB0ICs9IFVSSS5kZWNvZGVRdWVyeSh1cmkuaGFzaCgpLCB0cnVlKTtcbiAgICByZXR1cm4gdDtcbiAgfTtcblxuICAvLyByZXNvbHZpbmcgcmVsYXRpdmUgYW5kIGFic29sdXRlIFVSTHNcbiAgcC5hYnNvbHV0ZVRvID0gZnVuY3Rpb24oYmFzZSkge1xuICAgIHZhciByZXNvbHZlZCA9IHRoaXMuY2xvbmUoKTtcbiAgICB2YXIgcHJvcGVydGllcyA9IFsncHJvdG9jb2wnLCAndXNlcm5hbWUnLCAncGFzc3dvcmQnLCAnaG9zdG5hbWUnLCAncG9ydCddO1xuICAgIHZhciBiYXNlZGlyLCBpLCBwO1xuXG4gICAgaWYgKHRoaXMuX3BhcnRzLnVybikge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdVUk5zIGRvIG5vdCBoYXZlIGFueSBnZW5lcmFsbHkgZGVmaW5lZCBoaWVyYXJjaGljYWwgY29tcG9uZW50cycpO1xuICAgIH1cblxuICAgIGlmICghKGJhc2UgaW5zdGFuY2VvZiBVUkkpKSB7XG4gICAgICBiYXNlID0gbmV3IFVSSShiYXNlKTtcbiAgICB9XG5cbiAgICBpZiAoIXJlc29sdmVkLl9wYXJ0cy5wcm90b2NvbCkge1xuICAgICAgcmVzb2x2ZWQuX3BhcnRzLnByb3RvY29sID0gYmFzZS5fcGFydHMucHJvdG9jb2w7XG4gICAgfVxuXG4gICAgaWYgKHRoaXMuX3BhcnRzLmhvc3RuYW1lKSB7XG4gICAgICByZXR1cm4gcmVzb2x2ZWQ7XG4gICAgfVxuXG4gICAgZm9yIChpID0gMDsgKHAgPSBwcm9wZXJ0aWVzW2ldKTsgaSsrKSB7XG4gICAgICByZXNvbHZlZC5fcGFydHNbcF0gPSBiYXNlLl9wYXJ0c1twXTtcbiAgICB9XG5cbiAgICBpZiAoIXJlc29sdmVkLl9wYXJ0cy5wYXRoKSB7XG4gICAgICByZXNvbHZlZC5fcGFydHMucGF0aCA9IGJhc2UuX3BhcnRzLnBhdGg7XG4gICAgICBpZiAoIXJlc29sdmVkLl9wYXJ0cy5xdWVyeSkge1xuICAgICAgICByZXNvbHZlZC5fcGFydHMucXVlcnkgPSBiYXNlLl9wYXJ0cy5xdWVyeTtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKHJlc29sdmVkLl9wYXJ0cy5wYXRoLnN1YnN0cmluZygtMikgPT09ICcuLicpIHtcbiAgICAgIHJlc29sdmVkLl9wYXJ0cy5wYXRoICs9ICcvJztcbiAgICB9XG5cbiAgICBpZiAocmVzb2x2ZWQucGF0aCgpLmNoYXJBdCgwKSAhPT0gJy8nKSB7XG4gICAgICBiYXNlZGlyID0gYmFzZS5kaXJlY3RvcnkoKTtcbiAgICAgIGJhc2VkaXIgPSBiYXNlZGlyID8gYmFzZWRpciA6IGJhc2UucGF0aCgpLmluZGV4T2YoJy8nKSA9PT0gMCA/ICcvJyA6ICcnO1xuICAgICAgcmVzb2x2ZWQuX3BhcnRzLnBhdGggPSAoYmFzZWRpciA/IChiYXNlZGlyICsgJy8nKSA6ICcnKSArIHJlc29sdmVkLl9wYXJ0cy5wYXRoO1xuICAgICAgcmVzb2x2ZWQubm9ybWFsaXplUGF0aCgpO1xuICAgIH1cblxuICAgIHJlc29sdmVkLmJ1aWxkKCk7XG4gICAgcmV0dXJuIHJlc29sdmVkO1xuICB9O1xuICBwLnJlbGF0aXZlVG8gPSBmdW5jdGlvbihiYXNlKSB7XG4gICAgdmFyIHJlbGF0aXZlID0gdGhpcy5jbG9uZSgpLm5vcm1hbGl6ZSgpO1xuICAgIHZhciByZWxhdGl2ZVBhcnRzLCBiYXNlUGFydHMsIGNvbW1vbiwgcmVsYXRpdmVQYXRoLCBiYXNlUGF0aDtcblxuICAgIGlmIChyZWxhdGl2ZS5fcGFydHMudXJuKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1VSTnMgZG8gbm90IGhhdmUgYW55IGdlbmVyYWxseSBkZWZpbmVkIGhpZXJhcmNoaWNhbCBjb21wb25lbnRzJyk7XG4gICAgfVxuXG4gICAgYmFzZSA9IG5ldyBVUkkoYmFzZSkubm9ybWFsaXplKCk7XG4gICAgcmVsYXRpdmVQYXJ0cyA9IHJlbGF0aXZlLl9wYXJ0cztcbiAgICBiYXNlUGFydHMgPSBiYXNlLl9wYXJ0cztcbiAgICByZWxhdGl2ZVBhdGggPSByZWxhdGl2ZS5wYXRoKCk7XG4gICAgYmFzZVBhdGggPSBiYXNlLnBhdGgoKTtcblxuICAgIGlmIChyZWxhdGl2ZVBhdGguY2hhckF0KDApICE9PSAnLycpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignVVJJIGlzIGFscmVhZHkgcmVsYXRpdmUnKTtcbiAgICB9XG5cbiAgICBpZiAoYmFzZVBhdGguY2hhckF0KDApICE9PSAnLycpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignQ2Fubm90IGNhbGN1bGF0ZSBhIFVSSSByZWxhdGl2ZSB0byBhbm90aGVyIHJlbGF0aXZlIFVSSScpO1xuICAgIH1cblxuICAgIGlmIChyZWxhdGl2ZVBhcnRzLnByb3RvY29sID09PSBiYXNlUGFydHMucHJvdG9jb2wpIHtcbiAgICAgIHJlbGF0aXZlUGFydHMucHJvdG9jb2wgPSBudWxsO1xuICAgIH1cblxuICAgIGlmIChyZWxhdGl2ZVBhcnRzLnVzZXJuYW1lICE9PSBiYXNlUGFydHMudXNlcm5hbWUgfHwgcmVsYXRpdmVQYXJ0cy5wYXNzd29yZCAhPT0gYmFzZVBhcnRzLnBhc3N3b3JkKSB7XG4gICAgICByZXR1cm4gcmVsYXRpdmUuYnVpbGQoKTtcbiAgICB9XG5cbiAgICBpZiAocmVsYXRpdmVQYXJ0cy5wcm90b2NvbCAhPT0gbnVsbCB8fCByZWxhdGl2ZVBhcnRzLnVzZXJuYW1lICE9PSBudWxsIHx8IHJlbGF0aXZlUGFydHMucGFzc3dvcmQgIT09IG51bGwpIHtcbiAgICAgIHJldHVybiByZWxhdGl2ZS5idWlsZCgpO1xuICAgIH1cblxuICAgIGlmIChyZWxhdGl2ZVBhcnRzLmhvc3RuYW1lID09PSBiYXNlUGFydHMuaG9zdG5hbWUgJiYgcmVsYXRpdmVQYXJ0cy5wb3J0ID09PSBiYXNlUGFydHMucG9ydCkge1xuICAgICAgcmVsYXRpdmVQYXJ0cy5ob3N0bmFtZSA9IG51bGw7XG4gICAgICByZWxhdGl2ZVBhcnRzLnBvcnQgPSBudWxsO1xuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gcmVsYXRpdmUuYnVpbGQoKTtcbiAgICB9XG5cbiAgICBpZiAocmVsYXRpdmVQYXRoID09PSBiYXNlUGF0aCkge1xuICAgICAgcmVsYXRpdmVQYXJ0cy5wYXRoID0gJyc7XG4gICAgICByZXR1cm4gcmVsYXRpdmUuYnVpbGQoKTtcbiAgICB9XG5cbiAgICAvLyBkZXRlcm1pbmUgY29tbW9uIHN1YiBwYXRoXG4gICAgY29tbW9uID0gVVJJLmNvbW1vblBhdGgocmVsYXRpdmVQYXRoLCBiYXNlUGF0aCk7XG5cbiAgICAvLyBJZiB0aGUgcGF0aHMgaGF2ZSBub3RoaW5nIGluIGNvbW1vbiwgcmV0dXJuIGEgcmVsYXRpdmUgVVJMIHdpdGggdGhlIGFic29sdXRlIHBhdGguXG4gICAgaWYgKCFjb21tb24pIHtcbiAgICAgIHJldHVybiByZWxhdGl2ZS5idWlsZCgpO1xuICAgIH1cblxuICAgIHZhciBwYXJlbnRzID0gYmFzZVBhcnRzLnBhdGhcbiAgICAgIC5zdWJzdHJpbmcoY29tbW9uLmxlbmd0aClcbiAgICAgIC5yZXBsYWNlKC9bXlxcL10qJC8sICcnKVxuICAgICAgLnJlcGxhY2UoLy4qP1xcLy9nLCAnLi4vJyk7XG5cbiAgICByZWxhdGl2ZVBhcnRzLnBhdGggPSAocGFyZW50cyArIHJlbGF0aXZlUGFydHMucGF0aC5zdWJzdHJpbmcoY29tbW9uLmxlbmd0aCkpIHx8ICcuLyc7XG5cbiAgICByZXR1cm4gcmVsYXRpdmUuYnVpbGQoKTtcbiAgfTtcblxuICAvLyBjb21wYXJpbmcgVVJJc1xuICBwLmVxdWFscyA9IGZ1bmN0aW9uKHVyaSkge1xuICAgIHZhciBvbmUgPSB0aGlzLmNsb25lKCk7XG4gICAgdmFyIHR3byA9IG5ldyBVUkkodXJpKTtcbiAgICB2YXIgb25lX21hcCA9IHt9O1xuICAgIHZhciB0d29fbWFwID0ge307XG4gICAgdmFyIGNoZWNrZWQgPSB7fTtcbiAgICB2YXIgb25lX3F1ZXJ5LCB0d29fcXVlcnksIGtleTtcblxuICAgIG9uZS5ub3JtYWxpemUoKTtcbiAgICB0d28ubm9ybWFsaXplKCk7XG5cbiAgICAvLyBleGFjdCBtYXRjaFxuICAgIGlmIChvbmUudG9TdHJpbmcoKSA9PT0gdHdvLnRvU3RyaW5nKCkpIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIC8vIGV4dHJhY3QgcXVlcnkgc3RyaW5nXG4gICAgb25lX3F1ZXJ5ID0gb25lLnF1ZXJ5KCk7XG4gICAgdHdvX3F1ZXJ5ID0gdHdvLnF1ZXJ5KCk7XG4gICAgb25lLnF1ZXJ5KCcnKTtcbiAgICB0d28ucXVlcnkoJycpO1xuXG4gICAgLy8gZGVmaW5pdGVseSBub3QgZXF1YWwgaWYgbm90IGV2ZW4gbm9uLXF1ZXJ5IHBhcnRzIG1hdGNoXG4gICAgaWYgKG9uZS50b1N0cmluZygpICE9PSB0d28udG9TdHJpbmcoKSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cblxuICAgIC8vIHF1ZXJ5IHBhcmFtZXRlcnMgaGF2ZSB0aGUgc2FtZSBsZW5ndGgsIGV2ZW4gaWYgdGhleSdyZSBwZXJtdXRlZFxuICAgIGlmIChvbmVfcXVlcnkubGVuZ3RoICE9PSB0d29fcXVlcnkubGVuZ3RoKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgb25lX21hcCA9IFVSSS5wYXJzZVF1ZXJ5KG9uZV9xdWVyeSwgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSk7XG4gICAgdHdvX21hcCA9IFVSSS5wYXJzZVF1ZXJ5KHR3b19xdWVyeSwgdGhpcy5fcGFydHMuZXNjYXBlUXVlcnlTcGFjZSk7XG5cbiAgICBmb3IgKGtleSBpbiBvbmVfbWFwKSB7XG4gICAgICBpZiAoaGFzT3duLmNhbGwob25lX21hcCwga2V5KSkge1xuICAgICAgICBpZiAoIWlzQXJyYXkob25lX21hcFtrZXldKSkge1xuICAgICAgICAgIGlmIChvbmVfbWFwW2tleV0gIT09IHR3b19tYXBba2V5XSkge1xuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmICghYXJyYXlzRXF1YWwob25lX21hcFtrZXldLCB0d29fbWFwW2tleV0pKSB7XG4gICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgY2hlY2tlZFtrZXldID0gdHJ1ZTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBmb3IgKGtleSBpbiB0d29fbWFwKSB7XG4gICAgICBpZiAoaGFzT3duLmNhbGwodHdvX21hcCwga2V5KSkge1xuICAgICAgICBpZiAoIWNoZWNrZWRba2V5XSkge1xuICAgICAgICAgIC8vIHR3byBjb250YWlucyBhIHBhcmFtZXRlciBub3QgcHJlc2VudCBpbiBvbmVcbiAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gdHJ1ZTtcbiAgfTtcblxuICAvLyBzdGF0ZVxuICBwLmR1cGxpY2F0ZVF1ZXJ5UGFyYW1ldGVycyA9IGZ1bmN0aW9uKHYpIHtcbiAgICB0aGlzLl9wYXJ0cy5kdXBsaWNhdGVRdWVyeVBhcmFtZXRlcnMgPSAhIXY7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgcC5lc2NhcGVRdWVyeVNwYWNlID0gZnVuY3Rpb24odikge1xuICAgIHRoaXMuX3BhcnRzLmVzY2FwZVF1ZXJ5U3BhY2UgPSAhIXY7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgcmV0dXJuIFVSSTtcbn0pKTtcbiIsIi8qISBodHRwOi8vbXRocy5iZS9wdW55Y29kZSB2MS4yLjMgYnkgQG1hdGhpYXMgKi9cbjsoZnVuY3Rpb24ocm9vdCkge1xuXG5cdC8qKiBEZXRlY3QgZnJlZSB2YXJpYWJsZXMgKi9cblx0dmFyIGZyZWVFeHBvcnRzID0gdHlwZW9mIGV4cG9ydHMgPT0gJ29iamVjdCcgJiYgZXhwb3J0cztcblx0dmFyIGZyZWVNb2R1bGUgPSB0eXBlb2YgbW9kdWxlID09ICdvYmplY3QnICYmIG1vZHVsZSAmJlxuXHRcdG1vZHVsZS5leHBvcnRzID09IGZyZWVFeHBvcnRzICYmIG1vZHVsZTtcblx0dmFyIGZyZWVHbG9iYWwgPSB0eXBlb2YgZ2xvYmFsID09ICdvYmplY3QnICYmIGdsb2JhbDtcblx0aWYgKGZyZWVHbG9iYWwuZ2xvYmFsID09PSBmcmVlR2xvYmFsIHx8IGZyZWVHbG9iYWwud2luZG93ID09PSBmcmVlR2xvYmFsKSB7XG5cdFx0cm9vdCA9IGZyZWVHbG9iYWw7XG5cdH1cblxuXHQvKipcblx0ICogVGhlIGBwdW55Y29kZWAgb2JqZWN0LlxuXHQgKiBAbmFtZSBwdW55Y29kZVxuXHQgKiBAdHlwZSBPYmplY3Rcblx0ICovXG5cdHZhciBwdW55Y29kZSxcblxuXHQvKiogSGlnaGVzdCBwb3NpdGl2ZSBzaWduZWQgMzItYml0IGZsb2F0IHZhbHVlICovXG5cdG1heEludCA9IDIxNDc0ODM2NDcsIC8vIGFrYS4gMHg3RkZGRkZGRiBvciAyXjMxLTFcblxuXHQvKiogQm9vdHN0cmluZyBwYXJhbWV0ZXJzICovXG5cdGJhc2UgPSAzNixcblx0dE1pbiA9IDEsXG5cdHRNYXggPSAyNixcblx0c2tldyA9IDM4LFxuXHRkYW1wID0gNzAwLFxuXHRpbml0aWFsQmlhcyA9IDcyLFxuXHRpbml0aWFsTiA9IDEyOCwgLy8gMHg4MFxuXHRkZWxpbWl0ZXIgPSAnLScsIC8vICdcXHgyRCdcblxuXHQvKiogUmVndWxhciBleHByZXNzaW9ucyAqL1xuXHRyZWdleFB1bnljb2RlID0gL154bi0tLyxcblx0cmVnZXhOb25BU0NJSSA9IC9bXiAtfl0vLCAvLyB1bnByaW50YWJsZSBBU0NJSSBjaGFycyArIG5vbi1BU0NJSSBjaGFyc1xuXHRyZWdleFNlcGFyYXRvcnMgPSAvXFx4MkV8XFx1MzAwMnxcXHVGRjBFfFxcdUZGNjEvZywgLy8gUkZDIDM0OTAgc2VwYXJhdG9yc1xuXG5cdC8qKiBFcnJvciBtZXNzYWdlcyAqL1xuXHRlcnJvcnMgPSB7XG5cdFx0J292ZXJmbG93JzogJ092ZXJmbG93OiBpbnB1dCBuZWVkcyB3aWRlciBpbnRlZ2VycyB0byBwcm9jZXNzJyxcblx0XHQnbm90LWJhc2ljJzogJ0lsbGVnYWwgaW5wdXQgPj0gMHg4MCAobm90IGEgYmFzaWMgY29kZSBwb2ludCknLFxuXHRcdCdpbnZhbGlkLWlucHV0JzogJ0ludmFsaWQgaW5wdXQnXG5cdH0sXG5cblx0LyoqIENvbnZlbmllbmNlIHNob3J0Y3V0cyAqL1xuXHRiYXNlTWludXNUTWluID0gYmFzZSAtIHRNaW4sXG5cdGZsb29yID0gTWF0aC5mbG9vcixcblx0c3RyaW5nRnJvbUNoYXJDb2RlID0gU3RyaW5nLmZyb21DaGFyQ29kZSxcblxuXHQvKiogVGVtcG9yYXJ5IHZhcmlhYmxlICovXG5cdGtleTtcblxuXHQvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuXHQvKipcblx0ICogQSBnZW5lcmljIGVycm9yIHV0aWxpdHkgZnVuY3Rpb24uXG5cdCAqIEBwcml2YXRlXG5cdCAqIEBwYXJhbSB7U3RyaW5nfSB0eXBlIFRoZSBlcnJvciB0eXBlLlxuXHQgKiBAcmV0dXJucyB7RXJyb3J9IFRocm93cyBhIGBSYW5nZUVycm9yYCB3aXRoIHRoZSBhcHBsaWNhYmxlIGVycm9yIG1lc3NhZ2UuXG5cdCAqL1xuXHRmdW5jdGlvbiBlcnJvcih0eXBlKSB7XG5cdFx0dGhyb3cgUmFuZ2VFcnJvcihlcnJvcnNbdHlwZV0pO1xuXHR9XG5cblx0LyoqXG5cdCAqIEEgZ2VuZXJpYyBgQXJyYXkjbWFwYCB1dGlsaXR5IGZ1bmN0aW9uLlxuXHQgKiBAcHJpdmF0ZVxuXHQgKiBAcGFyYW0ge0FycmF5fSBhcnJheSBUaGUgYXJyYXkgdG8gaXRlcmF0ZSBvdmVyLlxuXHQgKiBAcGFyYW0ge0Z1bmN0aW9ufSBjYWxsYmFjayBUaGUgZnVuY3Rpb24gdGhhdCBnZXRzIGNhbGxlZCBmb3IgZXZlcnkgYXJyYXlcblx0ICogaXRlbS5cblx0ICogQHJldHVybnMge0FycmF5fSBBIG5ldyBhcnJheSBvZiB2YWx1ZXMgcmV0dXJuZWQgYnkgdGhlIGNhbGxiYWNrIGZ1bmN0aW9uLlxuXHQgKi9cblx0ZnVuY3Rpb24gbWFwKGFycmF5LCBmbikge1xuXHRcdHZhciBsZW5ndGggPSBhcnJheS5sZW5ndGg7XG5cdFx0d2hpbGUgKGxlbmd0aC0tKSB7XG5cdFx0XHRhcnJheVtsZW5ndGhdID0gZm4oYXJyYXlbbGVuZ3RoXSk7XG5cdFx0fVxuXHRcdHJldHVybiBhcnJheTtcblx0fVxuXG5cdC8qKlxuXHQgKiBBIHNpbXBsZSBgQXJyYXkjbWFwYC1saWtlIHdyYXBwZXIgdG8gd29yayB3aXRoIGRvbWFpbiBuYW1lIHN0cmluZ3MuXG5cdCAqIEBwcml2YXRlXG5cdCAqIEBwYXJhbSB7U3RyaW5nfSBkb21haW4gVGhlIGRvbWFpbiBuYW1lLlxuXHQgKiBAcGFyYW0ge0Z1bmN0aW9ufSBjYWxsYmFjayBUaGUgZnVuY3Rpb24gdGhhdCBnZXRzIGNhbGxlZCBmb3IgZXZlcnlcblx0ICogY2hhcmFjdGVyLlxuXHQgKiBAcmV0dXJucyB7QXJyYXl9IEEgbmV3IHN0cmluZyBvZiBjaGFyYWN0ZXJzIHJldHVybmVkIGJ5IHRoZSBjYWxsYmFja1xuXHQgKiBmdW5jdGlvbi5cblx0ICovXG5cdGZ1bmN0aW9uIG1hcERvbWFpbihzdHJpbmcsIGZuKSB7XG5cdFx0cmV0dXJuIG1hcChzdHJpbmcuc3BsaXQocmVnZXhTZXBhcmF0b3JzKSwgZm4pLmpvaW4oJy4nKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBDcmVhdGVzIGFuIGFycmF5IGNvbnRhaW5pbmcgdGhlIG51bWVyaWMgY29kZSBwb2ludHMgb2YgZWFjaCBVbmljb2RlXG5cdCAqIGNoYXJhY3RlciBpbiB0aGUgc3RyaW5nLiBXaGlsZSBKYXZhU2NyaXB0IHVzZXMgVUNTLTIgaW50ZXJuYWxseSxcblx0ICogdGhpcyBmdW5jdGlvbiB3aWxsIGNvbnZlcnQgYSBwYWlyIG9mIHN1cnJvZ2F0ZSBoYWx2ZXMgKGVhY2ggb2Ygd2hpY2hcblx0ICogVUNTLTIgZXhwb3NlcyBhcyBzZXBhcmF0ZSBjaGFyYWN0ZXJzKSBpbnRvIGEgc2luZ2xlIGNvZGUgcG9pbnQsXG5cdCAqIG1hdGNoaW5nIFVURi0xNi5cblx0ICogQHNlZSBgcHVueWNvZGUudWNzMi5lbmNvZGVgXG5cdCAqIEBzZWUgPGh0dHA6Ly9tYXRoaWFzYnluZW5zLmJlL25vdGVzL2phdmFzY3JpcHQtZW5jb2Rpbmc+XG5cdCAqIEBtZW1iZXJPZiBwdW55Y29kZS51Y3MyXG5cdCAqIEBuYW1lIGRlY29kZVxuXHQgKiBAcGFyYW0ge1N0cmluZ30gc3RyaW5nIFRoZSBVbmljb2RlIGlucHV0IHN0cmluZyAoVUNTLTIpLlxuXHQgKiBAcmV0dXJucyB7QXJyYXl9IFRoZSBuZXcgYXJyYXkgb2YgY29kZSBwb2ludHMuXG5cdCAqL1xuXHRmdW5jdGlvbiB1Y3MyZGVjb2RlKHN0cmluZykge1xuXHRcdHZhciBvdXRwdXQgPSBbXSxcblx0XHQgICAgY291bnRlciA9IDAsXG5cdFx0ICAgIGxlbmd0aCA9IHN0cmluZy5sZW5ndGgsXG5cdFx0ICAgIHZhbHVlLFxuXHRcdCAgICBleHRyYTtcblx0XHR3aGlsZSAoY291bnRlciA8IGxlbmd0aCkge1xuXHRcdFx0dmFsdWUgPSBzdHJpbmcuY2hhckNvZGVBdChjb3VudGVyKyspO1xuXHRcdFx0aWYgKHZhbHVlID49IDB4RDgwMCAmJiB2YWx1ZSA8PSAweERCRkYgJiYgY291bnRlciA8IGxlbmd0aCkge1xuXHRcdFx0XHQvLyBoaWdoIHN1cnJvZ2F0ZSwgYW5kIHRoZXJlIGlzIGEgbmV4dCBjaGFyYWN0ZXJcblx0XHRcdFx0ZXh0cmEgPSBzdHJpbmcuY2hhckNvZGVBdChjb3VudGVyKyspO1xuXHRcdFx0XHRpZiAoKGV4dHJhICYgMHhGQzAwKSA9PSAweERDMDApIHsgLy8gbG93IHN1cnJvZ2F0ZVxuXHRcdFx0XHRcdG91dHB1dC5wdXNoKCgodmFsdWUgJiAweDNGRikgPDwgMTApICsgKGV4dHJhICYgMHgzRkYpICsgMHgxMDAwMCk7XG5cdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0Ly8gdW5tYXRjaGVkIHN1cnJvZ2F0ZTsgb25seSBhcHBlbmQgdGhpcyBjb2RlIHVuaXQsIGluIGNhc2UgdGhlIG5leHRcblx0XHRcdFx0XHQvLyBjb2RlIHVuaXQgaXMgdGhlIGhpZ2ggc3Vycm9nYXRlIG9mIGEgc3Vycm9nYXRlIHBhaXJcblx0XHRcdFx0XHRvdXRwdXQucHVzaCh2YWx1ZSk7XG5cdFx0XHRcdFx0Y291bnRlci0tO1xuXHRcdFx0XHR9XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRvdXRwdXQucHVzaCh2YWx1ZSk7XG5cdFx0XHR9XG5cdFx0fVxuXHRcdHJldHVybiBvdXRwdXQ7XG5cdH1cblxuXHQvKipcblx0ICogQ3JlYXRlcyBhIHN0cmluZyBiYXNlZCBvbiBhbiBhcnJheSBvZiBudW1lcmljIGNvZGUgcG9pbnRzLlxuXHQgKiBAc2VlIGBwdW55Y29kZS51Y3MyLmRlY29kZWBcblx0ICogQG1lbWJlck9mIHB1bnljb2RlLnVjczJcblx0ICogQG5hbWUgZW5jb2RlXG5cdCAqIEBwYXJhbSB7QXJyYXl9IGNvZGVQb2ludHMgVGhlIGFycmF5IG9mIG51bWVyaWMgY29kZSBwb2ludHMuXG5cdCAqIEByZXR1cm5zIHtTdHJpbmd9IFRoZSBuZXcgVW5pY29kZSBzdHJpbmcgKFVDUy0yKS5cblx0ICovXG5cdGZ1bmN0aW9uIHVjczJlbmNvZGUoYXJyYXkpIHtcblx0XHRyZXR1cm4gbWFwKGFycmF5LCBmdW5jdGlvbih2YWx1ZSkge1xuXHRcdFx0dmFyIG91dHB1dCA9ICcnO1xuXHRcdFx0aWYgKHZhbHVlID4gMHhGRkZGKSB7XG5cdFx0XHRcdHZhbHVlIC09IDB4MTAwMDA7XG5cdFx0XHRcdG91dHB1dCArPSBzdHJpbmdGcm9tQ2hhckNvZGUodmFsdWUgPj4+IDEwICYgMHgzRkYgfCAweEQ4MDApO1xuXHRcdFx0XHR2YWx1ZSA9IDB4REMwMCB8IHZhbHVlICYgMHgzRkY7XG5cdFx0XHR9XG5cdFx0XHRvdXRwdXQgKz0gc3RyaW5nRnJvbUNoYXJDb2RlKHZhbHVlKTtcblx0XHRcdHJldHVybiBvdXRwdXQ7XG5cdFx0fSkuam9pbignJyk7XG5cdH1cblxuXHQvKipcblx0ICogQ29udmVydHMgYSBiYXNpYyBjb2RlIHBvaW50IGludG8gYSBkaWdpdC9pbnRlZ2VyLlxuXHQgKiBAc2VlIGBkaWdpdFRvQmFzaWMoKWBcblx0ICogQHByaXZhdGVcblx0ICogQHBhcmFtIHtOdW1iZXJ9IGNvZGVQb2ludCBUaGUgYmFzaWMgbnVtZXJpYyBjb2RlIHBvaW50IHZhbHVlLlxuXHQgKiBAcmV0dXJucyB7TnVtYmVyfSBUaGUgbnVtZXJpYyB2YWx1ZSBvZiBhIGJhc2ljIGNvZGUgcG9pbnQgKGZvciB1c2UgaW5cblx0ICogcmVwcmVzZW50aW5nIGludGVnZXJzKSBpbiB0aGUgcmFuZ2UgYDBgIHRvIGBiYXNlIC0gMWAsIG9yIGBiYXNlYCBpZlxuXHQgKiB0aGUgY29kZSBwb2ludCBkb2VzIG5vdCByZXByZXNlbnQgYSB2YWx1ZS5cblx0ICovXG5cdGZ1bmN0aW9uIGJhc2ljVG9EaWdpdChjb2RlUG9pbnQpIHtcblx0XHRpZiAoY29kZVBvaW50IC0gNDggPCAxMCkge1xuXHRcdFx0cmV0dXJuIGNvZGVQb2ludCAtIDIyO1xuXHRcdH1cblx0XHRpZiAoY29kZVBvaW50IC0gNjUgPCAyNikge1xuXHRcdFx0cmV0dXJuIGNvZGVQb2ludCAtIDY1O1xuXHRcdH1cblx0XHRpZiAoY29kZVBvaW50IC0gOTcgPCAyNikge1xuXHRcdFx0cmV0dXJuIGNvZGVQb2ludCAtIDk3O1xuXHRcdH1cblx0XHRyZXR1cm4gYmFzZTtcblx0fVxuXG5cdC8qKlxuXHQgKiBDb252ZXJ0cyBhIGRpZ2l0L2ludGVnZXIgaW50byBhIGJhc2ljIGNvZGUgcG9pbnQuXG5cdCAqIEBzZWUgYGJhc2ljVG9EaWdpdCgpYFxuXHQgKiBAcHJpdmF0ZVxuXHQgKiBAcGFyYW0ge051bWJlcn0gZGlnaXQgVGhlIG51bWVyaWMgdmFsdWUgb2YgYSBiYXNpYyBjb2RlIHBvaW50LlxuXHQgKiBAcmV0dXJucyB7TnVtYmVyfSBUaGUgYmFzaWMgY29kZSBwb2ludCB3aG9zZSB2YWx1ZSAod2hlbiB1c2VkIGZvclxuXHQgKiByZXByZXNlbnRpbmcgaW50ZWdlcnMpIGlzIGBkaWdpdGAsIHdoaWNoIG5lZWRzIHRvIGJlIGluIHRoZSByYW5nZVxuXHQgKiBgMGAgdG8gYGJhc2UgLSAxYC4gSWYgYGZsYWdgIGlzIG5vbi16ZXJvLCB0aGUgdXBwZXJjYXNlIGZvcm0gaXNcblx0ICogdXNlZDsgZWxzZSwgdGhlIGxvd2VyY2FzZSBmb3JtIGlzIHVzZWQuIFRoZSBiZWhhdmlvciBpcyB1bmRlZmluZWRcblx0ICogaWYgYGZsYWdgIGlzIG5vbi16ZXJvIGFuZCBgZGlnaXRgIGhhcyBubyB1cHBlcmNhc2UgZm9ybS5cblx0ICovXG5cdGZ1bmN0aW9uIGRpZ2l0VG9CYXNpYyhkaWdpdCwgZmxhZykge1xuXHRcdC8vICAwLi4yNSBtYXAgdG8gQVNDSUkgYS4ueiBvciBBLi5aXG5cdFx0Ly8gMjYuLjM1IG1hcCB0byBBU0NJSSAwLi45XG5cdFx0cmV0dXJuIGRpZ2l0ICsgMjIgKyA3NSAqIChkaWdpdCA8IDI2KSAtICgoZmxhZyAhPSAwKSA8PCA1KTtcblx0fVxuXG5cdC8qKlxuXHQgKiBCaWFzIGFkYXB0YXRpb24gZnVuY3Rpb24gYXMgcGVyIHNlY3Rpb24gMy40IG9mIFJGQyAzNDkyLlxuXHQgKiBodHRwOi8vdG9vbHMuaWV0Zi5vcmcvaHRtbC9yZmMzNDkyI3NlY3Rpb24tMy40XG5cdCAqIEBwcml2YXRlXG5cdCAqL1xuXHRmdW5jdGlvbiBhZGFwdChkZWx0YSwgbnVtUG9pbnRzLCBmaXJzdFRpbWUpIHtcblx0XHR2YXIgayA9IDA7XG5cdFx0ZGVsdGEgPSBmaXJzdFRpbWUgPyBmbG9vcihkZWx0YSAvIGRhbXApIDogZGVsdGEgPj4gMTtcblx0XHRkZWx0YSArPSBmbG9vcihkZWx0YSAvIG51bVBvaW50cyk7XG5cdFx0Zm9yICgvKiBubyBpbml0aWFsaXphdGlvbiAqLzsgZGVsdGEgPiBiYXNlTWludXNUTWluICogdE1heCA+PiAxOyBrICs9IGJhc2UpIHtcblx0XHRcdGRlbHRhID0gZmxvb3IoZGVsdGEgLyBiYXNlTWludXNUTWluKTtcblx0XHR9XG5cdFx0cmV0dXJuIGZsb29yKGsgKyAoYmFzZU1pbnVzVE1pbiArIDEpICogZGVsdGEgLyAoZGVsdGEgKyBza2V3KSk7XG5cdH1cblxuXHQvKipcblx0ICogQ29udmVydHMgYSBQdW55Y29kZSBzdHJpbmcgb2YgQVNDSUktb25seSBzeW1ib2xzIHRvIGEgc3RyaW5nIG9mIFVuaWNvZGVcblx0ICogc3ltYm9scy5cblx0ICogQG1lbWJlck9mIHB1bnljb2RlXG5cdCAqIEBwYXJhbSB7U3RyaW5nfSBpbnB1dCBUaGUgUHVueWNvZGUgc3RyaW5nIG9mIEFTQ0lJLW9ubHkgc3ltYm9scy5cblx0ICogQHJldHVybnMge1N0cmluZ30gVGhlIHJlc3VsdGluZyBzdHJpbmcgb2YgVW5pY29kZSBzeW1ib2xzLlxuXHQgKi9cblx0ZnVuY3Rpb24gZGVjb2RlKGlucHV0KSB7XG5cdFx0Ly8gRG9uJ3QgdXNlIFVDUy0yXG5cdFx0dmFyIG91dHB1dCA9IFtdLFxuXHRcdCAgICBpbnB1dExlbmd0aCA9IGlucHV0Lmxlbmd0aCxcblx0XHQgICAgb3V0LFxuXHRcdCAgICBpID0gMCxcblx0XHQgICAgbiA9IGluaXRpYWxOLFxuXHRcdCAgICBiaWFzID0gaW5pdGlhbEJpYXMsXG5cdFx0ICAgIGJhc2ljLFxuXHRcdCAgICBqLFxuXHRcdCAgICBpbmRleCxcblx0XHQgICAgb2xkaSxcblx0XHQgICAgdyxcblx0XHQgICAgayxcblx0XHQgICAgZGlnaXQsXG5cdFx0ICAgIHQsXG5cdFx0ICAgIGxlbmd0aCxcblx0XHQgICAgLyoqIENhY2hlZCBjYWxjdWxhdGlvbiByZXN1bHRzICovXG5cdFx0ICAgIGJhc2VNaW51c1Q7XG5cblx0XHQvLyBIYW5kbGUgdGhlIGJhc2ljIGNvZGUgcG9pbnRzOiBsZXQgYGJhc2ljYCBiZSB0aGUgbnVtYmVyIG9mIGlucHV0IGNvZGVcblx0XHQvLyBwb2ludHMgYmVmb3JlIHRoZSBsYXN0IGRlbGltaXRlciwgb3IgYDBgIGlmIHRoZXJlIGlzIG5vbmUsIHRoZW4gY29weVxuXHRcdC8vIHRoZSBmaXJzdCBiYXNpYyBjb2RlIHBvaW50cyB0byB0aGUgb3V0cHV0LlxuXG5cdFx0YmFzaWMgPSBpbnB1dC5sYXN0SW5kZXhPZihkZWxpbWl0ZXIpO1xuXHRcdGlmIChiYXNpYyA8IDApIHtcblx0XHRcdGJhc2ljID0gMDtcblx0XHR9XG5cblx0XHRmb3IgKGogPSAwOyBqIDwgYmFzaWM7ICsraikge1xuXHRcdFx0Ly8gaWYgaXQncyBub3QgYSBiYXNpYyBjb2RlIHBvaW50XG5cdFx0XHRpZiAoaW5wdXQuY2hhckNvZGVBdChqKSA+PSAweDgwKSB7XG5cdFx0XHRcdGVycm9yKCdub3QtYmFzaWMnKTtcblx0XHRcdH1cblx0XHRcdG91dHB1dC5wdXNoKGlucHV0LmNoYXJDb2RlQXQoaikpO1xuXHRcdH1cblxuXHRcdC8vIE1haW4gZGVjb2RpbmcgbG9vcDogc3RhcnQganVzdCBhZnRlciB0aGUgbGFzdCBkZWxpbWl0ZXIgaWYgYW55IGJhc2ljIGNvZGVcblx0XHQvLyBwb2ludHMgd2VyZSBjb3BpZWQ7IHN0YXJ0IGF0IHRoZSBiZWdpbm5pbmcgb3RoZXJ3aXNlLlxuXG5cdFx0Zm9yIChpbmRleCA9IGJhc2ljID4gMCA/IGJhc2ljICsgMSA6IDA7IGluZGV4IDwgaW5wdXRMZW5ndGg7IC8qIG5vIGZpbmFsIGV4cHJlc3Npb24gKi8pIHtcblxuXHRcdFx0Ly8gYGluZGV4YCBpcyB0aGUgaW5kZXggb2YgdGhlIG5leHQgY2hhcmFjdGVyIHRvIGJlIGNvbnN1bWVkLlxuXHRcdFx0Ly8gRGVjb2RlIGEgZ2VuZXJhbGl6ZWQgdmFyaWFibGUtbGVuZ3RoIGludGVnZXIgaW50byBgZGVsdGFgLFxuXHRcdFx0Ly8gd2hpY2ggZ2V0cyBhZGRlZCB0byBgaWAuIFRoZSBvdmVyZmxvdyBjaGVja2luZyBpcyBlYXNpZXJcblx0XHRcdC8vIGlmIHdlIGluY3JlYXNlIGBpYCBhcyB3ZSBnbywgdGhlbiBzdWJ0cmFjdCBvZmYgaXRzIHN0YXJ0aW5nXG5cdFx0XHQvLyB2YWx1ZSBhdCB0aGUgZW5kIHRvIG9idGFpbiBgZGVsdGFgLlxuXHRcdFx0Zm9yIChvbGRpID0gaSwgdyA9IDEsIGsgPSBiYXNlOyAvKiBubyBjb25kaXRpb24gKi87IGsgKz0gYmFzZSkge1xuXG5cdFx0XHRcdGlmIChpbmRleCA+PSBpbnB1dExlbmd0aCkge1xuXHRcdFx0XHRcdGVycm9yKCdpbnZhbGlkLWlucHV0Jyk7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHRkaWdpdCA9IGJhc2ljVG9EaWdpdChpbnB1dC5jaGFyQ29kZUF0KGluZGV4KyspKTtcblxuXHRcdFx0XHRpZiAoZGlnaXQgPj0gYmFzZSB8fCBkaWdpdCA+IGZsb29yKChtYXhJbnQgLSBpKSAvIHcpKSB7XG5cdFx0XHRcdFx0ZXJyb3IoJ292ZXJmbG93Jyk7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHRpICs9IGRpZ2l0ICogdztcblx0XHRcdFx0dCA9IGsgPD0gYmlhcyA/IHRNaW4gOiAoayA+PSBiaWFzICsgdE1heCA/IHRNYXggOiBrIC0gYmlhcyk7XG5cblx0XHRcdFx0aWYgKGRpZ2l0IDwgdCkge1xuXHRcdFx0XHRcdGJyZWFrO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0YmFzZU1pbnVzVCA9IGJhc2UgLSB0O1xuXHRcdFx0XHRpZiAodyA+IGZsb29yKG1heEludCAvIGJhc2VNaW51c1QpKSB7XG5cdFx0XHRcdFx0ZXJyb3IoJ292ZXJmbG93Jyk7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHR3ICo9IGJhc2VNaW51c1Q7XG5cblx0XHRcdH1cblxuXHRcdFx0b3V0ID0gb3V0cHV0Lmxlbmd0aCArIDE7XG5cdFx0XHRiaWFzID0gYWRhcHQoaSAtIG9sZGksIG91dCwgb2xkaSA9PSAwKTtcblxuXHRcdFx0Ly8gYGlgIHdhcyBzdXBwb3NlZCB0byB3cmFwIGFyb3VuZCBmcm9tIGBvdXRgIHRvIGAwYCxcblx0XHRcdC8vIGluY3JlbWVudGluZyBgbmAgZWFjaCB0aW1lLCBzbyB3ZSdsbCBmaXggdGhhdCBub3c6XG5cdFx0XHRpZiAoZmxvb3IoaSAvIG91dCkgPiBtYXhJbnQgLSBuKSB7XG5cdFx0XHRcdGVycm9yKCdvdmVyZmxvdycpO1xuXHRcdFx0fVxuXG5cdFx0XHRuICs9IGZsb29yKGkgLyBvdXQpO1xuXHRcdFx0aSAlPSBvdXQ7XG5cblx0XHRcdC8vIEluc2VydCBgbmAgYXQgcG9zaXRpb24gYGlgIG9mIHRoZSBvdXRwdXRcblx0XHRcdG91dHB1dC5zcGxpY2UoaSsrLCAwLCBuKTtcblxuXHRcdH1cblxuXHRcdHJldHVybiB1Y3MyZW5jb2RlKG91dHB1dCk7XG5cdH1cblxuXHQvKipcblx0ICogQ29udmVydHMgYSBzdHJpbmcgb2YgVW5pY29kZSBzeW1ib2xzIHRvIGEgUHVueWNvZGUgc3RyaW5nIG9mIEFTQ0lJLW9ubHlcblx0ICogc3ltYm9scy5cblx0ICogQG1lbWJlck9mIHB1bnljb2RlXG5cdCAqIEBwYXJhbSB7U3RyaW5nfSBpbnB1dCBUaGUgc3RyaW5nIG9mIFVuaWNvZGUgc3ltYm9scy5cblx0ICogQHJldHVybnMge1N0cmluZ30gVGhlIHJlc3VsdGluZyBQdW55Y29kZSBzdHJpbmcgb2YgQVNDSUktb25seSBzeW1ib2xzLlxuXHQgKi9cblx0ZnVuY3Rpb24gZW5jb2RlKGlucHV0KSB7XG5cdFx0dmFyIG4sXG5cdFx0ICAgIGRlbHRhLFxuXHRcdCAgICBoYW5kbGVkQ1BDb3VudCxcblx0XHQgICAgYmFzaWNMZW5ndGgsXG5cdFx0ICAgIGJpYXMsXG5cdFx0ICAgIGosXG5cdFx0ICAgIG0sXG5cdFx0ICAgIHEsXG5cdFx0ICAgIGssXG5cdFx0ICAgIHQsXG5cdFx0ICAgIGN1cnJlbnRWYWx1ZSxcblx0XHQgICAgb3V0cHV0ID0gW10sXG5cdFx0ICAgIC8qKiBgaW5wdXRMZW5ndGhgIHdpbGwgaG9sZCB0aGUgbnVtYmVyIG9mIGNvZGUgcG9pbnRzIGluIGBpbnB1dGAuICovXG5cdFx0ICAgIGlucHV0TGVuZ3RoLFxuXHRcdCAgICAvKiogQ2FjaGVkIGNhbGN1bGF0aW9uIHJlc3VsdHMgKi9cblx0XHQgICAgaGFuZGxlZENQQ291bnRQbHVzT25lLFxuXHRcdCAgICBiYXNlTWludXNULFxuXHRcdCAgICBxTWludXNUO1xuXG5cdFx0Ly8gQ29udmVydCB0aGUgaW5wdXQgaW4gVUNTLTIgdG8gVW5pY29kZVxuXHRcdGlucHV0ID0gdWNzMmRlY29kZShpbnB1dCk7XG5cblx0XHQvLyBDYWNoZSB0aGUgbGVuZ3RoXG5cdFx0aW5wdXRMZW5ndGggPSBpbnB1dC5sZW5ndGg7XG5cblx0XHQvLyBJbml0aWFsaXplIHRoZSBzdGF0ZVxuXHRcdG4gPSBpbml0aWFsTjtcblx0XHRkZWx0YSA9IDA7XG5cdFx0YmlhcyA9IGluaXRpYWxCaWFzO1xuXG5cdFx0Ly8gSGFuZGxlIHRoZSBiYXNpYyBjb2RlIHBvaW50c1xuXHRcdGZvciAoaiA9IDA7IGogPCBpbnB1dExlbmd0aDsgKytqKSB7XG5cdFx0XHRjdXJyZW50VmFsdWUgPSBpbnB1dFtqXTtcblx0XHRcdGlmIChjdXJyZW50VmFsdWUgPCAweDgwKSB7XG5cdFx0XHRcdG91dHB1dC5wdXNoKHN0cmluZ0Zyb21DaGFyQ29kZShjdXJyZW50VmFsdWUpKTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHRoYW5kbGVkQ1BDb3VudCA9IGJhc2ljTGVuZ3RoID0gb3V0cHV0Lmxlbmd0aDtcblxuXHRcdC8vIGBoYW5kbGVkQ1BDb3VudGAgaXMgdGhlIG51bWJlciBvZiBjb2RlIHBvaW50cyB0aGF0IGhhdmUgYmVlbiBoYW5kbGVkO1xuXHRcdC8vIGBiYXNpY0xlbmd0aGAgaXMgdGhlIG51bWJlciBvZiBiYXNpYyBjb2RlIHBvaW50cy5cblxuXHRcdC8vIEZpbmlzaCB0aGUgYmFzaWMgc3RyaW5nIC0gaWYgaXQgaXMgbm90IGVtcHR5IC0gd2l0aCBhIGRlbGltaXRlclxuXHRcdGlmIChiYXNpY0xlbmd0aCkge1xuXHRcdFx0b3V0cHV0LnB1c2goZGVsaW1pdGVyKTtcblx0XHR9XG5cblx0XHQvLyBNYWluIGVuY29kaW5nIGxvb3A6XG5cdFx0d2hpbGUgKGhhbmRsZWRDUENvdW50IDwgaW5wdXRMZW5ndGgpIHtcblxuXHRcdFx0Ly8gQWxsIG5vbi1iYXNpYyBjb2RlIHBvaW50cyA8IG4gaGF2ZSBiZWVuIGhhbmRsZWQgYWxyZWFkeS4gRmluZCB0aGUgbmV4dFxuXHRcdFx0Ly8gbGFyZ2VyIG9uZTpcblx0XHRcdGZvciAobSA9IG1heEludCwgaiA9IDA7IGogPCBpbnB1dExlbmd0aDsgKytqKSB7XG5cdFx0XHRcdGN1cnJlbnRWYWx1ZSA9IGlucHV0W2pdO1xuXHRcdFx0XHRpZiAoY3VycmVudFZhbHVlID49IG4gJiYgY3VycmVudFZhbHVlIDwgbSkge1xuXHRcdFx0XHRcdG0gPSBjdXJyZW50VmFsdWU7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0Ly8gSW5jcmVhc2UgYGRlbHRhYCBlbm91Z2ggdG8gYWR2YW5jZSB0aGUgZGVjb2RlcidzIDxuLGk+IHN0YXRlIHRvIDxtLDA+LFxuXHRcdFx0Ly8gYnV0IGd1YXJkIGFnYWluc3Qgb3ZlcmZsb3dcblx0XHRcdGhhbmRsZWRDUENvdW50UGx1c09uZSA9IGhhbmRsZWRDUENvdW50ICsgMTtcblx0XHRcdGlmIChtIC0gbiA+IGZsb29yKChtYXhJbnQgLSBkZWx0YSkgLyBoYW5kbGVkQ1BDb3VudFBsdXNPbmUpKSB7XG5cdFx0XHRcdGVycm9yKCdvdmVyZmxvdycpO1xuXHRcdFx0fVxuXG5cdFx0XHRkZWx0YSArPSAobSAtIG4pICogaGFuZGxlZENQQ291bnRQbHVzT25lO1xuXHRcdFx0biA9IG07XG5cblx0XHRcdGZvciAoaiA9IDA7IGogPCBpbnB1dExlbmd0aDsgKytqKSB7XG5cdFx0XHRcdGN1cnJlbnRWYWx1ZSA9IGlucHV0W2pdO1xuXG5cdFx0XHRcdGlmIChjdXJyZW50VmFsdWUgPCBuICYmICsrZGVsdGEgPiBtYXhJbnQpIHtcblx0XHRcdFx0XHRlcnJvcignb3ZlcmZsb3cnKTtcblx0XHRcdFx0fVxuXG5cdFx0XHRcdGlmIChjdXJyZW50VmFsdWUgPT0gbikge1xuXHRcdFx0XHRcdC8vIFJlcHJlc2VudCBkZWx0YSBhcyBhIGdlbmVyYWxpemVkIHZhcmlhYmxlLWxlbmd0aCBpbnRlZ2VyXG5cdFx0XHRcdFx0Zm9yIChxID0gZGVsdGEsIGsgPSBiYXNlOyAvKiBubyBjb25kaXRpb24gKi87IGsgKz0gYmFzZSkge1xuXHRcdFx0XHRcdFx0dCA9IGsgPD0gYmlhcyA/IHRNaW4gOiAoayA+PSBiaWFzICsgdE1heCA/IHRNYXggOiBrIC0gYmlhcyk7XG5cdFx0XHRcdFx0XHRpZiAocSA8IHQpIHtcblx0XHRcdFx0XHRcdFx0YnJlYWs7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRxTWludXNUID0gcSAtIHQ7XG5cdFx0XHRcdFx0XHRiYXNlTWludXNUID0gYmFzZSAtIHQ7XG5cdFx0XHRcdFx0XHRvdXRwdXQucHVzaChcblx0XHRcdFx0XHRcdFx0c3RyaW5nRnJvbUNoYXJDb2RlKGRpZ2l0VG9CYXNpYyh0ICsgcU1pbnVzVCAlIGJhc2VNaW51c1QsIDApKVxuXHRcdFx0XHRcdFx0KTtcblx0XHRcdFx0XHRcdHEgPSBmbG9vcihxTWludXNUIC8gYmFzZU1pbnVzVCk7XG5cdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0b3V0cHV0LnB1c2goc3RyaW5nRnJvbUNoYXJDb2RlKGRpZ2l0VG9CYXNpYyhxLCAwKSkpO1xuXHRcdFx0XHRcdGJpYXMgPSBhZGFwdChkZWx0YSwgaGFuZGxlZENQQ291bnRQbHVzT25lLCBoYW5kbGVkQ1BDb3VudCA9PSBiYXNpY0xlbmd0aCk7XG5cdFx0XHRcdFx0ZGVsdGEgPSAwO1xuXHRcdFx0XHRcdCsraGFuZGxlZENQQ291bnQ7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0KytkZWx0YTtcblx0XHRcdCsrbjtcblxuXHRcdH1cblx0XHRyZXR1cm4gb3V0cHV0LmpvaW4oJycpO1xuXHR9XG5cblx0LyoqXG5cdCAqIENvbnZlcnRzIGEgUHVueWNvZGUgc3RyaW5nIHJlcHJlc2VudGluZyBhIGRvbWFpbiBuYW1lIHRvIFVuaWNvZGUuIE9ubHkgdGhlXG5cdCAqIFB1bnljb2RlZCBwYXJ0cyBvZiB0aGUgZG9tYWluIG5hbWUgd2lsbCBiZSBjb252ZXJ0ZWQsIGkuZS4gaXQgZG9lc24ndFxuXHQgKiBtYXR0ZXIgaWYgeW91IGNhbGwgaXQgb24gYSBzdHJpbmcgdGhhdCBoYXMgYWxyZWFkeSBiZWVuIGNvbnZlcnRlZCB0b1xuXHQgKiBVbmljb2RlLlxuXHQgKiBAbWVtYmVyT2YgcHVueWNvZGVcblx0ICogQHBhcmFtIHtTdHJpbmd9IGRvbWFpbiBUaGUgUHVueWNvZGUgZG9tYWluIG5hbWUgdG8gY29udmVydCB0byBVbmljb2RlLlxuXHQgKiBAcmV0dXJucyB7U3RyaW5nfSBUaGUgVW5pY29kZSByZXByZXNlbnRhdGlvbiBvZiB0aGUgZ2l2ZW4gUHVueWNvZGVcblx0ICogc3RyaW5nLlxuXHQgKi9cblx0ZnVuY3Rpb24gdG9Vbmljb2RlKGRvbWFpbikge1xuXHRcdHJldHVybiBtYXBEb21haW4oZG9tYWluLCBmdW5jdGlvbihzdHJpbmcpIHtcblx0XHRcdHJldHVybiByZWdleFB1bnljb2RlLnRlc3Qoc3RyaW5nKVxuXHRcdFx0XHQ/IGRlY29kZShzdHJpbmcuc2xpY2UoNCkudG9Mb3dlckNhc2UoKSlcblx0XHRcdFx0OiBzdHJpbmc7XG5cdFx0fSk7XG5cdH1cblxuXHQvKipcblx0ICogQ29udmVydHMgYSBVbmljb2RlIHN0cmluZyByZXByZXNlbnRpbmcgYSBkb21haW4gbmFtZSB0byBQdW55Y29kZS4gT25seSB0aGVcblx0ICogbm9uLUFTQ0lJIHBhcnRzIG9mIHRoZSBkb21haW4gbmFtZSB3aWxsIGJlIGNvbnZlcnRlZCwgaS5lLiBpdCBkb2Vzbid0XG5cdCAqIG1hdHRlciBpZiB5b3UgY2FsbCBpdCB3aXRoIGEgZG9tYWluIHRoYXQncyBhbHJlYWR5IGluIEFTQ0lJLlxuXHQgKiBAbWVtYmVyT2YgcHVueWNvZGVcblx0ICogQHBhcmFtIHtTdHJpbmd9IGRvbWFpbiBUaGUgZG9tYWluIG5hbWUgdG8gY29udmVydCwgYXMgYSBVbmljb2RlIHN0cmluZy5cblx0ICogQHJldHVybnMge1N0cmluZ30gVGhlIFB1bnljb2RlIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBnaXZlbiBkb21haW4gbmFtZS5cblx0ICovXG5cdGZ1bmN0aW9uIHRvQVNDSUkoZG9tYWluKSB7XG5cdFx0cmV0dXJuIG1hcERvbWFpbihkb21haW4sIGZ1bmN0aW9uKHN0cmluZykge1xuXHRcdFx0cmV0dXJuIHJlZ2V4Tm9uQVNDSUkudGVzdChzdHJpbmcpXG5cdFx0XHRcdD8gJ3huLS0nICsgZW5jb2RlKHN0cmluZylcblx0XHRcdFx0OiBzdHJpbmc7XG5cdFx0fSk7XG5cdH1cblxuXHQvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuXHQvKiogRGVmaW5lIHRoZSBwdWJsaWMgQVBJICovXG5cdHB1bnljb2RlID0ge1xuXHRcdC8qKlxuXHRcdCAqIEEgc3RyaW5nIHJlcHJlc2VudGluZyB0aGUgY3VycmVudCBQdW55Y29kZS5qcyB2ZXJzaW9uIG51bWJlci5cblx0XHQgKiBAbWVtYmVyT2YgcHVueWNvZGVcblx0XHQgKiBAdHlwZSBTdHJpbmdcblx0XHQgKi9cblx0XHQndmVyc2lvbic6ICcxLjIuMycsXG5cdFx0LyoqXG5cdFx0ICogQW4gb2JqZWN0IG9mIG1ldGhvZHMgdG8gY29udmVydCBmcm9tIEphdmFTY3JpcHQncyBpbnRlcm5hbCBjaGFyYWN0ZXJcblx0XHQgKiByZXByZXNlbnRhdGlvbiAoVUNTLTIpIHRvIFVuaWNvZGUgY29kZSBwb2ludHMsIGFuZCBiYWNrLlxuXHRcdCAqIEBzZWUgPGh0dHA6Ly9tYXRoaWFzYnluZW5zLmJlL25vdGVzL2phdmFzY3JpcHQtZW5jb2Rpbmc+XG5cdFx0ICogQG1lbWJlck9mIHB1bnljb2RlXG5cdFx0ICogQHR5cGUgT2JqZWN0XG5cdFx0ICovXG5cdFx0J3VjczInOiB7XG5cdFx0XHQnZGVjb2RlJzogdWNzMmRlY29kZSxcblx0XHRcdCdlbmNvZGUnOiB1Y3MyZW5jb2RlXG5cdFx0fSxcblx0XHQnZGVjb2RlJzogZGVjb2RlLFxuXHRcdCdlbmNvZGUnOiBlbmNvZGUsXG5cdFx0J3RvQVNDSUknOiB0b0FTQ0lJLFxuXHRcdCd0b1VuaWNvZGUnOiB0b1VuaWNvZGVcblx0fTtcblxuXHQvKiogRXhwb3NlIGBwdW55Y29kZWAgKi9cblx0Ly8gU29tZSBBTUQgYnVpbGQgb3B0aW1pemVycywgbGlrZSByLmpzLCBjaGVjayBmb3Igc3BlY2lmaWMgY29uZGl0aW9uIHBhdHRlcm5zXG5cdC8vIGxpa2UgdGhlIGZvbGxvd2luZzpcblx0aWYgKFxuXHRcdHR5cGVvZiBkZWZpbmUgPT0gJ2Z1bmN0aW9uJyAmJlxuXHRcdHR5cGVvZiBkZWZpbmUuYW1kID09ICdvYmplY3QnICYmXG5cdFx0ZGVmaW5lLmFtZFxuXHQpIHtcblx0XHRkZWZpbmUoZnVuY3Rpb24oKSB7XG5cdFx0XHRyZXR1cm4gcHVueWNvZGU7XG5cdFx0fSk7XG5cdH1cdGVsc2UgaWYgKGZyZWVFeHBvcnRzICYmICFmcmVlRXhwb3J0cy5ub2RlVHlwZSkge1xuXHRcdGlmIChmcmVlTW9kdWxlKSB7IC8vIGluIE5vZGUuanMgb3IgUmluZ29KUyB2MC44LjArXG5cdFx0XHRmcmVlTW9kdWxlLmV4cG9ydHMgPSBwdW55Y29kZTtcblx0XHR9IGVsc2UgeyAvLyBpbiBOYXJ3aGFsIG9yIFJpbmdvSlMgdjAuNy4wLVxuXHRcdFx0Zm9yIChrZXkgaW4gcHVueWNvZGUpIHtcblx0XHRcdFx0cHVueWNvZGUuaGFzT3duUHJvcGVydHkoa2V5KSAmJiAoZnJlZUV4cG9ydHNba2V5XSA9IHB1bnljb2RlW2tleV0pO1xuXHRcdFx0fVxuXHRcdH1cblx0fSBlbHNlIHsgLy8gaW4gUmhpbm8gb3IgYSB3ZWIgYnJvd3NlclxuXHRcdHJvb3QucHVueWNvZGUgPSBwdW55Y29kZTtcblx0fVxuXG59KHRoaXMpKTtcbiIsIid1c2Ugc3RyaWN0JztcblxuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywge1xuICB2YWx1ZTogdHJ1ZVxufSk7XG5cbmZ1bmN0aW9uIF9pbnRlcm9wUmVxdWlyZURlZmF1bHQob2JqKSB7IHJldHVybiBvYmogJiYgb2JqLl9fZXNNb2R1bGUgPyBvYmogOiB7ICdkZWZhdWx0Jzogb2JqIH07IH1cblxudmFyIF9oZWxwZXJzRXZlbnQgPSByZXF1aXJlKCcuL2hlbHBlcnMvZXZlbnQnKTtcblxudmFyIF9oZWxwZXJzRXZlbnQyID0gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChfaGVscGVyc0V2ZW50KTtcblxudmFyIF9oZWxwZXJzTWVzc2FnZUV2ZW50ID0gcmVxdWlyZSgnLi9oZWxwZXJzL21lc3NhZ2UtZXZlbnQnKTtcblxudmFyIF9oZWxwZXJzTWVzc2FnZUV2ZW50MiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2hlbHBlcnNNZXNzYWdlRXZlbnQpO1xuXG52YXIgX2hlbHBlcnNDbG9zZUV2ZW50ID0gcmVxdWlyZSgnLi9oZWxwZXJzL2Nsb3NlLWV2ZW50Jyk7XG5cbnZhciBfaGVscGVyc0Nsb3NlRXZlbnQyID0gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChfaGVscGVyc0Nsb3NlRXZlbnQpO1xuXG4vKlxuKiBDcmVhdGVzIGFuIEV2ZW50IG9iamVjdCBhbmQgZXh0ZW5kcyBpdCB0byBhbGxvdyBmdWxsIG1vZGlmaWNhdGlvbiBvZlxuKiBpdHMgcHJvcGVydGllcy5cbipcbiogQHBhcmFtIHtvYmplY3R9IGNvbmZpZyAtIHdpdGhpbiBjb25maWcgeW91IHdpbGwgbmVlZCB0byBwYXNzIHR5cGUgYW5kIG9wdGlvbmFsbHkgdGFyZ2V0XG4qL1xuZnVuY3Rpb24gY3JlYXRlRXZlbnQoY29uZmlnKSB7XG4gIHZhciB0eXBlID0gY29uZmlnLnR5cGU7XG4gIHZhciB0YXJnZXQgPSBjb25maWcudGFyZ2V0O1xuXG4gIHZhciBldmVudE9iamVjdCA9IG5ldyBfaGVscGVyc0V2ZW50MlsnZGVmYXVsdCddKHR5cGUpO1xuXG4gIGlmICh0YXJnZXQpIHtcbiAgICBldmVudE9iamVjdC50YXJnZXQgPSB0YXJnZXQ7XG4gICAgZXZlbnRPYmplY3Quc3JjRWxlbWVudCA9IHRhcmdldDtcbiAgICBldmVudE9iamVjdC5jdXJyZW50VGFyZ2V0ID0gdGFyZ2V0O1xuICB9XG5cbiAgcmV0dXJuIGV2ZW50T2JqZWN0O1xufVxuXG4vKlxuKiBDcmVhdGVzIGEgTWVzc2FnZUV2ZW50IG9iamVjdCBhbmQgZXh0ZW5kcyBpdCB0byBhbGxvdyBmdWxsIG1vZGlmaWNhdGlvbiBvZlxuKiBpdHMgcHJvcGVydGllcy5cbipcbiogQHBhcmFtIHtvYmplY3R9IGNvbmZpZyAtIHdpdGhpbiBjb25maWcgeW91IHdpbGwgbmVlZCB0byBwYXNzIHR5cGUsIG9yaWdpbiwgZGF0YSBhbmQgb3B0aW9uYWxseSB0YXJnZXRcbiovXG5mdW5jdGlvbiBjcmVhdGVNZXNzYWdlRXZlbnQoY29uZmlnKSB7XG4gIHZhciB0eXBlID0gY29uZmlnLnR5cGU7XG4gIHZhciBvcmlnaW4gPSBjb25maWcub3JpZ2luO1xuICB2YXIgZGF0YSA9IGNvbmZpZy5kYXRhO1xuICB2YXIgdGFyZ2V0ID0gY29uZmlnLnRhcmdldDtcblxuICB2YXIgbWVzc2FnZUV2ZW50ID0gbmV3IF9oZWxwZXJzTWVzc2FnZUV2ZW50MlsnZGVmYXVsdCddKHR5cGUsIHtcbiAgICBkYXRhOiBkYXRhLFxuICAgIG9yaWdpbjogb3JpZ2luXG4gIH0pO1xuXG4gIGlmICh0YXJnZXQpIHtcbiAgICBtZXNzYWdlRXZlbnQudGFyZ2V0ID0gdGFyZ2V0O1xuICAgIG1lc3NhZ2VFdmVudC5zcmNFbGVtZW50ID0gdGFyZ2V0O1xuICAgIG1lc3NhZ2VFdmVudC5jdXJyZW50VGFyZ2V0ID0gdGFyZ2V0O1xuICB9XG5cbiAgcmV0dXJuIG1lc3NhZ2VFdmVudDtcbn1cblxuLypcbiogQ3JlYXRlcyBhIENsb3NlRXZlbnQgb2JqZWN0IGFuZCBleHRlbmRzIGl0IHRvIGFsbG93IGZ1bGwgbW9kaWZpY2F0aW9uIG9mXG4qIGl0cyBwcm9wZXJ0aWVzLlxuKlxuKiBAcGFyYW0ge29iamVjdH0gY29uZmlnIC0gd2l0aGluIGNvbmZpZyB5b3Ugd2lsbCBuZWVkIHRvIHBhc3MgdHlwZSBhbmQgb3B0aW9uYWxseSB0YXJnZXQsIGNvZGUsIGFuZCByZWFzb25cbiovXG5mdW5jdGlvbiBjcmVhdGVDbG9zZUV2ZW50KGNvbmZpZykge1xuICB2YXIgY29kZSA9IGNvbmZpZy5jb2RlO1xuICB2YXIgcmVhc29uID0gY29uZmlnLnJlYXNvbjtcbiAgdmFyIHR5cGUgPSBjb25maWcudHlwZTtcbiAgdmFyIHRhcmdldCA9IGNvbmZpZy50YXJnZXQ7XG4gIHZhciB3YXNDbGVhbiA9IGNvbmZpZy53YXNDbGVhbjtcblxuICBpZiAoIXdhc0NsZWFuKSB7XG4gICAgd2FzQ2xlYW4gPSBjb2RlID09PSAxMDAwO1xuICB9XG5cbiAgdmFyIGNsb3NlRXZlbnQgPSBuZXcgX2hlbHBlcnNDbG9zZUV2ZW50MlsnZGVmYXVsdCddKHR5cGUsIHtcbiAgICBjb2RlOiBjb2RlLFxuICAgIHJlYXNvbjogcmVhc29uLFxuICAgIHdhc0NsZWFuOiB3YXNDbGVhblxuICB9KTtcblxuICBpZiAodGFyZ2V0KSB7XG4gICAgY2xvc2VFdmVudC50YXJnZXQgPSB0YXJnZXQ7XG4gICAgY2xvc2VFdmVudC5zcmNFbGVtZW50ID0gdGFyZ2V0O1xuICAgIGNsb3NlRXZlbnQuY3VycmVudFRhcmdldCA9IHRhcmdldDtcbiAgfVxuXG4gIHJldHVybiBjbG9zZUV2ZW50O1xufVxuXG5leHBvcnRzLmNyZWF0ZUV2ZW50ID0gY3JlYXRlRXZlbnQ7XG5leHBvcnRzLmNyZWF0ZU1lc3NhZ2VFdmVudCA9IGNyZWF0ZU1lc3NhZ2VFdmVudDtcbmV4cG9ydHMuY3JlYXRlQ2xvc2VFdmVudCA9IGNyZWF0ZUNsb3NlRXZlbnQ7IiwiJ3VzZSBzdHJpY3QnO1xuXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7XG4gIHZhbHVlOiB0cnVlXG59KTtcblxudmFyIF9jcmVhdGVDbGFzcyA9IChmdW5jdGlvbiAoKSB7IGZ1bmN0aW9uIGRlZmluZVByb3BlcnRpZXModGFyZ2V0LCBwcm9wcykgeyBmb3IgKHZhciBpID0gMDsgaSA8IHByb3BzLmxlbmd0aDsgaSsrKSB7IHZhciBkZXNjcmlwdG9yID0gcHJvcHNbaV07IGRlc2NyaXB0b3IuZW51bWVyYWJsZSA9IGRlc2NyaXB0b3IuZW51bWVyYWJsZSB8fCBmYWxzZTsgZGVzY3JpcHRvci5jb25maWd1cmFibGUgPSB0cnVlOyBpZiAoJ3ZhbHVlJyBpbiBkZXNjcmlwdG9yKSBkZXNjcmlwdG9yLndyaXRhYmxlID0gdHJ1ZTsgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRhcmdldCwgZGVzY3JpcHRvci5rZXksIGRlc2NyaXB0b3IpOyB9IH0gcmV0dXJuIGZ1bmN0aW9uIChDb25zdHJ1Y3RvciwgcHJvdG9Qcm9wcywgc3RhdGljUHJvcHMpIHsgaWYgKHByb3RvUHJvcHMpIGRlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IucHJvdG90eXBlLCBwcm90b1Byb3BzKTsgaWYgKHN0YXRpY1Byb3BzKSBkZWZpbmVQcm9wZXJ0aWVzKENvbnN0cnVjdG9yLCBzdGF0aWNQcm9wcyk7IHJldHVybiBDb25zdHJ1Y3RvcjsgfTsgfSkoKTtcblxuZnVuY3Rpb24gX2NsYXNzQ2FsbENoZWNrKGluc3RhbmNlLCBDb25zdHJ1Y3RvcikgeyBpZiAoIShpbnN0YW5jZSBpbnN0YW5jZW9mIENvbnN0cnVjdG9yKSkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdDYW5ub3QgY2FsbCBhIGNsYXNzIGFzIGEgZnVuY3Rpb24nKTsgfSB9XG5cbnZhciBfaGVscGVyc0FycmF5SGVscGVycyA9IHJlcXVpcmUoJy4vaGVscGVycy9hcnJheS1oZWxwZXJzJyk7XG5cbi8qXG4qIEV2ZW50VGFyZ2V0IGlzIGFuIGludGVyZmFjZSBpbXBsZW1lbnRlZCBieSBvYmplY3RzIHRoYXQgY2FuXG4qIHJlY2VpdmUgZXZlbnRzIGFuZCBtYXkgaGF2ZSBsaXN0ZW5lcnMgZm9yIHRoZW0uXG4qXG4qIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9FdmVudFRhcmdldFxuKi9cblxudmFyIEV2ZW50VGFyZ2V0ID0gKGZ1bmN0aW9uICgpIHtcbiAgZnVuY3Rpb24gRXZlbnRUYXJnZXQoKSB7XG4gICAgX2NsYXNzQ2FsbENoZWNrKHRoaXMsIEV2ZW50VGFyZ2V0KTtcblxuICAgIHRoaXMubGlzdGVuZXJzID0ge307XG4gIH1cblxuICAvKlxuICAqIFRpZXMgYSBsaXN0ZW5lciBmdW5jdGlvbiB0byBhIGV2ZW50IHR5cGUgd2hpY2ggY2FuIGxhdGVyIGJlIGludm9rZWQgdmlhIHRoZVxuICAqIGRpc3BhdGNoRXZlbnQgbWV0aG9kLlxuICAqXG4gICogQHBhcmFtIHtzdHJpbmd9IHR5cGUgLSB0aGUgdHlwZSBvZiBldmVudCAoaWU6ICdvcGVuJywgJ21lc3NhZ2UnLCBldGMuKVxuICAqIEBwYXJhbSB7ZnVuY3Rpb259IGxpc3RlbmVyIC0gdGhlIGNhbGxiYWNrIGZ1bmN0aW9uIHRvIGludm9rZSB3aGVuZXZlciBhIGV2ZW50IGlzIGRpc3BhdGNoZWQgbWF0Y2hpbmcgdGhlIGdpdmVuIHR5cGVcbiAgKiBAcGFyYW0ge2Jvb2xlYW59IHVzZUNhcHR1cmUgLSBOL0EgVE9ETzogaW1wbGVtZW50IHVzZUNhcHR1cmUgZnVuY3Rpb25hbGl0eVxuICAqL1xuXG4gIF9jcmVhdGVDbGFzcyhFdmVudFRhcmdldCwgW3tcbiAgICBrZXk6ICdhZGRFdmVudExpc3RlbmVyJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gYWRkRXZlbnRMaXN0ZW5lcih0eXBlLCBsaXN0ZW5lciAvKiAsIHVzZUNhcHR1cmUgKi8pIHtcbiAgICAgIGlmICh0eXBlb2YgbGlzdGVuZXIgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgaWYgKCFBcnJheS5pc0FycmF5KHRoaXMubGlzdGVuZXJzW3R5cGVdKSkge1xuICAgICAgICAgIHRoaXMubGlzdGVuZXJzW3R5cGVdID0gW107XG4gICAgICAgIH1cblxuICAgICAgICAvLyBPbmx5IGFkZCB0aGUgc2FtZSBmdW5jdGlvbiBvbmNlXG4gICAgICAgIGlmICgoMCwgX2hlbHBlcnNBcnJheUhlbHBlcnMuZmlsdGVyKSh0aGlzLmxpc3RlbmVyc1t0eXBlXSwgZnVuY3Rpb24gKGl0ZW0pIHtcbiAgICAgICAgICByZXR1cm4gaXRlbSA9PT0gbGlzdGVuZXI7XG4gICAgICAgIH0pLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgIHRoaXMubGlzdGVuZXJzW3R5cGVdLnB1c2gobGlzdGVuZXIpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgLypcbiAgICAqIFJlbW92ZXMgdGhlIGxpc3RlbmVyIHNvIGl0IHdpbGwgbm8gbG9uZ2VyIGJlIGludm9rZWQgdmlhIHRoZSBkaXNwYXRjaEV2ZW50IG1ldGhvZC5cbiAgICAqXG4gICAgKiBAcGFyYW0ge3N0cmluZ30gdHlwZSAtIHRoZSB0eXBlIG9mIGV2ZW50IChpZTogJ29wZW4nLCAnbWVzc2FnZScsIGV0Yy4pXG4gICAgKiBAcGFyYW0ge2Z1bmN0aW9ufSBsaXN0ZW5lciAtIHRoZSBjYWxsYmFjayBmdW5jdGlvbiB0byBpbnZva2Ugd2hlbmV2ZXIgYSBldmVudCBpcyBkaXNwYXRjaGVkIG1hdGNoaW5nIHRoZSBnaXZlbiB0eXBlXG4gICAgKiBAcGFyYW0ge2Jvb2xlYW59IHVzZUNhcHR1cmUgLSBOL0EgVE9ETzogaW1wbGVtZW50IHVzZUNhcHR1cmUgZnVuY3Rpb25hbGl0eVxuICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICdyZW1vdmVFdmVudExpc3RlbmVyJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gcmVtb3ZlRXZlbnRMaXN0ZW5lcih0eXBlLCByZW1vdmluZ0xpc3RlbmVyIC8qICwgdXNlQ2FwdHVyZSAqLykge1xuICAgICAgdmFyIGFycmF5T2ZMaXN0ZW5lcnMgPSB0aGlzLmxpc3RlbmVyc1t0eXBlXTtcbiAgICAgIHRoaXMubGlzdGVuZXJzW3R5cGVdID0gKDAsIF9oZWxwZXJzQXJyYXlIZWxwZXJzLnJlamVjdCkoYXJyYXlPZkxpc3RlbmVycywgZnVuY3Rpb24gKGxpc3RlbmVyKSB7XG4gICAgICAgIHJldHVybiBsaXN0ZW5lciA9PT0gcmVtb3ZpbmdMaXN0ZW5lcjtcbiAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qXG4gICAgKiBJbnZva2VzIGFsbCBsaXN0ZW5lciBmdW5jdGlvbnMgdGhhdCBhcmUgbGlzdGVuaW5nIHRvIHRoZSBnaXZlbiBldmVudC50eXBlIHByb3BlcnR5LiBFYWNoXG4gICAgKiBsaXN0ZW5lciB3aWxsIGJlIHBhc3NlZCB0aGUgZXZlbnQgYXMgdGhlIGZpcnN0IGFyZ3VtZW50LlxuICAgICpcbiAgICAqIEBwYXJhbSB7b2JqZWN0fSBldmVudCAtIGV2ZW50IG9iamVjdCB3aGljaCB3aWxsIGJlIHBhc3NlZCB0byBhbGwgbGlzdGVuZXJzIG9mIHRoZSBldmVudC50eXBlIHByb3BlcnR5XG4gICAgKi9cbiAgfSwge1xuICAgIGtleTogJ2Rpc3BhdGNoRXZlbnQnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBkaXNwYXRjaEV2ZW50KGV2ZW50KSB7XG4gICAgICB2YXIgX3RoaXMgPSB0aGlzO1xuXG4gICAgICBmb3IgKHZhciBfbGVuID0gYXJndW1lbnRzLmxlbmd0aCwgY3VzdG9tQXJndW1lbnRzID0gQXJyYXkoX2xlbiA+IDEgPyBfbGVuIC0gMSA6IDApLCBfa2V5ID0gMTsgX2tleSA8IF9sZW47IF9rZXkrKykge1xuICAgICAgICBjdXN0b21Bcmd1bWVudHNbX2tleSAtIDFdID0gYXJndW1lbnRzW19rZXldO1xuICAgICAgfVxuXG4gICAgICB2YXIgZXZlbnROYW1lID0gZXZlbnQudHlwZTtcbiAgICAgIHZhciBsaXN0ZW5lcnMgPSB0aGlzLmxpc3RlbmVyc1tldmVudE5hbWVdO1xuXG4gICAgICBpZiAoIUFycmF5LmlzQXJyYXkobGlzdGVuZXJzKSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG5cbiAgICAgIGxpc3RlbmVycy5mb3JFYWNoKGZ1bmN0aW9uIChsaXN0ZW5lcikge1xuICAgICAgICBpZiAoY3VzdG9tQXJndW1lbnRzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICBsaXN0ZW5lci5hcHBseShfdGhpcywgY3VzdG9tQXJndW1lbnRzKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBsaXN0ZW5lci5jYWxsKF90aGlzLCBldmVudCk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgIH1cbiAgfV0pO1xuXG4gIHJldHVybiBFdmVudFRhcmdldDtcbn0pKCk7XG5cbmV4cG9ydHNbJ2RlZmF1bHQnXSA9IEV2ZW50VGFyZ2V0O1xubW9kdWxlLmV4cG9ydHMgPSBleHBvcnRzWydkZWZhdWx0J107IiwiXCJ1c2Ugc3RyaWN0XCI7XG5cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwge1xuICB2YWx1ZTogdHJ1ZVxufSk7XG5leHBvcnRzLnJlamVjdCA9IHJlamVjdDtcbmV4cG9ydHMuZmlsdGVyID0gZmlsdGVyO1xuXG5mdW5jdGlvbiByZWplY3QoYXJyYXksIGNhbGxiYWNrKSB7XG4gIHZhciByZXN1bHRzID0gW107XG4gIGFycmF5LmZvckVhY2goZnVuY3Rpb24gKGl0ZW1JbkFycmF5KSB7XG4gICAgaWYgKCFjYWxsYmFjayhpdGVtSW5BcnJheSkpIHtcbiAgICAgIHJlc3VsdHMucHVzaChpdGVtSW5BcnJheSk7XG4gICAgfVxuICB9KTtcblxuICByZXR1cm4gcmVzdWx0cztcbn1cblxuZnVuY3Rpb24gZmlsdGVyKGFycmF5LCBjYWxsYmFjaykge1xuICB2YXIgcmVzdWx0cyA9IFtdO1xuICBhcnJheS5mb3JFYWNoKGZ1bmN0aW9uIChpdGVtSW5BcnJheSkge1xuICAgIGlmIChjYWxsYmFjayhpdGVtSW5BcnJheSkpIHtcbiAgICAgIHJlc3VsdHMucHVzaChpdGVtSW5BcnJheSk7XG4gICAgfVxuICB9KTtcblxuICByZXR1cm4gcmVzdWx0cztcbn0iLCIvKlxuKiBodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9BUEkvQ2xvc2VFdmVudFxuKi9cblwidXNlIHN0cmljdFwiO1xuXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHtcbiAgdmFsdWU6IHRydWVcbn0pO1xudmFyIGNvZGVzID0ge1xuICBDTE9TRV9OT1JNQUw6IDEwMDAsXG4gIENMT1NFX0dPSU5HX0FXQVk6IDEwMDEsXG4gIENMT1NFX1BST1RPQ09MX0VSUk9SOiAxMDAyLFxuICBDTE9TRV9VTlNVUFBPUlRFRDogMTAwMyxcbiAgQ0xPU0VfTk9fU1RBVFVTOiAxMDA1LFxuICBDTE9TRV9BQk5PUk1BTDogMTAwNixcbiAgQ0xPU0VfVE9PX0xBUkdFOiAxMDA5XG59O1xuXG5leHBvcnRzW1wiZGVmYXVsdFwiXSA9IGNvZGVzO1xubW9kdWxlLmV4cG9ydHMgPSBleHBvcnRzW1wiZGVmYXVsdFwiXTsiLCIndXNlIHN0cmljdCc7XG5cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHtcbiAgdmFsdWU6IHRydWVcbn0pO1xuXG52YXIgX2dldCA9IGZ1bmN0aW9uIGdldChfeDIsIF94MywgX3g0KSB7IHZhciBfYWdhaW4gPSB0cnVlOyBfZnVuY3Rpb246IHdoaWxlIChfYWdhaW4pIHsgdmFyIG9iamVjdCA9IF94MiwgcHJvcGVydHkgPSBfeDMsIHJlY2VpdmVyID0gX3g0OyBfYWdhaW4gPSBmYWxzZTsgaWYgKG9iamVjdCA9PT0gbnVsbCkgb2JqZWN0ID0gRnVuY3Rpb24ucHJvdG90eXBlOyB2YXIgZGVzYyA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3Iob2JqZWN0LCBwcm9wZXJ0eSk7IGlmIChkZXNjID09PSB1bmRlZmluZWQpIHsgdmFyIHBhcmVudCA9IE9iamVjdC5nZXRQcm90b3R5cGVPZihvYmplY3QpOyBpZiAocGFyZW50ID09PSBudWxsKSB7IHJldHVybiB1bmRlZmluZWQ7IH0gZWxzZSB7IF94MiA9IHBhcmVudDsgX3gzID0gcHJvcGVydHk7IF94NCA9IHJlY2VpdmVyOyBfYWdhaW4gPSB0cnVlOyBkZXNjID0gcGFyZW50ID0gdW5kZWZpbmVkOyBjb250aW51ZSBfZnVuY3Rpb247IH0gfSBlbHNlIGlmICgndmFsdWUnIGluIGRlc2MpIHsgcmV0dXJuIGRlc2MudmFsdWU7IH0gZWxzZSB7IHZhciBnZXR0ZXIgPSBkZXNjLmdldDsgaWYgKGdldHRlciA9PT0gdW5kZWZpbmVkKSB7IHJldHVybiB1bmRlZmluZWQ7IH0gcmV0dXJuIGdldHRlci5jYWxsKHJlY2VpdmVyKTsgfSB9IH07XG5cbmZ1bmN0aW9uIF9pbnRlcm9wUmVxdWlyZURlZmF1bHQob2JqKSB7IHJldHVybiBvYmogJiYgb2JqLl9fZXNNb2R1bGUgPyBvYmogOiB7ICdkZWZhdWx0Jzogb2JqIH07IH1cblxuZnVuY3Rpb24gX2NsYXNzQ2FsbENoZWNrKGluc3RhbmNlLCBDb25zdHJ1Y3RvcikgeyBpZiAoIShpbnN0YW5jZSBpbnN0YW5jZW9mIENvbnN0cnVjdG9yKSkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdDYW5ub3QgY2FsbCBhIGNsYXNzIGFzIGEgZnVuY3Rpb24nKTsgfSB9XG5cbmZ1bmN0aW9uIF9pbmhlcml0cyhzdWJDbGFzcywgc3VwZXJDbGFzcykgeyBpZiAodHlwZW9mIHN1cGVyQ2xhc3MgIT09ICdmdW5jdGlvbicgJiYgc3VwZXJDbGFzcyAhPT0gbnVsbCkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdTdXBlciBleHByZXNzaW9uIG11c3QgZWl0aGVyIGJlIG51bGwgb3IgYSBmdW5jdGlvbiwgbm90ICcgKyB0eXBlb2Ygc3VwZXJDbGFzcyk7IH0gc3ViQ2xhc3MucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZShzdXBlckNsYXNzICYmIHN1cGVyQ2xhc3MucHJvdG90eXBlLCB7IGNvbnN0cnVjdG9yOiB7IHZhbHVlOiBzdWJDbGFzcywgZW51bWVyYWJsZTogZmFsc2UsIHdyaXRhYmxlOiB0cnVlLCBjb25maWd1cmFibGU6IHRydWUgfSB9KTsgaWYgKHN1cGVyQ2xhc3MpIE9iamVjdC5zZXRQcm90b3R5cGVPZiA/IE9iamVjdC5zZXRQcm90b3R5cGVPZihzdWJDbGFzcywgc3VwZXJDbGFzcykgOiBzdWJDbGFzcy5fX3Byb3RvX18gPSBzdXBlckNsYXNzOyB9XG5cbnZhciBfZXZlbnRQcm90b3R5cGUgPSByZXF1aXJlKCcuL2V2ZW50LXByb3RvdHlwZScpO1xuXG52YXIgX2V2ZW50UHJvdG90eXBlMiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2V2ZW50UHJvdG90eXBlKTtcblxudmFyIENsb3NlRXZlbnQgPSAoZnVuY3Rpb24gKF9FdmVudFByb3RvdHlwZSkge1xuICBfaW5oZXJpdHMoQ2xvc2VFdmVudCwgX0V2ZW50UHJvdG90eXBlKTtcblxuICBmdW5jdGlvbiBDbG9zZUV2ZW50KHR5cGUpIHtcbiAgICB2YXIgZXZlbnRJbml0Q29uZmlnID0gYXJndW1lbnRzLmxlbmd0aCA8PSAxIHx8IGFyZ3VtZW50c1sxXSA9PT0gdW5kZWZpbmVkID8ge30gOiBhcmd1bWVudHNbMV07XG5cbiAgICBfY2xhc3NDYWxsQ2hlY2sodGhpcywgQ2xvc2VFdmVudCk7XG5cbiAgICBfZ2V0KE9iamVjdC5nZXRQcm90b3R5cGVPZihDbG9zZUV2ZW50LnByb3RvdHlwZSksICdjb25zdHJ1Y3RvcicsIHRoaXMpLmNhbGwodGhpcyk7XG5cbiAgICBpZiAoIXR5cGUpIHtcbiAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0ZhaWxlZCB0byBjb25zdHJ1Y3QgXFwnQ2xvc2VFdmVudFxcJzogMSBhcmd1bWVudCByZXF1aXJlZCwgYnV0IG9ubHkgMCBwcmVzZW50LicpO1xuICAgIH1cblxuICAgIGlmICh0eXBlb2YgZXZlbnRJbml0Q29uZmlnICE9PSAnb2JqZWN0Jykge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignRmFpbGVkIHRvIGNvbnN0cnVjdCBcXCdDbG9zZUV2ZW50XFwnOiBwYXJhbWV0ZXIgMiAoXFwnZXZlbnRJbml0RGljdFxcJykgaXMgbm90IGFuIG9iamVjdCcpO1xuICAgIH1cblxuICAgIHZhciBidWJibGVzID0gZXZlbnRJbml0Q29uZmlnLmJ1YmJsZXM7XG4gICAgdmFyIGNhbmNlbGFibGUgPSBldmVudEluaXRDb25maWcuY2FuY2VsYWJsZTtcbiAgICB2YXIgY29kZSA9IGV2ZW50SW5pdENvbmZpZy5jb2RlO1xuICAgIHZhciByZWFzb24gPSBldmVudEluaXRDb25maWcucmVhc29uO1xuICAgIHZhciB3YXNDbGVhbiA9IGV2ZW50SW5pdENvbmZpZy53YXNDbGVhbjtcblxuICAgIHRoaXMudHlwZSA9IFN0cmluZyh0eXBlKTtcbiAgICB0aGlzLnRpbWVTdGFtcCA9IERhdGUubm93KCk7XG4gICAgdGhpcy50YXJnZXQgPSBudWxsO1xuICAgIHRoaXMuc3JjRWxlbWVudCA9IG51bGw7XG4gICAgdGhpcy5yZXR1cm5WYWx1ZSA9IHRydWU7XG4gICAgdGhpcy5pc1RydXN0ZWQgPSBmYWxzZTtcbiAgICB0aGlzLmV2ZW50UGhhc2UgPSAwO1xuICAgIHRoaXMuZGVmYXVsdFByZXZlbnRlZCA9IGZhbHNlO1xuICAgIHRoaXMuY3VycmVudFRhcmdldCA9IG51bGw7XG4gICAgdGhpcy5jYW5jZWxhYmxlID0gY2FuY2VsYWJsZSA/IEJvb2xlYW4oY2FuY2VsYWJsZSkgOiBmYWxzZTtcbiAgICB0aGlzLmNhbm5jZWxCdWJibGUgPSBmYWxzZTtcbiAgICB0aGlzLmJ1YmJsZXMgPSBidWJibGVzID8gQm9vbGVhbihidWJibGVzKSA6IGZhbHNlO1xuICAgIHRoaXMuY29kZSA9IHR5cGVvZiBjb2RlID09PSAnbnVtYmVyJyA/IE51bWJlcihjb2RlKSA6IDA7XG4gICAgdGhpcy5yZWFzb24gPSByZWFzb24gPyBTdHJpbmcocmVhc29uKSA6ICcnO1xuICAgIHRoaXMud2FzQ2xlYW4gPSB3YXNDbGVhbiA/IEJvb2xlYW4od2FzQ2xlYW4pIDogZmFsc2U7XG4gIH1cblxuICByZXR1cm4gQ2xvc2VFdmVudDtcbn0pKF9ldmVudFByb3RvdHlwZTJbJ2RlZmF1bHQnXSk7XG5cbmV4cG9ydHNbJ2RlZmF1bHQnXSA9IENsb3NlRXZlbnQ7XG5tb2R1bGUuZXhwb3J0cyA9IGV4cG9ydHNbJ2RlZmF1bHQnXTsiLCIvKlxuKiBUaGlzIGRlbGF5IGFsbG93cyB0aGUgdGhyZWFkIHRvIGZpbmlzaCBhc3NpZ25pbmcgaXRzIG9uKiBtZXRob2RzXG4qIGJlZm9yZSBpbnZva2luZyB0aGUgZGVsYXkgY2FsbGJhY2suIFRoaXMgaXMgcHVyZWx5IGEgdGltaW5nIGhhY2suXG4qIGh0dHA6Ly9nZWVrYWJ5dGUuYmxvZ3Nwb3QuY29tLzIwMTQvMDEvamF2YXNjcmlwdC1lZmZlY3Qtb2Ytc2V0dGluZy1zZXR0aW1lb3V0Lmh0bWxcbipcbiogQHBhcmFtIHtjYWxsYmFjazogZnVuY3Rpb259IHRoZSBjYWxsYmFjayB3aGljaCB3aWxsIGJlIGludm9rZWQgYWZ0ZXIgdGhlIHRpbWVvdXRcbiogQHBhcm1hIHtjb250ZXh0OiBvYmplY3R9IHRoZSBjb250ZXh0IGluIHdoaWNoIHRvIGludm9rZSB0aGUgZnVuY3Rpb25cbiovXG5cInVzZSBzdHJpY3RcIjtcblxuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7XG4gIHZhbHVlOiB0cnVlXG59KTtcbmZ1bmN0aW9uIGRlbGF5KGNhbGxiYWNrLCBjb250ZXh0KSB7XG4gIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZW91dCh0aW1lb3V0Q29udGV4dCkge1xuICAgIGNhbGxiYWNrLmNhbGwodGltZW91dENvbnRleHQpO1xuICB9LCA0LCBjb250ZXh0KTtcbn1cblxuZXhwb3J0c1tcImRlZmF1bHRcIl0gPSBkZWxheTtcbm1vZHVsZS5leHBvcnRzID0gZXhwb3J0c1tcImRlZmF1bHRcIl07IiwiJ3VzZSBzdHJpY3QnO1xuXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7XG4gIHZhbHVlOiB0cnVlXG59KTtcblxudmFyIF9jcmVhdGVDbGFzcyA9IChmdW5jdGlvbiAoKSB7IGZ1bmN0aW9uIGRlZmluZVByb3BlcnRpZXModGFyZ2V0LCBwcm9wcykgeyBmb3IgKHZhciBpID0gMDsgaSA8IHByb3BzLmxlbmd0aDsgaSsrKSB7IHZhciBkZXNjcmlwdG9yID0gcHJvcHNbaV07IGRlc2NyaXB0b3IuZW51bWVyYWJsZSA9IGRlc2NyaXB0b3IuZW51bWVyYWJsZSB8fCBmYWxzZTsgZGVzY3JpcHRvci5jb25maWd1cmFibGUgPSB0cnVlOyBpZiAoJ3ZhbHVlJyBpbiBkZXNjcmlwdG9yKSBkZXNjcmlwdG9yLndyaXRhYmxlID0gdHJ1ZTsgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRhcmdldCwgZGVzY3JpcHRvci5rZXksIGRlc2NyaXB0b3IpOyB9IH0gcmV0dXJuIGZ1bmN0aW9uIChDb25zdHJ1Y3RvciwgcHJvdG9Qcm9wcywgc3RhdGljUHJvcHMpIHsgaWYgKHByb3RvUHJvcHMpIGRlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IucHJvdG90eXBlLCBwcm90b1Byb3BzKTsgaWYgKHN0YXRpY1Byb3BzKSBkZWZpbmVQcm9wZXJ0aWVzKENvbnN0cnVjdG9yLCBzdGF0aWNQcm9wcyk7IHJldHVybiBDb25zdHJ1Y3RvcjsgfTsgfSkoKTtcblxuZnVuY3Rpb24gX2NsYXNzQ2FsbENoZWNrKGluc3RhbmNlLCBDb25zdHJ1Y3RvcikgeyBpZiAoIShpbnN0YW5jZSBpbnN0YW5jZW9mIENvbnN0cnVjdG9yKSkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdDYW5ub3QgY2FsbCBhIGNsYXNzIGFzIGEgZnVuY3Rpb24nKTsgfSB9XG5cbnZhciBFdmVudFByb3RvdHlwZSA9IChmdW5jdGlvbiAoKSB7XG4gIGZ1bmN0aW9uIEV2ZW50UHJvdG90eXBlKCkge1xuICAgIF9jbGFzc0NhbGxDaGVjayh0aGlzLCBFdmVudFByb3RvdHlwZSk7XG4gIH1cblxuICBfY3JlYXRlQ2xhc3MoRXZlbnRQcm90b3R5cGUsIFt7XG4gICAga2V5OiAnc3RvcFByb3BhZ2F0aW9uJyxcblxuICAgIC8vIE5vb3BzXG4gICAgdmFsdWU6IGZ1bmN0aW9uIHN0b3BQcm9wYWdhdGlvbigpIHt9XG4gIH0sIHtcbiAgICBrZXk6ICdzdG9wSW1tZWRpYXRlUHJvcGFnYXRpb24nLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBzdG9wSW1tZWRpYXRlUHJvcGFnYXRpb24oKSB7fVxuXG4gICAgLy8gaWYgbm8gYXJndW1lbnRzIGFyZSBwYXNzZWQgdGhlbiB0aGUgdHlwZSBpcyBzZXQgdG8gXCJ1bmRlZmluZWRcIiBvblxuICAgIC8vIGNocm9tZSBhbmQgc2FmYXJpLlxuICB9LCB7XG4gICAga2V5OiAnaW5pdEV2ZW50JyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gaW5pdEV2ZW50KCkge1xuICAgICAgdmFyIHR5cGUgPSBhcmd1bWVudHMubGVuZ3RoIDw9IDAgfHwgYXJndW1lbnRzWzBdID09PSB1bmRlZmluZWQgPyAndW5kZWZpbmVkJyA6IGFyZ3VtZW50c1swXTtcbiAgICAgIHZhciBidWJibGVzID0gYXJndW1lbnRzLmxlbmd0aCA8PSAxIHx8IGFyZ3VtZW50c1sxXSA9PT0gdW5kZWZpbmVkID8gZmFsc2UgOiBhcmd1bWVudHNbMV07XG4gICAgICB2YXIgY2FuY2VsYWJsZSA9IGFyZ3VtZW50cy5sZW5ndGggPD0gMiB8fCBhcmd1bWVudHNbMl0gPT09IHVuZGVmaW5lZCA/IGZhbHNlIDogYXJndW1lbnRzWzJdO1xuXG4gICAgICBPYmplY3QuYXNzaWduKHRoaXMsIHtcbiAgICAgICAgdHlwZTogU3RyaW5nKHR5cGUpLFxuICAgICAgICBidWJibGVzOiBCb29sZWFuKGJ1YmJsZXMpLFxuICAgICAgICBjYW5jZWxhYmxlOiBCb29sZWFuKGNhbmNlbGFibGUpXG4gICAgICB9KTtcbiAgICB9XG4gIH1dKTtcblxuICByZXR1cm4gRXZlbnRQcm90b3R5cGU7XG59KSgpO1xuXG5leHBvcnRzWydkZWZhdWx0J10gPSBFdmVudFByb3RvdHlwZTtcbm1vZHVsZS5leHBvcnRzID0gZXhwb3J0c1snZGVmYXVsdCddOyIsIid1c2Ugc3RyaWN0JztcblxuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywge1xuICB2YWx1ZTogdHJ1ZVxufSk7XG5cbnZhciBfZ2V0ID0gZnVuY3Rpb24gZ2V0KF94MiwgX3gzLCBfeDQpIHsgdmFyIF9hZ2FpbiA9IHRydWU7IF9mdW5jdGlvbjogd2hpbGUgKF9hZ2FpbikgeyB2YXIgb2JqZWN0ID0gX3gyLCBwcm9wZXJ0eSA9IF94MywgcmVjZWl2ZXIgPSBfeDQ7IF9hZ2FpbiA9IGZhbHNlOyBpZiAob2JqZWN0ID09PSBudWxsKSBvYmplY3QgPSBGdW5jdGlvbi5wcm90b3R5cGU7IHZhciBkZXNjID0gT2JqZWN0LmdldE93blByb3BlcnR5RGVzY3JpcHRvcihvYmplY3QsIHByb3BlcnR5KTsgaWYgKGRlc2MgPT09IHVuZGVmaW5lZCkgeyB2YXIgcGFyZW50ID0gT2JqZWN0LmdldFByb3RvdHlwZU9mKG9iamVjdCk7IGlmIChwYXJlbnQgPT09IG51bGwpIHsgcmV0dXJuIHVuZGVmaW5lZDsgfSBlbHNlIHsgX3gyID0gcGFyZW50OyBfeDMgPSBwcm9wZXJ0eTsgX3g0ID0gcmVjZWl2ZXI7IF9hZ2FpbiA9IHRydWU7IGRlc2MgPSBwYXJlbnQgPSB1bmRlZmluZWQ7IGNvbnRpbnVlIF9mdW5jdGlvbjsgfSB9IGVsc2UgaWYgKCd2YWx1ZScgaW4gZGVzYykgeyByZXR1cm4gZGVzYy52YWx1ZTsgfSBlbHNlIHsgdmFyIGdldHRlciA9IGRlc2MuZ2V0OyBpZiAoZ2V0dGVyID09PSB1bmRlZmluZWQpIHsgcmV0dXJuIHVuZGVmaW5lZDsgfSByZXR1cm4gZ2V0dGVyLmNhbGwocmVjZWl2ZXIpOyB9IH0gfTtcblxuZnVuY3Rpb24gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChvYmopIHsgcmV0dXJuIG9iaiAmJiBvYmouX19lc01vZHVsZSA/IG9iaiA6IHsgJ2RlZmF1bHQnOiBvYmogfTsgfVxuXG5mdW5jdGlvbiBfY2xhc3NDYWxsQ2hlY2soaW5zdGFuY2UsIENvbnN0cnVjdG9yKSB7IGlmICghKGluc3RhbmNlIGluc3RhbmNlb2YgQ29uc3RydWN0b3IpKSB7IHRocm93IG5ldyBUeXBlRXJyb3IoJ0Nhbm5vdCBjYWxsIGEgY2xhc3MgYXMgYSBmdW5jdGlvbicpOyB9IH1cblxuZnVuY3Rpb24gX2luaGVyaXRzKHN1YkNsYXNzLCBzdXBlckNsYXNzKSB7IGlmICh0eXBlb2Ygc3VwZXJDbGFzcyAhPT0gJ2Z1bmN0aW9uJyAmJiBzdXBlckNsYXNzICE9PSBudWxsKSB7IHRocm93IG5ldyBUeXBlRXJyb3IoJ1N1cGVyIGV4cHJlc3Npb24gbXVzdCBlaXRoZXIgYmUgbnVsbCBvciBhIGZ1bmN0aW9uLCBub3QgJyArIHR5cGVvZiBzdXBlckNsYXNzKTsgfSBzdWJDbGFzcy5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKHN1cGVyQ2xhc3MgJiYgc3VwZXJDbGFzcy5wcm90b3R5cGUsIHsgY29uc3RydWN0b3I6IHsgdmFsdWU6IHN1YkNsYXNzLCBlbnVtZXJhYmxlOiBmYWxzZSwgd3JpdGFibGU6IHRydWUsIGNvbmZpZ3VyYWJsZTogdHJ1ZSB9IH0pOyBpZiAoc3VwZXJDbGFzcykgT2JqZWN0LnNldFByb3RvdHlwZU9mID8gT2JqZWN0LnNldFByb3RvdHlwZU9mKHN1YkNsYXNzLCBzdXBlckNsYXNzKSA6IHN1YkNsYXNzLl9fcHJvdG9fXyA9IHN1cGVyQ2xhc3M7IH1cblxudmFyIF9ldmVudFByb3RvdHlwZSA9IHJlcXVpcmUoJy4vZXZlbnQtcHJvdG90eXBlJyk7XG5cbnZhciBfZXZlbnRQcm90b3R5cGUyID0gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChfZXZlbnRQcm90b3R5cGUpO1xuXG52YXIgRXZlbnQgPSAoZnVuY3Rpb24gKF9FdmVudFByb3RvdHlwZSkge1xuICBfaW5oZXJpdHMoRXZlbnQsIF9FdmVudFByb3RvdHlwZSk7XG5cbiAgZnVuY3Rpb24gRXZlbnQodHlwZSkge1xuICAgIHZhciBldmVudEluaXRDb25maWcgPSBhcmd1bWVudHMubGVuZ3RoIDw9IDEgfHwgYXJndW1lbnRzWzFdID09PSB1bmRlZmluZWQgPyB7fSA6IGFyZ3VtZW50c1sxXTtcblxuICAgIF9jbGFzc0NhbGxDaGVjayh0aGlzLCBFdmVudCk7XG5cbiAgICBfZ2V0KE9iamVjdC5nZXRQcm90b3R5cGVPZihFdmVudC5wcm90b3R5cGUpLCAnY29uc3RydWN0b3InLCB0aGlzKS5jYWxsKHRoaXMpO1xuXG4gICAgaWYgKCF0eXBlKSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdGYWlsZWQgdG8gY29uc3RydWN0IFxcJ0V2ZW50XFwnOiAxIGFyZ3VtZW50IHJlcXVpcmVkLCBidXQgb25seSAwIHByZXNlbnQuJyk7XG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiBldmVudEluaXRDb25maWcgIT09ICdvYmplY3QnKSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdGYWlsZWQgdG8gY29uc3RydWN0IFxcJ0V2ZW50XFwnOiBwYXJhbWV0ZXIgMiAoXFwnZXZlbnRJbml0RGljdFxcJykgaXMgbm90IGFuIG9iamVjdCcpO1xuICAgIH1cblxuICAgIHZhciBidWJibGVzID0gZXZlbnRJbml0Q29uZmlnLmJ1YmJsZXM7XG4gICAgdmFyIGNhbmNlbGFibGUgPSBldmVudEluaXRDb25maWcuY2FuY2VsYWJsZTtcblxuICAgIHRoaXMudHlwZSA9IFN0cmluZyh0eXBlKTtcbiAgICB0aGlzLnRpbWVTdGFtcCA9IERhdGUubm93KCk7XG4gICAgdGhpcy50YXJnZXQgPSBudWxsO1xuICAgIHRoaXMuc3JjRWxlbWVudCA9IG51bGw7XG4gICAgdGhpcy5yZXR1cm5WYWx1ZSA9IHRydWU7XG4gICAgdGhpcy5pc1RydXN0ZWQgPSBmYWxzZTtcbiAgICB0aGlzLmV2ZW50UGhhc2UgPSAwO1xuICAgIHRoaXMuZGVmYXVsdFByZXZlbnRlZCA9IGZhbHNlO1xuICAgIHRoaXMuY3VycmVudFRhcmdldCA9IG51bGw7XG4gICAgdGhpcy5jYW5jZWxhYmxlID0gY2FuY2VsYWJsZSA/IEJvb2xlYW4oY2FuY2VsYWJsZSkgOiBmYWxzZTtcbiAgICB0aGlzLmNhbm5jZWxCdWJibGUgPSBmYWxzZTtcbiAgICB0aGlzLmJ1YmJsZXMgPSBidWJibGVzID8gQm9vbGVhbihidWJibGVzKSA6IGZhbHNlO1xuICB9XG5cbiAgcmV0dXJuIEV2ZW50O1xufSkoX2V2ZW50UHJvdG90eXBlMlsnZGVmYXVsdCddKTtcblxuZXhwb3J0c1snZGVmYXVsdCddID0gRXZlbnQ7XG5tb2R1bGUuZXhwb3J0cyA9IGV4cG9ydHNbJ2RlZmF1bHQnXTsiLCIndXNlIHN0cmljdCc7XG5cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHtcbiAgdmFsdWU6IHRydWVcbn0pO1xuXG52YXIgX2dldCA9IGZ1bmN0aW9uIGdldChfeDIsIF94MywgX3g0KSB7IHZhciBfYWdhaW4gPSB0cnVlOyBfZnVuY3Rpb246IHdoaWxlIChfYWdhaW4pIHsgdmFyIG9iamVjdCA9IF94MiwgcHJvcGVydHkgPSBfeDMsIHJlY2VpdmVyID0gX3g0OyBfYWdhaW4gPSBmYWxzZTsgaWYgKG9iamVjdCA9PT0gbnVsbCkgb2JqZWN0ID0gRnVuY3Rpb24ucHJvdG90eXBlOyB2YXIgZGVzYyA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3Iob2JqZWN0LCBwcm9wZXJ0eSk7IGlmIChkZXNjID09PSB1bmRlZmluZWQpIHsgdmFyIHBhcmVudCA9IE9iamVjdC5nZXRQcm90b3R5cGVPZihvYmplY3QpOyBpZiAocGFyZW50ID09PSBudWxsKSB7IHJldHVybiB1bmRlZmluZWQ7IH0gZWxzZSB7IF94MiA9IHBhcmVudDsgX3gzID0gcHJvcGVydHk7IF94NCA9IHJlY2VpdmVyOyBfYWdhaW4gPSB0cnVlOyBkZXNjID0gcGFyZW50ID0gdW5kZWZpbmVkOyBjb250aW51ZSBfZnVuY3Rpb247IH0gfSBlbHNlIGlmICgndmFsdWUnIGluIGRlc2MpIHsgcmV0dXJuIGRlc2MudmFsdWU7IH0gZWxzZSB7IHZhciBnZXR0ZXIgPSBkZXNjLmdldDsgaWYgKGdldHRlciA9PT0gdW5kZWZpbmVkKSB7IHJldHVybiB1bmRlZmluZWQ7IH0gcmV0dXJuIGdldHRlci5jYWxsKHJlY2VpdmVyKTsgfSB9IH07XG5cbmZ1bmN0aW9uIF9pbnRlcm9wUmVxdWlyZURlZmF1bHQob2JqKSB7IHJldHVybiBvYmogJiYgb2JqLl9fZXNNb2R1bGUgPyBvYmogOiB7ICdkZWZhdWx0Jzogb2JqIH07IH1cblxuZnVuY3Rpb24gX2NsYXNzQ2FsbENoZWNrKGluc3RhbmNlLCBDb25zdHJ1Y3RvcikgeyBpZiAoIShpbnN0YW5jZSBpbnN0YW5jZW9mIENvbnN0cnVjdG9yKSkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdDYW5ub3QgY2FsbCBhIGNsYXNzIGFzIGEgZnVuY3Rpb24nKTsgfSB9XG5cbmZ1bmN0aW9uIF9pbmhlcml0cyhzdWJDbGFzcywgc3VwZXJDbGFzcykgeyBpZiAodHlwZW9mIHN1cGVyQ2xhc3MgIT09ICdmdW5jdGlvbicgJiYgc3VwZXJDbGFzcyAhPT0gbnVsbCkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdTdXBlciBleHByZXNzaW9uIG11c3QgZWl0aGVyIGJlIG51bGwgb3IgYSBmdW5jdGlvbiwgbm90ICcgKyB0eXBlb2Ygc3VwZXJDbGFzcyk7IH0gc3ViQ2xhc3MucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZShzdXBlckNsYXNzICYmIHN1cGVyQ2xhc3MucHJvdG90eXBlLCB7IGNvbnN0cnVjdG9yOiB7IHZhbHVlOiBzdWJDbGFzcywgZW51bWVyYWJsZTogZmFsc2UsIHdyaXRhYmxlOiB0cnVlLCBjb25maWd1cmFibGU6IHRydWUgfSB9KTsgaWYgKHN1cGVyQ2xhc3MpIE9iamVjdC5zZXRQcm90b3R5cGVPZiA/IE9iamVjdC5zZXRQcm90b3R5cGVPZihzdWJDbGFzcywgc3VwZXJDbGFzcykgOiBzdWJDbGFzcy5fX3Byb3RvX18gPSBzdXBlckNsYXNzOyB9XG5cbnZhciBfZXZlbnRQcm90b3R5cGUgPSByZXF1aXJlKCcuL2V2ZW50LXByb3RvdHlwZScpO1xuXG52YXIgX2V2ZW50UHJvdG90eXBlMiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2V2ZW50UHJvdG90eXBlKTtcblxudmFyIE1lc3NhZ2VFdmVudCA9IChmdW5jdGlvbiAoX0V2ZW50UHJvdG90eXBlKSB7XG4gIF9pbmhlcml0cyhNZXNzYWdlRXZlbnQsIF9FdmVudFByb3RvdHlwZSk7XG5cbiAgZnVuY3Rpb24gTWVzc2FnZUV2ZW50KHR5cGUpIHtcbiAgICB2YXIgZXZlbnRJbml0Q29uZmlnID0gYXJndW1lbnRzLmxlbmd0aCA8PSAxIHx8IGFyZ3VtZW50c1sxXSA9PT0gdW5kZWZpbmVkID8ge30gOiBhcmd1bWVudHNbMV07XG5cbiAgICBfY2xhc3NDYWxsQ2hlY2sodGhpcywgTWVzc2FnZUV2ZW50KTtcblxuICAgIF9nZXQoT2JqZWN0LmdldFByb3RvdHlwZU9mKE1lc3NhZ2VFdmVudC5wcm90b3R5cGUpLCAnY29uc3RydWN0b3InLCB0aGlzKS5jYWxsKHRoaXMpO1xuXG4gICAgaWYgKCF0eXBlKSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdGYWlsZWQgdG8gY29uc3RydWN0IFxcJ01lc3NhZ2VFdmVudFxcJzogMSBhcmd1bWVudCByZXF1aXJlZCwgYnV0IG9ubHkgMCBwcmVzZW50LicpO1xuICAgIH1cblxuICAgIGlmICh0eXBlb2YgZXZlbnRJbml0Q29uZmlnICE9PSAnb2JqZWN0Jykge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignRmFpbGVkIHRvIGNvbnN0cnVjdCBcXCdNZXNzYWdlRXZlbnRcXCc6IHBhcmFtZXRlciAyIChcXCdldmVudEluaXREaWN0XFwnKSBpcyBub3QgYW4gb2JqZWN0Jyk7XG4gICAgfVxuXG4gICAgdmFyIGJ1YmJsZXMgPSBldmVudEluaXRDb25maWcuYnViYmxlcztcbiAgICB2YXIgY2FuY2VsYWJsZSA9IGV2ZW50SW5pdENvbmZpZy5jYW5jZWxhYmxlO1xuICAgIHZhciBkYXRhID0gZXZlbnRJbml0Q29uZmlnLmRhdGE7XG4gICAgdmFyIG9yaWdpbiA9IGV2ZW50SW5pdENvbmZpZy5vcmlnaW47XG4gICAgdmFyIGxhc3RFdmVudElkID0gZXZlbnRJbml0Q29uZmlnLmxhc3RFdmVudElkO1xuICAgIHZhciBwb3J0cyA9IGV2ZW50SW5pdENvbmZpZy5wb3J0cztcblxuICAgIHRoaXMudHlwZSA9IFN0cmluZyh0eXBlKTtcbiAgICB0aGlzLnRpbWVTdGFtcCA9IERhdGUubm93KCk7XG4gICAgdGhpcy50YXJnZXQgPSBudWxsO1xuICAgIHRoaXMuc3JjRWxlbWVudCA9IG51bGw7XG4gICAgdGhpcy5yZXR1cm5WYWx1ZSA9IHRydWU7XG4gICAgdGhpcy5pc1RydXN0ZWQgPSBmYWxzZTtcbiAgICB0aGlzLmV2ZW50UGhhc2UgPSAwO1xuICAgIHRoaXMuZGVmYXVsdFByZXZlbnRlZCA9IGZhbHNlO1xuICAgIHRoaXMuY3VycmVudFRhcmdldCA9IG51bGw7XG4gICAgdGhpcy5jYW5jZWxhYmxlID0gY2FuY2VsYWJsZSA/IEJvb2xlYW4oY2FuY2VsYWJsZSkgOiBmYWxzZTtcbiAgICB0aGlzLmNhbm5jZWxCdWJibGUgPSBmYWxzZTtcbiAgICB0aGlzLmJ1YmJsZXMgPSBidWJibGVzID8gQm9vbGVhbihidWJibGVzKSA6IGZhbHNlO1xuICAgIHRoaXMub3JpZ2luID0gb3JpZ2luID8gU3RyaW5nKG9yaWdpbikgOiAnJztcbiAgICB0aGlzLnBvcnRzID0gdHlwZW9mIHBvcnRzID09PSAndW5kZWZpbmVkJyA/IG51bGwgOiBwb3J0cztcbiAgICB0aGlzLmRhdGEgPSB0eXBlb2YgZGF0YSA9PT0gJ3VuZGVmaW5lZCcgPyBudWxsIDogZGF0YTtcbiAgICB0aGlzLmxhc3RFdmVudElkID0gbGFzdEV2ZW50SWQgPyBTdHJpbmcobGFzdEV2ZW50SWQpIDogJyc7XG4gIH1cblxuICByZXR1cm4gTWVzc2FnZUV2ZW50O1xufSkoX2V2ZW50UHJvdG90eXBlMlsnZGVmYXVsdCddKTtcblxuZXhwb3J0c1snZGVmYXVsdCddID0gTWVzc2FnZUV2ZW50O1xubW9kdWxlLmV4cG9ydHMgPSBleHBvcnRzWydkZWZhdWx0J107IiwiJ3VzZSBzdHJpY3QnO1xuXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7XG4gIHZhbHVlOiB0cnVlXG59KTtcblxuZnVuY3Rpb24gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChvYmopIHsgcmV0dXJuIG9iaiAmJiBvYmouX19lc01vZHVsZSA/IG9iaiA6IHsgJ2RlZmF1bHQnOiBvYmogfTsgfVxuXG52YXIgX3NlcnZlciA9IHJlcXVpcmUoJy4vc2VydmVyJyk7XG5cbnZhciBfc2VydmVyMiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX3NlcnZlcik7XG5cbnZhciBfc29ja2V0SW8gPSByZXF1aXJlKCcuL3NvY2tldC1pbycpO1xuXG52YXIgX3NvY2tldElvMiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX3NvY2tldElvKTtcblxudmFyIF93ZWJzb2NrZXQgPSByZXF1aXJlKCcuL3dlYnNvY2tldCcpO1xuXG52YXIgX3dlYnNvY2tldDIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF93ZWJzb2NrZXQpO1xuXG5pZiAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgd2luZG93Lk1vY2tTZXJ2ZXIgPSBfc2VydmVyMlsnZGVmYXVsdCddO1xuICB3aW5kb3cuTW9ja1dlYlNvY2tldCA9IF93ZWJzb2NrZXQyWydkZWZhdWx0J107XG4gIHdpbmRvdy5Nb2NrU29ja2V0SU8gPSBfc29ja2V0SW8yWydkZWZhdWx0J107XG59XG5cbnZhciBTZXJ2ZXIgPSBfc2VydmVyMlsnZGVmYXVsdCddO1xuZXhwb3J0cy5TZXJ2ZXIgPSBTZXJ2ZXI7XG52YXIgV2ViU29ja2V0ID0gX3dlYnNvY2tldDJbJ2RlZmF1bHQnXTtcbmV4cG9ydHMuV2ViU29ja2V0ID0gV2ViU29ja2V0O1xudmFyIFNvY2tldElPID0gX3NvY2tldElvMlsnZGVmYXVsdCddO1xuZXhwb3J0cy5Tb2NrZXRJTyA9IFNvY2tldElPOyIsIid1c2Ugc3RyaWN0JztcblxuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywge1xuICB2YWx1ZTogdHJ1ZVxufSk7XG5cbnZhciBfY3JlYXRlQ2xhc3MgPSAoZnVuY3Rpb24gKCkgeyBmdW5jdGlvbiBkZWZpbmVQcm9wZXJ0aWVzKHRhcmdldCwgcHJvcHMpIHsgZm9yICh2YXIgaSA9IDA7IGkgPCBwcm9wcy5sZW5ndGg7IGkrKykgeyB2YXIgZGVzY3JpcHRvciA9IHByb3BzW2ldOyBkZXNjcmlwdG9yLmVudW1lcmFibGUgPSBkZXNjcmlwdG9yLmVudW1lcmFibGUgfHwgZmFsc2U7IGRlc2NyaXB0b3IuY29uZmlndXJhYmxlID0gdHJ1ZTsgaWYgKCd2YWx1ZScgaW4gZGVzY3JpcHRvcikgZGVzY3JpcHRvci53cml0YWJsZSA9IHRydWU7IE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0YXJnZXQsIGRlc2NyaXB0b3Iua2V5LCBkZXNjcmlwdG9yKTsgfSB9IHJldHVybiBmdW5jdGlvbiAoQ29uc3RydWN0b3IsIHByb3RvUHJvcHMsIHN0YXRpY1Byb3BzKSB7IGlmIChwcm90b1Byb3BzKSBkZWZpbmVQcm9wZXJ0aWVzKENvbnN0cnVjdG9yLnByb3RvdHlwZSwgcHJvdG9Qcm9wcyk7IGlmIChzdGF0aWNQcm9wcykgZGVmaW5lUHJvcGVydGllcyhDb25zdHJ1Y3Rvciwgc3RhdGljUHJvcHMpOyByZXR1cm4gQ29uc3RydWN0b3I7IH07IH0pKCk7XG5cbmZ1bmN0aW9uIF9jbGFzc0NhbGxDaGVjayhpbnN0YW5jZSwgQ29uc3RydWN0b3IpIHsgaWYgKCEoaW5zdGFuY2UgaW5zdGFuY2VvZiBDb25zdHJ1Y3RvcikpIHsgdGhyb3cgbmV3IFR5cGVFcnJvcignQ2Fubm90IGNhbGwgYSBjbGFzcyBhcyBhIGZ1bmN0aW9uJyk7IH0gfVxuXG52YXIgX2hlbHBlcnNBcnJheUhlbHBlcnMgPSByZXF1aXJlKCcuL2hlbHBlcnMvYXJyYXktaGVscGVycycpO1xuXG4vKlxuKiBUaGUgbmV0d29yayBicmlkZ2UgaXMgYSB3YXkgZm9yIHRoZSBtb2NrIHdlYnNvY2tldCBvYmplY3QgdG8gJ2NvbW11bmljYXRlJyB3aXRoXG4qIGFsbCBhdmFsaWJsZSBzZXJ2ZXJzLiBUaGlzIGlzIGEgc2luZ2xldG9uIG9iamVjdCBzbyBpdCBpcyBpbXBvcnRhbnQgdGhhdCB5b3VcbiogY2xlYW4gdXAgdXJsTWFwIHdoZW5ldmVyIHlvdSBhcmUgZmluaXNoZWQuXG4qL1xuXG52YXIgTmV0d29ya0JyaWRnZSA9IChmdW5jdGlvbiAoKSB7XG4gIGZ1bmN0aW9uIE5ldHdvcmtCcmlkZ2UoKSB7XG4gICAgX2NsYXNzQ2FsbENoZWNrKHRoaXMsIE5ldHdvcmtCcmlkZ2UpO1xuXG4gICAgdGhpcy51cmxNYXAgPSB7fTtcbiAgfVxuXG4gIC8qXG4gICogQXR0YWNoZXMgYSB3ZWJzb2NrZXQgb2JqZWN0IHRvIHRoZSB1cmxNYXAgaGFzaCBzbyB0aGF0IGl0IGNhbiBmaW5kIHRoZSBzZXJ2ZXJcbiAgKiBpdCBpcyBjb25uZWN0ZWQgdG8gYW5kIHRoZSBzZXJ2ZXIgaW4gdHVybiBjYW4gZmluZCBpdC5cbiAgKlxuICAqIEBwYXJhbSB7b2JqZWN0fSB3ZWJzb2NrZXQgLSB3ZWJzb2NrZXQgb2JqZWN0IHRvIGFkZCB0byB0aGUgdXJsTWFwIGhhc2hcbiAgKiBAcGFyYW0ge3N0cmluZ30gdXJsXG4gICovXG5cbiAgX2NyZWF0ZUNsYXNzKE5ldHdvcmtCcmlkZ2UsIFt7XG4gICAga2V5OiAnYXR0YWNoV2ViU29ja2V0JyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gYXR0YWNoV2ViU29ja2V0KHdlYnNvY2tldCwgdXJsKSB7XG4gICAgICB2YXIgY29ubmVjdGlvbkxvb2t1cCA9IHRoaXMudXJsTWFwW3VybF07XG5cbiAgICAgIGlmIChjb25uZWN0aW9uTG9va3VwICYmIGNvbm5lY3Rpb25Mb29rdXAuc2VydmVyICYmIGNvbm5lY3Rpb25Mb29rdXAud2Vic29ja2V0cy5pbmRleE9mKHdlYnNvY2tldCkgPT09IC0xKSB7XG4gICAgICAgIGNvbm5lY3Rpb25Mb29rdXAud2Vic29ja2V0cy5wdXNoKHdlYnNvY2tldCk7XG4gICAgICAgIHJldHVybiBjb25uZWN0aW9uTG9va3VwLnNlcnZlcjtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvKlxuICAgICogQXR0YWNoZXMgYSB3ZWJzb2NrZXQgdG8gYSByb29tXG4gICAgKi9cbiAgfSwge1xuICAgIGtleTogJ2FkZE1lbWJlcnNoaXBUb1Jvb20nLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBhZGRNZW1iZXJzaGlwVG9Sb29tKHdlYnNvY2tldCwgcm9vbSkge1xuICAgICAgdmFyIGNvbm5lY3Rpb25Mb29rdXAgPSB0aGlzLnVybE1hcFt3ZWJzb2NrZXQudXJsXTtcblxuICAgICAgaWYgKGNvbm5lY3Rpb25Mb29rdXAgJiYgY29ubmVjdGlvbkxvb2t1cC5zZXJ2ZXIgJiYgY29ubmVjdGlvbkxvb2t1cC53ZWJzb2NrZXRzLmluZGV4T2Yod2Vic29ja2V0KSAhPT0gLTEpIHtcbiAgICAgICAgaWYgKCFjb25uZWN0aW9uTG9va3VwLnJvb21NZW1iZXJzaGlwc1tyb29tXSkge1xuICAgICAgICAgIGNvbm5lY3Rpb25Mb29rdXAucm9vbU1lbWJlcnNoaXBzW3Jvb21dID0gW107XG4gICAgICAgIH1cblxuICAgICAgICBjb25uZWN0aW9uTG9va3VwLnJvb21NZW1iZXJzaGlwc1tyb29tXS5wdXNoKHdlYnNvY2tldCk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLypcbiAgICAqIEF0dGFjaGVzIGEgc2VydmVyIG9iamVjdCB0byB0aGUgdXJsTWFwIGhhc2ggc28gdGhhdCBpdCBjYW4gZmluZCBhIHdlYnNvY2tldHNcbiAgICAqIHdoaWNoIGFyZSBjb25uZWN0ZWQgdG8gaXQgYW5kIHNvIHRoYXQgd2Vic29ja2V0cyBjYW4gaW4gdHVybiBjYW4gZmluZCBpdC5cbiAgICAqXG4gICAgKiBAcGFyYW0ge29iamVjdH0gc2VydmVyIC0gc2VydmVyIG9iamVjdCB0byBhZGQgdG8gdGhlIHVybE1hcCBoYXNoXG4gICAgKiBAcGFyYW0ge3N0cmluZ30gdXJsXG4gICAgKi9cbiAgfSwge1xuICAgIGtleTogJ2F0dGFjaFNlcnZlcicsXG4gICAgdmFsdWU6IGZ1bmN0aW9uIGF0dGFjaFNlcnZlcihzZXJ2ZXIsIHVybCkge1xuICAgICAgdmFyIGNvbm5lY3Rpb25Mb29rdXAgPSB0aGlzLnVybE1hcFt1cmxdO1xuXG4gICAgICBpZiAoIWNvbm5lY3Rpb25Mb29rdXApIHtcbiAgICAgICAgdGhpcy51cmxNYXBbdXJsXSA9IHtcbiAgICAgICAgICBzZXJ2ZXI6IHNlcnZlcixcbiAgICAgICAgICB3ZWJzb2NrZXRzOiBbXSxcbiAgICAgICAgICByb29tTWVtYmVyc2hpcHM6IHt9XG4gICAgICAgIH07XG5cbiAgICAgICAgcmV0dXJuIHNlcnZlcjtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvKlxuICAgICogRmluZHMgdGhlIHNlcnZlciB3aGljaCBpcyAncnVubmluZycgb24gdGhlIGdpdmVuIHVybC5cbiAgICAqXG4gICAgKiBAcGFyYW0ge3N0cmluZ30gdXJsIC0gdGhlIHVybCB0byB1c2UgdG8gZmluZCB3aGljaCBzZXJ2ZXIgaXMgcnVubmluZyBvbiBpdFxuICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICdzZXJ2ZXJMb29rdXAnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBzZXJ2ZXJMb29rdXAodXJsKSB7XG4gICAgICB2YXIgY29ubmVjdGlvbkxvb2t1cCA9IHRoaXMudXJsTWFwW3VybF07XG5cbiAgICAgIGlmIChjb25uZWN0aW9uTG9va3VwKSB7XG4gICAgICAgIHJldHVybiBjb25uZWN0aW9uTG9va3VwLnNlcnZlcjtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvKlxuICAgICogRmluZHMgYWxsIHdlYnNvY2tldHMgd2hpY2ggaXMgJ2xpc3RlbmluZycgb24gdGhlIGdpdmVuIHVybC5cbiAgICAqXG4gICAgKiBAcGFyYW0ge3N0cmluZ30gdXJsIC0gdGhlIHVybCB0byB1c2UgdG8gZmluZCBhbGwgd2Vic29ja2V0cyB3aGljaCBhcmUgYXNzb2NpYXRlZCB3aXRoIGl0XG4gICAgKiBAcGFyYW0ge3N0cmluZ30gcm9vbSAtIGlmIGEgcm9vbSBpcyBwcm92aWRlZCwgd2lsbCBvbmx5IHJldHVybiBzb2NrZXRzIGluIHRoaXMgcm9vbVxuICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICd3ZWJzb2NrZXRzTG9va3VwJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gd2Vic29ja2V0c0xvb2t1cCh1cmwsIHJvb20pIHtcbiAgICAgIHZhciBjb25uZWN0aW9uTG9va3VwID0gdGhpcy51cmxNYXBbdXJsXTtcblxuICAgICAgaWYgKCFjb25uZWN0aW9uTG9va3VwKSB7XG4gICAgICAgIHJldHVybiBbXTtcbiAgICAgIH1cblxuICAgICAgaWYgKHJvb20pIHtcbiAgICAgICAgdmFyIG1lbWJlcnMgPSBjb25uZWN0aW9uTG9va3VwLnJvb21NZW1iZXJzaGlwc1tyb29tXTtcbiAgICAgICAgcmV0dXJuIG1lbWJlcnMgPyBtZW1iZXJzIDogW107XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBjb25uZWN0aW9uTG9va3VwLndlYnNvY2tldHM7XG4gICAgfVxuXG4gICAgLypcbiAgICAqIFJlbW92ZXMgdGhlIGVudHJ5IGFzc29jaWF0ZWQgd2l0aCB0aGUgdXJsLlxuICAgICpcbiAgICAqIEBwYXJhbSB7c3RyaW5nfSB1cmxcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAncmVtb3ZlU2VydmVyJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gcmVtb3ZlU2VydmVyKHVybCkge1xuICAgICAgZGVsZXRlIHRoaXMudXJsTWFwW3VybF07XG4gICAgfVxuXG4gICAgLypcbiAgICAqIFJlbW92ZXMgdGhlIGluZGl2aWR1YWwgd2Vic29ja2V0IGZyb20gdGhlIG1hcCBvZiBhc3NvY2lhdGVkIHdlYnNvY2tldHMuXG4gICAgKlxuICAgICogQHBhcmFtIHtvYmplY3R9IHdlYnNvY2tldCAtIHdlYnNvY2tldCBvYmplY3QgdG8gcmVtb3ZlIGZyb20gdGhlIHVybCBtYXBcbiAgICAqIEBwYXJhbSB7c3RyaW5nfSB1cmxcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAncmVtb3ZlV2ViU29ja2V0JyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gcmVtb3ZlV2ViU29ja2V0KHdlYnNvY2tldCwgdXJsKSB7XG4gICAgICB2YXIgY29ubmVjdGlvbkxvb2t1cCA9IHRoaXMudXJsTWFwW3VybF07XG5cbiAgICAgIGlmIChjb25uZWN0aW9uTG9va3VwKSB7XG4gICAgICAgIGNvbm5lY3Rpb25Mb29rdXAud2Vic29ja2V0cyA9ICgwLCBfaGVscGVyc0FycmF5SGVscGVycy5yZWplY3QpKGNvbm5lY3Rpb25Mb29rdXAud2Vic29ja2V0cywgZnVuY3Rpb24gKHNvY2tldCkge1xuICAgICAgICAgIHJldHVybiBzb2NrZXQgPT09IHdlYnNvY2tldDtcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLypcbiAgICAqIFJlbW92ZXMgYSB3ZWJzb2NrZXQgZnJvbSBhIHJvb21cbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAncmVtb3ZlTWVtYmVyc2hpcEZyb21Sb29tJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gcmVtb3ZlTWVtYmVyc2hpcEZyb21Sb29tKHdlYnNvY2tldCwgcm9vbSkge1xuICAgICAgdmFyIGNvbm5lY3Rpb25Mb29rdXAgPSB0aGlzLnVybE1hcFt3ZWJzb2NrZXQudXJsXTtcbiAgICAgIHZhciBtZW1iZXJzaGlwcyA9IGNvbm5lY3Rpb25Mb29rdXAucm9vbU1lbWJlcnNoaXBzW3Jvb21dO1xuXG4gICAgICBpZiAoY29ubmVjdGlvbkxvb2t1cCAmJiBtZW1iZXJzaGlwcyAhPT0gbnVsbCkge1xuICAgICAgICBjb25uZWN0aW9uTG9va3VwLnJvb21NZW1iZXJzaGlwc1tyb29tXSA9ICgwLCBfaGVscGVyc0FycmF5SGVscGVycy5yZWplY3QpKG1lbWJlcnNoaXBzLCBmdW5jdGlvbiAoc29ja2V0KSB7XG4gICAgICAgICAgcmV0dXJuIHNvY2tldCA9PT0gd2Vic29ja2V0O1xuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9XG4gIH1dKTtcblxuICByZXR1cm4gTmV0d29ya0JyaWRnZTtcbn0pKCk7XG5cbmV4cG9ydHNbJ2RlZmF1bHQnXSA9IG5ldyBOZXR3b3JrQnJpZGdlKCk7XG4vLyBOb3RlOiB0aGlzIGlzIGEgc2luZ2xldG9uXG5tb2R1bGUuZXhwb3J0cyA9IGV4cG9ydHNbJ2RlZmF1bHQnXTsiLCIndXNlIHN0cmljdCc7XG5cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHtcbiAgdmFsdWU6IHRydWVcbn0pO1xuXG52YXIgX2NyZWF0ZUNsYXNzID0gKGZ1bmN0aW9uICgpIHsgZnVuY3Rpb24gZGVmaW5lUHJvcGVydGllcyh0YXJnZXQsIHByb3BzKSB7IGZvciAodmFyIGkgPSAwOyBpIDwgcHJvcHMubGVuZ3RoOyBpKyspIHsgdmFyIGRlc2NyaXB0b3IgPSBwcm9wc1tpXTsgZGVzY3JpcHRvci5lbnVtZXJhYmxlID0gZGVzY3JpcHRvci5lbnVtZXJhYmxlIHx8IGZhbHNlOyBkZXNjcmlwdG9yLmNvbmZpZ3VyYWJsZSA9IHRydWU7IGlmICgndmFsdWUnIGluIGRlc2NyaXB0b3IpIGRlc2NyaXB0b3Iud3JpdGFibGUgPSB0cnVlOyBPYmplY3QuZGVmaW5lUHJvcGVydHkodGFyZ2V0LCBkZXNjcmlwdG9yLmtleSwgZGVzY3JpcHRvcik7IH0gfSByZXR1cm4gZnVuY3Rpb24gKENvbnN0cnVjdG9yLCBwcm90b1Byb3BzLCBzdGF0aWNQcm9wcykgeyBpZiAocHJvdG9Qcm9wcykgZGVmaW5lUHJvcGVydGllcyhDb25zdHJ1Y3Rvci5wcm90b3R5cGUsIHByb3RvUHJvcHMpOyBpZiAoc3RhdGljUHJvcHMpIGRlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IsIHN0YXRpY1Byb3BzKTsgcmV0dXJuIENvbnN0cnVjdG9yOyB9OyB9KSgpO1xuXG52YXIgX2dldCA9IGZ1bmN0aW9uIGdldChfeDQsIF94NSwgX3g2KSB7IHZhciBfYWdhaW4gPSB0cnVlOyBfZnVuY3Rpb246IHdoaWxlIChfYWdhaW4pIHsgdmFyIG9iamVjdCA9IF94NCwgcHJvcGVydHkgPSBfeDUsIHJlY2VpdmVyID0gX3g2OyBfYWdhaW4gPSBmYWxzZTsgaWYgKG9iamVjdCA9PT0gbnVsbCkgb2JqZWN0ID0gRnVuY3Rpb24ucHJvdG90eXBlOyB2YXIgZGVzYyA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3Iob2JqZWN0LCBwcm9wZXJ0eSk7IGlmIChkZXNjID09PSB1bmRlZmluZWQpIHsgdmFyIHBhcmVudCA9IE9iamVjdC5nZXRQcm90b3R5cGVPZihvYmplY3QpOyBpZiAocGFyZW50ID09PSBudWxsKSB7IHJldHVybiB1bmRlZmluZWQ7IH0gZWxzZSB7IF94NCA9IHBhcmVudDsgX3g1ID0gcHJvcGVydHk7IF94NiA9IHJlY2VpdmVyOyBfYWdhaW4gPSB0cnVlOyBkZXNjID0gcGFyZW50ID0gdW5kZWZpbmVkOyBjb250aW51ZSBfZnVuY3Rpb247IH0gfSBlbHNlIGlmICgndmFsdWUnIGluIGRlc2MpIHsgcmV0dXJuIGRlc2MudmFsdWU7IH0gZWxzZSB7IHZhciBnZXR0ZXIgPSBkZXNjLmdldDsgaWYgKGdldHRlciA9PT0gdW5kZWZpbmVkKSB7IHJldHVybiB1bmRlZmluZWQ7IH0gcmV0dXJuIGdldHRlci5jYWxsKHJlY2VpdmVyKTsgfSB9IH07XG5cbmZ1bmN0aW9uIF9pbnRlcm9wUmVxdWlyZURlZmF1bHQob2JqKSB7IHJldHVybiBvYmogJiYgb2JqLl9fZXNNb2R1bGUgPyBvYmogOiB7ICdkZWZhdWx0Jzogb2JqIH07IH1cblxuZnVuY3Rpb24gX2NsYXNzQ2FsbENoZWNrKGluc3RhbmNlLCBDb25zdHJ1Y3RvcikgeyBpZiAoIShpbnN0YW5jZSBpbnN0YW5jZW9mIENvbnN0cnVjdG9yKSkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdDYW5ub3QgY2FsbCBhIGNsYXNzIGFzIGEgZnVuY3Rpb24nKTsgfSB9XG5cbmZ1bmN0aW9uIF9pbmhlcml0cyhzdWJDbGFzcywgc3VwZXJDbGFzcykgeyBpZiAodHlwZW9mIHN1cGVyQ2xhc3MgIT09ICdmdW5jdGlvbicgJiYgc3VwZXJDbGFzcyAhPT0gbnVsbCkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKCdTdXBlciBleHByZXNzaW9uIG11c3QgZWl0aGVyIGJlIG51bGwgb3IgYSBmdW5jdGlvbiwgbm90ICcgKyB0eXBlb2Ygc3VwZXJDbGFzcyk7IH0gc3ViQ2xhc3MucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZShzdXBlckNsYXNzICYmIHN1cGVyQ2xhc3MucHJvdG90eXBlLCB7IGNvbnN0cnVjdG9yOiB7IHZhbHVlOiBzdWJDbGFzcywgZW51bWVyYWJsZTogZmFsc2UsIHdyaXRhYmxlOiB0cnVlLCBjb25maWd1cmFibGU6IHRydWUgfSB9KTsgaWYgKHN1cGVyQ2xhc3MpIE9iamVjdC5zZXRQcm90b3R5cGVPZiA/IE9iamVjdC5zZXRQcm90b3R5cGVPZihzdWJDbGFzcywgc3VwZXJDbGFzcykgOiBzdWJDbGFzcy5fX3Byb3RvX18gPSBzdXBlckNsYXNzOyB9XG5cbnZhciBfdXJpanMgPSByZXF1aXJlKCd1cmlqcycpO1xuXG52YXIgX3VyaWpzMiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX3VyaWpzKTtcblxudmFyIF93ZWJzb2NrZXQgPSByZXF1aXJlKCcuL3dlYnNvY2tldCcpO1xuXG52YXIgX3dlYnNvY2tldDIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF93ZWJzb2NrZXQpO1xuXG52YXIgX2V2ZW50VGFyZ2V0ID0gcmVxdWlyZSgnLi9ldmVudC10YXJnZXQnKTtcblxudmFyIF9ldmVudFRhcmdldDIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF9ldmVudFRhcmdldCk7XG5cbnZhciBfbmV0d29ya0JyaWRnZSA9IHJlcXVpcmUoJy4vbmV0d29yay1icmlkZ2UnKTtcblxudmFyIF9uZXR3b3JrQnJpZGdlMiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX25ldHdvcmtCcmlkZ2UpO1xuXG52YXIgX2hlbHBlcnNDbG9zZUNvZGVzID0gcmVxdWlyZSgnLi9oZWxwZXJzL2Nsb3NlLWNvZGVzJyk7XG5cbnZhciBfaGVscGVyc0Nsb3NlQ29kZXMyID0gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChfaGVscGVyc0Nsb3NlQ29kZXMpO1xuXG52YXIgX2V2ZW50RmFjdG9yeSA9IHJlcXVpcmUoJy4vZXZlbnQtZmFjdG9yeScpO1xuXG4vKlxuKiBodHRwczovL2dpdGh1Yi5jb20vd2Vic29ja2V0cy93cyNzZXJ2ZXItZXhhbXBsZVxuKi9cblxudmFyIFNlcnZlciA9IChmdW5jdGlvbiAoX0V2ZW50VGFyZ2V0KSB7XG4gIF9pbmhlcml0cyhTZXJ2ZXIsIF9FdmVudFRhcmdldCk7XG5cbiAgLypcbiAgKiBAcGFyYW0ge3N0cmluZ30gdXJsXG4gICovXG5cbiAgZnVuY3Rpb24gU2VydmVyKHVybCkge1xuICAgIF9jbGFzc0NhbGxDaGVjayh0aGlzLCBTZXJ2ZXIpO1xuXG4gICAgX2dldChPYmplY3QuZ2V0UHJvdG90eXBlT2YoU2VydmVyLnByb3RvdHlwZSksICdjb25zdHJ1Y3RvcicsIHRoaXMpLmNhbGwodGhpcyk7XG4gICAgdGhpcy51cmwgPSAoMCwgX3VyaWpzMlsnZGVmYXVsdCddKSh1cmwpLnRvU3RyaW5nKCk7XG4gICAgdmFyIHNlcnZlciA9IF9uZXR3b3JrQnJpZGdlMlsnZGVmYXVsdCddLmF0dGFjaFNlcnZlcih0aGlzLCB0aGlzLnVybCk7XG5cbiAgICBpZiAoIXNlcnZlcikge1xuICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KCgwLCBfZXZlbnRGYWN0b3J5LmNyZWF0ZUV2ZW50KSh7IHR5cGU6ICdlcnJvcicgfSkpO1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdBIG1vY2sgc2VydmVyIGlzIGFscmVhZHkgbGlzdGVuaW5nIG9uIHRoaXMgdXJsJyk7XG4gICAgfVxuICB9XG5cbiAgLypcbiAgICogQWx0ZXJuYXRpdmUgY29uc3RydWN0b3IgdG8gc3VwcG9ydCBuYW1lc3BhY2VzIGluIHNvY2tldC5pb1xuICAgKlxuICAgKiBodHRwOi8vc29ja2V0LmlvL2RvY3Mvcm9vbXMtYW5kLW5hbWVzcGFjZXMvI2N1c3RvbS1uYW1lc3BhY2VzXG4gICAqL1xuXG4gIC8qXG4gICogVGhpcyBpcyB0aGUgbWFpbiBmdW5jdGlvbiBmb3IgdGhlIG1vY2sgc2VydmVyIHRvIHN1YnNjcmliZSB0byB0aGUgb24gZXZlbnRzLlxuICAqXG4gICogaWU6IG1vY2tTZXJ2ZXIub24oJ2Nvbm5lY3Rpb24nLCBmdW5jdGlvbigpIHsgY29uc29sZS5sb2coJ2EgbW9jayBjbGllbnQgY29ubmVjdGVkJyk7IH0pO1xuICAqXG4gICogQHBhcmFtIHtzdHJpbmd9IHR5cGUgLSBUaGUgZXZlbnQga2V5IHRvIHN1YnNjcmliZSB0by4gVmFsaWQga2V5cyBhcmU6IGNvbm5lY3Rpb24sIG1lc3NhZ2UsIGFuZCBjbG9zZS5cbiAgKiBAcGFyYW0ge2Z1bmN0aW9ufSBjYWxsYmFjayAtIFRoZSBjYWxsYmFjayB3aGljaCBzaG91bGQgYmUgY2FsbGVkIHdoZW4gYSBjZXJ0YWluIGV2ZW50IGlzIGZpcmVkLlxuICAqL1xuXG4gIF9jcmVhdGVDbGFzcyhTZXJ2ZXIsIFt7XG4gICAga2V5OiAnb24nLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBvbih0eXBlLCBjYWxsYmFjaykge1xuICAgICAgdGhpcy5hZGRFdmVudExpc3RlbmVyKHR5cGUsIGNhbGxiYWNrKTtcbiAgICB9XG5cbiAgICAvKlxuICAgICogVGhpcyBzZW5kIGZ1bmN0aW9uIHdpbGwgbm90aWZ5IGFsbCBtb2NrIGNsaWVudHMgdmlhIHRoZWlyIG9ubWVzc2FnZSBjYWxsYmFja3MgdGhhdCB0aGUgc2VydmVyXG4gICAgKiBoYXMgYSBtZXNzYWdlIGZvciB0aGVtLlxuICAgICpcbiAgICAqIEBwYXJhbSB7Kn0gZGF0YSAtIEFueSBqYXZhc2NyaXB0IG9iamVjdCB3aGljaCB3aWxsIGJlIGNyYWZ0ZWQgaW50byBhIE1lc3NhZ2VPYmplY3QuXG4gICAgKi9cbiAgfSwge1xuICAgIGtleTogJ3NlbmQnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBzZW5kKGRhdGEpIHtcbiAgICAgIHZhciBvcHRpb25zID0gYXJndW1lbnRzLmxlbmd0aCA8PSAxIHx8IGFyZ3VtZW50c1sxXSA9PT0gdW5kZWZpbmVkID8ge30gOiBhcmd1bWVudHNbMV07XG5cbiAgICAgIHRoaXMuZW1pdCgnbWVzc2FnZScsIGRhdGEsIG9wdGlvbnMpO1xuICAgIH1cblxuICAgIC8qXG4gICAgKiBTZW5kcyBhIGdlbmVyaWMgbWVzc2FnZSBldmVudCB0byBhbGwgbW9jayBjbGllbnRzLlxuICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICdlbWl0JyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gZW1pdChldmVudCwgZGF0YSkge1xuICAgICAgdmFyIF90aGlzMiA9IHRoaXM7XG5cbiAgICAgIHZhciBvcHRpb25zID0gYXJndW1lbnRzLmxlbmd0aCA8PSAyIHx8IGFyZ3VtZW50c1syXSA9PT0gdW5kZWZpbmVkID8ge30gOiBhcmd1bWVudHNbMl07XG4gICAgICB2YXIgd2Vic29ja2V0cyA9IG9wdGlvbnMud2Vic29ja2V0cztcblxuICAgICAgaWYgKCF3ZWJzb2NrZXRzKSB7XG4gICAgICAgIHdlYnNvY2tldHMgPSBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS53ZWJzb2NrZXRzTG9va3VwKHRoaXMudXJsKTtcbiAgICAgIH1cblxuICAgICAgd2Vic29ja2V0cy5mb3JFYWNoKGZ1bmN0aW9uIChzb2NrZXQpIHtcbiAgICAgICAgc29ja2V0LmRpc3BhdGNoRXZlbnQoKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlTWVzc2FnZUV2ZW50KSh7XG4gICAgICAgICAgdHlwZTogZXZlbnQsXG4gICAgICAgICAgZGF0YTogZGF0YSxcbiAgICAgICAgICBvcmlnaW46IF90aGlzMi51cmwsXG4gICAgICAgICAgdGFyZ2V0OiBzb2NrZXRcbiAgICAgICAgfSkpO1xuICAgICAgfSk7XG4gICAgfVxuXG4gICAgLypcbiAgICAqIENsb3NlcyB0aGUgY29ubmVjdGlvbiBhbmQgdHJpZ2dlcnMgdGhlIG9uY2xvc2UgbWV0aG9kIG9mIGFsbCBsaXN0ZW5pbmdcbiAgICAqIHdlYnNvY2tldHMuIEFmdGVyIHRoYXQgaXQgcmVtb3ZlcyBpdHNlbGYgZnJvbSB0aGUgdXJsTWFwIHNvIGFub3RoZXIgc2VydmVyXG4gICAgKiBjb3VsZCBhZGQgaXRzZWxmIHRvIHRoZSB1cmwuXG4gICAgKlxuICAgICogQHBhcmFtIHtvYmplY3R9IG9wdGlvbnNcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAnY2xvc2UnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBjbG9zZSgpIHtcbiAgICAgIHZhciBvcHRpb25zID0gYXJndW1lbnRzLmxlbmd0aCA8PSAwIHx8IGFyZ3VtZW50c1swXSA9PT0gdW5kZWZpbmVkID8ge30gOiBhcmd1bWVudHNbMF07XG4gICAgICB2YXIgY29kZSA9IG9wdGlvbnMuY29kZTtcbiAgICAgIHZhciByZWFzb24gPSBvcHRpb25zLnJlYXNvbjtcbiAgICAgIHZhciB3YXNDbGVhbiA9IG9wdGlvbnMud2FzQ2xlYW47XG5cbiAgICAgIHZhciBsaXN0ZW5lcnMgPSBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS53ZWJzb2NrZXRzTG9va3VwKHRoaXMudXJsKTtcblxuICAgICAgbGlzdGVuZXJzLmZvckVhY2goZnVuY3Rpb24gKHNvY2tldCkge1xuICAgICAgICBzb2NrZXQucmVhZHlTdGF0ZSA9IF93ZWJzb2NrZXQyWydkZWZhdWx0J10uQ0xPU0U7XG4gICAgICAgIHNvY2tldC5kaXNwYXRjaEV2ZW50KCgwLCBfZXZlbnRGYWN0b3J5LmNyZWF0ZUNsb3NlRXZlbnQpKHtcbiAgICAgICAgICB0eXBlOiAnY2xvc2UnLFxuICAgICAgICAgIHRhcmdldDogc29ja2V0LFxuICAgICAgICAgIGNvZGU6IGNvZGUgfHwgX2hlbHBlcnNDbG9zZUNvZGVzMlsnZGVmYXVsdCddLkNMT1NFX05PUk1BTCxcbiAgICAgICAgICByZWFzb246IHJlYXNvbiB8fCAnJyxcbiAgICAgICAgICB3YXNDbGVhbjogd2FzQ2xlYW5cbiAgICAgICAgfSkpO1xuICAgICAgfSk7XG5cbiAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudCgoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVDbG9zZUV2ZW50KSh7IHR5cGU6ICdjbG9zZScgfSksIHRoaXMpO1xuICAgICAgX25ldHdvcmtCcmlkZ2UyWydkZWZhdWx0J10ucmVtb3ZlU2VydmVyKHRoaXMudXJsKTtcbiAgICB9XG5cbiAgICAvKlxuICAgICogUmV0dXJucyBhbiBhcnJheSBvZiB3ZWJzb2NrZXRzIHdoaWNoIGFyZSBsaXN0ZW5pbmcgdG8gdGhpcyBzZXJ2ZXJcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAnY2xpZW50cycsXG4gICAgdmFsdWU6IGZ1bmN0aW9uIGNsaWVudHMoKSB7XG4gICAgICByZXR1cm4gX25ldHdvcmtCcmlkZ2UyWydkZWZhdWx0J10ud2Vic29ja2V0c0xvb2t1cCh0aGlzLnVybCk7XG4gICAgfVxuXG4gICAgLypcbiAgICAqIFByZXBhcmVzIGEgbWV0aG9kIHRvIHN1Ym1pdCBhbiBldmVudCB0byBtZW1iZXJzIG9mIHRoZSByb29tXG4gICAgKlxuICAgICogZS5nLiBzZXJ2ZXIudG8oJ215LXJvb20nKS5lbWl0KCdoaSEnKTtcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAndG8nLFxuICAgIHZhbHVlOiBmdW5jdGlvbiB0byhyb29tKSB7XG4gICAgICB2YXIgX3RoaXMgPSB0aGlzO1xuICAgICAgdmFyIHdlYnNvY2tldHMgPSBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS53ZWJzb2NrZXRzTG9va3VwKHRoaXMudXJsLCByb29tKTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIGVtaXQ6IGZ1bmN0aW9uIGVtaXQoZXZlbnQsIGRhdGEpIHtcbiAgICAgICAgICBfdGhpcy5lbWl0KGV2ZW50LCBkYXRhLCB7IHdlYnNvY2tldHM6IHdlYnNvY2tldHMgfSk7XG4gICAgICAgIH1cbiAgICAgIH07XG4gICAgfVxuICB9XSk7XG5cbiAgcmV0dXJuIFNlcnZlcjtcbn0pKF9ldmVudFRhcmdldDJbJ2RlZmF1bHQnXSk7XG5cblNlcnZlci5vZiA9IGZ1bmN0aW9uIG9mKHVybCkge1xuICByZXR1cm4gbmV3IFNlcnZlcih1cmwpO1xufTtcblxuZXhwb3J0c1snZGVmYXVsdCddID0gU2VydmVyO1xubW9kdWxlLmV4cG9ydHMgPSBleHBvcnRzWydkZWZhdWx0J107IiwiJ3VzZSBzdHJpY3QnO1xuXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7XG4gIHZhbHVlOiB0cnVlXG59KTtcblxudmFyIF9jcmVhdGVDbGFzcyA9IChmdW5jdGlvbiAoKSB7IGZ1bmN0aW9uIGRlZmluZVByb3BlcnRpZXModGFyZ2V0LCBwcm9wcykgeyBmb3IgKHZhciBpID0gMDsgaSA8IHByb3BzLmxlbmd0aDsgaSsrKSB7IHZhciBkZXNjcmlwdG9yID0gcHJvcHNbaV07IGRlc2NyaXB0b3IuZW51bWVyYWJsZSA9IGRlc2NyaXB0b3IuZW51bWVyYWJsZSB8fCBmYWxzZTsgZGVzY3JpcHRvci5jb25maWd1cmFibGUgPSB0cnVlOyBpZiAoJ3ZhbHVlJyBpbiBkZXNjcmlwdG9yKSBkZXNjcmlwdG9yLndyaXRhYmxlID0gdHJ1ZTsgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRhcmdldCwgZGVzY3JpcHRvci5rZXksIGRlc2NyaXB0b3IpOyB9IH0gcmV0dXJuIGZ1bmN0aW9uIChDb25zdHJ1Y3RvciwgcHJvdG9Qcm9wcywgc3RhdGljUHJvcHMpIHsgaWYgKHByb3RvUHJvcHMpIGRlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IucHJvdG90eXBlLCBwcm90b1Byb3BzKTsgaWYgKHN0YXRpY1Byb3BzKSBkZWZpbmVQcm9wZXJ0aWVzKENvbnN0cnVjdG9yLCBzdGF0aWNQcm9wcyk7IHJldHVybiBDb25zdHJ1Y3RvcjsgfTsgfSkoKTtcblxudmFyIF9nZXQgPSBmdW5jdGlvbiBnZXQoX3gzLCBfeDQsIF94NSkgeyB2YXIgX2FnYWluID0gdHJ1ZTsgX2Z1bmN0aW9uOiB3aGlsZSAoX2FnYWluKSB7IHZhciBvYmplY3QgPSBfeDMsIHByb3BlcnR5ID0gX3g0LCByZWNlaXZlciA9IF94NTsgX2FnYWluID0gZmFsc2U7IGlmIChvYmplY3QgPT09IG51bGwpIG9iamVjdCA9IEZ1bmN0aW9uLnByb3RvdHlwZTsgdmFyIGRlc2MgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKG9iamVjdCwgcHJvcGVydHkpOyBpZiAoZGVzYyA9PT0gdW5kZWZpbmVkKSB7IHZhciBwYXJlbnQgPSBPYmplY3QuZ2V0UHJvdG90eXBlT2Yob2JqZWN0KTsgaWYgKHBhcmVudCA9PT0gbnVsbCkgeyByZXR1cm4gdW5kZWZpbmVkOyB9IGVsc2UgeyBfeDMgPSBwYXJlbnQ7IF94NCA9IHByb3BlcnR5OyBfeDUgPSByZWNlaXZlcjsgX2FnYWluID0gdHJ1ZTsgZGVzYyA9IHBhcmVudCA9IHVuZGVmaW5lZDsgY29udGludWUgX2Z1bmN0aW9uOyB9IH0gZWxzZSBpZiAoJ3ZhbHVlJyBpbiBkZXNjKSB7IHJldHVybiBkZXNjLnZhbHVlOyB9IGVsc2UgeyB2YXIgZ2V0dGVyID0gZGVzYy5nZXQ7IGlmIChnZXR0ZXIgPT09IHVuZGVmaW5lZCkgeyByZXR1cm4gdW5kZWZpbmVkOyB9IHJldHVybiBnZXR0ZXIuY2FsbChyZWNlaXZlcik7IH0gfSB9O1xuXG5mdW5jdGlvbiBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KG9iaikgeyByZXR1cm4gb2JqICYmIG9iai5fX2VzTW9kdWxlID8gb2JqIDogeyAnZGVmYXVsdCc6IG9iaiB9OyB9XG5cbmZ1bmN0aW9uIF9jbGFzc0NhbGxDaGVjayhpbnN0YW5jZSwgQ29uc3RydWN0b3IpIHsgaWYgKCEoaW5zdGFuY2UgaW5zdGFuY2VvZiBDb25zdHJ1Y3RvcikpIHsgdGhyb3cgbmV3IFR5cGVFcnJvcignQ2Fubm90IGNhbGwgYSBjbGFzcyBhcyBhIGZ1bmN0aW9uJyk7IH0gfVxuXG5mdW5jdGlvbiBfaW5oZXJpdHMoc3ViQ2xhc3MsIHN1cGVyQ2xhc3MpIHsgaWYgKHR5cGVvZiBzdXBlckNsYXNzICE9PSAnZnVuY3Rpb24nICYmIHN1cGVyQ2xhc3MgIT09IG51bGwpIHsgdGhyb3cgbmV3IFR5cGVFcnJvcignU3VwZXIgZXhwcmVzc2lvbiBtdXN0IGVpdGhlciBiZSBudWxsIG9yIGEgZnVuY3Rpb24sIG5vdCAnICsgdHlwZW9mIHN1cGVyQ2xhc3MpOyB9IHN1YkNsYXNzLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoc3VwZXJDbGFzcyAmJiBzdXBlckNsYXNzLnByb3RvdHlwZSwgeyBjb25zdHJ1Y3RvcjogeyB2YWx1ZTogc3ViQ2xhc3MsIGVudW1lcmFibGU6IGZhbHNlLCB3cml0YWJsZTogdHJ1ZSwgY29uZmlndXJhYmxlOiB0cnVlIH0gfSk7IGlmIChzdXBlckNsYXNzKSBPYmplY3Quc2V0UHJvdG90eXBlT2YgPyBPYmplY3Quc2V0UHJvdG90eXBlT2Yoc3ViQ2xhc3MsIHN1cGVyQ2xhc3MpIDogc3ViQ2xhc3MuX19wcm90b19fID0gc3VwZXJDbGFzczsgfVxuXG52YXIgX3VyaWpzID0gcmVxdWlyZSgndXJpanMnKTtcblxudmFyIF91cmlqczIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF91cmlqcyk7XG5cbnZhciBfaGVscGVyc0RlbGF5ID0gcmVxdWlyZSgnLi9oZWxwZXJzL2RlbGF5Jyk7XG5cbnZhciBfaGVscGVyc0RlbGF5MiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2hlbHBlcnNEZWxheSk7XG5cbnZhciBfZXZlbnRUYXJnZXQgPSByZXF1aXJlKCcuL2V2ZW50LXRhcmdldCcpO1xuXG52YXIgX2V2ZW50VGFyZ2V0MiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2V2ZW50VGFyZ2V0KTtcblxudmFyIF9uZXR3b3JrQnJpZGdlID0gcmVxdWlyZSgnLi9uZXR3b3JrLWJyaWRnZScpO1xuXG52YXIgX25ldHdvcmtCcmlkZ2UyID0gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChfbmV0d29ya0JyaWRnZSk7XG5cbnZhciBfaGVscGVyc0Nsb3NlQ29kZXMgPSByZXF1aXJlKCcuL2hlbHBlcnMvY2xvc2UtY29kZXMnKTtcblxudmFyIF9oZWxwZXJzQ2xvc2VDb2RlczIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF9oZWxwZXJzQ2xvc2VDb2Rlcyk7XG5cbnZhciBfZXZlbnRGYWN0b3J5ID0gcmVxdWlyZSgnLi9ldmVudC1mYWN0b3J5Jyk7XG5cbi8qXG4qIFRoZSBzb2NrZXQtaW8gY2xhc3MgaXMgZGVzaWduZWQgdG8gbWltaWNrIHRoZSByZWFsIEFQSSBhcyBjbG9zZWx5IGFzIHBvc3NpYmxlLlxuKlxuKiBodHRwOi8vc29ja2V0LmlvL2RvY3MvXG4qL1xuXG52YXIgU29ja2V0SU8gPSAoZnVuY3Rpb24gKF9FdmVudFRhcmdldCkge1xuICBfaW5oZXJpdHMoU29ja2V0SU8sIF9FdmVudFRhcmdldCk7XG5cbiAgLypcbiAgKiBAcGFyYW0ge3N0cmluZ30gdXJsXG4gICovXG5cbiAgZnVuY3Rpb24gU29ja2V0SU8oKSB7XG4gICAgdmFyIF90aGlzID0gdGhpcztcblxuICAgIHZhciB1cmwgPSBhcmd1bWVudHMubGVuZ3RoIDw9IDAgfHwgYXJndW1lbnRzWzBdID09PSB1bmRlZmluZWQgPyAnc29ja2V0LmlvJyA6IGFyZ3VtZW50c1swXTtcbiAgICB2YXIgcHJvdG9jb2wgPSBhcmd1bWVudHMubGVuZ3RoIDw9IDEgfHwgYXJndW1lbnRzWzFdID09PSB1bmRlZmluZWQgPyAnJyA6IGFyZ3VtZW50c1sxXTtcblxuICAgIF9jbGFzc0NhbGxDaGVjayh0aGlzLCBTb2NrZXRJTyk7XG5cbiAgICBfZ2V0KE9iamVjdC5nZXRQcm90b3R5cGVPZihTb2NrZXRJTy5wcm90b3R5cGUpLCAnY29uc3RydWN0b3InLCB0aGlzKS5jYWxsKHRoaXMpO1xuXG4gICAgdGhpcy5iaW5hcnlUeXBlID0gJ2Jsb2InO1xuICAgIHRoaXMudXJsID0gKDAsIF91cmlqczJbJ2RlZmF1bHQnXSkodXJsKS50b1N0cmluZygpO1xuICAgIHRoaXMucmVhZHlTdGF0ZSA9IFNvY2tldElPLkNPTk5FQ1RJTkc7XG4gICAgdGhpcy5wcm90b2NvbCA9ICcnO1xuXG4gICAgaWYgKHR5cGVvZiBwcm90b2NvbCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHRoaXMucHJvdG9jb2wgPSBwcm90b2NvbDtcbiAgICB9IGVsc2UgaWYgKEFycmF5LmlzQXJyYXkocHJvdG9jb2wpICYmIHByb3RvY29sLmxlbmd0aCA+IDApIHtcbiAgICAgIHRoaXMucHJvdG9jb2wgPSBwcm90b2NvbFswXTtcbiAgICB9XG5cbiAgICB2YXIgc2VydmVyID0gX25ldHdvcmtCcmlkZ2UyWydkZWZhdWx0J10uYXR0YWNoV2ViU29ja2V0KHRoaXMsIHRoaXMudXJsKTtcblxuICAgIC8qXG4gICAgKiBEZWxheSB0cmlnZ2VyaW5nIHRoZSBjb25uZWN0aW9uIGV2ZW50cyBzbyB0aGV5IGNhbiBiZSBkZWZpbmVkIGluIHRpbWUuXG4gICAgKi9cbiAgICAoMCwgX2hlbHBlcnNEZWxheTJbJ2RlZmF1bHQnXSkoZnVuY3Rpb24gZGVsYXlDYWxsYmFjaygpIHtcbiAgICAgIGlmIChzZXJ2ZXIpIHtcbiAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gU29ja2V0SU8uT1BFTjtcbiAgICAgICAgc2VydmVyLmRpc3BhdGNoRXZlbnQoKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlRXZlbnQpKHsgdHlwZTogJ2Nvbm5lY3Rpb24nIH0pLCBzZXJ2ZXIsIHRoaXMpO1xuICAgICAgICBzZXJ2ZXIuZGlzcGF0Y2hFdmVudCgoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVFdmVudCkoeyB0eXBlOiAnY29ubmVjdCcgfSksIHNlcnZlciwgdGhpcyk7IC8vIGFsaWFzXG4gICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudCgoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVFdmVudCkoeyB0eXBlOiAnY29ubmVjdCcsIHRhcmdldDogdGhpcyB9KSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aGlzLnJlYWR5U3RhdGUgPSBTb2NrZXRJTy5DTE9TRUQ7XG4gICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudCgoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVFdmVudCkoeyB0eXBlOiAnZXJyb3InLCB0YXJnZXQ6IHRoaXMgfSkpO1xuICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQoKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlQ2xvc2VFdmVudCkoe1xuICAgICAgICAgIHR5cGU6ICdjbG9zZScsXG4gICAgICAgICAgdGFyZ2V0OiB0aGlzLFxuICAgICAgICAgIGNvZGU6IF9oZWxwZXJzQ2xvc2VDb2RlczJbJ2RlZmF1bHQnXS5DTE9TRV9OT1JNQUxcbiAgICAgICAgfSkpO1xuXG4gICAgICAgIGNvbnNvbGUuZXJyb3IoJ1NvY2tldC5pbyBjb25uZWN0aW9uIHRvIFxcJycgKyB0aGlzLnVybCArICdcXCcgZmFpbGVkJyk7XG4gICAgICB9XG4gICAgfSwgdGhpcyk7XG5cbiAgICAvKipcbiAgICAgIEFkZCBhbiBhbGlhc2VkIGV2ZW50IGxpc3RlbmVyIGZvciBjbG9zZSAvIGRpc2Nvbm5lY3RcbiAgICAgKi9cbiAgICB0aGlzLmFkZEV2ZW50TGlzdGVuZXIoJ2Nsb3NlJywgZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICBfdGhpcy5kaXNwYXRjaEV2ZW50KCgwLCBfZXZlbnRGYWN0b3J5LmNyZWF0ZUNsb3NlRXZlbnQpKHtcbiAgICAgICAgdHlwZTogJ2Rpc2Nvbm5lY3QnLFxuICAgICAgICB0YXJnZXQ6IGV2ZW50LnRhcmdldCxcbiAgICAgICAgY29kZTogZXZlbnQuY29kZVxuICAgICAgfSkpO1xuICAgIH0pO1xuICB9XG5cbiAgLypcbiAgKiBDbG9zZXMgdGhlIFNvY2tldElPIGNvbm5lY3Rpb24gb3IgY29ubmVjdGlvbiBhdHRlbXB0LCBpZiBhbnkuXG4gICogSWYgdGhlIGNvbm5lY3Rpb24gaXMgYWxyZWFkeSBDTE9TRUQsIHRoaXMgbWV0aG9kIGRvZXMgbm90aGluZy5cbiAgKi9cblxuICBfY3JlYXRlQ2xhc3MoU29ja2V0SU8sIFt7XG4gICAga2V5OiAnY2xvc2UnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBjbG9zZSgpIHtcbiAgICAgIGlmICh0aGlzLnJlYWR5U3RhdGUgIT09IFNvY2tldElPLk9QRU4pIHtcbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICAgIH1cblxuICAgICAgdmFyIHNlcnZlciA9IF9uZXR3b3JrQnJpZGdlMlsnZGVmYXVsdCddLnNlcnZlckxvb2t1cCh0aGlzLnVybCk7XG4gICAgICBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS5yZW1vdmVXZWJTb2NrZXQodGhpcywgdGhpcy51cmwpO1xuXG4gICAgICB0aGlzLnJlYWR5U3RhdGUgPSBTb2NrZXRJTy5DTE9TRUQ7XG4gICAgICB0aGlzLmRpc3BhdGNoRXZlbnQoKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlQ2xvc2VFdmVudCkoe1xuICAgICAgICB0eXBlOiAnY2xvc2UnLFxuICAgICAgICB0YXJnZXQ6IHRoaXMsXG4gICAgICAgIGNvZGU6IF9oZWxwZXJzQ2xvc2VDb2RlczJbJ2RlZmF1bHQnXS5DTE9TRV9OT1JNQUxcbiAgICAgIH0pKTtcblxuICAgICAgaWYgKHNlcnZlcikge1xuICAgICAgICBzZXJ2ZXIuZGlzcGF0Y2hFdmVudCgoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVDbG9zZUV2ZW50KSh7XG4gICAgICAgICAgdHlwZTogJ2Rpc2Nvbm5lY3QnLFxuICAgICAgICAgIHRhcmdldDogdGhpcyxcbiAgICAgICAgICBjb2RlOiBfaGVscGVyc0Nsb3NlQ29kZXMyWydkZWZhdWx0J10uQ0xPU0VfTk9STUFMXG4gICAgICAgIH0pLCBzZXJ2ZXIpO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8qXG4gICAgKiBBbGlhcyBmb3IgU29ja2V0I2Nsb3NlXG4gICAgKlxuICAgICogaHR0cHM6Ly9naXRodWIuY29tL3NvY2tldGlvL3NvY2tldC5pby1jbGllbnQvYmxvYi9tYXN0ZXIvbGliL3NvY2tldC5qcyNMMzgzXG4gICAgKi9cbiAgfSwge1xuICAgIGtleTogJ2Rpc2Nvbm5lY3QnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBkaXNjb25uZWN0KCkge1xuICAgICAgdGhpcy5jbG9zZSgpO1xuICAgIH1cblxuICAgIC8qXG4gICAgKiBTdWJtaXRzIGFuIGV2ZW50IHRvIHRoZSBzZXJ2ZXIgd2l0aCBhIHBheWxvYWRcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAnZW1pdCcsXG4gICAgdmFsdWU6IGZ1bmN0aW9uIGVtaXQoZXZlbnQsIGRhdGEpIHtcbiAgICAgIGlmICh0aGlzLnJlYWR5U3RhdGUgIT09IFNvY2tldElPLk9QRU4pIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdTb2NrZXRJTyBpcyBhbHJlYWR5IGluIENMT1NJTkcgb3IgQ0xPU0VEIHN0YXRlJyk7XG4gICAgICB9XG5cbiAgICAgIHZhciBtZXNzYWdlRXZlbnQgPSAoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVNZXNzYWdlRXZlbnQpKHtcbiAgICAgICAgdHlwZTogZXZlbnQsXG4gICAgICAgIG9yaWdpbjogdGhpcy51cmwsXG4gICAgICAgIGRhdGE6IGRhdGFcbiAgICAgIH0pO1xuXG4gICAgICB2YXIgc2VydmVyID0gX25ldHdvcmtCcmlkZ2UyWydkZWZhdWx0J10uc2VydmVyTG9va3VwKHRoaXMudXJsKTtcblxuICAgICAgaWYgKHNlcnZlcikge1xuICAgICAgICBzZXJ2ZXIuZGlzcGF0Y2hFdmVudChtZXNzYWdlRXZlbnQsIGRhdGEpO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8qXG4gICAgKiBTdWJtaXRzIGEgJ21lc3NhZ2UnIGV2ZW50IHRvIHRoZSBzZXJ2ZXIuXG4gICAgKlxuICAgICogU2hvdWxkIGJlaGF2ZSBleGFjdGx5IGxpa2UgV2ViU29ja2V0I3NlbmRcbiAgICAqXG4gICAgKiBodHRwczovL2dpdGh1Yi5jb20vc29ja2V0aW8vc29ja2V0LmlvLWNsaWVudC9ibG9iL21hc3Rlci9saWIvc29ja2V0LmpzI0wxMTNcbiAgICAqL1xuICB9LCB7XG4gICAga2V5OiAnc2VuZCcsXG4gICAgdmFsdWU6IGZ1bmN0aW9uIHNlbmQoZGF0YSkge1xuICAgICAgdGhpcy5lbWl0KCdtZXNzYWdlJywgZGF0YSk7XG4gICAgfVxuXG4gICAgLypcbiAgICAqIEZvciByZWdpc3RlcmluZyBldmVudHMgdG8gYmUgcmVjZWl2ZWQgZnJvbSB0aGUgc2VydmVyXG4gICAgKi9cbiAgfSwge1xuICAgIGtleTogJ29uJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gb24odHlwZSwgY2FsbGJhY2spIHtcbiAgICAgIHRoaXMuYWRkRXZlbnRMaXN0ZW5lcih0eXBlLCBjYWxsYmFjayk7XG4gICAgfVxuXG4gICAgLypcbiAgICAgKiBKb2luIGEgcm9vbSBvbiBhIHNlcnZlclxuICAgICAqXG4gICAgICogaHR0cDovL3NvY2tldC5pby9kb2NzL3Jvb21zLWFuZC1uYW1lc3BhY2VzLyNqb2luaW5nLWFuZC1sZWF2aW5nXG4gICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICdqb2luJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gam9pbihyb29tKSB7XG4gICAgICBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS5hZGRNZW1iZXJzaGlwVG9Sb29tKHRoaXMsIHJvb20pO1xuICAgIH1cblxuICAgIC8qXG4gICAgICogR2V0IHRoZSB3ZWJzb2NrZXQgdG8gbGVhdmUgdGhlIHJvb21cbiAgICAgKlxuICAgICAqIGh0dHA6Ly9zb2NrZXQuaW8vZG9jcy9yb29tcy1hbmQtbmFtZXNwYWNlcy8jam9pbmluZy1hbmQtbGVhdmluZ1xuICAgICAqL1xuICB9LCB7XG4gICAga2V5OiAnbGVhdmUnLFxuICAgIHZhbHVlOiBmdW5jdGlvbiBsZWF2ZShyb29tKSB7XG4gICAgICBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS5yZW1vdmVNZW1iZXJzaGlwRnJvbVJvb20odGhpcywgcm9vbSk7XG4gICAgfVxuXG4gICAgLypcbiAgICAgKiBJbnZva2VzIGFsbCBsaXN0ZW5lciBmdW5jdGlvbnMgdGhhdCBhcmUgbGlzdGVuaW5nIHRvIHRoZSBnaXZlbiBldmVudC50eXBlIHByb3BlcnR5LiBFYWNoXG4gICAgICogbGlzdGVuZXIgd2lsbCBiZSBwYXNzZWQgdGhlIGV2ZW50IGFzIHRoZSBmaXJzdCBhcmd1bWVudC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB7b2JqZWN0fSBldmVudCAtIGV2ZW50IG9iamVjdCB3aGljaCB3aWxsIGJlIHBhc3NlZCB0byBhbGwgbGlzdGVuZXJzIG9mIHRoZSBldmVudC50eXBlIHByb3BlcnR5XG4gICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICdkaXNwYXRjaEV2ZW50JyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gZGlzcGF0Y2hFdmVudChldmVudCkge1xuICAgICAgdmFyIF90aGlzMiA9IHRoaXM7XG5cbiAgICAgIGZvciAodmFyIF9sZW4gPSBhcmd1bWVudHMubGVuZ3RoLCBjdXN0b21Bcmd1bWVudHMgPSBBcnJheShfbGVuID4gMSA/IF9sZW4gLSAxIDogMCksIF9rZXkgPSAxOyBfa2V5IDwgX2xlbjsgX2tleSsrKSB7XG4gICAgICAgIGN1c3RvbUFyZ3VtZW50c1tfa2V5IC0gMV0gPSBhcmd1bWVudHNbX2tleV07XG4gICAgICB9XG5cbiAgICAgIHZhciBldmVudE5hbWUgPSBldmVudC50eXBlO1xuICAgICAgdmFyIGxpc3RlbmVycyA9IHRoaXMubGlzdGVuZXJzW2V2ZW50TmFtZV07XG5cbiAgICAgIGlmICghQXJyYXkuaXNBcnJheShsaXN0ZW5lcnMpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cblxuICAgICAgbGlzdGVuZXJzLmZvckVhY2goZnVuY3Rpb24gKGxpc3RlbmVyKSB7XG4gICAgICAgIGlmIChjdXN0b21Bcmd1bWVudHMubGVuZ3RoID4gMCkge1xuICAgICAgICAgIGxpc3RlbmVyLmFwcGx5KF90aGlzMiwgY3VzdG9tQXJndW1lbnRzKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAvLyBSZWd1bGFyIFdlYlNvY2tldHMgZXhwZWN0IGEgTWVzc2FnZUV2ZW50IGJ1dCBTb2NrZXRpby5pbyBqdXN0IHdhbnRzIHJhdyBkYXRhXG4gICAgICAgICAgLy8gIHBheWxvYWQgaW5zdGFuY2VvZiBNZXNzYWdlRXZlbnQgd29ya3MsIGJ1dCB5b3UgY2FuJ3QgaXNudGFuY2Ugb2YgTm9kZUV2ZW50XG4gICAgICAgICAgLy8gIGZvciBub3cgd2UgZGV0ZWN0IGlmIHRoZSBvdXRwdXQgaGFzIGRhdGEgZGVmaW5lZCBvbiBpdFxuICAgICAgICAgIGxpc3RlbmVyLmNhbGwoX3RoaXMyLCBldmVudC5kYXRhID8gZXZlbnQuZGF0YSA6IGV2ZW50KTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfVxuICB9XSk7XG5cbiAgcmV0dXJuIFNvY2tldElPO1xufSkoX2V2ZW50VGFyZ2V0MlsnZGVmYXVsdCddKTtcblxuU29ja2V0SU8uQ09OTkVDVElORyA9IDA7XG5Tb2NrZXRJTy5PUEVOID0gMTtcblNvY2tldElPLkNMT1NJTkcgPSAyO1xuU29ja2V0SU8uQ0xPU0VEID0gMztcblxuLypcbiogU3RhdGljIGNvbnN0cnVjdG9yIG1ldGhvZHMgZm9yIHRoZSBJTyBTb2NrZXRcbiovXG52YXIgSU8gPSBmdW5jdGlvbiBpb0NvbnN0cnVjdG9yKHVybCkge1xuICByZXR1cm4gbmV3IFNvY2tldElPKHVybCk7XG59O1xuXG4vKlxuKiBBbGlhcyB0aGUgcmF3IElPKCkgY29uc3RydWN0b3JcbiovXG5JTy5jb25uZWN0ID0gZnVuY3Rpb24gaW9Db25uZWN0KHVybCkge1xuICAvKiBlc2xpbnQtZGlzYWJsZSBuZXctY2FwICovXG4gIHJldHVybiBJTyh1cmwpO1xuICAvKiBlc2xpbnQtZW5hYmxlIG5ldy1jYXAgKi9cbn07XG5cbmV4cG9ydHNbJ2RlZmF1bHQnXSA9IElPO1xubW9kdWxlLmV4cG9ydHMgPSBleHBvcnRzWydkZWZhdWx0J107IiwiJ3VzZSBzdHJpY3QnO1xuXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7XG4gIHZhbHVlOiB0cnVlXG59KTtcblxudmFyIF9jcmVhdGVDbGFzcyA9IChmdW5jdGlvbiAoKSB7IGZ1bmN0aW9uIGRlZmluZVByb3BlcnRpZXModGFyZ2V0LCBwcm9wcykgeyBmb3IgKHZhciBpID0gMDsgaSA8IHByb3BzLmxlbmd0aDsgaSsrKSB7IHZhciBkZXNjcmlwdG9yID0gcHJvcHNbaV07IGRlc2NyaXB0b3IuZW51bWVyYWJsZSA9IGRlc2NyaXB0b3IuZW51bWVyYWJsZSB8fCBmYWxzZTsgZGVzY3JpcHRvci5jb25maWd1cmFibGUgPSB0cnVlOyBpZiAoJ3ZhbHVlJyBpbiBkZXNjcmlwdG9yKSBkZXNjcmlwdG9yLndyaXRhYmxlID0gdHJ1ZTsgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRhcmdldCwgZGVzY3JpcHRvci5rZXksIGRlc2NyaXB0b3IpOyB9IH0gcmV0dXJuIGZ1bmN0aW9uIChDb25zdHJ1Y3RvciwgcHJvdG9Qcm9wcywgc3RhdGljUHJvcHMpIHsgaWYgKHByb3RvUHJvcHMpIGRlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IucHJvdG90eXBlLCBwcm90b1Byb3BzKTsgaWYgKHN0YXRpY1Byb3BzKSBkZWZpbmVQcm9wZXJ0aWVzKENvbnN0cnVjdG9yLCBzdGF0aWNQcm9wcyk7IHJldHVybiBDb25zdHJ1Y3RvcjsgfTsgfSkoKTtcblxudmFyIF9nZXQgPSBmdW5jdGlvbiBnZXQoX3gyLCBfeDMsIF94NCkgeyB2YXIgX2FnYWluID0gdHJ1ZTsgX2Z1bmN0aW9uOiB3aGlsZSAoX2FnYWluKSB7IHZhciBvYmplY3QgPSBfeDIsIHByb3BlcnR5ID0gX3gzLCByZWNlaXZlciA9IF94NDsgX2FnYWluID0gZmFsc2U7IGlmIChvYmplY3QgPT09IG51bGwpIG9iamVjdCA9IEZ1bmN0aW9uLnByb3RvdHlwZTsgdmFyIGRlc2MgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKG9iamVjdCwgcHJvcGVydHkpOyBpZiAoZGVzYyA9PT0gdW5kZWZpbmVkKSB7IHZhciBwYXJlbnQgPSBPYmplY3QuZ2V0UHJvdG90eXBlT2Yob2JqZWN0KTsgaWYgKHBhcmVudCA9PT0gbnVsbCkgeyByZXR1cm4gdW5kZWZpbmVkOyB9IGVsc2UgeyBfeDIgPSBwYXJlbnQ7IF94MyA9IHByb3BlcnR5OyBfeDQgPSByZWNlaXZlcjsgX2FnYWluID0gdHJ1ZTsgZGVzYyA9IHBhcmVudCA9IHVuZGVmaW5lZDsgY29udGludWUgX2Z1bmN0aW9uOyB9IH0gZWxzZSBpZiAoJ3ZhbHVlJyBpbiBkZXNjKSB7IHJldHVybiBkZXNjLnZhbHVlOyB9IGVsc2UgeyB2YXIgZ2V0dGVyID0gZGVzYy5nZXQ7IGlmIChnZXR0ZXIgPT09IHVuZGVmaW5lZCkgeyByZXR1cm4gdW5kZWZpbmVkOyB9IHJldHVybiBnZXR0ZXIuY2FsbChyZWNlaXZlcik7IH0gfSB9O1xuXG5mdW5jdGlvbiBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KG9iaikgeyByZXR1cm4gb2JqICYmIG9iai5fX2VzTW9kdWxlID8gb2JqIDogeyAnZGVmYXVsdCc6IG9iaiB9OyB9XG5cbmZ1bmN0aW9uIF9jbGFzc0NhbGxDaGVjayhpbnN0YW5jZSwgQ29uc3RydWN0b3IpIHsgaWYgKCEoaW5zdGFuY2UgaW5zdGFuY2VvZiBDb25zdHJ1Y3RvcikpIHsgdGhyb3cgbmV3IFR5cGVFcnJvcignQ2Fubm90IGNhbGwgYSBjbGFzcyBhcyBhIGZ1bmN0aW9uJyk7IH0gfVxuXG5mdW5jdGlvbiBfaW5oZXJpdHMoc3ViQ2xhc3MsIHN1cGVyQ2xhc3MpIHsgaWYgKHR5cGVvZiBzdXBlckNsYXNzICE9PSAnZnVuY3Rpb24nICYmIHN1cGVyQ2xhc3MgIT09IG51bGwpIHsgdGhyb3cgbmV3IFR5cGVFcnJvcignU3VwZXIgZXhwcmVzc2lvbiBtdXN0IGVpdGhlciBiZSBudWxsIG9yIGEgZnVuY3Rpb24sIG5vdCAnICsgdHlwZW9mIHN1cGVyQ2xhc3MpOyB9IHN1YkNsYXNzLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoc3VwZXJDbGFzcyAmJiBzdXBlckNsYXNzLnByb3RvdHlwZSwgeyBjb25zdHJ1Y3RvcjogeyB2YWx1ZTogc3ViQ2xhc3MsIGVudW1lcmFibGU6IGZhbHNlLCB3cml0YWJsZTogdHJ1ZSwgY29uZmlndXJhYmxlOiB0cnVlIH0gfSk7IGlmIChzdXBlckNsYXNzKSBPYmplY3Quc2V0UHJvdG90eXBlT2YgPyBPYmplY3Quc2V0UHJvdG90eXBlT2Yoc3ViQ2xhc3MsIHN1cGVyQ2xhc3MpIDogc3ViQ2xhc3MuX19wcm90b19fID0gc3VwZXJDbGFzczsgfVxuXG52YXIgX3VyaWpzID0gcmVxdWlyZSgndXJpanMnKTtcblxudmFyIF91cmlqczIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF91cmlqcyk7XG5cbnZhciBfaGVscGVyc0RlbGF5ID0gcmVxdWlyZSgnLi9oZWxwZXJzL2RlbGF5Jyk7XG5cbnZhciBfaGVscGVyc0RlbGF5MiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2hlbHBlcnNEZWxheSk7XG5cbnZhciBfZXZlbnRUYXJnZXQgPSByZXF1aXJlKCcuL2V2ZW50LXRhcmdldCcpO1xuXG52YXIgX2V2ZW50VGFyZ2V0MiA9IF9pbnRlcm9wUmVxdWlyZURlZmF1bHQoX2V2ZW50VGFyZ2V0KTtcblxudmFyIF9uZXR3b3JrQnJpZGdlID0gcmVxdWlyZSgnLi9uZXR3b3JrLWJyaWRnZScpO1xuXG52YXIgX25ldHdvcmtCcmlkZ2UyID0gX2ludGVyb3BSZXF1aXJlRGVmYXVsdChfbmV0d29ya0JyaWRnZSk7XG5cbnZhciBfaGVscGVyc0Nsb3NlQ29kZXMgPSByZXF1aXJlKCcuL2hlbHBlcnMvY2xvc2UtY29kZXMnKTtcblxudmFyIF9oZWxwZXJzQ2xvc2VDb2RlczIgPSBfaW50ZXJvcFJlcXVpcmVEZWZhdWx0KF9oZWxwZXJzQ2xvc2VDb2Rlcyk7XG5cbnZhciBfZXZlbnRGYWN0b3J5ID0gcmVxdWlyZSgnLi9ldmVudC1mYWN0b3J5Jyk7XG5cbi8qXG4qIFRoZSBtYWluIHdlYnNvY2tldCBjbGFzcyB3aGljaCBpcyBkZXNpZ25lZCB0byBtaW1pY2sgdGhlIG5hdGl2ZSBXZWJTb2NrZXQgY2xhc3MgYXMgY2xvc2VcbiogYXMgcG9zc2libGUuXG4qXG4qIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9XZWJTb2NrZXRcbiovXG5cbnZhciBXZWJTb2NrZXQgPSAoZnVuY3Rpb24gKF9FdmVudFRhcmdldCkge1xuICBfaW5oZXJpdHMoV2ViU29ja2V0LCBfRXZlbnRUYXJnZXQpO1xuXG4gIC8qXG4gICogQHBhcmFtIHtzdHJpbmd9IHVybFxuICAqL1xuXG4gIGZ1bmN0aW9uIFdlYlNvY2tldCh1cmwpIHtcbiAgICB2YXIgcHJvdG9jb2wgPSBhcmd1bWVudHMubGVuZ3RoIDw9IDEgfHwgYXJndW1lbnRzWzFdID09PSB1bmRlZmluZWQgPyAnJyA6IGFyZ3VtZW50c1sxXTtcblxuICAgIF9jbGFzc0NhbGxDaGVjayh0aGlzLCBXZWJTb2NrZXQpO1xuXG4gICAgX2dldChPYmplY3QuZ2V0UHJvdG90eXBlT2YoV2ViU29ja2V0LnByb3RvdHlwZSksICdjb25zdHJ1Y3RvcicsIHRoaXMpLmNhbGwodGhpcyk7XG5cbiAgICBpZiAoIXVybCkge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignRmFpbGVkIHRvIGNvbnN0cnVjdCBcXCdXZWJTb2NrZXRcXCc6IDEgYXJndW1lbnQgcmVxdWlyZWQsIGJ1dCBvbmx5IDAgcHJlc2VudC4nKTtcbiAgICB9XG5cbiAgICB0aGlzLmJpbmFyeVR5cGUgPSAnYmxvYic7XG4gICAgdGhpcy51cmwgPSAoMCwgX3VyaWpzMlsnZGVmYXVsdCddKSh1cmwpLnRvU3RyaW5nKCk7XG4gICAgdGhpcy5yZWFkeVN0YXRlID0gV2ViU29ja2V0LkNPTk5FQ1RJTkc7XG4gICAgdGhpcy5wcm90b2NvbCA9ICcnO1xuXG4gICAgaWYgKHR5cGVvZiBwcm90b2NvbCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHRoaXMucHJvdG9jb2wgPSBwcm90b2NvbDtcbiAgICB9IGVsc2UgaWYgKEFycmF5LmlzQXJyYXkocHJvdG9jb2wpICYmIHByb3RvY29sLmxlbmd0aCA+IDApIHtcbiAgICAgIHRoaXMucHJvdG9jb2wgPSBwcm90b2NvbFswXTtcbiAgICB9XG5cbiAgICAvKlxuICAgICogSW4gb3JkZXIgdG8gY2FwdHVyZSB0aGUgY2FsbGJhY2sgZnVuY3Rpb24gd2UgbmVlZCB0byBkZWZpbmUgY3VzdG9tIHNldHRlcnMuXG4gICAgKiBUbyBpbGx1c3RyYXRlOlxuICAgICogICBteVNvY2tldC5vbm9wZW4gPSBmdW5jdGlvbigpIHsgYWxlcnQodHJ1ZSkgfTtcbiAgICAqXG4gICAgKiBUaGUgb25seSB3YXkgdG8gY2FwdHVyZSB0aGF0IGZ1bmN0aW9uIGFuZCBob2xkIG9udG8gaXQgZm9yIGxhdGVyIGlzIHdpdGggdGhlXG4gICAgKiBiZWxvdyBjb2RlOlxuICAgICovXG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnRpZXModGhpcywge1xuICAgICAgb25vcGVuOiB7XG4gICAgICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICAgICAgZ2V0OiBmdW5jdGlvbiBnZXQoKSB7XG4gICAgICAgICAgcmV0dXJuIHRoaXMubGlzdGVuZXJzLm9wZW47XG4gICAgICAgIH0sXG4gICAgICAgIHNldDogZnVuY3Rpb24gc2V0KGxpc3RlbmVyKSB7XG4gICAgICAgICAgdGhpcy5hZGRFdmVudExpc3RlbmVyKCdvcGVuJywgbGlzdGVuZXIpO1xuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgb25tZXNzYWdlOiB7XG4gICAgICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICAgICAgZ2V0OiBmdW5jdGlvbiBnZXQoKSB7XG4gICAgICAgICAgcmV0dXJuIHRoaXMubGlzdGVuZXJzLm1lc3NhZ2U7XG4gICAgICAgIH0sXG4gICAgICAgIHNldDogZnVuY3Rpb24gc2V0KGxpc3RlbmVyKSB7XG4gICAgICAgICAgdGhpcy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgbGlzdGVuZXIpO1xuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgb25jbG9zZToge1xuICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgICAgIGdldDogZnVuY3Rpb24gZ2V0KCkge1xuICAgICAgICAgIHJldHVybiB0aGlzLmxpc3RlbmVycy5jbG9zZTtcbiAgICAgICAgfSxcbiAgICAgICAgc2V0OiBmdW5jdGlvbiBzZXQobGlzdGVuZXIpIHtcbiAgICAgICAgICB0aGlzLmFkZEV2ZW50TGlzdGVuZXIoJ2Nsb3NlJywgbGlzdGVuZXIpO1xuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgb25lcnJvcjoge1xuICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgICAgIGdldDogZnVuY3Rpb24gZ2V0KCkge1xuICAgICAgICAgIHJldHVybiB0aGlzLmxpc3RlbmVycy5lcnJvcjtcbiAgICAgICAgfSxcbiAgICAgICAgc2V0OiBmdW5jdGlvbiBzZXQobGlzdGVuZXIpIHtcbiAgICAgICAgICB0aGlzLmFkZEV2ZW50TGlzdGVuZXIoJ2Vycm9yJywgbGlzdGVuZXIpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICB2YXIgc2VydmVyID0gX25ldHdvcmtCcmlkZ2UyWydkZWZhdWx0J10uYXR0YWNoV2ViU29ja2V0KHRoaXMsIHRoaXMudXJsKTtcblxuICAgIC8qXG4gICAgKiBUaGlzIGRlbGF5IGlzIG5lZWRlZCBzbyB0aGF0IHdlIGRvbnQgdHJpZ2dlciBhbiBldmVudCBiZWZvcmUgdGhlIGNhbGxiYWNrcyBoYXZlIGJlZW5cbiAgICAqIHNldHVwLiBGb3IgZXhhbXBsZTpcbiAgICAqXG4gICAgKiB2YXIgc29ja2V0ID0gbmV3IFdlYlNvY2tldCgnd3M6Ly9sb2NhbGhvc3QnKTtcbiAgICAqXG4gICAgKiAvLyBJZiB3ZSBkb250IGhhdmUgdGhlIGRlbGF5IHRoZW4gdGhlIGV2ZW50IHdvdWxkIGJlIHRyaWdnZXJlZCByaWdodCBoZXJlIGFuZCB0aGlzIGlzXG4gICAgKiAvLyBiZWZvcmUgdGhlIG9ub3BlbiBoYWQgYSBjaGFuY2UgdG8gcmVnaXN0ZXIgaXRzZWxmLlxuICAgICpcbiAgICAqIHNvY2tldC5vbm9wZW4gPSAoKSA9PiB7IC8vIHRoaXMgd291bGQgbmV2ZXIgYmUgY2FsbGVkIH07XG4gICAgKlxuICAgICogLy8gYW5kIHdpdGggdGhlIGRlbGF5IHRoZSBldmVudCBnZXRzIHRyaWdnZXJlZCBoZXJlIGFmdGVyIGFsbCBvZiB0aGUgY2FsbGJhY2tzIGhhdmUgYmVlblxuICAgICogLy8gcmVnaXN0ZXJlZCA6LSlcbiAgICAqL1xuICAgICgwLCBfaGVscGVyc0RlbGF5MlsnZGVmYXVsdCddKShmdW5jdGlvbiBkZWxheUNhbGxiYWNrKCkge1xuICAgICAgaWYgKHNlcnZlcikge1xuICAgICAgICB0aGlzLnJlYWR5U3RhdGUgPSBXZWJTb2NrZXQuT1BFTjtcbiAgICAgICAgc2VydmVyLmRpc3BhdGNoRXZlbnQoKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlRXZlbnQpKHsgdHlwZTogJ2Nvbm5lY3Rpb24nIH0pLCBzZXJ2ZXIsIHRoaXMpO1xuICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQoKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlRXZlbnQpKHsgdHlwZTogJ29wZW4nLCB0YXJnZXQ6IHRoaXMgfSkpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gV2ViU29ja2V0LkNMT1NFRDtcbiAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KCgwLCBfZXZlbnRGYWN0b3J5LmNyZWF0ZUV2ZW50KSh7IHR5cGU6ICdlcnJvcicsIHRhcmdldDogdGhpcyB9KSk7XG4gICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudCgoMCwgX2V2ZW50RmFjdG9yeS5jcmVhdGVDbG9zZUV2ZW50KSh7IHR5cGU6ICdjbG9zZScsIHRhcmdldDogdGhpcywgY29kZTogX2hlbHBlcnNDbG9zZUNvZGVzMlsnZGVmYXVsdCddLkNMT1NFX05PUk1BTCB9KSk7XG5cbiAgICAgICAgY29uc29sZS5lcnJvcignV2ViU29ja2V0IGNvbm5lY3Rpb24gdG8gXFwnJyArIHRoaXMudXJsICsgJ1xcJyBmYWlsZWQnKTtcbiAgICAgIH1cbiAgICB9LCB0aGlzKTtcbiAgfVxuXG4gIC8qXG4gICogVHJhbnNtaXRzIGRhdGEgdG8gdGhlIHNlcnZlciBvdmVyIHRoZSBXZWJTb2NrZXQgY29ubmVjdGlvbi5cbiAgKlxuICAqIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9XZWJTb2NrZXQjc2VuZCgpXG4gICovXG5cbiAgX2NyZWF0ZUNsYXNzKFdlYlNvY2tldCwgW3tcbiAgICBrZXk6ICdzZW5kJyxcbiAgICB2YWx1ZTogZnVuY3Rpb24gc2VuZChkYXRhKSB7XG4gICAgICBpZiAodGhpcy5yZWFkeVN0YXRlID09PSBXZWJTb2NrZXQuQ0xPU0lORyB8fCB0aGlzLnJlYWR5U3RhdGUgPT09IFdlYlNvY2tldC5DTE9TRUQpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdXZWJTb2NrZXQgaXMgYWxyZWFkeSBpbiBDTE9TSU5HIG9yIENMT1NFRCBzdGF0ZScpO1xuICAgICAgfVxuXG4gICAgICB2YXIgbWVzc2FnZUV2ZW50ID0gKDAsIF9ldmVudEZhY3RvcnkuY3JlYXRlTWVzc2FnZUV2ZW50KSh7XG4gICAgICAgIHR5cGU6ICdtZXNzYWdlJyxcbiAgICAgICAgb3JpZ2luOiB0aGlzLnVybCxcbiAgICAgICAgZGF0YTogZGF0YVxuICAgICAgfSk7XG5cbiAgICAgIHZhciBzZXJ2ZXIgPSBfbmV0d29ya0JyaWRnZTJbJ2RlZmF1bHQnXS5zZXJ2ZXJMb29rdXAodGhpcy51cmwpO1xuXG4gICAgICBpZiAoc2VydmVyKSB7XG4gICAgICAgIHNlcnZlci5kaXNwYXRjaEV2ZW50KG1lc3NhZ2VFdmVudCwgZGF0YSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLypcbiAgICAqIENsb3NlcyB0aGUgV2ViU29ja2V0IGNvbm5lY3Rpb24gb3IgY29ubmVjdGlvbiBhdHRlbXB0LCBpZiBhbnkuXG4gICAgKiBJZiB0aGUgY29ubmVjdGlvbiBpcyBhbHJlYWR5IENMT1NFRCwgdGhpcyBtZXRob2QgZG9lcyBub3RoaW5nLlxuICAgICpcbiAgICAqIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9XZWJTb2NrZXQjY2xvc2UoKVxuICAgICovXG4gIH0sIHtcbiAgICBrZXk6ICdjbG9zZScsXG4gICAgdmFsdWU6IGZ1bmN0aW9uIGNsb3NlKCkge1xuICAgICAgaWYgKHRoaXMucmVhZHlTdGF0ZSAhPT0gV2ViU29ja2V0Lk9QRU4pIHtcbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICAgIH1cblxuICAgICAgdmFyIHNlcnZlciA9IF9uZXR3b3JrQnJpZGdlMlsnZGVmYXVsdCddLnNlcnZlckxvb2t1cCh0aGlzLnVybCk7XG4gICAgICB2YXIgY2xvc2VFdmVudCA9ICgwLCBfZXZlbnRGYWN0b3J5LmNyZWF0ZUNsb3NlRXZlbnQpKHtcbiAgICAgICAgdHlwZTogJ2Nsb3NlJyxcbiAgICAgICAgdGFyZ2V0OiB0aGlzLFxuICAgICAgICBjb2RlOiBfaGVscGVyc0Nsb3NlQ29kZXMyWydkZWZhdWx0J10uQ0xPU0VfTk9STUFMXG4gICAgICB9KTtcblxuICAgICAgX25ldHdvcmtCcmlkZ2UyWydkZWZhdWx0J10ucmVtb3ZlV2ViU29ja2V0KHRoaXMsIHRoaXMudXJsKTtcblxuICAgICAgdGhpcy5yZWFkeVN0YXRlID0gV2ViU29ja2V0LkNMT1NFRDtcbiAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudChjbG9zZUV2ZW50KTtcblxuICAgICAgaWYgKHNlcnZlcikge1xuICAgICAgICBzZXJ2ZXIuZGlzcGF0Y2hFdmVudChjbG9zZUV2ZW50LCBzZXJ2ZXIpO1xuICAgICAgfVxuICAgIH1cbiAgfV0pO1xuXG4gIHJldHVybiBXZWJTb2NrZXQ7XG59KShfZXZlbnRUYXJnZXQyWydkZWZhdWx0J10pO1xuXG5XZWJTb2NrZXQuQ09OTkVDVElORyA9IDA7XG5XZWJTb2NrZXQuT1BFTiA9IDE7XG5XZWJTb2NrZXQuQ0xPU0lORyA9IDI7XG5XZWJTb2NrZXQuQ0xPU0VEID0gMztcblxuZXhwb3J0c1snZGVmYXVsdCddID0gV2ViU29ja2V0O1xubW9kdWxlLmV4cG9ydHMgPSBleHBvcnRzWydkZWZhdWx0J107Il19
diff --git a/actioncable/test/server/broadcasting_test.rb b/actioncable/test/server/broadcasting_test.rb
new file mode 100644
index 0000000000..3b4a7eaf90
--- /dev/null
+++ b/actioncable/test/server/broadcasting_test.rb
@@ -0,0 +1,15 @@
+require "test_helper"
+
+class BroadcastingTest < ActiveSupport::TestCase
+ class TestServer
+ include ActionCable::Server::Broadcasting
+ end
+
+ test "fetching a broadcaster converts the broadcasting queue to a string" do
+ broadcasting = :test_queue
+ server = TestServer.new
+ broadcaster = server.broadcaster_for(broadcasting)
+
+ assert_equal "test_queue", broadcaster.broadcasting
+ end
+end
diff --git a/actioncable/test/stubs/test_connection.rb b/actioncable/test/stubs/test_connection.rb
index da98201900..885450dda6 100644
--- a/actioncable/test/stubs/test_connection.rb
+++ b/actioncable/test/stubs/test_connection.rb
@@ -1,25 +1,33 @@
require 'stubs/user'
class TestConnection
- attr_reader :identifiers, :logger, :current_user, :transmissions
+ attr_reader :identifiers, :logger, :current_user, :server, :transmissions
- def initialize(user = User.new("lifo"))
+ delegate :pubsub, to: :server
+
+ def initialize(user = User.new("lifo"), coder: ActiveSupport::JSON, subscription_adapter: SuccessAdapter)
+ @coder = coder
@identifiers = [ :current_user ]
@current_user = user
@logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new)
+ @server = TestServer.new(subscription_adapter: subscription_adapter)
@transmissions = []
end
- def pubsub
- SuccessAdapter.new(TestServer.new)
+ def transmit(cable_message)
+ @transmissions << encode(cable_message)
end
- def transmit(data)
- @transmissions << data
+ def last_transmission
+ decode @transmissions.last if @transmissions.any?
end
- def last_transmission
- @transmissions.last
+ def decode(websocket_message)
+ @coder.decode websocket_message
+ end
+
+ def encode(cable_message)
+ @coder.encode cable_message
end
end
diff --git a/actioncable/test/stubs/test_server.rb b/actioncable/test/stubs/test_server.rb
index 6e6541a952..b86f422a13 100644
--- a/actioncable/test/stubs/test_server.rb
+++ b/actioncable/test/stubs/test_server.rb
@@ -2,18 +2,37 @@ require 'ostruct'
class TestServer
include ActionCable::Server::Connections
+ include ActionCable::Server::Broadcasting
- attr_reader :logger, :config
+ attr_reader :logger, :config, :mutex
- def initialize
+ def initialize(subscription_adapter: SuccessAdapter)
@logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new)
- @config = OpenStruct.new(log_tags: [], subscription_adapter: SuccessAdapter)
+
+ @config = OpenStruct.new(log_tags: [], subscription_adapter: subscription_adapter)
+ @config.use_faye = ENV['FAYE'].present?
+ @config.client_socket_class = if @config.use_faye
+ ActionCable::Connection::FayeClientSocket
+ else
+ ActionCable::Connection::ClientSocket
+ end
+
+ @mutex = Monitor.new
end
def pubsub
- @config.subscription_adapter.new(self)
+ @pubsub ||= @config.subscription_adapter.new(self)
+ end
+
+ def event_loop
+ @event_loop ||= if @config.use_faye
+ ActionCable::Connection::FayeEventLoop.new
+ else
+ ActionCable::Connection::StreamEventLoop.new
+ end
end
- def send_async
+ def worker_pool
+ @worker_pool ||= ActionCable::Server::Worker.new(max_size: 5)
end
end
diff --git a/actioncable/test/subscription_adapter/base_test.rb b/actioncable/test/subscription_adapter/base_test.rb
index 7a7ae131e6..256dce673f 100644
--- a/actioncable/test/subscription_adapter/base_test.rb
+++ b/actioncable/test/subscription_adapter/base_test.rb
@@ -43,7 +43,7 @@ class ActionCable::SubscriptionAdapter::BaseTest < ActionCable::TestCase
assert_respond_to(SuccessAdapter.new(@server), :broadcast)
- assert_nothing_raised NotImplementedError do
+ assert_nothing_raised do
broadcast
end
end
@@ -55,7 +55,7 @@ class ActionCable::SubscriptionAdapter::BaseTest < ActionCable::TestCase
assert_respond_to(SuccessAdapter.new(@server), :subscribe)
- assert_nothing_raised NotImplementedError do
+ assert_nothing_raised do
subscribe
end
end
@@ -66,7 +66,7 @@ class ActionCable::SubscriptionAdapter::BaseTest < ActionCable::TestCase
assert_respond_to(SuccessAdapter.new(@server), :unsubscribe)
- assert_nothing_raised NotImplementedError do
+ assert_nothing_raised do
unsubscribe
end
end
diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb
index d4a13be889..285c690df0 100644
--- a/actioncable/test/subscription_adapter/common.rb
+++ b/actioncable/test/subscription_adapter/common.rb
@@ -1,7 +1,6 @@
require 'test_helper'
require 'concurrent'
-require 'action_cable/process/logging'
require 'active_support/core_ext/hash/indifferent_access'
require 'pathname'
@@ -10,23 +9,9 @@ module CommonSubscriptionAdapterTest
WAIT_WHEN_NOT_EXPECTING_EVENT = 0.2
def setup
- # TODO: ActionCable requires a *lot* of setup at the moment...
- ::Object.const_set(:ApplicationCable, Module.new)
- ::ApplicationCable.const_set(:Connection, Class.new(ActionCable::Connection::Base))
-
- ::Object.const_set(:Rails, Module.new)
- ::Rails.singleton_class.send(:define_method, :root) { Pathname.new(__dir__) }
-
server = ActionCable::Server::Base.new
- server.config = ActionCable::Server::Configuration.new
- inner_logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
- server.config.logger = ActionCable::Connection::TaggedLoggerProxy.new(inner_logger, tags: [])
-
-
- # and now the "real" setup for our test:
- spawn_eventmachine
-
server.config.cable = cable_config.with_indifferent_access
+ server.config.use_faye = ENV['FAYE'].present?
adapter_klass = server.config.pubsub_adapter
@@ -35,17 +20,7 @@ module CommonSubscriptionAdapterTest
end
def teardown
- @tx_adapter.shutdown if @tx_adapter && @tx_adapter != @rx_adapter
- @rx_adapter.shutdown if @rx_adapter
-
- begin
- ::Object.send(:remove_const, :ApplicationCable)
- rescue NameError
- end
- begin
- ::Object.send(:remove_const, :Rails)
- rescue NameError
- end
+ [@rx_adapter, @tx_adapter].uniq.each(&:shutdown)
end
diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb
new file mode 100644
index 0000000000..6d20e6ed78
--- /dev/null
+++ b/actioncable/test/subscription_adapter/evented_redis_test.rb
@@ -0,0 +1,21 @@
+require 'test_helper'
+require_relative './common'
+
+class EventedRedisAdapterTest < ActionCable::TestCase
+ include CommonSubscriptionAdapterTest
+
+ def setup
+ super
+
+ # em-hiredis is warning-rich
+ @previous_verbose, $VERBOSE = $VERBOSE, nil
+ end
+
+ def teardown
+ $VERBOSE = @previous_verbose
+ end
+
+ def cable_config
+ { adapter: 'evented_redis', url: 'redis://127.0.0.1:6379/12' }
+ end
+end
diff --git a/actioncable/test/subscription_adapter/postgresql_test.rb b/actioncable/test/subscription_adapter/postgresql_test.rb
index 64c632b0cd..214352a0b2 100644
--- a/actioncable/test/subscription_adapter/postgresql_test.rb
+++ b/actioncable/test/subscription_adapter/postgresql_test.rb
@@ -15,8 +15,16 @@ class PostgresqlAdapterTest < ActionCable::TestCase
local_config = ARTest.config['arunit']
database_config.update local_config if local_config
end
+
ActiveRecord::Base.establish_connection database_config
+ begin
+ ActiveRecord::Base.connection
+ rescue
+ @rx_adapter = @tx_adapter = nil
+ skip "Couldn't connect to PostgreSQL: #{database_config.inspect}"
+ end
+
super
end
diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb
index 8d52832c87..4f34dd86c9 100644
--- a/actioncable/test/subscription_adapter/redis_test.rb
+++ b/actioncable/test/subscription_adapter/redis_test.rb
@@ -5,6 +5,12 @@ class RedisAdapterTest < ActionCable::TestCase
include CommonSubscriptionAdapterTest
def cable_config
- { adapter: 'redis', url: 'redis://127.0.0.1:6379/12' }
+ { adapter: 'redis', driver: 'ruby', url: 'redis://127.0.0.1:6379/12' }
+ end
+end
+
+class RedisAdapterTest::Hiredis < RedisAdapterTest
+ def cable_config
+ super.merge(driver: 'hiredis')
end
end
diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb
index 6636ce078b..0a9ee7ce77 100644
--- a/actioncable/test/test_helper.rb
+++ b/actioncable/test/test_helper.rb
@@ -1,40 +1,73 @@
-require File.expand_path('../../../load_paths', __FILE__)
-
require 'action_cable'
require 'active_support/testing/autorun'
-
require 'puma'
-
require 'mocha/setup'
-
require 'rack/mock'
+begin
+ require 'byebug'
+rescue LoadError
+end
+
# Require all the stubs and models
Dir[File.dirname(__FILE__) + '/stubs/*.rb'].each {|file| require file }
-require 'faye/websocket'
-class << Faye::WebSocket
- remove_method :ensure_reactor_running
+if ENV['FAYE'].present?
+ require 'faye/websocket'
+ class << Faye::WebSocket
+ remove_method :ensure_reactor_running
- # We don't want Faye to start the EM reactor in tests because it makes testing much harder.
- # We want to be able to start and stop EM loop in tests to make things simpler.
- def ensure_reactor_running
- # no-op
+ # We don't want Faye to start the EM reactor in tests because it makes testing much harder.
+ # We want to be able to start and stop EM loop in tests to make things simpler.
+ def ensure_reactor_running
+ # no-op
+ end
end
end
-class ActionCable::TestCase < ActiveSupport::TestCase
+module EventMachineConcurrencyHelpers
+ def wait_for_async
+ EM.run_deferred_callbacks
+ end
+
def run_in_eventmachine
+ failure = nil
EM.run do
- yield
-
- EM.run_deferred_callbacks
- EM.stop
+ begin
+ yield
+ rescue => ex
+ failure = ex
+ ensure
+ wait_for_async
+ EM.stop if EM.reactor_running?
+ end
end
+ raise failure if failure
+ end
+end
+
+module ConcurrentRubyConcurrencyHelpers
+ def wait_for_async
+ wait_for_executor Concurrent.global_io_executor
end
- def spawn_eventmachine
- Thread.new { EventMachine.run } unless EventMachine.reactor_running?
+ def run_in_eventmachine
+ yield
+ wait_for_async
+ end
+end
+
+class ActionCable::TestCase < ActiveSupport::TestCase
+ if ENV['FAYE'].present?
+ include EventMachineConcurrencyHelpers
+ else
+ include ConcurrentRubyConcurrencyHelpers
+ end
+
+ def wait_for_executor(executor)
+ until executor.completed_task_count == executor.scheduled_task_count
+ sleep 0.1
+ end
end
end
diff --git a/actioncable/test/worker_test.rb b/actioncable/test/worker_test.rb
index 4a699cde27..e2c81fe312 100644
--- a/actioncable/test/worker_test.rb
+++ b/actioncable/test/worker_test.rb
@@ -17,7 +17,9 @@ class WorkerTest < ActiveSupport::TestCase
end
def logger
- ActionCable.server.logger
+ # Impersonating a connection requires a TaggedLoggerProxy'ied logger.
+ inner_logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
+ ActionCable::Connection::TaggedLoggerProxy.new(inner_logger, tags: [])
end
end
@@ -31,22 +33,12 @@ class WorkerTest < ActiveSupport::TestCase
end
test "invoke" do
- @worker.invoke @receiver, :run
+ @worker.invoke @receiver, :run, connection: @receiver.connection
assert_equal :run, @receiver.last_action
end
test "invoke with arguments" do
- @worker.invoke @receiver, :process, "Hello"
+ @worker.invoke @receiver, :process, "Hello", connection: @receiver.connection
assert_equal [ :process, "Hello" ], @receiver.last_action
end
-
- test "running periodic timers with a proc" do
- @worker.run_periodic_timer @receiver, @receiver.method(:run)
- assert_equal :run, @receiver.last_action
- end
-
- test "running periodic timers with a method" do
- @worker.run_periodic_timer @receiver, :run
- assert_equal :run, @receiver.last_action
- end
end
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index e74e0bc20a..3b9f503a0b 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,94 +1,6 @@
-## Rails 5.0.0.beta1 (December 18, 2015) ##
+* Exception handling: use `rescue_from` to handle exceptions raised by
+ mailer actions, by message delivery, and by deferred delivery jobs.
-* `config.force_ssl = true` will set
- `config.action_mailer.default_url_options = { protocol: 'https' }`.
+ *Jeremy Daer*
- *Andrew Kampjes*
-
-* Add `config.action_mailer.deliver_later_queue_name` configuration to set the
- mailer queue name.
-
- *Chris McGrath*
-
-* `assert_emails` in block form use the given number as expected value.
- This makes the error message much easier to understand.
-
- *Yuji Yaginuma*
-
-* Add support for inline images in mailer previews by using an interceptor
- class to convert cid: urls in image src attributes to data urls.
-
- *Andrew White*
-
-* Mailer preview now uses `url_for` to fix links to emails for apps running on
- a subdirectory.
-
- *Remo Mueller*
-
-* Mailer previews no longer crash when the `mail` method wasn't called
- (`NullMail`).
-
- Fixes #19849.
-
- *Yves Senn*
-
-* Make sure labels and values line up in mailer previews.
-
- *Yves Senn*
-
-* Add `assert_enqueued_emails` and `assert_no_enqueued_emails`.
-
- Example:
-
- def test_emails
- assert_enqueued_emails 2 do
- ContactMailer.welcome.deliver_later
- ContactMailer.welcome.deliver_later
- end
- end
-
- def test_no_emails
- assert_no_enqueued_emails do
- # No emails enqueued here
- end
- end
-
- *George Claghorn*
-
-* Add `_mailer` suffix to mailers created via generator, following the same
- naming convention used in controllers and jobs.
-
- *Carlos Souza*
-
-* Remove deprecated `*_path` helpers in email views.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `deliver` and `deliver!` methods.
-
- *claudiob*
-
-* Template lookup now respects default locale and I18n fallbacks.
-
- Given the following templates:
-
- mailer/demo.html.erb
- mailer/demo.en.html.erb
- mailer/demo.pt.html.erb
-
- 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.
-
- Now `mailer/demo.en.html.erb` has precedence over the file without locale.
-
- Also, it is possible to give a fallback.
-
- mailer/demo.pt.html.erb
- mailer/demo.pt-BR.html.erb
-
- So if the locale is `pt-PT`, `mailer/demo.pt.html.erb` will be rendered given
- the right I18n fallback configuration.
-
- *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.
+Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actionmailer/CHANGELOG.md) for previous changes.
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index 7197ea5e27..416c9ee33a 100644
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -3,6 +3,8 @@ require 'rake/testtask'
desc "Default Task"
task default: [ :test ]
+task :package
+
# Run the unit tests
Rake::TestTask.new { |t|
t.libs << "test"
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index fa6043fdd7..25e3bcb2e9 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -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.5'
+ s.add_dependency 'rails-dom-testing', '~> 2.0'
end
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index bb3cb1be45..e766221008 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -5,6 +5,7 @@ require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/anonymous'
require 'action_mailer/log_subscriber'
+require 'action_mailer/rescuable'
module ActionMailer
# Action Mailer allows you to send email from your application using a mailer model and views.
@@ -86,7 +87,7 @@ module ActionMailer
# Like Action Controller, each mailer class has a corresponding view directory in which each
# method of the class looks for a template with its name.
#
- # To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same
+ # To define a template to be used with a mailer, 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_mailer/welcome.text.erb</tt> would be used to generate the email.
#
@@ -144,7 +145,7 @@ module ActionMailer
# mail.deliver_now # generates and sends the email now
#
# The <tt>ActionMailer::MessageDelivery</tt> class is a wrapper around a delegate that will call
- # your method to generate the mail. If you want direct access to delegator, or <tt>Mail::Message</tt>,
+ # your method to generate the mail. If you want direct access to the delegator, or <tt>Mail::Message</tt>,
# you can call the <tt>message</tt> method on the <tt>ActionMailer::MessageDelivery</tt> object.
#
# NotifierMailer.welcome(User.first).message # => a Mail::Message object
@@ -163,7 +164,7 @@ module ActionMailer
#
# Multipart messages can also be used implicitly because Action Mailer will automatically detect and use
# multipart templates, where each template is named after the name of the action, followed by the content
- # type. Each such detected template will be added as a separate part to the message.
+ # type. Each such detected template will be added to the message, as a separate part.
#
# For example, if the following templates exist:
# * signup_notification.text.erb
@@ -288,8 +289,8 @@ module ActionMailer
# end
#
# Note that the proc is evaluated right at the start of the mail message generation, so if you
- # set something in the default using a proc, and then set the same thing inside of your
- # mailer method, it will get over written by the mailer method.
+ # set something in the default hash using a proc, and then set the same thing inside of your
+ # mailer method, it will get overwritten by the mailer method.
#
# It is also possible to set these default options that will be used in all mailers through
# the <tt>default_options=</tt> configuration in <tt>config/application.rb</tt>:
@@ -389,9 +390,9 @@ module ActionMailer
# to use it. Defaults to <tt>true</tt>.
# * <tt>:openssl_verify_mode</tt> - When using TLS, you can set how OpenSSL checks the certificate. This is
# really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name
- # of an OpenSSL verify constant (<tt>'none'</tt>, <tt>'peer'</tt>, <tt>'client_once'</tt>,
- # <tt>'fail_if_no_peer_cert'</tt>) or directly the constant (<tt>OpenSSL::SSL::VERIFY_NONE</tt>,
- # <tt>OpenSSL::SSL::VERIFY_PEER</tt>, ...).
+ # of an OpenSSL verify constant (<tt>'none'</tt> or <tt>'peer'</tt>) or directly the constant
+ # (<tt>OpenSSL::SSL::VERIFY_NONE</tt> or <tt>OpenSSL::SSL::VERIFY_PEER</tt>).
+ # <tt>:ssl/:tls</tt> Enables the SMTP connection to use SMTP/TLS (SMTPS: SMTP over direct TLS connection)
#
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
# * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
@@ -419,6 +420,7 @@ module ActionMailer
# * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>.
class Base < AbstractController::Base
include DeliveryMethods
+ include Rescuable
include Previews
abstract!
@@ -430,6 +432,7 @@ module ActionMailer
include AbstractController::Translation
include AbstractController::AssetPaths
include AbstractController::Callbacks
+ include AbstractController::Caching
include ActionView::Layouts
@@ -484,7 +487,7 @@ module ActionMailer
end
private :observer_class_for
- # Returns the name of current mailer. This method is also being used as a path for a view lookup.
+ # Returns the name of the current mailer. This method is also being used as a path for a view lookup.
# If this is an anonymous mailer, this method will return +anonymous+ instead.
def mailer_name
@mailer_name ||= anonymous? ? "anonymous" : name.underscore
@@ -664,7 +667,7 @@ module ActionMailer
#
# You can also specify overrides if you want by passing a hash instead of a string:
#
- # mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip',
+ # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip',
# content: File.read('/path/to/filename.jpg')}
#
# If you want to use encoding other than Base64 then you will need to pass encoding
@@ -672,7 +675,7 @@ module ActionMailer
# data:
#
# file_content = SpecialEncode(File.read('/path/to/filename.jpg'))
- # mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip',
+ # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip',
# encoding: 'SpecialEncoding',
# content: file_content }
#
@@ -947,6 +950,18 @@ module ActionMailer
container.add_part(part)
end
+ # This and #instrument_name is for caching instrument
+ def instrument_payload(key)
+ {
+ mailer: mailer_name,
+ key: key
+ }
+ end
+
+ def instrument_name
+ "action_mailer"
+ end
+
ActiveSupport.run_load_hooks(:action_mailer, self)
end
end
diff --git a/actionmailer/lib/action_mailer/delivery_job.rb b/actionmailer/lib/action_mailer/delivery_job.rb
index 52772af2d3..d371c1b61a 100644
--- a/actionmailer/lib/action_mailer/delivery_job.rb
+++ b/actionmailer/lib/action_mailer/delivery_job.rb
@@ -3,11 +3,32 @@ 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.
+ #
+ # Exceptions are rescued and handled by the mailer class.
class DeliveryJob < ActiveJob::Base # :nodoc:
queue_as { ActionMailer::Base.deliver_later_queue_name }
+ rescue_from StandardError, with: :handle_exception_with_mailer_class
+
def perform(mailer, mail_method, delivery_method, *args) #:nodoc:
mailer.constantize.public_send(mail_method, *args).send(delivery_method)
end
+
+ private
+ # "Deserialize" the mailer class name by hand in case another argument
+ # (like a Global ID reference) raised DeserializationError.
+ def mailer_class
+ if mailer = Array(@serialized_arguments).first || Array(arguments).first
+ mailer.constantize
+ end
+ end
+
+ def handle_exception_with_mailer_class(exception)
+ if klass = mailer_class
+ klass.handle_exception exception
+ else
+ raise exception
+ end
+ end
end
end
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index 4758b55a2a..571c8e7d2a 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -36,7 +36,7 @@ module ActionMailer
add_delivery_method :sendmail, Mail::Sendmail,
location: '/usr/sbin/sendmail',
- arguments: '-i -t'
+ arguments: '-i'
add_delivery_method :test, Mail::TestMailer
end
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
index d28568b6a1..7dafceef2b 100644
--- a/actionmailer/lib/action_mailer/gem_version.rb
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -6,9 +6,9 @@ module ActionMailer
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta1.1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
index 6d02b39225..419d6c7b93 100644
--- a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
+++ b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
@@ -3,7 +3,7 @@ require 'base64'
module ActionMailer
# Implements a mailer preview interceptor that converts image tag src attributes
# that use inline cid: style urls to data: style urls so that they are visible
- # when previewing a HTML email in a web browser.
+ # when previewing an HTML email in a web browser.
#
# This interceptor is enabled by default. To disable it, delete it from the
# <tt>ActionMailer::Base.preview_interceptors</tt> array:
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index 5fcb5a0c88..c5ba5f9f1d 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -1,7 +1,6 @@
require 'delegate'
module ActionMailer
-
# The <tt>ActionMailer::MessageDelivery</tt> class is used by
# <tt>ActionMailer::Base</tt> when creating a new mailer.
# <tt>MessageDelivery</tt> is a wrapper (+Delegator+ subclass) around a lazy
@@ -14,29 +13,35 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job
# Notifier.welcome(User.first).message # a Mail::Message object
class MessageDelivery < Delegator
- def initialize(mailer, mail_method, *args) #:nodoc:
- @mailer = mailer
- @mail_method = mail_method
- @args = args
+ def initialize(mailer_class, action, *args) #:nodoc:
+ @mailer_class, @action, @args = mailer_class, action, args
+
+ # The mail is only processed if we try to call any methods on it.
+ # Typical usage will leave it unloaded and call deliver_later.
+ @processed_mailer = nil
+ @mail_message = nil
end
+ # Method calls are delegated to the Mail::Message that's ready to deliver.
def __getobj__ #:nodoc:
- @obj ||= begin
- mailer = @mailer.new
- mailer.process @mail_method, *@args
- mailer.message
- end
+ @mail_message ||= processed_mailer.message
end
- def __setobj__(obj) #:nodoc:
- @obj = obj
+ # Unused except for delegator internals (dup, marshaling).
+ def __setobj__(mail_message) #:nodoc:
+ @mail_message = mail_message
end
- # Returns the Mail::Message object
+ # Returns the resulting Mail::Message
def message
__getobj__
end
+ # Was the delegate loaded, causing the mailer action to be processed?
+ def processed?
+ @processed_mailer || @mail_message
+ end
+
# Enqueues the email to be delivered through Active Job. When the
# job runs it will send the email using +deliver_now!+. That means
# that the message will be sent bypassing checking +perform_deliveries+
@@ -77,7 +82,9 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_now!
#
def deliver_now!
- message.deliver!
+ processed_mailer.handle_exceptions do
+ message.deliver!
+ end
end
# Delivers an email:
@@ -85,14 +92,34 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_now
#
def deliver_now
- message.deliver
+ processed_mailer.handle_exceptions do
+ message.deliver
+ end
end
private
+ # Returns the processed Mailer instance. We keep this instance
+ # on hand so we can delegate exception handling to it.
+ def processed_mailer
+ @processed_mailer ||= @mailer_class.new.tap do |mailer|
+ mailer.process @action, *@args
+ end
+ end
def enqueue_delivery(delivery_method, options={})
- args = @mailer.name, @mail_method.to_s, delivery_method.to_s, *@args
- ActionMailer::DeliveryJob.set(options).perform_later(*args)
+ if processed?
+ ::Kernel.raise "You've accessed the message before asking to " \
+ "deliver it later, so you may have made local changes that would " \
+ "be silently lost if we enqueued a job to deliver it. Why? Only " \
+ "the mailer method *arguments* are passed with the delivery job! " \
+ "Do not access the message in any way if you mean to deliver it " \
+ "later. Workarounds: 1. don't touch the message before calling " \
+ "#deliver_later, 2. only touch the message *within your mailer " \
+ "method*, or 3. use a custom Active Job instead of #deliver_later."
+ else
+ args = @mailer_class.name, @action.to_s, delivery_method.to_s, *@args
+ ::ActionMailer::DeliveryJob.set(options).perform_later(*args)
+ end
end
end
end
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index fa707021c7..a727ed38e9 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -25,6 +25,7 @@ module ActionMailer
options.javascripts_dir ||= paths["public/javascripts"].first
options.stylesheets_dir ||= paths["public/stylesheets"].first
options.show_previews = Rails.env.development? if options.show_previews.nil?
+ options.cache_store ||= Rails.cache
if options.show_previews
options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil
@@ -44,14 +45,9 @@ module ActionMailer
register_observers(options.delete(:observers))
options.each { |k,v| send("#{k}=", v) }
-
- if options.show_previews
- app.routes.prepend do
- get '/rails/mailers' => "rails/mailers#index"
- get '/rails/mailers/*path' => "rails/mailers#preview"
- end
- end
end
+
+ ActiveSupport.on_load(:action_dispatch_integration_test) { include ActionMailer::TestCase::ClearTestDeliveries }
end
initializer "action_mailer.compile_config_methods" do
@@ -60,9 +56,18 @@ module ActionMailer
end
end
- config.after_initialize do
- if ActionMailer::Base.preview_path
- ActiveSupport::Dependencies.autoload_paths << ActionMailer::Base.preview_path
+ config.after_initialize do |app|
+ options = app.config.action_mailer
+
+ if options.show_previews
+ app.routes.prepend do
+ get '/rails/mailers' => "rails/mailers#index", internal: true
+ get '/rails/mailers/*path' => "rails/mailers#preview", internal: true
+ end
+
+ if options.preview_path
+ ActiveSupport::Dependencies.autoload_paths << options.preview_path
+ end
end
end
end
diff --git a/actionmailer/lib/action_mailer/rescuable.rb b/actionmailer/lib/action_mailer/rescuable.rb
new file mode 100644
index 0000000000..f2eabfa057
--- /dev/null
+++ b/actionmailer/lib/action_mailer/rescuable.rb
@@ -0,0 +1,27 @@
+module ActionMailer #:nodoc:
+ # Provides `rescue_from` for mailers. Wraps mailer action processing,
+ # mail job processing, and mail delivery.
+ module Rescuable
+ extend ActiveSupport::Concern
+ include ActiveSupport::Rescuable
+
+ class_methods do
+ def handle_exception(exception) #:nodoc:
+ rescue_with_handler(exception) || raise(exception)
+ end
+ end
+
+ def handle_exceptions #:nodoc:
+ yield
+ rescue => exception
+ rescue_with_handler(exception) || raise
+ end
+
+ private
+ def process(*)
+ handle_exceptions do
+ super
+ end
+ end
+ end
+end
diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb
index 0aa15e31ba..b045e883ad 100644
--- a/actionmailer/lib/action_mailer/test_case.rb
+++ b/actionmailer/lib/action_mailer/test_case.rb
@@ -11,6 +11,23 @@ module ActionMailer
end
class TestCase < ActiveSupport::TestCase
+ module ClearTestDeliveries
+ extend ActiveSupport::Concern
+
+ included do
+ setup :clear_test_deliveries
+ teardown :clear_test_deliveries
+ end
+
+ private
+
+ def clear_test_deliveries
+ if ActionMailer::Base.delivery_method == :test
+ ActionMailer::Base.deliveries.clear
+ end
+ end
+ end
+
module Behavior
extend ActiveSupport::Concern
@@ -61,12 +78,12 @@ module ActionMailer
set_delivery_method :test
@old_perform_deliveries = ActionMailer::Base.perform_deliveries
ActionMailer::Base.perform_deliveries = true
+ ActionMailer::Base.deliveries.clear
end
def restore_test_deliveries # :nodoc:
restore_delivery_method
ActionMailer::Base.perform_deliveries = @old_perform_deliveries
- ActionMailer::Base.deliveries.clear
end
def set_delivery_method(method) # :nodoc:
@@ -75,6 +92,7 @@ module ActionMailer
end
def restore_delivery_method # :nodoc:
+ ActionMailer::Base.deliveries.clear
ActionMailer::Base.delivery_method = @old_delivery_method
end
diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
index 5a5c9d32bb..01bdfb0685 100644
--- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
+++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
@@ -9,13 +9,28 @@ module Rails
def create_mailer_file
template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}_mailer.rb")
+
+ in_root do
+ if self.behavior == :invoke && !File.exist?(application_mailer_file_name)
+ template 'application_mailer.rb', application_mailer_file_name
+ end
+ end
end
hook_for :template_engine, :test_framework
protected
def file_name
- @_file_name ||= super.gsub(/\_mailer/i, '')
+ @_file_name ||= super.gsub(/_mailer/i, '')
+ end
+
+ private
+ def application_mailer_file_name
+ @_application_mailer_file_name ||= if mountable_engine?
+ "app/mailers/#{namespaced_path}/application_mailer.rb"
+ else
+ "app/mailers/application_mailer.rb"
+ 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..00fb9bd48f
--- /dev/null
+++ b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb
@@ -0,0 +1,6 @@
+<% module_namespacing do -%>
+class ApplicationMailer < ActionMailer::Base
+ default from: 'from@example.com'
+ layout 'mailer'
+end
+<% end %>
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 285b2cfcb5..8d740ac863 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -1,4 +1,3 @@
-require File.expand_path('../../../load_paths', __FILE__)
require 'active_support/core_ext/kernel/reporting'
# These are the normal settings that will be set up by Railties
diff --git a/actionmailer/test/caching_test.rb b/actionmailer/test/caching_test.rb
new file mode 100644
index 0000000000..22e1bdb5f1
--- /dev/null
+++ b/actionmailer/test/caching_test.rb
@@ -0,0 +1,271 @@
+require 'fileutils'
+require 'abstract_unit'
+require 'mailers/base_mailer'
+require 'mailers/caching_mailer'
+
+CACHE_DIR = 'test_cache'
+# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
+FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
+
+class FragmentCachingMailer < ActionMailer::Base
+ abstract!
+
+ def some_action; end
+end
+
+class BaseCachingTest < ActiveSupport::TestCase
+ def setup
+ super
+ @store = ActiveSupport::Cache::MemoryStore.new
+ @mailer = FragmentCachingMailer.new
+ @mailer.perform_caching = true
+ @mailer.cache_store = @store
+ end
+
+ def test_fragment_cache_key
+ assert_equal 'views/what a key', @mailer.fragment_cache_key('what a key')
+ end
+end
+
+class FragmentCachingTest < BaseCachingTest
+ def test_read_fragment_with_caching_enabled
+ @store.write('views/name', 'value')
+ assert_equal 'value', @mailer.read_fragment('name')
+ end
+
+ def test_read_fragment_with_caching_disabled
+ @mailer.perform_caching = false
+ @store.write('views/name', 'value')
+ assert_nil @mailer.read_fragment('name')
+ end
+
+ def test_fragment_exist_with_caching_enabled
+ @store.write('views/name', 'value')
+ assert @mailer.fragment_exist?('name')
+ assert !@mailer.fragment_exist?('other_name')
+ end
+
+ def test_fragment_exist_with_caching_disabled
+ @mailer.perform_caching = false
+ @store.write('views/name', 'value')
+ assert !@mailer.fragment_exist?('name')
+ assert !@mailer.fragment_exist?('other_name')
+ end
+
+ def test_write_fragment_with_caching_enabled
+ assert_nil @store.read('views/name')
+ assert_equal 'value', @mailer.write_fragment('name', 'value')
+ assert_equal 'value', @store.read('views/name')
+ end
+
+ def test_write_fragment_with_caching_disabled
+ assert_nil @store.read('views/name')
+ @mailer.perform_caching = false
+ assert_equal 'value', @mailer.write_fragment('name', 'value')
+ assert_nil @store.read('views/name')
+ end
+
+ def test_expire_fragment_with_simple_key
+ @store.write('views/name', 'value')
+ @mailer.expire_fragment 'name'
+ assert_nil @store.read('views/name')
+ end
+
+ def test_expire_fragment_with_regexp
+ @store.write('views/name', 'value')
+ @store.write('views/another_name', 'another_value')
+ @store.write('views/primalgrasp', 'will not expire ;-)')
+
+ @mailer.expire_fragment(/name/)
+
+ assert_nil @store.read('views/name')
+ assert_nil @store.read('views/another_name')
+ assert_equal 'will not expire ;-)', @store.read('views/primalgrasp')
+ end
+
+ def test_fragment_for
+ @store.write('views/expensive', 'fragment content')
+ fragment_computed = false
+
+ view_context = @mailer.view_context
+
+ buffer = 'generated till now -> '.html_safe
+ buffer << view_context.send(:fragment_for, 'expensive') { fragment_computed = true }
+
+ assert !fragment_computed
+ assert_equal 'generated till now -> fragment content', buffer
+ end
+
+ def test_html_safety
+ assert_nil @store.read('views/name')
+ content = 'value'.html_safe
+ assert_equal content, @mailer.write_fragment('name', content)
+
+ cached = @store.read('views/name')
+ assert_equal content, cached
+ assert_equal String, cached.class
+
+ html_safe = @mailer.read_fragment('name')
+ assert_equal content, html_safe
+ assert html_safe.html_safe?
+ end
+end
+
+class FunctionalFragmentCachingTest < BaseCachingTest
+ def setup
+ super
+ @store = ActiveSupport::Cache::MemoryStore.new
+ @mailer = CachingMailer.new
+ @mailer.perform_caching = true
+ @mailer.cache_store = @store
+ end
+
+ def test_fragment_caching
+ email = @mailer.fragment_cache
+ expected_body = "\"Welcome\""
+
+ assert_match expected_body, email.body.encoded
+ assert_match expected_body,
+ @store.read("views/caching/#{template_digest("caching_mailer/fragment_cache")}")
+ end
+
+ def test_fragment_caching_in_partials
+ email = @mailer.fragment_cache_in_partials
+ expected_body = 'Old fragment caching in a partial'
+ assert_match(expected_body, email.body.encoded)
+
+ assert_match(expected_body,
+ @store.read("views/caching/#{template_digest("caching_mailer/_partial")}"))
+ end
+
+ def test_skip_fragment_cache_digesting
+ email = @mailer.skip_fragment_cache_digesting
+ expected_body = "No Digest"
+
+ assert_match expected_body, email.body.encoded
+ assert_match expected_body, @store.read("views/no_digest")
+ end
+
+ def test_fragment_caching_options
+ time = Time.now
+ email = @mailer.fragment_caching_options
+ expected_body = "No Digest"
+
+ assert_match expected_body, email.body.encoded
+ Time.stub(:now, time + 11) do
+ assert_nil @store.read("views/no_digest")
+ end
+ end
+
+ def test_multipart_fragment_caching
+ email = @mailer.multipart_cache
+
+ expected_text_body = "\"Welcome text\""
+ expected_html_body = "\"Welcome html\""
+ encoded_body = email.body.encoded
+ assert_match expected_text_body, encoded_body
+ assert_match expected_html_body, encoded_body
+ assert_match expected_text_body,
+ @store.read("views/text_caching")
+ assert_match expected_html_body,
+ @store.read("views/html_caching")
+ end
+
+ def test_fragment_cache_instrumentation
+ payload = nil
+
+ subscriber = proc do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ payload = event.payload
+ end
+
+ ActiveSupport::Notifications.subscribed(subscriber, "read_fragment.action_mailer") do
+ @mailer.fragment_cache
+ end
+
+ assert_equal "caching_mailer", payload[:mailer]
+ assert_equal "views/caching/#{template_digest("caching_mailer/fragment_cache")}", payload[:key]
+ end
+
+ private
+
+ def template_digest(name)
+ ActionView::Digestor.digest(name: name, finder: @mailer.lookup_context)
+ end
+end
+
+class CacheHelperOutputBufferTest < BaseCachingTest
+
+ class MockController
+ def read_fragment(name, options)
+ return false
+ end
+
+ def write_fragment(name, fragment, options)
+ fragment
+ end
+ end
+
+ def setup
+ super
+ end
+
+ def test_output_buffer
+ output_buffer = ActionView::OutputBuffer.new
+ controller = MockController.new
+ cache_helper = Class.new do
+ def self.controller; end;
+ def self.output_buffer; end;
+ def self.output_buffer=; end;
+ end
+ cache_helper.extend(ActionView::Helpers::CacheHelper)
+
+ cache_helper.stub :controller, controller do
+ cache_helper.stub :output_buffer, output_buffer do
+ assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do
+ assert_nothing_raised do
+ cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil }
+ end
+ end
+ end
+ end
+ end
+
+ def test_safe_buffer
+ output_buffer = ActiveSupport::SafeBuffer.new
+ controller = MockController.new
+ cache_helper = Class.new do
+ def self.controller; end;
+ def self.output_buffer; end;
+ def self.output_buffer=; end;
+ end
+ cache_helper.extend(ActionView::Helpers::CacheHelper)
+
+ cache_helper.stub :controller, controller do
+ cache_helper.stub :output_buffer, output_buffer do
+ assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do
+ assert_nothing_raised do
+ cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil }
+ end
+ end
+ end
+ end
+ end
+end
+
+class ViewCacheDependencyTest < BaseCachingTest
+ class NoDependenciesMailer < ActionMailer::Base
+ end
+ class HasDependenciesMailer < ActionMailer::Base
+ view_cache_dependency { "trombone" }
+ view_cache_dependency { "flute" }
+ end
+
+ def test_view_cache_dependencies_are_empty_by_default
+ assert NoDependenciesMailer.new.view_cache_dependencies.empty?
+ end
+
+ def test_view_cache_dependencies_are_listed_in_declaration_order
+ assert_equal %w(trombone flute), HasDependenciesMailer.new.view_cache_dependencies
+ end
+end
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index 2786fe0d07..bcbd036f26 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -39,7 +39,7 @@ class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase
test "default sendmail settings" do
settings = {
location: '/usr/sbin/sendmail',
- arguments: '-i -t'
+ arguments: '-i'
}
assert_equal settings, ActionMailer::Base.sendmail_settings
end
diff --git a/actionmailer/test/fixtures/caching_mailer/_partial.html.erb b/actionmailer/test/fixtures/caching_mailer/_partial.html.erb
new file mode 100644
index 0000000000..8e965f52b4
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/_partial.html.erb
@@ -0,0 +1,3 @@
+<% cache :caching do %>
+ Old fragment caching in a partial
+<% end %>
diff --git a/actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb b/actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb
new file mode 100644
index 0000000000..90189627da
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb
@@ -0,0 +1,3 @@
+<% cache :caching do %>
+"Welcome"
+<% end %>
diff --git a/actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb b/actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb
new file mode 100644
index 0000000000..2957d083e8
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb
@@ -0,0 +1 @@
+<%= render "partial" %>
diff --git a/actionmailer/test/fixtures/caching_mailer/fragment_caching_options.html.erb b/actionmailer/test/fixtures/caching_mailer/fragment_caching_options.html.erb
new file mode 100644
index 0000000000..0541ac321b
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/fragment_caching_options.html.erb
@@ -0,0 +1,3 @@
+<%= cache :no_digest, skip_digest: true, expires_in: 0 do %>
+ No Digest
+<% end %>
diff --git a/actionmailer/test/fixtures/caching_mailer/multipart_cache.html.erb b/actionmailer/test/fixtures/caching_mailer/multipart_cache.html.erb
new file mode 100644
index 0000000000..0d26baa2d7
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/multipart_cache.html.erb
@@ -0,0 +1,3 @@
+<% cache :html_caching, skip_digest: true do %>
+ "Welcome html"
+<% end %>
diff --git a/actionmailer/test/fixtures/caching_mailer/multipart_cache.text.erb b/actionmailer/test/fixtures/caching_mailer/multipart_cache.text.erb
new file mode 100644
index 0000000000..ef97326e03
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/multipart_cache.text.erb
@@ -0,0 +1,3 @@
+<% cache :text_caching, skip_digest: true do %>
+ "Welcome text"
+<% end %>
diff --git a/actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb b/actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb
new file mode 100644
index 0000000000..0d52429a81
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb
@@ -0,0 +1,3 @@
+<%= cache :no_digest, skip_digest: true do %>
+ No Digest
+<% end %>
diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb
index 6124ffeb52..50c4b74eb8 100644
--- a/actionmailer/test/i18n_with_controller_test.rb
+++ b/actionmailer/test/i18n_with_controller_test.rb
@@ -25,7 +25,9 @@ end
class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new
Routes.draw do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
class RoutedRackApp
diff --git a/actionmailer/test/mailers/caching_mailer.rb b/actionmailer/test/mailers/caching_mailer.rb
new file mode 100644
index 0000000000..92d3cff7c9
--- /dev/null
+++ b/actionmailer/test/mailers/caching_mailer.rb
@@ -0,0 +1,23 @@
+class CachingMailer < ActionMailer::Base
+ self.mailer_name = "caching_mailer"
+
+ def fragment_cache
+ mail(subject: "welcome", template_name: "fragment_cache")
+ end
+
+ def fragment_cache_in_partials
+ mail(subject: "welcome", template_name: "fragment_cache_in_partials")
+ end
+
+ def skip_fragment_cache_digesting
+ mail(subject: "welcome", template_name: "skip_fragment_cache_digesting")
+ end
+
+ def fragment_caching_options
+ mail(subject: "welcome", template_name: "fragment_caching_options")
+ end
+
+ def multipart_cache
+ mail(subject: "welcome", template_name: "multipart_cache")
+ end
+end
diff --git a/actionmailer/test/mailers/delayed_mailer.rb b/actionmailer/test/mailers/delayed_mailer.rb
index 62d4baa434..e6211ef028 100644
--- a/actionmailer/test/mailers/delayed_mailer.rb
+++ b/actionmailer/test/mailers/delayed_mailer.rb
@@ -1,6 +1,26 @@
+require 'active_job/arguments'
+
+class DelayedMailerError < StandardError; end
+
class DelayedMailer < ActionMailer::Base
+ cattr_accessor :last_error
+ cattr_accessor :last_rescue_from_instance
+
+ rescue_from DelayedMailerError do |error|
+ @@last_error = error
+ @@last_rescue_from_instance = self
+ end
+
+ rescue_from ActiveJob::DeserializationError do |error|
+ @@last_error = error
+ @@last_rescue_from_instance = self
+ end
def test_message(*)
mail(from: 'test-sender@test.com', to: 'test-receiver@test.com', subject: 'Test Subject', body: 'Test Body')
end
+
+ def test_raise(klass_name)
+ raise klass_name.constantize, 'boom'
+ end
end
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index b834cdd08c..aaed94d519 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -12,16 +12,23 @@ class MessageDeliveryTest < ActiveSupport::TestCase
ActionMailer::Base.deliver_later_queue_name = :test_queue
ActionMailer::Base.delivery_method = :test
ActiveJob::Base.logger = Logger.new(nil)
- @mail = DelayedMailer.test_message(1, 2, 3)
ActionMailer::Base.deliveries.clear
ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
+
+ DelayedMailer.last_error = nil
+ DelayedMailer.last_rescue_from_instance = nil
+
+ @mail = DelayedMailer.test_message(1, 2, 3)
end
teardown do
ActiveJob::Base.logger = @previous_logger
ActionMailer::Base.delivery_method = @previous_delivery_method
ActionMailer::Base.deliver_later_queue_name = @previous_deliver_later_queue_name
+
+ DelayedMailer.last_error = nil
+ DelayedMailer.last_rescue_from_instance = nil
end
test 'should have a message' do
@@ -93,4 +100,54 @@ class MessageDeliveryTest < ActiveSupport::TestCase
@mail.deliver_later(queue: :another_queue)
end
end
+
+ test 'deliver_later after accessing the message is disallowed' do
+ @mail.message # Load the message, which calls the mailer method.
+
+ assert_raise RuntimeError do
+ @mail.deliver_later
+ end
+ end
+
+ test 'job delegates error handling to mailer' do
+ # Superclass not rescued by mailer's rescue_from RuntimeError
+ message = DelayedMailer.test_raise('StandardError')
+ assert_raise(StandardError) { message.deliver_later }
+ assert_nil DelayedMailer.last_error
+ assert_nil DelayedMailer.last_rescue_from_instance
+
+ # Rescued by mailer's rescue_from RuntimeError
+ message = DelayedMailer.test_raise('DelayedMailerError')
+ assert_nothing_raised { message.deliver_later }
+ assert_equal 'boom', DelayedMailer.last_error.message
+ assert_kind_of DelayedMailer, DelayedMailer.last_rescue_from_instance
+ end
+
+ class DeserializationErrorFixture
+ include GlobalID::Identification
+
+ def self.find(id)
+ raise 'boom, missing find'
+ end
+
+ attr_reader :id
+ def initialize(id = 1)
+ @id = id
+ end
+
+ def to_global_id(options = {})
+ super app: 'foo'
+ end
+ end
+
+ test 'job delegates deserialization errors to mailer class' do
+ # Inject an argument that can't be deserialized.
+ message = DelayedMailer.test_message(DeserializationErrorFixture.new)
+
+ # DeserializationError is raised, rescued, and delegated to the handler
+ # on the mailer class.
+ assert_nothing_raised { message.deliver_later }
+ assert_equal DelayedMailer, DelayedMailer.last_rescue_from_instance
+ assert_equal 'Error while trying to deserialize arguments: boom, missing find', DelayedMailer.last_error.message
+ end
end
diff --git a/actionmailer/test/test_case_test.rb b/actionmailer/test/test_case_test.rb
index 86fd37bea6..5d8d3c3b36 100644
--- a/actionmailer/test/test_case_test.rb
+++ b/actionmailer/test/test_case_test.rb
@@ -3,6 +3,44 @@ require 'abstract_unit'
class TestTestMailer < ActionMailer::Base
end
+class ClearTestDeliveriesMixinTest < ActiveSupport::TestCase
+ include ActionMailer::TestCase::ClearTestDeliveries
+
+ def before_setup
+ ActionMailer::Base.delivery_method, @original_delivery_method = :test, ActionMailer::Base.delivery_method
+ ActionMailer::Base.deliveries << 'better clear me, setup'
+ super
+ end
+
+ def after_teardown
+ super
+ assert_equal [], ActionMailer::Base.deliveries
+ ActionMailer::Base.delivery_method = @original_delivery_method
+ end
+
+ def test_deliveries_are_cleared_on_setup_and_teardown
+ assert_equal [], ActionMailer::Base.deliveries
+ ActionMailer::Base.deliveries << 'better clear me, teardown'
+ end
+end
+
+class MailerDeliveriesClearingTest < ActionMailer::TestCase
+ def before_setup
+ ActionMailer::Base.deliveries << 'better clear me, setup'
+ super
+ end
+
+ def after_teardown
+ super
+ assert_equal [], ActionMailer::Base.deliveries
+ end
+
+ def test_deliveries_are_cleared_on_setup_and_teardown
+ assert_equal [], ActionMailer::Base.deliveries
+ ActionMailer::Base.deliveries << 'better clear me, teardown'
+ end
+end
+
class CrazyNameMailerTest < ActionMailer::TestCase
tests TestTestMailer
diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb
index 7928fe9542..70bd05055f 100644
--- a/actionmailer/test/url_test.rb
+++ b/actionmailer/test/url_test.rb
@@ -79,9 +79,11 @@ class ActionMailerUrlTest < ActionMailer::TestCase
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'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ get '/welcome' => 'foo#bar', as: 'welcome'
+ get '/dummy_model' => 'foo#baz', as: 'dummy_model'
+ end
end
# string
@@ -108,8 +110,10 @@ class ActionMailerUrlTest < ActionMailer::TestCase
UrlTestMailer.delivery_method = :test
AppRoutes.draw do
- get ':controller(/:action(/:id))'
- get '/welcome' => "foo#bar", as: "welcome"
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ get '/welcome' => "foo#bar", as: "welcome"
+ end
end
expected = new_mail
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index f6ffe45490..be911b147c 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,653 +1,2 @@
-* Response etags to always be weak: Prefixes 'W/' to value returned by
- `ActionDispatch::Http::Cache::Response#etag=`, such that etags set in
- `fresh_when` and `stale?` are weak.
- Fixes #17556.
-
- *Abhishek Yadav*
-
-* Provide the name of HTTP Status code in assertions.
-
- *Sean Collins*
-
-* More explicit error message when running `rake routes`. `CONTROLLER` argument
- can now be supplied in different ways:
- `Rails::WelcomeController`, `Rails::Welcome`, `rails/welcome`.
-
- Fixes #22918.
-
- *Edouard Chin*
-
-* Allow `ActionController::Parameters` instances as an argument to URL
- helper methods. An `ArgumentError` will be raised if the passed parameters
- are not secure.
-
- Fixes #22832.
-
- *Prathamesh Sonpatki*
-
-* Add option for per-form CSRF tokens.
-
- *Greg Ose & Ben Toews*
-
-* Add tests and documentation for `ActionController::Renderers::use_renderers`.
-
- *Benjamin Fleischer*
-
-* Fix `ActionController::Parameters#convert_parameters_to_hashes` to return filtered
- or unfiltered values based on from where it is called, `to_h` or `to_unsafe_h`
- respectively.
-
- Fixes #22841.
-
- *Prathamesh Sonpatki*
-
-* Add `ActionController::Parameters#include?`
-
- *Justin Coyne*
-
-## Rails 5.0.0.beta1 (December 18, 2015) ##
-
-* Deprecate `redirect_to :back` in favor of `redirect_back`, which accepts a
- required `fallback_location` argument, thus eliminating the possibility of a
- `RedirectBackError`.
-
- *Derek Prior*
-
-* Add `redirect_back` method to `ActionController::Redirecting` to provide a
- way to safely redirect to the `HTTP_REFERER` if it is present, falling back
- to a provided redirect otherwise.
-
- *Derek Prior*
-
-* `ActionController::TestCase` will be moved to it's own gem in Rails 5.1
-
- With the speed improvements made to `ActionDispatch::IntegrationTest` we no
- longer need to keep two separate code bases for testing controllers. In
- Rails 5.1 `ActionController::TestCase` will be deprecated and moved into a
- gem outside of Rails source.
-
- This is a documentation deprecation so that going forward so new tests will use
- `ActionDispatch::IntegrationTest` instead of `ActionController::TestCase`.
-
- *Eileen M. Uchitelle*
-
-* Add a `response_format` option to `ActionDispatch::DebugExceptions`
- to configure the format of the response when errors occur in
- development mode.
-
- If `response_format` is `:default` the debug info will be rendered
- in an HTML page. In the other hand, if the provided value is `:api`
- the debug info will be rendered in the original response format.
-
- *Jorge Bejar*
-
-* Change the `protect_from_forgery` prepend default to `false`.
-
- Per this comment
- https://github.com/rails/rails/pull/18334#issuecomment-69234050 we want
- `protect_from_forgery` to default to `prepend: false`.
-
- `protect_from_forgery` will now be inserted into the callback chain at the
- point it is called in your application. This is useful for cases where you
- want to `protect_from_forgery` after you perform required authentication
- callbacks or other callbacks that are required to run after forgery protection.
-
- If you want `protect_from_forgery` callbacks to always run first, regardless of
- position they are called in your application then you can add `prepend: true`
- to your `protect_from_forgery` call.
-
- Example:
-
- ```ruby
- protect_from_forgery prepend: true
- ```
-
- *Eileen M. Uchitelle*
-
-* In url_for, never append a question mark to the URL when the query string
- is empty anyway. (It used to do that when called like `url_for(controller:
- 'x', action: 'y', q: {})`.)
-
- *Paul Grayson*
-
-* Catch invalid UTF-8 querystring values and respond with BadRequest
-
- Check querystring params for invalid UTF-8 characters, and raise an
- ActionController::BadRequest error if present. Previously these strings
- would typically trigger errors further down the stack.
-
- *Grey Baker*
-
-* Parse RSS/ATOM responses as XML, not HTML.
-
- *Alexander Kaupanin*
-
-* Show helpful message in `BadRequest` exceptions due to invalid path
- parameter encodings.
-
- Fixes #21923.
-
- *Agis Anastasopoulos*
-
-* Add the ability of returning arbitrary headers to `ActionDispatch::Static`.
-
- Now ActionDispatch::Static can accept HTTP headers so that developers
- will have control of returning arbitrary headers like
- 'Access-Control-Allow-Origin' when a response is delivered. They can be
- configured with `#config`:
-
- Example:
-
- config.public_file_server.headers = {
- "Cache-Control" => "public, max-age=60",
- "Access-Control-Allow-Origin" => "http://rubyonrails.org"
- }
-
- *Yuki Nishijima*
-
-* Allow multiple `root` routes in same scope level. Example:
-
- Example:
-
- root 'blog#show', constraints: ->(req) { Hostname.blog_site?(req.host) }
- root 'landing#show'
-
- *Rafael Sales*
-
-* Fix regression in mounted engine named routes generation for app deployed to
- a subdirectory. `relative_url_root` was prepended to the path twice (e.g.
- "/subdir/subdir/engine_path" instead of "/subdir/engine_path")
-
- Fixes #20920. Fixes #21459.
-
- *Matthew Erhard*
-
-* `ActionDispatch::Response#new` no longer applies default headers. If you want
- default headers applied to the response object, then call
- `ActionDispatch::Response.create`. This change only impacts people who are
- directly constructing an `ActionDispatch::Response` object.
-
-* Accessing mime types via constants like `Mime::HTML` is deprecated. Please
- change code like this:
-
- Mime::HTML
-
- To this:
-
- Mime[:html]
-
- This change is so that Rails will not manage a list of constants, and fixes
- an issue where if a type isn't registered you could possibly get the wrong
- object.
-
- `Mime[:html]` is available in older versions of Rails, too, so you can
- safely change libraries and plugins and maintain compatibility with
- multiple versions of Rails.
-
-* `url_for` does not modify its arguments when generating polymorphic URLs.
-
- *Bernerd Schaefer*
-
-* Make it easier to opt in to `config.force_ssl` and `config.ssl_options` by
- making them less dangerous to try and easier to disable.
-
- SSL redirect:
- * Move `:host` and `:port` options within `redirect: { … }`. Deprecate.
- * Introduce `:status` and `:body` to customize the redirect response.
- The 301 permanent default makes it difficult to test the redirect and
- back out of it since browsers remember the 301. Test with a 302 or 307
- instead, then switch to 301 once you're confident that all is well.
-
- HTTP Strict Transport Security (HSTS):
- * Shorter max-age. Shorten the default max-age from 1 year to 180 days,
- the low end for https://www.ssllabs.com/ssltest/ grading and greater
- than the 18-week minimum to qualify for browser preload lists.
- * Disabling HSTS. Setting `hsts: false` now sets `hsts { expires: 0 }`
- instead of omitting the header. Omitting does nothing to disable HSTS
- since browsers hang on to your previous settings until they expire.
- Sending `{ hsts: { expires: 0 }}` flushes out old browser settings and
- actually disables HSTS:
- http://tools.ietf.org/html/rfc6797#section-6.1.1
- * HSTS Preload. Introduce `preload: true` to set the `preload` flag,
- indicating that your site may be included in browser preload lists,
- including Chrome, Firefox, Safari, IE11, and Edge. Submit your site:
- https://hstspreload.appspot.com
-
- *Jeremy Daer*
-
-* Update `ActionController::TestSession#fetch` to behave more like
- `ActionDispatch::Request::Session#fetch` when using non-string keys.
-
- *Jeremy Friesen*
-
-* Using strings or symbols for middleware class names is deprecated. Convert
- things like this:
-
- middleware.use "Foo::Bar"
-
- to this:
-
- middleware.use Foo::Bar
-
-* `ActionController::TestSession` now accepts a default value as well as
- a block for generating a default value based off the key provided.
-
- This fixes calls to `session#fetch` in `ApplicationController` instances that
- take more two arguments or a block from raising `ArgumentError: wrong
- number of arguments (2 for 1)` when performing controller tests.
-
- *Matthew Gerrior*
-
-* Fix `ActionController::Parameters#fetch` overwriting `KeyError` returned by
- default block.
-
- *Jonas Schuber Erlandsson*, *Roque Pinel*
-
-* `ActionController::Parameters` no longer inherits from
- `HashWithIndifferentAccess`
-
- Inheriting from `HashWithIndifferentAccess` allowed users to call any
- enumerable methods on `Parameters` object, resulting in a risk of losing the
- `permitted?` status or even getting back a pure `Hash` object instead of
- a `Parameters` object with proper sanitization.
-
- By not inheriting from `HashWithIndifferentAccess`, we are able to make
- sure that all methods that are defined in `Parameters` object will return
- a proper `Parameters` object with a correct `permitted?` flag.
-
- *Prem Sichanugrist*
-
-* Replaced `ActiveSupport::Concurrency::Latch` with `Concurrent::CountDownLatch`
- from the concurrent-ruby gem.
-
- *Jerry D'Antonio*
-
-* Add ability to filter parameters based on parent keys.
-
- # matches {credit_card: {code: "xxxx"}}
- # doesn't match {file: { code: "xxxx"}}
- config.filter_parameters += [ "credit_card.code" ]
-
- See #13897.
-
- *Guillaume Malette*
-
-* Deprecate passing first parameter as `Hash` and default status code for `head` method.
-
- *Mehmet Emin İNAÇ*
-
-* Adds`Rack::Utils::ParameterTypeError` and `Rack::Utils::InvalidParameterError`
- to the rescue_responses hash in `ExceptionWrapper` (Rack recommends
- integrators serve 400s for both of these).
-
- *Grey Baker*
-
-* Add support for API only apps.
- `ActionController::API` is added as a replacement of
- `ActionController::Base` for this kind of applications.
-
- *Santiago Pastorino*, *Jorge Bejar*
-
-* Remove `assigns` and `assert_template`. Both methods have been extracted
- into a gem at https://github.com/rails/rails-controller-testing.
-
- See #18950.
-
- *Alan Guo Xiang Tan*
-
-* `FileHandler` and `Static` middleware initializers accept `index` argument
- to configure the directory index file name. Defaults to `index` (as in
- `index.html`).
-
- See #20017.
-
- *Eliot Sykes*
-
-* Deprecate `:nothing` option for `render` method.
-
- *Mehmet Emin İNAÇ*
-
-* Fix `rake routes` not showing the right format when
- nesting multiple routes.
-
- See #18373.
-
- *Ravil Bayramgalin*
-
-* Add ability to override default form builder for a controller.
-
- class AdminController < ApplicationController
- default_form_builder AdminFormBuilder
- end
-
- *Kevin McPhillips*
-
-* For actions with no corresponding templates, render `head :no_content`
- instead of raising an error. This allows for slimmer API controller
- methods that simply work, without needing further instructions.
-
- See #19036.
-
- *Stephen Bussey*
-
-* Provide friendlier access to request variants.
-
- request.variant = :phone
- request.variant.phone? # true
- request.variant.tablet? # false
-
- request.variant = [:phone, :tablet]
- request.variant.phone? # true
- request.variant.desktop? # false
- request.variant.any?(:phone, :desktop) # true
- request.variant.any?(:desktop, :watch) # false
-
- *George Claghorn*
-
-* Fix regression where a gzip file response would have a Content-type,
- even when it was a 304 status code.
-
- See #19271.
-
- *Kohei Suzuki*
-
-* Fix handling of empty `X_FORWARDED_HOST` header in `raw_host_with_port`.
-
- Previously, an empty `X_FORWARDED_HOST` header would cause
- `Actiondispatch::Http:URL.raw_host_with_port` to return `nil`, causing
- `Actiondispatch::Http:URL.host` to raise a `NoMethodError`.
-
- *Adam Forsyth*
-
-* Allow `Bearer` as token-keyword in `Authorization-Header`.
-
- Additionally to `Token`, the keyword `Bearer` is acceptable as a keyword
- for the auth-token. The `Bearer` keyword is described in the original
- OAuth RFC and used in libraries like Angular-JWT.
-
- See #19094.
-
- *Peter Schröder*
-
-* Drop request class from `RouteSet` constructor.
-
- If you would like to use a custom request class, please subclass and implement
- the `request_class` method.
-
- *tenderlove@ruby-lang.org*
-
-* Fallback to `ENV['RAILS_RELATIVE_URL_ROOT']` in `url_for`.
-
- 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.
-
- Fixes #5122.
-
- *Yasyf Mohamedali*
-
-* 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.
-
- *Guo Xiang Tan*
-
-* Fix regression in functional tests. Responses should have default headers
- assigned.
-
- See #18423.
-
- *Jeremy Kemper*, *Yves Senn*
-
-* 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).
-
- *Iain Beeston*
-
-* 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]`.
-
- *David Ilizarov*
-
-* 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.
-
- Fixes #18402.
-
- *Ravil Bayramgalin*
-
-* Non-string authenticity tokens do not raise NoMethodError when decoding
- the masked token.
-
- *Ville Lautanala*
-
-* Add `http_cache_forever` to Action Controller, so we can cache a response
- that never gets expired.
-
- *arthurnn*
-
-* `ActionController#translate` supports symbols as shortcuts.
- When a shortcut is given it also performs the lookup without the action
- name.
-
- *Max Melentiev*
-
-* 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.
-
- # Before
- def index
- @articles = Article.all
- fresh_when(etag: @articles, last_modified: @articles.maximum(:updated_at))
- end
-
- # After
- def index
- @articles = Article.all
- fresh_when(@articles)
- end
-
- *claudiob*
-
-* Explicitly ignored wildcard verbs when searching for HEAD routes before fallback
-
- 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.
-
- Example:
-
- draw do
- get '/home' => 'test#index'
- mount rack_app, at: '/'
- end
- head '/home'
- assert_response :success
-
- 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.
-
- *Terence Sun*
-
-* Migrating xhr methods to keyword arguments syntax
- in `ActionController::TestCase` and `ActionDispatch::Integration`
-
- Old syntax:
-
- xhr :get, :create, params: { id: 1 }
-
- New syntax example:
-
- get :create, params: { id: 1 }, xhr: true
-
- *Kir Shatrov*
-
-* Migrating to keyword arguments syntax in `ActionController::TestCase` and
- `ActionDispatch::Integration` HTTP request methods.
-
- Example:
-
- post :create, params: { y: x }, session: { a: 'b' }
- get :view, params: { id: 1 }
- get :view, params: { id: 1 }, format: :json
-
- *Kir Shatrov*
-
-* Preserve default url options when generating URLs.
-
- Fixes an issue that would cause `default_url_options` to be lost when
- generating URLs with fewer positional arguments than parameters in the
- route definition.
-
- *Tekin Suleyman*
-
-* Deprecate `*_via_redirect` integration test methods.
-
- Use `follow_redirect!` manually after the request call for the same behavior.
-
- *Aditya Kapoor*
-
-* Add `ActionController::Renderer` to render arbitrary templates
- outside controller actions.
-
- Its functionality is accessible through class methods `render` and
- `renderer` of `ActionController::Base`.
-
- *Ravil Bayramgalin*
-
-* Support `:assigns` option when rendering with controllers/mailers.
-
- *Ravil Bayramgalin*
-
-* Default headers, removed in controller actions, are no longer reapplied on
- the test response.
-
- *Jonas Baumann*
-
-* Deprecate all `*_filter` callbacks in favor of `*_action` callbacks.
-
- *Rafael Mendonça França*
-
-* Allow you to pass `prepend: false` to `protect_from_forgery` to have the
- verification callback appended instead of prepended to the chain.
- This allows you to let the verification step depend on prior callbacks.
-
- Example:
-
- class ApplicationController < ActionController::Base
- before_action :authenticate
- protect_from_forgery prepend: false, unless: -> { @authenticated_by.oauth? }
-
- private
- def authenticate
- if oauth_request?
- # authenticate with oauth
- @authenticated_by = 'oauth'.inquiry
- else
- # authenticate with cookies
- @authenticated_by = 'cookie'.inquiry
- end
- end
- end
-
- *Josef Šimánek*
-
-* Remove `ActionController::HideActions`.
-
- *Ravil Bayramgalin*
-
-* Remove `respond_to`/`respond_with` placeholder methods, this functionality
- has been extracted to the `responders` gem.
-
- *Carlos Antonio da Silva*
-
-* Remove deprecated assertion files.
-
- *Rafael Mendonça França*
-
-* Remove deprecated usage of string keys in URL helpers.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `only_path` option on `*_path` helpers.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `NamedRouteCollection#helpers`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated support to define routes with `:to` option that doesn't contain `#`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActionDispatch::Response#to_ary`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActionDispatch::Request#deep_munge`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActionDispatch::Http::Parameters#symbolized_path_parameters`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated option `use_route` in controller tests.
-
- *Rafael Mendonça França*
-
-* Ensure `append_info_to_payload` is called even if an exception is raised.
-
- Fixes an issue where when an exception is raised in the request the additional
- payload data is not available.
-
- See #14903.
-
- *Dieter Komendera*, *Margus Pärt*
-
-* Correctly rely on the response's status code to handle calls to `head`.
-
- *Robin Dupret*
-
-* Using `head` method returns empty response_body instead
- of returning a single space " ".
-
- 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.
-
- Fixes #18253.
-
- *Prathamesh Sonpatki*
-
-* Fix how polymorphic routes works with objects that implement `to_model`.
-
- *Travis Grathwell*
-
-* Stop converting empty arrays in `params` to `nil`.
-
- This behavior was introduced in response to CVE-2012-2660, CVE-2012-2694
- and CVE-2013-0155
-
- ActiveRecord now issues a safe query when passing an empty array into
- 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).
-
- *Chris Sinjakli*
-
-* Fixed usage of optional scopes in url helpers.
-
- *Alex Robbin*
-
-* Fixed handling of positional url helper arguments when `format: false`.
-
- Fixes #17819.
-
- *Andrew White*, *Tatiana Soukiassian*
-
-Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionpack/CHANGELOG.md) for previous changes.
+Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actionpack/CHANGELOG.md) for previous changes.
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 601263bfac..4a05c067a9 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -5,6 +5,8 @@ test_files = Dir.glob('test/**/*_test.rb')
desc "Default Task"
task :default => :test
+task :package
+
# Run the unit tests
Rake::TestTask.new do |t|
t.libs << 'test'
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 28d8bc3091..965fafff5f 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.homepage = 'http://rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
s.require_path = 'lib'
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
s.add_dependency 'rack', '~> 2.x'
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 'rails-dom-testing', '~> 2.0'
s.add_dependency 'actionview', version
s.add_development_dependency 'activemodel', version
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb
index 56c4033387..1e57cbaac4 100644
--- a/actionpack/lib/abstract_controller.rb
+++ b/actionpack/lib/abstract_controller.rb
@@ -6,6 +6,7 @@ module AbstractController
extend ActiveSupport::Autoload
autoload :Base
+ autoload :Caching
autoload :Callbacks
autoload :Collector
autoload :DoubleRenderError, "abstract_controller/rendering"
@@ -15,4 +16,9 @@ module AbstractController
autoload :Translation
autoload :AssetPaths
autoload :UrlFor
+
+ def self.eager_load!
+ super
+ AbstractController::Caching.eager_load!
+ end
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 8edea0f52b..aa06f70433 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,13 +1,11 @@
require 'erubis'
+require 'abstract_controller/error'
require 'active_support/configurable'
require 'active_support/descendants_tracker'
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/attr_internal'
module AbstractController
- class Error < StandardError #:nodoc:
- end
-
# Raised when a non-existing controller action is triggered.
class ActionNotFound < StandardError
end
@@ -78,7 +76,7 @@ module AbstractController
end
end
- # action_methods are cached and there is sometimes need to refresh
+ # action_methods are cached and there is sometimes a need to refresh
# them. ::clear_action_methods! allows you to do that, so next time
# you run action_methods, they will be recalculated.
def clear_action_methods!
@@ -152,6 +150,13 @@ module AbstractController
_find_action_name(action_name)
end
+ # Tests if a response body is set. Used to determine if the
+ # +process_action+ callback needs to be terminated in
+ # +AbstractController::Callbacks+.
+ def performed?
+ response_body
+ end
+
# Returns true if the given controller is capable of rendering
# a path. A subclass of +AbstractController::Base+
# may return false. An Email controller for example does not
diff --git a/actionpack/lib/abstract_controller/caching.rb b/actionpack/lib/abstract_controller/caching.rb
new file mode 100644
index 0000000000..0dea50889a
--- /dev/null
+++ b/actionpack/lib/abstract_controller/caching.rb
@@ -0,0 +1,62 @@
+module AbstractController
+ module Caching
+ extend ActiveSupport::Concern
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :Fragments
+ end
+
+ module ConfigMethods
+ def cache_store
+ config.cache_store
+ end
+
+ def cache_store=(store)
+ config.cache_store = ActiveSupport::Cache.lookup_store(store)
+ end
+
+ private
+ def cache_configured?
+ perform_caching && cache_store
+ end
+ end
+
+ include ConfigMethods
+ include AbstractController::Caching::Fragments
+
+ included do
+ extend ConfigMethods
+
+ config_accessor :default_static_extension
+ self.default_static_extension ||= '.html'
+
+ config_accessor :perform_caching
+ self.perform_caching = true if perform_caching.nil?
+
+ class_attribute :_view_cache_dependencies
+ self._view_cache_dependencies = []
+ helper_method :view_cache_dependencies if respond_to?(:helper_method)
+ end
+
+ module ClassMethods
+ def view_cache_dependency(&dependency)
+ self._view_cache_dependencies += [dependency]
+ end
+ end
+
+ def view_cache_dependencies
+ self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact
+ end
+
+ protected
+ # Convenience accessor.
+ def cache(key, options = {}, &block)
+ if cache_configured?
+ cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
+ else
+ yield
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb
index b9ad51a9cf..3257a731ed 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/abstract_controller/caching/fragments.rb
@@ -1,4 +1,4 @@
-module ActionController
+module AbstractController
module Caching
# Fragment caching is used for caching various blocks within
# views without caching the entire action as a whole. This is
@@ -135,13 +135,8 @@ module ActionController
end
def instrument_fragment_cache(name, key) # :nodoc:
- payload = {
- controller: controller_name,
- action: action_name,
- key: key
- }
-
- ActiveSupport::Notifications.instrument("#{name}.action_controller", payload) { yield }
+ payload = instrument_payload(key)
+ ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", payload) { yield }
end
end
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index d63ce9c1c3..3ef8da86fa 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, result_lambda) { result_lambda.call if result_lambda.is_a?(Proc); controller.response_body },
+ terminator: ->(controller, result_lambda) { result_lambda.call if result_lambda.is_a?(Proc); controller.performed? },
skip_after_callbacks_if_terminated: true
end
diff --git a/actionpack/lib/abstract_controller/error.rb b/actionpack/lib/abstract_controller/error.rb
new file mode 100644
index 0000000000..7fafce4dd4
--- /dev/null
+++ b/actionpack/lib/abstract_controller/error.rb
@@ -0,0 +1,4 @@
+module AbstractController
+ class Error < StandardError #:nodoc:
+ end
+end
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index d84c238a62..ab4355296b 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -38,7 +38,8 @@ module AbstractController
end
# Declare a controller method as a helper. For example, the following
- # makes the +current_user+ controller method available to the view:
+ # makes the +current_user+ and +logged_in?+ controller methods available
+ # to the view:
# class ApplicationController < ActionController::Base
# helper_method :current_user, :logged_in?
#
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index e765d73ce4..4ba2c26949 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -1,3 +1,4 @@
+require 'abstract_controller/error'
require 'active_support/concern'
require 'active_support/core_ext/class/attribute'
require 'action_view'
@@ -59,9 +60,7 @@ module AbstractController
end
DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %i(
- @_action_name @_response_body @_formats @_prefixes @_config
- @_view_context_class @_view_renderer @_lookup_context
- @_routes @_db_runtime
+ @_action_name @_response_body @_formats @_prefixes
)
# This method should return a hash with assigns.
@@ -123,7 +122,7 @@ module AbstractController
def _normalize_render(*args, &block)
options = _normalize_args(*args, &block)
#TODO: remove defined? when we restore AP <=> AV dependency
- if defined?(request) && request.variant.present?
+ if defined?(request) && !request.nil? && request.variant.present?
options[:variant] = request.variant
end
_normalize_options(options)
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 40f33a9de0..62f5905205 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -9,12 +9,15 @@ module ActionController
autoload :API
autoload :Base
- autoload :Caching
autoload :Metal
autoload :Middleware
autoload :Renderer
autoload :FormBuilder
+ eager_autoload do
+ autoload :Caching
+ end
+
autoload_under "metal" do
autoload :ConditionalGet
autoload :Cookies
@@ -47,11 +50,6 @@ module ActionController
autoload :TestCase, 'action_controller/test_case'
autoload :TemplateAssertions, 'action_controller/test_case'
-
- def self.eager_load!
- super
- ActionController::Caching.eager_load!
- end
end
# Common Active Support usage in Action Controller
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
index ff12705abe..6bbebb7b4c 100644
--- a/actionpack/lib/action_controller/api.rb
+++ b/actionpack/lib/action_controller/api.rb
@@ -14,22 +14,22 @@ module ActionController
# flash, assets, and so on. This makes the entire controller stack thinner,
# suitable for API applications. It doesn't mean you won't have such
# features if you need them: they're all available for you to include in
- # your application, they're just not part of the default API Controller stack.
+ # your application, they're just not part of the default API controller stack.
#
- # By default, only the ApplicationController in a \Rails application inherits
- # from <tt>ActionController::API</tt>. All other controllers in turn inherit
- # from ApplicationController.
+ # Normally, +ApplicationController+ is the only controller that inherits from
+ # <tt>ActionController::API</tt>. All other controllers in turn inherit from
+ # +ApplicationController+.
#
# A sample controller could look like this:
#
# class PostsController < ApplicationController
# def index
- # @posts = Post.all
- # render json: @posts
+ # posts = Post.all
+ # render json: posts
# end
# end
#
- # Request, response and parameters objects all work the exact same way as
+ # Request, response, and parameters objects all work the exact same way as
# <tt>ActionController::Base</tt>.
#
# == Renders
@@ -37,18 +37,18 @@ module ActionController
# The default API Controller stack includes all renderers, which means you
# can use <tt>render :json</tt> and brothers freely in your controllers. Keep
# in mind that templates are not going to be rendered, so you need to ensure
- # your controller is calling either <tt>render</tt> or <tt>redirect</tt> in
- # all actions, otherwise it will return 204 No Content response.
+ # your controller is calling either <tt>render</tt> or <tt>redirect_to</tt> in
+ # all actions, otherwise it will return 204 No Content.
#
# def show
- # @post = Post.find(params[:id])
- # render json: @post
+ # post = Post.find(params[:id])
+ # render json: post
# end
#
# == Redirects
#
# Redirects are used to move from one action to another. You can use the
- # <tt>redirect</tt> method in your controllers in the same way as
+ # <tt>redirect_to</tt> method in your controllers in the same way as in
# <tt>ActionController::Base</tt>. For example:
#
# def create
@@ -56,7 +56,7 @@ module ActionController
# # do stuff here
# end
#
- # == Adding new behavior
+ # == Adding New Behavior
#
# In some scenarios you may want to add back some functionality provided by
# <tt>ActionController::Base</tt> that is not present by default in
@@ -72,18 +72,19 @@ module ActionController
#
# class PostsController < ApplicationController
# def index
- # @posts = Post.all
+ # posts = Post.all
#
# respond_to do |format|
- # format.json { render json: @posts }
- # format.xml { render xml: @posts }
+ # format.json { render json: posts }
+ # format.xml { render xml: posts }
# end
# end
# end
#
- # Quite straightforward. Make sure to check <tt>ActionController::Base</tt>
- # available modules if you want to include any other functionality that is
- # not provided by <tt>ActionController::API</tt> out of the box.
+ # Quite straightforward. Make sure to check the modules included in
+ # <tt>ActionController::Base</tt> if you want to use any other
+ # functionality that is not provided by <tt>ActionController::API</tt>
+ # out of the box.
class API < Metal
abstract!
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 04e5922ce8..d546d7260c 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -229,7 +229,7 @@ module ActionController
HttpAuthentication::Digest::ControllerMethods,
HttpAuthentication::Token::ControllerMethods,
- # Before callbacks should also be executed the earliest as possible, so
+ # Before callbacks should also be executed as early as possible, so
# also include them at the bottom.
AbstractController::Callbacks,
@@ -251,9 +251,10 @@ module ActionController
setup_renderer!
# Define some internal variables that should not be propagated to the view.
- PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [
- :@_params, :@_response, :@_request,
- :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ]
+ PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + %i(
+ @_params @_response @_request @_config @_url_options @_action_has_layout @_view_context_class
+ @_view_renderer @_lookup_context @_routes @_view_runtime @_db_runtime @_helper_proxy
+ )
def _protected_ivars # :nodoc:
PROTECTED_IVARS
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 0b8fa2ea09..a9a8508abc 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -1,6 +1,3 @@
-require 'fileutils'
-require 'uri'
-
module ActionController
# \Caching is a cheap way of speeding up slow applications by keeping the result of
# calculations, renderings, and database calls around for subsequent requests.
@@ -23,65 +20,25 @@ module ActionController
# config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211')
# config.action_controller.cache_store = MyOwnStore.new('parameter')
module Caching
- extend ActiveSupport::Concern
extend ActiveSupport::Autoload
-
- eager_autoload do
- autoload :Fragments
- end
-
- module ConfigMethods
- def cache_store
- config.cache_store
- end
-
- def cache_store=(store)
- config.cache_store = ActiveSupport::Cache.lookup_store(store)
- end
-
- private
- def cache_configured?
- perform_caching && cache_store
- end
- end
-
- include AbstractController::Callbacks
-
- include ConfigMethods
- include Fragments
+ extend ActiveSupport::Concern
included do
- extend ConfigMethods
-
- config_accessor :default_static_extension
- self.default_static_extension ||= '.html'
-
- config_accessor :perform_caching
- self.perform_caching = true if perform_caching.nil?
-
- class_attribute :_view_cache_dependencies
- self._view_cache_dependencies = []
- helper_method :view_cache_dependencies if respond_to?(:helper_method)
+ include AbstractController::Caching
end
- module ClassMethods
- def view_cache_dependency(&dependency)
- self._view_cache_dependencies += [dependency]
- end
- end
+ private
- def view_cache_dependencies
- self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact
- end
+ def instrument_payload(key)
+ {
+ controller: controller_name,
+ action: action_name,
+ key: key
+ }
+ end
- protected
- # Convenience accessor.
- def cache(key, options = {}, &block)
- if cache_configured?
- cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
- else
- yield
- end
+ def instrument_name
+ "action_controller"
end
end
end
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 4c9f14e409..a0917b4fdb 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -25,7 +25,9 @@ module ActionController
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
- message << " (#{additions.join(" | ".freeze)})" unless additions.blank?
+ message << " (#{additions.join(" | ".freeze)})" unless additions.empty?
+ message << "\n\n" if defined?(Rails.env) && Rails.env.development?
+
message
end
end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index f6a93a8940..f6e67b02d7 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -174,10 +174,8 @@ module ActionController
def response_body=(body)
body = [body] unless body.nil? || body.respond_to?(:each)
response.reset_body!
- body.each { |part|
- next if part.empty?
- response.write part
- }
+ return unless body
+ response.body = body
super
end
diff --git a/actionpack/lib/action_controller/metal/basic_implicit_render.rb b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
index 6c6f8381ff..cef65a362c 100644
--- a/actionpack/lib/action_controller/metal/basic_implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
@@ -1,5 +1,5 @@
module ActionController
- module BasicImplicitRender
+ module BasicImplicitRender # :nodoc:
def send_action(method, *args)
super.tap { default_render unless performed? }
end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index f8e0d9cf6c..e21449f376 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -36,8 +36,23 @@ module ActionController
#
# === Parameters:
#
- # * <tt>:etag</tt>.
- # * <tt>:last_modified</tt>.
+ # * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the
+ # +:weak_etag+ option.
+ # * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response.
+ # Requests that set If-None-Match header may return a 304 Not Modified
+ # response if it matches the ETag exactly. A weak ETag indicates semantic
+ # equivalence, not byte-for-byte equality, so they're good for caching
+ # HTML pages in browser caches. They can't be used for responses that
+ # must be byte-identical, like serving Range requests within a PDF file.
+ # * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response.
+ # Requests that set If-None-Match header may return a 304 Not Modified
+ # response if it matches the ETag exactly. A strong ETag implies exact
+ # equality: the response must match byte for byte. This is necessary for
+ # doing Range requests within a large video or PDF file, for example, or
+ # for compatibility with some CDNs that don't support weak ETags.
+ # * <tt>:last_modified</tt> Sets a "weak" last-update validator on the
+ # response. Subsequent requests that set If-Modified-Since may return a
+ # 304 Not Modified response if last_modified <= If-Modified-Since.
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
# +true+ if you want your application to be cacheable by other devices (proxy caches).
# * <tt>:template</tt> By default, the template digest for the current
@@ -86,12 +101,16 @@ module ActionController
#
# before_action { fresh_when @article, template: 'widgets/show' }
#
- def fresh_when(object = nil, etag: object, last_modified: nil, public: false, template: nil)
+ def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, template: nil)
+ weak_etag ||= etag || object unless strong_etag
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)
+ if strong_etag
+ response.strong_etag = combine_etags strong_etag,
+ last_modified: last_modified, public: public, template: template
+ elsif weak_etag || template
+ response.weak_etag = combine_etags weak_etag,
+ last_modified: last_modified, public: public, template: template
end
response.last_modified = last_modified if last_modified
@@ -107,8 +126,23 @@ module ActionController
#
# === Parameters:
#
- # * <tt>:etag</tt>.
- # * <tt>:last_modified</tt>.
+ # * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the
+ # +:weak_etag+ option.
+ # * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response.
+ # Requests that set If-None-Match header may return a 304 Not Modified
+ # response if it matches the ETag exactly. A weak ETag indicates semantic
+ # equivalence, not byte-for-byte equality, so they're good for caching
+ # HTML pages in browser caches. They can't be used for responses that
+ # must be byte-identical, like serving Range requests within a PDF file.
+ # * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response.
+ # Requests that set If-None-Match header may return a 304 Not Modified
+ # response if it matches the ETag exactly. A strong ETag implies exact
+ # equality: the response must match byte for byte. This is necessary for
+ # doing Range requests within a large video or PDF file, for example, or
+ # for compatibility with some CDNs that don't support weak ETags.
+ # * <tt>:last_modified</tt> Sets a "weak" last-update validator on the
+ # response. Subsequent requests that set If-Modified-Since may return a
+ # 304 Not Modified response if last_modified <= If-Modified-Since.
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
# +true+ if you want your application to be cacheable by other devices (proxy caches).
# * <tt>:template</tt> By default, the template digest for the current
@@ -180,12 +214,12 @@ module ActionController
# super if stale? @article, template: 'widgets/show'
# end
#
- 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)
+ def stale?(object = nil, **freshness_kwargs)
+ fresh_when(object, **freshness_kwargs)
!request.fresh?(response)
end
- # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a +private+
+ # Sets an HTTP 1.1 Cache-Control header. Defaults to issuing a +private+
# instruction, so that intermediate caches must not cache the response.
#
# expires_in 20.minutes
@@ -195,7 +229,7 @@ module ActionController
# This method will overwrite an existing Cache-Control header.
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
#
- # The method will also ensure a HTTP Date header for client compatibility.
+ # The method will also ensure an HTTP Date header for client compatibility.
def expires_in(seconds, options = {})
response.cache_control.merge!(
:max_age => seconds,
@@ -208,7 +242,7 @@ module ActionController
response.date = Time.now unless response.date?
end
- # Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should
+ # Sets an HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should
# occur by the browser or intermediate caches (like caching proxy servers).
def expires_now
response.cache_control.replace(:no_cache => true)
@@ -216,26 +250,23 @@ module ActionController
# 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,
+ # You can use this method when you have an 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')
+ def http_cache_forever(public: false)
expires_in 100.years, public: public
- yield if stale?(etag: "#{version}-#{request.fullpath}",
+ yield if stale?(etag: request.fullpath,
last_modified: Time.new(2011, 1, 1).utc,
public: public)
end
private
- def combine_etags(options)
- etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact
- etags.unshift options[:etag]
+ def combine_etags(validator, options)
+ [validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact
end
end
end
diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb
index f8efb2b076..44925641a1 100644
--- a/actionpack/lib/action_controller/metal/cookies.rb
+++ b/actionpack/lib/action_controller/metal/cookies.rb
@@ -3,7 +3,7 @@ module ActionController #:nodoc:
extend ActiveSupport::Concern
included do
- helper_method :cookies
+ helper_method :cookies if defined?(helper_method)
end
private
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 957e7a3019..6cd6130032 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -25,14 +25,13 @@ module ActionController #:nodoc:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
# Defaults to <tt>File.basename(path)</tt>.
# * <tt>:type</tt> - specifies an HTTP content type.
- # You can specify either a string or a symbol for a registered type register with
- # <tt>Mime::Type.register</tt>, for example :json
- # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
- # If no content type is registered for the extension, default type 'application/octet-stream' will be used.
+ # You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
+ # If omitted, the type will be inferred from the file extension specified in <tt>:filename</tt>.
+ # If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
- # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
+ # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser to guess the filename from
# the URL, which is necessary for i18n filenames on certain browsers
# (setting <tt>:filename</tt> overrides this option).
#
@@ -79,14 +78,14 @@ module ActionController #:nodoc:
# <tt>render plain: data</tt>, but also allows you to specify whether
# the browser should display the response as a file attachment (i.e. in a
# download dialog) or as inline data. You may also set the content type,
- # the apparent file name, and other things.
+ # the file name, and other things.
#
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
- # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
- # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
- # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
- # If no content type is registered for the extension, default type 'application/octet-stream' will be used.
+ # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
+ # You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
+ # If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
+ # If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index e31d65aac2..ea8e91ce24 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -2,17 +2,17 @@ require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'
module ActionController
- # This module provides a method which will redirect browser to use HTTPS
+ # This module provides a method which will redirect the browser to use HTTPS
# protocol. This will ensure that user's sensitive information will be
- # transferred safely over the internet. You _should_ always force browser
+ # transferred safely over the internet. You _should_ always force the browser
# to use HTTPS when you're transferring sensitive information such as
# user authentication, account information, or credit card information.
#
# Note that if you are really concerned about your application security,
# you might consider using +config.force_ssl+ in your config file instead.
# That will ensure all the data transferred via HTTPS protocol and prevent
- # user from getting session hijacked when accessing the site under unsecured
- # HTTP protocol.
+ # the user from getting their session hijacked when accessing the site over
+ # unsecured HTTP protocol.
module ForceSSL
extend ActiveSupport::Concern
include AbstractController::Callbacks
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index d3853e2e83..295f0cb66f 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -5,7 +5,7 @@ module ActionController
#
# In addition to using the standard template helpers provided, creating custom helpers to
# extract complicated logic or reusable functionality is strongly encouraged. By default, each controller
- # will include all helpers. These helpers are only accessible on the controller through <tt>.helpers</tt>
+ # will include all helpers. These helpers are only accessible on the controller through <tt>#helpers</tt>
#
# In previous versions of \Rails the controller will include a helper which
# matches the name of the controller, e.g., <tt>MyController</tt> will automatically
@@ -71,7 +71,7 @@ module ActionController
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
end
- # Provides a proxy to access helpers methods from outside the view.
+ # Provides a proxy to access helper methods from outside the view.
def helpers
@helper_proxy ||= begin
proxy = ActionView::Base.new
@@ -113,5 +113,10 @@ module ActionController
all_helpers_from_path(helpers_path)
end
end
+
+ # Provides a proxy to access helper methods from outside the view.
+ def helpers
+ @_helper_proxy ||= view_context
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 35be6d9300..4639348509 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -310,9 +310,9 @@ module ActionController
end
# Might want a shorter timeout depending on whether the request
- # is a PATCH, PUT, or POST, and if client is browser or web service.
+ # is a PATCH, PUT, or POST, and if the client is a browser or web service.
# Can be much shorter if the Stale directive is implemented. This would
- # allow a user to use new nonce without prompting user again for their
+ # allow a user to use new nonce without prompting the user again for their
# username and password.
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
return false if value.nil?
@@ -347,7 +347,12 @@ module ActionController
# private
# def authenticate
# authenticate_or_request_with_http_token do |token, options|
- # token == TOKEN
+ # # Compare the tokens in a time-constant manner, to mitigate
+ # # timing attacks.
+ # ActiveSupport::SecurityUtils.secure_compare(
+ # ::Digest::SHA256.hexdigest(token),
+ # ::Digest::SHA256.hexdigest(TOKEN)
+ # )
# end
# end
# end
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index 17fcc2fa02..6192fc0f9c 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -1,29 +1,62 @@
+require 'active_support/core_ext/string/strip'
+
module ActionController
+ # Handles implicit rendering for a controller action that does not
+ # explicitly respond with +render+, +respond_to+, +redirect+, or +head+.
+ #
+ # For API controllers, the implicit response is always 204 No Content.
+ #
+ # For all other controllers, we use these heuristics to decide whether to
+ # render a template, raise an error for a missing template, or respond with
+ # 204 No Content:
+ #
+ # First, if we DO find a template, it's rendered. Template lookup accounts
+ # for the action name, locales, format, variant, template handlers, and more
+ # (see +render+ for details).
+ #
+ # Second, if we DON'T find a template but the controller action does have
+ # templates for other formats, variants, etc., then we trust that you meant
+ # to provide a template for this response, too, and we raise
+ # <tt>ActionController::UnknownFormat</tt> with an explanation.
+ #
+ # Third, if we DON'T find a template AND the request is a page load in a web
+ # browser (technically, a non-XHR GET request for an HTML response) where
+ # you reasonably expect to have rendered a template, then we raise
+ # <tt>ActionView::UnknownFormat</tt> with an explanation.
+ #
+ # Finally, if we DON'T find a template AND the request isn't a browser page
+ # load, then we implicitly respond with 204 No Content.
module ImplicitRender
+ # :stopdoc:
include BasicImplicitRender
- # Renders the template corresponding to the controller action, if it exists.
- # The action name, format, and variant are all taken into account.
- # For example, the "new" action with an HTML format and variant "phone"
- # would try to render the <tt>new.html+phone.erb</tt> template.
- #
- # If no template is found <tt>ActionController::BasicImplicitRender</tt>'s implementation is called, unless
- # a block is passed. In that case, it will override the super implementation.
- #
- # default_render do
- # head 404 # No template was found
- # end
def default_render(*args)
if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
render(*args)
+ elsif any_templates?(action_name.to_s, _prefixes)
+ message = "#{self.class.name}\##{action_name} is missing a template " \
+ "for this request format and variant.\n" \
+ "\nrequest.formats: #{request.formats.map(&:to_s).inspect}" \
+ "\nrequest.variant: #{request.variant.inspect}"
+
+ raise ActionController::UnknownFormat, message
+ elsif interactive_browser_request?
+ message = "#{self.class.name}\##{action_name} is missing a template " \
+ "for this request format and variant.\n\n" \
+ "request.formats: #{request.formats.map(&:to_s).inspect}\n" \
+ "request.variant: #{request.variant.inspect}\n\n" \
+ "NOTE! For XHR/Ajax or API requests, this action would normally " \
+ "respond with 204 No Content: an empty white screen. Since you're " \
+ "loading it in a web browser, we assume that you expected to " \
+ "actually render a template, not… nothing, so we're showing an " \
+ "error to be extra-clear. If you expect 204 No Content, carry on. " \
+ "That's what you'll get from an XHR or API request. Give it a shot."
+
+ raise ActionController::UnknownFormat, message
else
- if block_given?
- yield(*args)
- else
- logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
- super
- end
+ logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
+ super
end
end
@@ -32,5 +65,10 @@ module ActionController
"default_render"
end
end
+
+ private
+ def interactive_browser_request?
+ request.get? && request.format == Mime[:html] && !request.xhr?
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 3dbf34eb2a..624a6d5b76 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -19,9 +19,10 @@ module ActionController
:controller => self.class.name,
:action => self.action_name,
:params => request.filtered_parameters,
- :format => request.format.try(:ref),
+ :headers => request.headers,
+ :format => request.format.ref,
:method => request.request_method,
- :path => (request.fullpath rescue "unknown")
+ :path => request.fullpath
}
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
@@ -74,8 +75,8 @@ module ActionController
ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
end
- # A hook which allows you to clean up any time taken into account in
- # views wrongly, like database querying time.
+ # A hook which allows you to clean up any time, wrongly taken into account in
+ # views, like database querying time.
#
# def cleanup_view_runtime
# super - time_taken_in_something_expensive
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index e3c540bf5f..5d395cd8bd 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -3,7 +3,7 @@ require 'delegate'
require 'active_support/json'
module ActionController
- # Mix this module in to your controller, and all actions in that controller
+ # Mix this module into your controller, and all actions in that controller
# will be able to stream data to the client as it's written.
#
# class MyController < ActionController::Base
@@ -20,7 +20,7 @@ module ActionController
# end
# end
#
- # There are a few caveats with this use. You *cannot* write headers after the
+ # There are a few caveats with this module. You *cannot* write headers after the
# response has been committed (Response#committed? will return truthy).
# Calling +write+ or +close+ on the response stream will cause the response
# object to be committed. Make sure all headers are set before calling write
@@ -163,14 +163,6 @@ module ActionController
end
end
- def each
- @response.sending!
- while str = @buf.pop
- yield str
- end
- @response.sent!
- end
-
# Write a 'close' event to the buffer; the producer/writing thread
# uses this to notify us that it's finished supplying content.
#
@@ -210,6 +202,14 @@ module ActionController
def call_on_error
@error_callback.call
end
+
+ private
+
+ def each_chunk(&block)
+ while str = @buf.pop
+ yield str
+ end
+ end
end
class Response < ActionDispatch::Response #:nodoc: all
@@ -237,39 +237,55 @@ module ActionController
# This processes the action in a child thread. It lets us return the
# response code and headers back up the rack stack, and still process
# the body in parallel with sending data to the client
- Thread.new {
- t2 = Thread.current
- t2.abort_on_exception = true
-
- # Since we're processing the view in a different thread, copy the
- # thread locals from the main thread to the child thread. :'(
- locals.each { |k,v| t2[k] = v }
-
- begin
- super(name)
- rescue => e
- if @_response.committed?
- begin
- @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
- @_response.stream.call_on_error
- rescue => exception
- log_error(exception)
- ensure
- log_error(e)
- @_response.stream.close
+ new_controller_thread {
+ ActiveSupport::Dependencies.interlock.running do
+ t2 = Thread.current
+
+ # Since we're processing the view in a different thread, copy the
+ # thread locals from the main thread to the child thread. :'(
+ locals.each { |k,v| t2[k] = v }
+
+ begin
+ super(name)
+ rescue => e
+ if @_response.committed?
+ begin
+ @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
+ @_response.stream.call_on_error
+ rescue => exception
+ log_error(exception)
+ ensure
+ log_error(e)
+ @_response.stream.close
+ end
+ else
+ error = e
end
- else
- error = e
+ ensure
+ @_response.commit!
end
- ensure
- @_response.commit!
end
}
- @_response.await_commit
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ @_response.await_commit
+ end
+
raise error if error
end
+ # Spawn a new thread to serve up the controller in. This is to get
+ # around the fact that Rack isn't based around IOs and we need to use
+ # a thread to stream data from the response bodies. Nobody should call
+ # this method except in Rails internals. Seriously!
+ def new_controller_thread # :nodoc:
+ Thread.new {
+ t2 = Thread.current
+ t2.abort_on_exception = true
+ yield
+ }
+ end
+
def log_error(exception)
logger = ActionController::Base.logger
return unless logger
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 173a14a1d2..2e89af1a5e 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -198,7 +198,7 @@ module ActionController #:nodoc:
_process_format(format)
_set_rendered_content_type format
response = collector.response
- response ? response.call : render({})
+ response.call if response
else
raise ActionController::UnknownFormat
end
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index b13ba06962..3c7cc15627 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -84,7 +84,7 @@ module ActionController
# redirect_back fallback_location: proc { edit_post_url(@post) }
#
# All options that can be passed to <tt>redirect_to</tt> are accepted as
- # options and the behavior is indetical.
+ # options and the behavior is identical.
def redirect_back(fallback_location:, **args)
if referer = request.headers["Referer"]
redirect_to referer, **args
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 90fb34e386..1735609cd9 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -103,7 +103,7 @@ module ActionController
#
# Both <tt>ActionController::Base</tt> and <tt>ActionController::API</tt>
# include <tt>ActionController::Renderers::All</tt>, making all renderers
- # avaialable in the controller. See <tt>Renderers::RENDERERS</tt> and <tt>Renderers.add</tt>.
+ # available in the controller. See <tt>Renderers::RENDERERS</tt> and <tt>Renderers.add</tt>.
#
# Since <tt>ActionController::Metal</tt> controllers cannot render, the controller
# must include <tt>AbstractController::Rendering</tt>, <tt>ActionController::Rendering</tt>,
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 91b3403ad5..0559fbc6ce 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -213,7 +213,7 @@ module ActionController #:nodoc:
if !verified_request?
if logger && log_warning_on_csrf_failure
- logger.warn "Can't verify CSRF token authenticity"
+ logger.warn "Can't verify CSRF token authenticity."
end
handle_unverified_request
end
@@ -235,7 +235,9 @@ module ActionController #:nodoc:
# we aren't serving an unauthorized cross-origin response.
def verify_same_origin_request
if marked_for_same_origin_verification? && non_xhr_javascript_response?
- logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING if logger
+ if logger && log_warning_on_csrf_failure
+ logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING
+ end
raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING
end
end
@@ -378,7 +380,9 @@ module ActionController #:nodoc:
end
def xor_byte_strings(s1, s2)
- s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
+ s2_bytes = s2.bytes
+ s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 }
+ s2_bytes.pack('C*')
end
# The form's authenticity parameter. Override to provide your own.
@@ -403,7 +407,8 @@ module ActionController #:nodoc:
end
def normalize_action_path(action_path)
- action_path.split('?').first.to_s.chomp('/')
+ uri = URI.parse(action_path)
+ uri.path.chomp('/')
end
end
end
diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb
index 81b9a7b9ed..17f4030f25 100644
--- a/actionpack/lib/action_controller/metal/rescue.rb
+++ b/actionpack/lib/action_controller/metal/rescue.rb
@@ -1,18 +1,11 @@
module ActionController #:nodoc:
- # This module is responsible to provide `rescue_from` helpers
- # to controllers and configure when detailed exceptions must be
+ # This module is responsible for providing `rescue_from` helpers
+ # to controllers and configuring when detailed exceptions must be
# shown.
module Rescue
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
- def rescue_with_handler(exception)
- if exception.cause && handler_for_rescue(exception.cause)
- exception = exception.cause
- end
- super(exception)
- end
-
# Override this method if you want to customize when detailed
# exceptions must be shown. This method is only called when
# consider_all_requests_local is false. By default, it returns
@@ -27,7 +20,7 @@ module ActionController #:nodoc:
super
rescue Exception => exception
request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions?
- rescue_with_handler(exception) || raise(exception)
+ rescue_with_handler(exception) || raise
end
end
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index d3382ef296..46589901fd 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -43,7 +43,7 @@ module ActionController
# == Action Controller \Parameters
#
- # Allows to choose which attributes should be whitelisted for mass updating
+ # Allows you to choose which attributes should be whitelisted for mass updating
# and thus prevent accidentally exposing that which shouldn't be exposed.
# Provides two methods for this purpose: #require and #permit. The former is
# used to mark parameters as required. The latter is used to set the parameter
@@ -109,7 +109,7 @@ module ActionController
cattr_accessor :permit_all_parameters, instance_accessor: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
- delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?, :inspect,
+ delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?,
:as_json, to: :@parameters
# By default, never raise an UnpermittedParameters exception if these
@@ -122,16 +122,6 @@ module ActionController
cattr_accessor :always_permitted_parameters
self.always_permitted_parameters = %w( controller action )
- def self.const_missing(const_name)
- 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.
- MSG
-
- always_permitted_parameters
- end
-
# Returns a new instance of <tt>ActionController::Parameters</tt>.
# Also, sets the +permitted+ attribute to the default value of
# <tt>ActionController::Parameters.permit_all_parameters</tt>.
@@ -154,17 +144,21 @@ module ActionController
end
# Returns true if another +Parameters+ object contains the same content and
- # permitted flag, or other Hash-like object contains the same content. This
- # override is in place so you can perform a comparison with `Hash`.
- def ==(other_hash)
- if other_hash.respond_to?(:permitted?)
- super
+ # permitted flag.
+ def ==(other)
+ if other.respond_to?(:permitted?)
+ self.permitted? == other.permitted? && self.parameters == other.parameters
+ elsif other.is_a?(Hash)
+ ActiveSupport::Deprecation.warn <<-WARNING.squish
+ Comparing equality between `ActionController::Parameters` and a
+ `Hash` is deprecated and will be removed in Rails 5.1. Please only do
+ comparisons between instances of `ActionController::Parameters`. If
+ you need to compare to a hash, first convert it using
+ `ActionController::Parameters#new`.
+ WARNING
+ @parameters == other.with_indifferent_access
else
- if other_hash.is_a?(Hash)
- @parameters == other_hash.with_indifferent_access
- else
- @parameters == other_hash
- end
+ @parameters == other
end
end
@@ -190,12 +184,19 @@ module ActionController
# Returns an unsafe, unfiltered
# <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of this
# parameter.
+ #
+ # params = ActionController::Parameters.new({
+ # name: 'Senjougahara Hitagi',
+ # oddity: 'Heavy stone crab'
+ # })
+ # params.to_unsafe_h
+ # # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"}
def to_unsafe_h
convert_parameters_to_hashes(@parameters, :to_unsafe_h)
end
alias_method :to_unsafe_hash, :to_unsafe_h
- # Convert all hashes in values into parameters, then yield each pair like
+ # Convert all hashes in values into parameters, then yield each pair in
# the same way as <tt>Hash#each_pair</tt>
def each_pair(&block)
@parameters.each_pair do |key, value|
@@ -277,7 +278,7 @@ module ActionController
# params = ActionController::Parameters.new(user: { ... }, profile: { ... })
# user_params, profile_params = params.require(:user, :profile)
#
- # Otherwise, the method reraises the first exception found:
+ # Otherwise, the method re-raises the first exception found:
#
# params = ActionController::Parameters.new(user: {}, profile: {})
# user_params, profile_params = params.require(:user, :profile)
@@ -436,6 +437,21 @@ module ActionController
)
end
+ if Hash.method_defined?(:dig)
+ # Extracts the nested parameter from the given +keys+ by calling +dig+
+ # at each step. Returns +nil+ if any intermediate step is +nil+.
+ #
+ # params = ActionController::Parameters.new(foo: { bar: { baz: 1 } })
+ # params.dig(:foo, :bar, :baz) # => 1
+ # params.dig(:foo, :zot, :xyz) # => nil
+ #
+ # params2 = ActionController::Parameters.new(foo: [10, 11, 12])
+ # params2.dig(:foo, 1) # => 11
+ def dig(*keys)
+ convert_value_to_parameters(@parameters.dig(*keys))
+ end
+ end
+
# Returns a new <tt>ActionController::Parameters</tt> instance that
# includes only the given +keys+. If the given +keys+
# don't exist, returns an empty hash.
@@ -584,6 +600,10 @@ module ActionController
dup
end
+ def inspect
+ "<#{self.class} #{@parameters} permitted: #{@permitted}>"
+ end
+
def method_missing(method_sym, *args, &block)
if @parameters.respond_to?(method_sym)
message = <<-DEPRECATE.squish
@@ -603,12 +623,14 @@ module ActionController
end
protected
+ attr_reader :parameters
+
def permitted=(new_permitted)
@permitted = new_permitted
end
def fields_for_style?
- @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
+ @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && (v.is_a?(Hash) || v.is_a?(Parameters)) }
end
private
@@ -734,6 +756,10 @@ module ActionController
end
end
+ def non_scalar?(value)
+ value.is_a?(Array) || value.is_a?(Parameters)
+ end
+
EMPTY_ARRAY = []
def hash_filter(params, filter)
filter = filter.with_indifferent_access
@@ -748,7 +774,7 @@ module ActionController
array_of_permitted_scalars?(self[key]) do |val|
params[key] = val
end
- else
+ elsif non_scalar?(value)
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
params[key] = each_element(value) do |element|
element.permit(*Array.wrap(filter[key]))
@@ -771,7 +797,7 @@ module ActionController
#
# class PeopleController < ActionController::Base
# # Using "Person.create(params[:person])" would raise an
- # # ActiveModel::ForbiddenAttributes exception because it'd
+ # # ActiveModel::ForbiddenAttributesError exception because it'd
# # be using mass assignment without an explicit permit step.
# # This is the recommended form:
# def create
@@ -799,7 +825,8 @@ module ActionController
# end
#
# In order to use <tt>accepts_nested_attributes_for</tt> with Strong \Parameters, you
- # will need to specify which nested attributes should be whitelisted.
+ # will need to specify which nested attributes should be whitelisted. You might want
+ # to allow +:id+ and +:_destroy+, see ActiveRecord::NestedAttributes for more information.
#
# class Person
# has_many :pets
@@ -819,7 +846,7 @@ module ActionController
# # It's mandatory to specify the nested attributes that should be whitelisted.
# # If you use `permit` with just the key that points to the nested attributes hash,
# # it will return an empty hash.
- # params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ])
+ # params.require(:person).permit(:name, :age, pets_attributes: [ :id, :name, :category ])
# end
# end
#
diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb
index e4d19e9dba..a8c8d66682 100644
--- a/actionpack/lib/action_controller/renderer.rb
+++ b/actionpack/lib/action_controller/renderer.rb
@@ -1,7 +1,7 @@
require 'active_support/core_ext/hash/keys'
module ActionController
- # ActionController::Renderer allows to render arbitrary templates
+ # ActionController::Renderer allows you to render arbitrary templates
# without requirement of being in controller actions.
#
# You get a concrete renderer class by invoking ActionController::Base#renderer.
@@ -13,11 +13,11 @@ module ActionController
#
# ApplicationController.renderer.render template: '...'
#
- # You can use a shortcut on controller to replace previous example with:
+ # You can use this shortcut in a controller, instead of the previous example:
#
# ApplicationController.render template: '...'
#
- # #render method allows you to use any options as when rendering in controller.
+ # #render allows you to use the same options that you can use when rendering in a controller.
# For example,
#
# FooController.render :action, locals: { ... }, assigns: { ... }
@@ -45,7 +45,7 @@ module ActionController
}.freeze
# Create a new renderer instance for a specific controller class.
- def self.for(controller, env = {}, defaults = DEFAULTS)
+ def self.for(controller, env = {}, defaults = DEFAULTS.dup)
new(controller, env, defaults)
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index b43bb9dc17..b1b3e87934 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -12,6 +12,17 @@ module ActionController
include Testing::Functional
end
+ module Live
+ # Disable controller / rendering threads in tests. User tests can access
+ # the database on the main thread, so they could open a txn, then the
+ # controller thread will open a new connection and try to access data
+ # that's only visible to the main thread's txn. This is the problem in #23483
+ remove_method :new_controller_thread
+ def new_controller_thread # :nodoc:
+ yield
+ end
+ end
+
# ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
# Please use ActionDispatch::IntegrationTest going forward.
class TestRequest < ActionDispatch::TestRequest #:nodoc:
@@ -41,7 +52,7 @@ module ActionController
self.session = session
self.session_options = TestSession::DEFAULT_OPTIONS
@custom_param_parsers = {
- Mime[:xml] => lambda { |raw_post| Hash.from_xml(raw_post)['hash'] }
+ xml: lambda { |raw_post| Hash.from_xml(raw_post)['hash'] }
}
end
@@ -94,7 +105,7 @@ module ActionController
when :url_encoded_form
data = non_path_parameters.to_query
else
- @custom_param_parsers[content_mime_type] = ->(_) { non_path_parameters }
+ @custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters }
data = non_path_parameters.to_query
end
end
@@ -417,7 +428,7 @@ module ActionController
end
alias xhr :xml_http_request
- # Simulate a HTTP request to +action+ by specifying request method,
+ # Simulate an HTTP request to +action+ by specifying request method,
# parameters and set/volley the response.
#
# - +action+: The controller action to call.
@@ -516,32 +527,37 @@ module ActionController
@request.set_header k, @controller.config.relative_url_root
end
- @controller.recycle!
- @controller.dispatch(action, @request, @response)
- @request = @controller.request
- @response = @controller.response
+ begin
+ @controller.recycle!
+ @controller.dispatch(action, @request, @response)
+ ensure
+ @request = @controller.request
+ @response = @controller.response
- @request.delete_header 'HTTP_COOKIE'
+ @request.delete_header 'HTTP_COOKIE'
- if @request.have_cookie_jar?
- unless @request.cookie_jar.committed?
- @request.cookie_jar.write(@response)
- self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
+ if @request.have_cookie_jar?
+ unless @request.cookie_jar.committed?
+ @request.cookie_jar.write(@response)
+ self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
+ end
end
- end
- @response.prepare!
+ @response.prepare!
- if flash_value = @request.flash.to_session_value
- @request.session['flash'] = flash_value
- else
- @request.session.delete('flash')
- end
+ if flash_value = @request.flash.to_session_value
+ @request.session['flash'] = flash_value
+ else
+ @request.session.delete('flash')
+ end
- if xhr
- @request.delete_header 'HTTP_X_REQUESTED_WITH'
- @request.delete_header 'HTTP_ACCEPT'
+ if xhr
+ @request.delete_header 'HTTP_X_REQUESTED_WITH'
+ @request.delete_header 'HTTP_ACCEPT'
+ end
+ @request.query_string = ''
+
+ @response.sent!
end
- @request.query_string = ''
@response
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 1e4df07d6e..01d49475de 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -51,8 +51,8 @@ module ActionDispatch
autoload :Cookies
autoload :DebugExceptions
autoload :ExceptionWrapper
+ autoload :Executor
autoload :Flash
- autoload :LoadInterlock
autoload :ParamsParser
autoload :PublicExceptions
autoload :Reloader
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 0f7898a3f8..9fa2e38ae3 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -17,9 +17,7 @@ module ActionDispatch
end
def if_none_match_etags
- (if_none_match ? if_none_match.split(/\s*,\s*/) : []).collect do |etag|
- etag.gsub(/^\"|\"$/, "")
- end
+ if_none_match ? if_none_match.split(/\s*,\s*/) : []
end
def not_modified?(modified_at)
@@ -28,8 +26,8 @@ module ActionDispatch
def etag_matches?(etag)
if etag
- etag = etag.gsub(/^\"|\"$/, "")
- if_none_match_etags.include?(etag)
+ validators = if_none_match_etags
+ validators.include?(etag) || validators.include?('*')
end
end
@@ -80,19 +78,63 @@ module ActionDispatch
set_header DATE, utc_time.httpdate
end
- def etag=(etag)
- key = ActiveSupport::Cache.expand_cache_key(etag)
- super %(W/"#{Digest::MD5.hexdigest(key)}")
+ # This method sets a weak ETag validator on the response so browsers
+ # and proxies may cache the response, keyed on the ETag. On subsequent
+ # requests, the If-None-Match header is set to the cached ETag. If it
+ # matches the current ETag, we can return a 304 Not Modified response
+ # with no body, letting the browser or proxy know that their cache is
+ # current. Big savings in request time and network bandwidth.
+ #
+ # Weak ETags are considered to be semantically equivalent but not
+ # byte-for-byte identical. This is perfect for browser caching of HTML
+ # pages where we don't care about exact equality, just what the user
+ # is viewing.
+ #
+ # Strong ETags are considered byte-for-byte identical. They allow a
+ # browser or proxy cache to support Range requests, useful for paging
+ # through a PDF file or scrubbing through a video. Some CDNs only
+ # support strong ETags and will ignore weak ETags entirely.
+ #
+ # Weak ETags are what we almost always need, so they're the default.
+ # Check out `#strong_etag=` to provide a strong ETag validator.
+ def etag=(weak_validators)
+ self.weak_etag = weak_validators
+ end
+
+ def weak_etag=(weak_validators)
+ set_header 'ETag', generate_weak_etag(weak_validators)
+ end
+
+ def strong_etag=(strong_validators)
+ set_header 'ETag', generate_strong_etag(strong_validators)
end
def etag?; etag; end
+ # True if an ETag is set and it's a weak validator (preceded with W/)
+ def weak_etag?
+ etag? && etag.starts_with?('W/"')
+ end
+
+ # True if an ETag is set and it isn't a weak validator (not preceded with W/)
+ def strong_etag?
+ etag? && !weak_etag?
+ end
+
private
DATE = 'Date'.freeze
LAST_MODIFIED = "Last-Modified".freeze
SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
+ def generate_weak_etag(validators)
+ "W/#{generate_strong_etag(validators)}"
+ end
+
+ def generate_strong_etag(validators)
+ %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}")
+ end
+
def cache_control_segments
if cache_control = _cache_control
cache_control.delete(' ').split(',')
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 9dcab79c3a..041eca48ca 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -4,9 +4,11 @@ module ActionDispatch
module Http
# Allows you to specify sensitive parameters which will be replaced from
# the request log by looking in the query string of the request and all
- # sub-hashes of the params hash to filter. If a block is given, each key and
- # value of the params hash and all sub-hashes is passed to it, the value
- # or key can be replaced using String#replace or similar method.
+ # sub-hashes of the params hash to filter. Filtering only certain sub-keys
+ # from a hash is possible by using the dot notation: 'credit_card.number'.
+ # If a block is given, each key and value of the params hash and all
+ # sub-hashes is passed to it, the value or key can be replaced using
+ # String#replace or similar method.
#
# env["action_dispatch.parameter_filter"] = [:password]
# => replaces the value to all keys matching /password/i with "[FILTERED]"
@@ -14,6 +16,10 @@ module ActionDispatch
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
#
+ # env["action_dispatch.parameter_filter"] = [ "credit_card.code" ]
+ # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
+ # change { file: { code: "xxxx"} }
+ #
# env["action_dispatch.parameter_filter"] = -> (k, v) do
# v.reverse! if k =~ /secret/i
# end
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 12f81dc1a5..69a934b7cd 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -2,9 +2,23 @@ module ActionDispatch
module Http
# Provides access to the request's HTTP headers from the environment.
#
- # env = { "CONTENT_TYPE" => "text/plain" }
+ # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
# headers = ActionDispatch::Http::Headers.new(env)
# headers["Content-Type"] # => "text/plain"
+ # headers["User-Agent"] # => "curl/7.43.0"
+ #
+ # Also note that when headers are mapped to CGI-like variables by the Rack
+ # server, both dashes and underscores are converted to underscores. This
+ # ambiguity cannot be resolved at this stage anymore. Both underscores and
+ # dashes have to be interpreted as if they were originally sent as dashes.
+ #
+ # # GET / HTTP/1.1
+ # # ...
+ # # User-Agent: curl/7.43.0
+ # # X_Custom_Header: token
+ #
+ # headers["X_Custom_Header"] # => nil
+ # headers["X-Custom-Header"] # => "token"
class Headers
CGI_VARIABLES = Set.new(%W[
AUTH_TYPE
@@ -101,7 +115,7 @@ module ActionDispatch
private
- # Converts a HTTP header name to an environment variable name if it is
+ # Converts an HTTP header name to an environment variable name if it is
# not contained within the headers hash.
def env_name(key)
key = key.to_s
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index e9b25339dc..0a58ce2b96 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -164,7 +164,7 @@ module ActionDispatch
end
def format_from_path_extension
- path = @env['action_dispatch.original_path'] || @env['PATH_INFO']
+ path = get_header('action_dispatch.original_path') || get_header('PATH_INFO')
if match = path && path.match(/\.(\w+)\z/)
Mime[match.captures.first]
end
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 463b5fe405..4672ea7199 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -1,3 +1,5 @@
+# -*- frozen-string-literal: true -*-
+
require 'singleton'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/string/starts_ends_with'
@@ -108,57 +110,56 @@ module Mime
end
end
- class AcceptList < Array #:nodoc:
- def assort!
- sort!
+ class AcceptList #:nodoc:
+ def self.sort!(list)
+ list.sort!
- text_xml_idx = find_item_by_name self, 'text/xml'
- app_xml_idx = find_item_by_name self, Mime[:xml].to_s
+ text_xml_idx = find_item_by_name list, 'text/xml'
+ app_xml_idx = find_item_by_name list, Mime[:xml].to_s
# Take care of the broken text/xml entry by renaming or deleting it
if text_xml_idx && app_xml_idx
- app_xml = self[app_xml_idx]
- text_xml = self[text_xml_idx]
+ app_xml = list[app_xml_idx]
+ text_xml = list[text_xml_idx]
app_xml.q = [text_xml.q, app_xml.q].max # set the q value to the max of the two
if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list
- self[app_xml_idx], self[text_xml_idx] = text_xml, app_xml
+ list[app_xml_idx], list[text_xml_idx] = text_xml, app_xml
app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx
end
- delete_at(text_xml_idx) # delete text_xml from the list
+ list.delete_at(text_xml_idx) # delete text_xml from the list
elsif text_xml_idx
- self[text_xml_idx].name = Mime[:xml].to_s
+ list[text_xml_idx].name = Mime[:xml].to_s
end
# Look for more specific XML-based types and sort them ahead of app/xml
if app_xml_idx
- app_xml = self[app_xml_idx]
+ app_xml = list[app_xml_idx]
idx = app_xml_idx
- while idx < length
- type = self[idx]
+ while idx < list.length
+ type = list[idx]
break if type.q < app_xml.q
if type.name.ends_with? '+xml'
- self[app_xml_idx], self[idx] = self[idx], app_xml
+ list[app_xml_idx], list[idx] = list[idx], app_xml
app_xml_idx = idx
end
idx += 1
end
end
- map! { |i| Mime::Type.lookup(i.name) }.uniq!
- to_a
+ list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
+ list
end
- private
- def find_item_by_name(array, name)
+ def self.find_item_by_name(array, name)
array.index { |item| item.name == name }
end
end
class << self
- TRAILING_STAR_REGEXP = /(text|application)\/\*/
+ TRAILING_STAR_REGEXP = /^(text|application)\/\*/
PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
def register_callback(&block)
@@ -198,21 +199,22 @@ module Mime
accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
else
- list, index = AcceptList.new, 0
+ list, index = [], 0
accept_header.split(',').each do |header|
params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
- if params.present?
- params.strip!
- params = parse_trailing_star(params) || [params]
+ next unless params
+ params.strip!
+ next if params.empty?
+
+ params = parse_trailing_star(params) || [params]
- params.each do |m|
- list << AcceptItem.new(index, m.to_s, q)
- index += 1
- end
+ params.each do |m|
+ list << AcceptItem.new(index, m.to_s, q)
+ index += 1
end
end
- list.assort!
+ AcceptList.sort! list
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index 87715205d9..8b04174f1f 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -14,20 +14,22 @@ Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
Mime::Type.register "image/gif", :gif, [], %w(gif)
Mime::Type.register "image/bmp", :bmp, [], %w(bmp)
Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff)
+Mime::Type.register "image/svg+xml", :svg
Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe)
Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
Mime::Type.register "application/rss+xml", :rss
Mime::Type.register "application/atom+xml", :atom
-Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml )
+Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml ), %w(yml yaml)
Mime::Type.register "multipart/form-data", :multipart_form
Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
# http://www.ietf.org/rfc/rfc4627.txt
# http://www.json.org/JSONRequest.html
-Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest application/vnd.api+json )
+Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
Mime::Type.register "application/pdf", :pdf, [], %w(pdf)
Mime::Type.register "application/zip", :zip, [], %w(zip)
+Mime::Type.register "application/gzip", :gzip, %w(application/x-gzip), %w(gz)
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index cca7376ffa..ff5031d7d5 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -1,22 +1,31 @@
module ActionDispatch
module Http
module Parameters
+ extend ActiveSupport::Concern
+
PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
DEFAULT_PARSERS = {
- Mime[:json] => lambda { |raw_post|
+ Mime[:json].symbol => -> (raw_post) {
data = ActiveSupport::JSON.decode(raw_post)
data.is_a?(Hash) ? data : {:_json => data}
}
}
- def self.included(klass)
- class << klass
- attr_accessor :parameter_parsers
+ included do
+ class << self
+ attr_reader :parameter_parsers
end
- klass.parameter_parsers = DEFAULT_PARSERS
+ self.parameter_parsers = DEFAULT_PARSERS
end
+
+ module ClassMethods
+ def parameter_parsers=(parsers) # :nodoc:
+ @parameter_parsers = parsers.transform_keys { |key| key.respond_to?(:symbol) ? key.symbol : key }
+ end
+ end
+
# Returns both GET and POST \parameters in a single hash.
def parameters
params = get_header("action_dispatch.request.parameters")
@@ -51,7 +60,7 @@ module ActionDispatch
def parse_formatted_parameters(parsers)
return yield if content_length.zero?
- strategy = parsers.fetch(content_mime_type) { return yield }
+ strategy = parsers.fetch(content_mime_type.symbol) { return yield }
begin
strategy.call(raw_post)
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 5427425ef7..b0ed681623 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -337,7 +337,6 @@ module ActionDispatch
else
self.session = {}
end
- self.flash = nil
end
def session=(session) #:nodoc:
@@ -403,6 +402,10 @@ module ActionDispatch
def commit_flash
end
+ def ssl?
+ super || scheme == 'wss'.freeze
+ end
+
private
def check_method(name)
HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 14f86c7c07..1515d59df3 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/module/attribute_accessors'
require 'action_dispatch/http/filter_redirect'
+require 'action_dispatch/http/cache'
require 'monitor'
module ActionDispatch # :nodoc:
@@ -67,7 +68,13 @@ module ActionDispatch # :nodoc:
alias_method :headers, :header
delegate :[], :[]=, :to => :@header
- delegate :each, :to => :@stream
+
+ def each(&block)
+ sending!
+ x = @stream.each(&block)
+ sent!
+ x
+ end
CONTENT_TYPE = "Content-Type".freeze
SET_COOKIE = "Set-Cookie".freeze
@@ -96,10 +103,10 @@ module ActionDispatch # :nodoc:
def body
@str_body ||= begin
- buf = ''
- each { |chunk| buf << chunk }
- buf
- end
+ buf = ''
+ each { |chunk| buf << chunk }
+ buf
+ end
end
def write(string)
@@ -111,10 +118,13 @@ module ActionDispatch # :nodoc:
end
def each(&block)
- @response.sending!
- x = @buf.each(&block)
- @response.sent!
- x
+ if @str_body
+ return enum_for(:each) unless block_given?
+
+ yield @str_body
+ else
+ each_chunk(&block)
+ end
end
def abort
@@ -128,6 +138,12 @@ module ActionDispatch # :nodoc:
def closed?
@closed
end
+
+ private
+
+ def each_chunk(&block)
+ @buf.each(&block) # extract into own method
+ end
end
def self.create(status = 200, header = {}, body = [], default_headers: self.default_headers)
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 37f41ae988..7a1350a46d 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -217,7 +217,7 @@ module ActionDispatch
@protocol ||= ssl? ? 'https://' : 'http://'
end
- # Returns the \host for this request, such as "example.com".
+ # Returns the \host and port for this request, such as "example.com:8080".
#
# class Request < Rack::Request
# include ActionDispatch::Http::URL
@@ -226,6 +226,9 @@ module ActionDispatch
# req = Request.new 'HTTP_HOST' => 'example.com'
# req.raw_host_with_port # => "example.com"
#
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req.raw_host_with_port # => "example.com:80"
+ #
# req = Request.new 'HTTP_HOST' => 'example.com:8080'
# req.raw_host_with_port # => "example.com:8080"
def raw_host_with_port
@@ -236,7 +239,7 @@ module ActionDispatch
end
end
- # Returns the host for this request, such as example.com.
+ # Returns the host for this request, such as "example.com".
#
# class Request < Rack::Request
# include ActionDispatch::Http::URL
@@ -249,12 +252,16 @@ module ActionDispatch
end
# Returns a \host:\port string for this request, such as "example.com" or
- # "example.com:8080".
+ # "example.com:8080". Port is only included if it is not a default port
+ # (80 or 443)
#
# class Request < Rack::Request
# include ActionDispatch::Http::URL
# end
#
+ # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req.host_with_port # => "example.com"
+ #
# req = Request.new 'HTTP_HOST' => 'example.com:80'
# req.host_with_port # => "example.com"
#
@@ -347,6 +354,17 @@ module ActionDispatch
standard_port? ? '' : ":#{port}"
end
+ # Returns the requested port, such as 8080, based on SERVER_PORT
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'SERVER_PORT' => '80'
+ # req.server_port # => 80
+ #
+ # req = Request.new 'SERVER_PORT' => '8080'
+ # req.server_port # => 8080
def server_port
get_header('SERVER_PORT').to_i
end
diff --git a/actionpack/lib/action_dispatch/journey/backwards.rb b/actionpack/lib/action_dispatch/journey/backwards.rb
deleted file mode 100644
index 3bd20fdf81..0000000000
--- a/actionpack/lib/action_dispatch/journey/backwards.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Rack # :nodoc:
- Mount = ActionDispatch::Journey::Router
- Mount::RouteSet = ActionDispatch::Journey::Router
- Mount::RegexpWithNamedGroups = ActionDispatch::Journey::Path::Pattern
-end
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index 0323360faa..200477b002 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -32,8 +32,13 @@ module ActionDispatch
defaults = route.defaults
required_parts = route.required_parts
- parameterized_parts.keep_if do |key, value|
- (defaults[key].nil? && value.present?) || value.to_s != defaults[key].to_s || required_parts.include?(key)
+
+ route.parts.reverse_each do |key|
+ break if defaults[key].nil? && parameterized_parts[key].present?
+ break if parameterized_parts[key].to_s != defaults[key].to_s
+ break if required_parts.include?(key)
+
+ parameterized_parts.delete(key)
end
return [route.format(parameterized_parts), params]
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index 35c2b1b86e..cfd6681dd1 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -3,7 +3,7 @@ module ActionDispatch
class Route # :nodoc:
attr_reader :app, :path, :defaults, :name, :precedence
- attr_reader :constraints
+ attr_reader :constraints, :internal
alias :conditions :constraints
module VerbMatchers
@@ -55,7 +55,7 @@ module ActionDispatch
##
# +path+ is a path constraint.
# +constraints+ is a hash of constraints to be applied to this route.
- def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence)
+ def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence, internal = false)
@name = name
@app = app
@path = path
@@ -70,6 +70,7 @@ module ActionDispatch
@decorated_ast = nil
@precedence = precedence
@path_formatter = @path.build_formatter
+ @internal = internal
end
def ast
@@ -81,7 +82,7 @@ module ActionDispatch
end
def requirements # :nodoc:
- # needed for rails `rake routes`
+ # needed for rails `rails routes`
@defaults.merge(path.requirements).delete_if { |_,v|
/.+?/ == v
}
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index f649588520..06cdce1724 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -16,9 +16,6 @@ module ActionDispatch
class RoutingError < ::StandardError # :nodoc:
end
- # :nodoc:
- VERSION = '2.0.0'
-
attr_accessor :routes
def initialize(routes)
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index f80df78582..c782779b34 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -7,7 +7,16 @@ module ActionDispatch
define_callbacks :call
class << self
- delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader"
+ def to_prepare(*args, &block)
+ ActiveSupport::Reloader.to_prepare(*args, &block)
+ end
+
+ def to_cleanup(*args, &block)
+ ActiveSupport::Reloader.to_complete(*args, &block)
+ end
+
+ deprecate to_prepare: 'use ActiveSupport::Reloader.to_prepare instead',
+ to_cleanup: 'use ActiveSupport::Reloader.to_complete instead'
def before(*args, &block)
set_callback(:call, :before, *args, &block)
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 3477aa8b29..f2f3150b56 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/hash/keys'
require 'active_support/key_generator'
require 'active_support/message_verifier'
require 'active_support/json'
+require 'rack/utils'
module ActionDispatch
class Request
@@ -337,7 +338,7 @@ module ActionDispatch
end
def to_header
- @cookies.map { |k,v| "#{k}=#{v}" }.join ';'
+ @cookies.map { |k,v| "#{escape(k)}=#{escape(v)}" }.join '; '
end
def handle_options(options) #:nodoc:
@@ -419,6 +420,10 @@ module ActionDispatch
private
+ def escape(string)
+ ::Rack::Utils.escape(string)
+ end
+
def make_set_cookie_header(header)
header = @set_cookies.inject(header) { |m, (k, v)|
if write_cookie?(v)
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index b55c937e0c..5f758d641a 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -67,18 +67,19 @@ module ActionDispatch
log_error(request, wrapper)
if request.get_header('action_dispatch.show_detailed_exceptions')
- case @response_format
- when :api
- render_for_api_application(request, wrapper)
- when :default
- render_for_default_application(request, wrapper)
+ content_type = request.formats.first
+
+ if api_request?(content_type)
+ render_for_api_request(content_type, wrapper)
+ else
+ render_for_browser_request(request, wrapper)
end
else
raise exception
end
end
- def render_for_default_application(request, wrapper)
+ def render_for_browser_request(request, wrapper)
template = create_template(request, wrapper)
file = "rescues/#{wrapper.rescue_template}"
@@ -92,7 +93,7 @@ module ActionDispatch
render(wrapper.status_code, body, format)
end
- def render_for_api_application(request, wrapper)
+ def render_for_api_request(content_type, wrapper)
body = {
status: wrapper.status_code,
error: Rack::Utils::HTTP_STATUS_CODES.fetch(
@@ -103,7 +104,6 @@ module ActionDispatch
traces: wrapper.traces
}
- content_type = request.formats.first
to_format = "to_#{content_type.to_sym}"
if content_type && body.respond_to?(to_format)
@@ -156,15 +156,20 @@ module ActionDispatch
trace = wrapper.framework_trace if trace.empty?
ActiveSupport::Deprecation.silence do
- message = "\n#{exception.class} (#{exception.message}):\n"
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
- message << " " << trace.join("\n ")
- logger.fatal("#{message}\n\n")
+ logger.fatal " "
+ logger.fatal "#{exception.class} (#{exception.message}):"
+ log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code)
+ logger.fatal " "
+ log_array logger, trace
end
end
+ def log_array(logger, array)
+ array.map { |line| logger.fatal line }
+ end
+
def logger(request)
- request.logger || stderr_logger
+ request.logger || ActionView::Base.logger || stderr_logger
end
def stderr_logger
@@ -176,5 +181,9 @@ module ActionDispatch
ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
end
end
+
+ def api_request?(content_type)
+ @response_format == :api && !content_type.html?
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 3b61824cc9..59edc66086 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -1,4 +1,3 @@
-require 'action_controller/metal/exceptions'
require 'active_support/core_ext/module/attribute_accessors'
require 'rack/utils'
diff --git a/actionpack/lib/action_dispatch/middleware/executor.rb b/actionpack/lib/action_dispatch/middleware/executor.rb
new file mode 100644
index 0000000000..06245b403b
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/executor.rb
@@ -0,0 +1,19 @@
+require 'rack/body_proxy'
+
+module ActionDispatch
+ class Executor
+ def initialize(app, executor)
+ @app, @executor = app, executor
+ end
+
+ def call(env)
+ state = @executor.run!
+ begin
+ response = @app.call(env)
+ returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
+ ensure
+ state.complete! unless returned
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index c51dcd542a..80703940ed 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -70,6 +70,11 @@ module ActionDispatch
session.delete('flash')
end
end
+
+ def reset_session # :nodoc
+ super
+ self.flash = nil
+ end
end
class FlashNow #:nodoc:
@@ -128,7 +133,7 @@ module ActionDispatch
def to_session_value #:nodoc:
flashes_to_keep = @flashes.except(*@discard)
return nil if flashes_to_keep.empty?
- {'flashes' => flashes_to_keep}
+ { 'discard' => [], 'flashes' => flashes_to_keep }
end
def initialize(flashes = {}, discard = []) #:nodoc:
diff --git a/actionpack/lib/action_dispatch/middleware/load_interlock.rb b/actionpack/lib/action_dispatch/middleware/load_interlock.rb
deleted file mode 100644
index 07f498319c..0000000000
--- a/actionpack/lib/action_dispatch/middleware/load_interlock.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'active_support/dependencies'
-require 'rack/body_proxy'
-
-module ActionDispatch
- class LoadInterlock
- def initialize(app)
- @app = app
- end
-
- def call(env)
- interlock = ActiveSupport::Dependencies.interlock
- interlock.start_running
- response = @app.call(env)
- body = Rack::BodyProxy.new(response[2]) { interlock.done_running }
- response[2] = body
- response
- ensure
- interlock.done_running unless body
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index c2a4f46e67..faf3262b8f 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -37,6 +37,8 @@ module ActionDispatch
# The +parsers+ argument can take Hash of parsers where key is identifying
# content mime type, and value is a lambda that is going to process data.
def self.new(app, parsers = {})
+ ActiveSupport::Deprecation.warn('ActionDispatch::ParamsParser is deprecated and will be removed in Rails 5.1. Configure the parameter parsing in ActionDispatch::Request.parameter_parsers.')
+ parsers = parsers.transform_keys { |key| key.respond_to?(:symbol) ? key.symbol : key }
ActionDispatch::Request.parameter_parsers = ActionDispatch::Request::DEFAULT_PARSERS.merge(parsers)
app
end
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index af9a29eb07..112bde6596 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -23,74 +23,32 @@ module ActionDispatch
# middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
# or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
#
- class Reloader
- include ActiveSupport::Callbacks
- include ActiveSupport::Deprecation::Reporting
-
- define_callbacks :prepare
- define_callbacks :cleanup
-
- # Add a prepare callback. Prepare callbacks are run before each request, prior
- # to ActionDispatch::Callback's before callbacks.
+ class Reloader < Executor
def self.to_prepare(*args, &block)
- unless block_given?
- warn "to_prepare without a block is deprecated. Please use a block"
- end
- set_callback(:prepare, *args, &block)
+ ActiveSupport::Reloader.to_prepare(*args, &block)
end
- # Add a cleanup callback. Cleanup callbacks are run after each request is
- # complete (after #close is called on the response body).
def self.to_cleanup(*args, &block)
- unless block_given?
- warn "to_cleanup without a block is deprecated. Please use a block"
- end
- set_callback(:cleanup, *args, &block)
+ ActiveSupport::Reloader.to_complete(*args, &block)
end
- # Execute all prepare callbacks.
def self.prepare!
- new(nil).prepare!
+ default_reloader.prepare!
end
- # Execute all cleanup callbacks.
def self.cleanup!
- new(nil).cleanup!
- end
-
- def initialize(app, condition=nil)
- @app = app
- @condition = condition || lambda { true }
- @validated = true
+ default_reloader.reload!
end
- def call(env)
- @validated = @condition.call
- prepare!
-
- response = @app.call(env)
- response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! }
+ class << self
+ attr_accessor :default_reloader # :nodoc:
- response
- rescue Exception
- cleanup!
- raise
+ deprecate to_prepare: 'use ActiveSupport::Reloader.to_prepare instead',
+ to_cleanup: 'use ActiveSupport::Reloader.to_complete instead',
+ prepare!: 'use Rails.application.reloader.prepare! instead',
+ cleanup!: 'use Rails.application.reloader.reload! instead of cleanup + prepare'
end
- def prepare! #:nodoc:
- run_callbacks :prepare if validated?
- end
-
- def cleanup! #:nodoc:
- run_callbacks :cleanup if validated?
- ensure
- @validated = true
- end
-
- private
-
- def validated? #:nodoc:
- @validated
- end
+ self.default_reloader = ActiveSupport::Reloader
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 429a98f236..dec9c60ef2 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -23,7 +23,7 @@ module ActionDispatch
# goes a step further than signed cookies in that encrypted cookies cannot
# be altered or read by users. This is the default starting in Rails 4.
#
- # If you have both secret_token and secret_key base set, your cookies will
+ # If you have both secret_token and secret_key_base set, your cookies will
# be encrypted, and signed cookies generated by Rails 3 will be
# transparently read and encrypted to provide a smooth upgrade path.
#
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 735b5939dd..ab3077b308 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -23,7 +23,7 @@ module ActionDispatch
# preload lists is `18.weeks`.
# * `subdomains`: Set to `true` to tell the browser to apply these settings
# to all subdomains. This protects your cookies from interception by a
- # vulnerable site on a subdomain. Defaults to `false`.
+ # vulnerable site on a subdomain. Defaults to `true`.
# * `preload`: Advertise that this site may be included in browsers'
# preloaded HSTS lists. HSTS protects your site on every visit *except the
# first visit* since it hasn't seen your HSTS header yet. To close this
@@ -34,6 +34,10 @@ module ActionDispatch
# original HSTS directive until it expires. Instead, use the header to tell browsers to
# expire HSTS immediately. Setting `hsts: false` is a shortcut for
# `hsts: { expires: 0 }`.
+ #
+ # Requests can opt-out of redirection with `exclude`:
+ #
+ # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
class SSL
# Default to 180 days, the low end for https://www.ssllabs.com/ssltest/
# and greater than the 18-week requirement for browser preload lists.
@@ -49,14 +53,26 @@ module ActionDispatch
if options[:host] || options[:port]
ActiveSupport::Deprecation.warn <<-end_warning.strip_heredoc
The `:host` and `:port` options are moving within `:redirect`:
- `config.ssl_options = { redirect: { host: …, port: … }}`.
+ `config.ssl_options = { redirect: { host: …, port: … } }`.
end_warning
@redirect = options.slice(:host, :port)
else
@redirect = redirect
end
+ @exclude = @redirect && @redirect[:exclude] || proc { !@redirect }
@secure_cookies = secure_cookies
+
+ if hsts != true && hsts != false && hsts[:subdomains].nil?
+ hsts[:subdomains] = false
+
+ ActiveSupport::Deprecation.warn <<-end_warning.strip_heredoc
+ In Rails 5.1, The `:subdomains` option of HSTS config will be treated as true if
+ unspecified. Set `config.ssl_options = { hsts: { subdomains: false } }` to opt out
+ of this behavior.
+ end_warning
+ end
+
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
end
@@ -69,7 +85,7 @@ module ActionDispatch
flag_cookies_as_secure! headers if @secure_cookies
end
else
- return redirect_to_https request if @redirect
+ return redirect_to_https request unless @exclude.call(request)
@app.call(env)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 41c220236a..2c5721dc22 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -27,8 +27,8 @@ module ActionDispatch
# in the server's `public/` directory (see Static#call).
def match?(path)
path = ::Rack::Utils.unescape_path path
- return false unless valid_path?(path)
- path = Rack::Utils.clean_path_info path
+ return false unless ::Rack::Utils.valid_path? path
+ path = ::Rack::Utils.clean_path_info path
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
@@ -94,10 +94,6 @@ module ActionDispatch
false
end
end
-
- def valid_path?(path)
- path.valid_encoding? && !path.include?("\0")
- end
end
# This middleware will attempt to return the contents of a file's body from
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index ddeea24bb3..e9e6a2e597 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -39,6 +39,8 @@ module ActionDispatch
config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
+ ActionDispatch::Reloader.default_reloader = app.reloader
+
ActionDispatch.test_app = app
end
end
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 42890225fa..47568f6ad0 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -9,7 +9,7 @@ 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, req, default_options)
session_was = find req
@@ -198,6 +198,10 @@ module ActionDispatch
@delegate.merge!(other)
end
+ def each(&block)
+ to_hash.each(&block)
+ end
+
private
def load_for_read!
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index d00b2c3eb5..61ebd0b8db 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -73,14 +73,14 @@ module ActionDispatch
# get 'post/:id' => 'posts#show'
# post 'post/:id' => 'posts#create_comment'
#
+ # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
+ # URL will route to the <tt>show</tt> action.
+ #
# If your route needs to respond to more than one HTTP method (or all methods) then using the
# <tt>:via</tt> option on <tt>match</tt> is preferable.
#
# match 'post/:id' => 'posts#show', via: [:get, :post]
#
- # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
- # URL will route to the <tt>show</tt> action.
- #
# == Named routes
#
# Routes can be named by passing an <tt>:as</tt> option,
@@ -89,7 +89,7 @@ module ActionDispatch
#
# Example:
#
- # # In routes.rb
+ # # In config/routes.rb
# get '/login' => 'accounts#login', as: 'login'
#
# # With render, redirect_to, tests, etc.
@@ -101,7 +101,7 @@ module ActionDispatch
#
# Use <tt>root</tt> as a shorthand to name a route for the root path "/".
#
- # # In routes.rb
+ # # In config/routes.rb
# root to: 'blogs#index'
#
# # would recognize http://www.example.com/ as
@@ -114,15 +114,15 @@ module ActionDispatch
# Note: when using +controller+, the route is simply named after the
# method you call on the block parameter rather than map.
#
- # # In routes.rb
+ # # In config/routes.rb
# controller :blog do
# get 'blog/show' => :list
# get 'blog/delete' => :delete
- # get 'blog/edit/:id' => :edit
+ # get 'blog/edit' => :edit
# end
#
# # provides named routes for show, delete, and edit
- # link_to @article.title, show_path(id: @article.id)
+ # link_to @article.title, blog_show_path(id: @article.id)
#
# == Pretty URLs
#
@@ -159,7 +159,7 @@ module ActionDispatch
#
# controller 'geocode' do
# get 'geocode/:postalcode' => :show, constraints: {
- # postalcode: /# Postcode format
+ # postalcode: /# Postalcode format
# \d{5} #Prefix
# (-\d{4})? #Suffix
# /x
@@ -196,7 +196,7 @@ module ActionDispatch
#
# Rails.application.reload_routes!
#
- # This will clear all named routes and reload routes.rb if the file has been modified from
+ # This will clear all named routes and reload config/routes.rb if the file has been modified from
# last load. To absolutely force reloading, use <tt>reload!</tt>.
#
# == Testing Routes
@@ -239,7 +239,7 @@ module ActionDispatch
#
# rails routes
#
- # Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>.
+ # Target specific controllers by prefixing the command with <tt>-c</tt> option.
#
module Routing
extend ActiveSupport::Autoload
@@ -252,5 +252,14 @@ module ActionDispatch
SEPARATORS = %w( / . ? ) #:nodoc:
HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
+
+ #:stopdoc:
+ INSECURE_URL_PARAMETERS_MESSAGE = <<-MSG.squish
+ Attempting to generate a URL from non-sanitized request parameters!
+
+ An attacker can inject malicious data into the generated URL, such as
+ changing the host. Whitelist and sanitize passed parameters to be secure.
+ MSG
+ #:startdoc:
end
end
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index 69e6dd5215..2459a45827 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -33,15 +33,15 @@ module ActionDispatch
end
def controller
- requirements[:controller] || ':controller'
+ parts.include?(:controller) ? ':controller' : requirements[:controller]
end
def action
- requirements[:action] || ':action'
+ parts.include?(:action) ? ':action' : requirements[:action]
end
def internal?
- controller.to_s =~ %r{\Arails/(info|mailers|welcome)}
+ internal
end
def engine?
@@ -51,7 +51,7 @@ module ActionDispatch
##
# This class is just used for displaying route information when someone
- # executes `rake routes` or looks at the RoutingError page.
+ # executes `rails routes` or looks at the RoutingError page.
# People should not use this class.
class RoutesInspector # :nodoc:
def initialize(routes)
@@ -60,12 +60,10 @@ module ActionDispatch
end
def format(formatter, filter = nil)
- routes_to_display = filter_routes(filter)
-
+ routes_to_display = filter_routes(normalize_filter(filter))
routes = collect_routes(routes_to_display)
-
if routes.none?
- formatter.no_routes(collect_routes(@routes), filter)
+ formatter.no_routes(collect_routes(@routes))
return formatter.result
end
@@ -82,10 +80,20 @@ module ActionDispatch
private
+ def normalize_filter(filter)
+ if filter.is_a?(Hash) && filter[:controller]
+ { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ }
+ elsif filter
+ { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ }
+ end
+ end
+
def filter_routes(filter)
if filter
- filter_name = filter.underscore.sub(/_controller$/, '')
- @routes.select { |route| route.defaults[:controller] == filter_name }
+ @routes.select do |route|
+ route_wrapper = RouteWrapper.new(route)
+ filter.any? { |default, value| route_wrapper.send(default) =~ value }
+ end
else
@routes
end
@@ -137,7 +145,7 @@ module ActionDispatch
@buffer << draw_header(routes)
end
- def no_routes(routes, filter)
+ def no_routes(routes)
@buffer <<
if routes.none?
<<-MESSAGE.strip_heredoc
@@ -145,8 +153,6 @@ module ActionDispatch
Please add some routes in config/routes.rb.
MESSAGE
- elsif missing_controller?(filter)
- "The controller #{filter} does not exist!"
else
"No routes were found for this controller"
end
@@ -154,10 +160,6 @@ module ActionDispatch
end
private
- def missing_controller?(controller_name)
- [ controller_name.camelize, "#{controller_name.camelize}Controller" ].none?(&:safe_constantize)
- end
-
def draw_section(routes)
header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length)
name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index afbaa45d20..40b6500553 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/array/extract_options'
@@ -107,6 +106,7 @@ module ActionDispatch
@ast = ast
@anchor = anchor
@via = via
+ @internal = options.delete(:internal)
path_params = ast.find_all(&:symbol?).map(&:to_sym)
@@ -120,7 +120,7 @@ module ActionDispatch
if options_constraints.is_a?(Hash)
@defaults = Hash[options_constraints.find_all { |key, default|
- URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
+ URL_OPTIONS.include?(key) && (String === default || Integer === default)
}].merge @defaults
@blocks = blocks
constraints.merge! options_constraints
@@ -137,6 +137,10 @@ module ActionDispatch
@conditions = Hash[conditions]
@defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
+ if path_params.include?(:action) && !@requirements.key?(:action)
+ @defaults[:action] ||= 'index'
+ end
+
@required_defaults = (split_options[:required_defaults] || []).map(&:first)
end
@@ -148,7 +152,8 @@ module ActionDispatch
required_defaults,
defaults,
request_method,
- precedence)
+ precedence,
+ @internal)
route
end
@@ -819,10 +824,10 @@ module ActionDispatch
if options[:constraints].is_a?(Hash)
defaults = options[:constraints].select do |k, v|
- URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
+ URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
end
- (options[:defaults] ||= {}).reverse_merge!(defaults)
+ options[:defaults] = defaults.merge(options[:defaults] || {})
else
block, options[:constraints] = options[:constraints], {}
end
@@ -1596,7 +1601,7 @@ module ActionDispatch
route_options = options.dup
if _path && option_path
ActiveSupport::Deprecation.warn <<-eowarn
-Specifying strings for both :path and the route path is deprecated. Change things like this:
+Specifying strings for both :path and the route path is deprecated. Change things like this:
match #{_path.inspect}, :path => #{option_path.inspect}
@@ -2083,8 +2088,7 @@ to this:
def each
node = self
- loop do
- break if node.equal? NULL
+ until node.equal? NULL
yield node
node = node.parent
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 846b5fa1fc..ed7130b58e 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -289,7 +289,7 @@ module ActionDispatch
if last.permitted?
args.pop.to_h
else
- raise ArgumentError, "Generating an URL from non sanitized request parameters is insecure!"
+ raise ArgumentError, ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE
end
end
helper.call self, args, options
@@ -513,6 +513,21 @@ module ActionDispatch
route = @set.add_route(name, mapping)
named_routes[name] = route if name
+
+ if route.segment_keys.include?(:controller)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Using a dynamic :controller segment in a route is deprecated and
+ will be removed in Rails 5.1.
+ MSG
+ end
+
+ if route.segment_keys.include?(:action)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Using a dynamic :action segment in a route is deprecated and
+ will be removed in Rails 5.1.
+ MSG
+ end
+
route
end
@@ -533,12 +548,10 @@ module ActionDispatch
@recall = recall
@set = set
- normalize_recall!
normalize_options!
normalize_controller_action_id!
use_relative_controller!
normalize_controller!
- normalize_action!
end
def controller
@@ -557,11 +570,6 @@ module ActionDispatch
end
end
- # Set 'index' as default action for recall
- def normalize_recall!
- @recall[:action] ||= 'index'
- end
-
def normalize_options!
# If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like
@@ -615,13 +623,6 @@ module ActionDispatch
end
end
- # Move 'index' action from options to recall
- def normalize_action!
- if @options[:action] == 'index'.freeze
- @recall[:action] = @options.delete(:action)
- end
- end
-
# Generates a path from routes, returns [path, params].
# If no route is generated the formatter will raise ActionController::UrlGenerationError
def generate
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index f91679593e..5ee138e6c6 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -173,7 +173,7 @@ module ActionDispatch
route_name)
when ActionController::Parameters
unless options.permitted?
- raise ArgumentError.new("Generating an URL from non sanitized request parameters is insecure!")
+ raise ArgumentError.new(ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE)
end
route_name = options.delete :use_route
_routes.url_for(options.to_h.symbolize_keys.
diff --git a/actionpack/lib/action_dispatch/testing/assertion_response.rb b/actionpack/lib/action_dispatch/testing/assertion_response.rb
index 3fb81ff083..404b96bbcd 100644
--- a/actionpack/lib/action_dispatch/testing/assertion_response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertion_response.rb
@@ -1,14 +1,7 @@
module ActionDispatch
- # This is a class that abstracts away an asserted response.
- # It purposely does not inherit from Response, because it doesn't need it.
- # That means it does not have headers or a body.
- #
- # As an input to the initializer, we take a Fixnum, a String, or a Symbol.
- # If it's a Fixnum or String, we figure out what its symbolized name.
- # If it's a Symbol, we figure out what its corresponding code is.
- # The resulting code will be a Fixnum, for real HTTP codes, and it will
- # be a String for the pseudo-HTTP codes, such as:
- # :success, :missing, :redirect and :error
+ # This is a class that abstracts away an asserted response. It purposely
+ # does not inherit from Response because it doesn't need it. That means it
+ # does not have headers or a body.
class AssertionResponse
attr_reader :code, :name
@@ -19,6 +12,9 @@ module ActionDispatch
error: "5XX"
}
+ # Accepts a specific response status code as an Integer (404) or String
+ # ('404') or a response status range as a Symbol pseudo-code (:success,
+ # indicating any 200-299 status code).
def initialize(code_or_name)
if code_or_name.is_a?(Symbol)
@name = code_or_name
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 78ef860548..44ad2c10d8 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -86,6 +86,7 @@ module ActionDispatch
end
# Load routes.rb if it hasn't been loaded.
+ options = options.clone
generated_path, query_string_keys = @routes.generate_extras(options, defaults)
found_extras = options.reject { |k, _| ! query_string_keys.include? k }
@@ -116,7 +117,7 @@ module ActionDispatch
# # Tests a route, providing a defaults hash
# assert_routing 'controller/action/9', {id: "9", item: "square"}, {controller: "controller", action: "action"}, {}, {item: "square"}
#
- # # Tests a route with a HTTP method
+ # # Tests a route with an HTTP method
# assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" })
def assert_routing(path, options, defaults={}, extras={}, message=nil)
assert_recognizes(options, path, extras, message)
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 711ca10419..8777666f9f 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -71,7 +71,7 @@ module ActionDispatch
end
# Performs an XMLHttpRequest request with the given parameters, mirroring
- # a request from the Prototype library.
+ # an AJAX request made from JavaScript.
#
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
@@ -95,7 +95,7 @@ module ActionDispatch
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`
+ `get "/posts", xhr: true` and `post "/posts/1", xhr: true`.
MSG
process(request_method, path, params: params, headers: headers, xhr: true)
@@ -122,6 +122,7 @@ module ActionDispatch
# params: { ref_id: 14 },
# headers: { "X-Test-Header" => "testvalue" }
def request_via_redirect(http_method, path, *args)
+ ActiveSupport::Deprecation.warn('`request_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.')
process_with_kwargs(http_method, path, *args)
follow_redirect! while redirect?
@@ -131,35 +132,35 @@ module ActionDispatch
# Performs a GET request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def get_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
+ ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.')
request_via_redirect(:get, path, *args)
end
# Performs a POST request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def post_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
+ ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.')
request_via_redirect(:post, path, *args)
end
# Performs a PATCH request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def patch_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
+ ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.')
request_via_redirect(:patch, path, *args)
end
# Performs a PUT request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def put_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
+ ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.')
request_via_redirect(:put, path, *args)
end
# Performs a DELETE request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def delete_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
+ ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.')
request_via_redirect(:delete, path, *args)
end
end
@@ -299,7 +300,7 @@ module ActionDispatch
end
end
- REQUEST_KWARGS = %i(params headers env xhr)
+ REQUEST_KWARGS = %i(params headers env xhr as)
def kwarg_request?(args)
args[0].respond_to?(:keys) && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
end
@@ -316,12 +317,15 @@ module ActionDispatch
params: { id: 1 },
headers: { 'X-Extra-Header' => '123' },
env: { 'action_dispatch.custom' => 'custom' },
- xhr: true
+ xhr: true,
+ as: :json
MSG
end
# Performs the actual request.
- def process(method, path, params: nil, headers: nil, env: nil, xhr: false)
+ def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil)
+ request_encoder = RequestEncoder.encoder(as)
+
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
@@ -330,14 +334,17 @@ module ActionDispatch
url_host += ":#{location.port}" if default != location.port
host! url_host
end
- path = location.query ? "#{location.path}?#{location.query}" : location.path
+ path = request_encoder.append_format_to location.path
+ path = location.query ? "#{path}?#{location.query}" : path
+ else
+ path = request_encoder.append_format_to path
end
hostname, port = host.split(':')
request_env = {
:method => method,
- :params => params,
+ :params => request_encoder.encode_params(params),
"SERVER_NAME" => hostname,
"SERVER_PORT" => port || (https? ? "443" : "80"),
@@ -347,7 +354,7 @@ module ActionDispatch
"REQUEST_URI" => path,
"HTTP_HOST" => host,
"REMOTE_ADDR" => remote_addr,
- "CONTENT_TYPE" => "application/x-www-form-urlencoded",
+ "CONTENT_TYPE" => request_encoder.content_type,
"HTTP_ACCEPT" => accept
}
@@ -376,6 +383,7 @@ module ActionDispatch
response = _mock_session.last_response
@response = ActionDispatch::TestResponse.from_response(response)
@response.request = @request
+ @response.response_parser = RequestEncoder.parser(@response.content_type)
@html_document = nil
@url_options = nil
@@ -387,6 +395,56 @@ module ActionDispatch
def build_full_uri(path, env)
"#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}"
end
+
+ class RequestEncoder # :nodoc:
+ @encoders = {}
+
+ attr_reader :response_parser
+
+ def initialize(mime_name, param_encoder, response_parser, url_encoded_form = false)
+ @mime = Mime[mime_name]
+
+ unless @mime
+ raise ArgumentError, "Can't register a request encoder for " \
+ "unregistered MIME Type: #{mime_name}. See `Mime::Type.register`."
+ end
+
+ @url_encoded_form = url_encoded_form
+ @path_format = ".#{@mime.symbol}" unless @url_encoded_form
+ @response_parser = response_parser || -> body { body }
+ @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc
+ end
+
+ def append_format_to(path)
+ path << @path_format unless @url_encoded_form
+ path
+ end
+
+ def content_type
+ @mime.to_s
+ end
+
+ def encode_params(params)
+ @param_encoder.call(params)
+ end
+
+ def self.parser(content_type)
+ mime = Mime::Type.lookup(content_type)
+ encoder(mime ? mime.ref : nil).response_parser
+ end
+
+ def self.encoder(name)
+ @encoders[name] || WWWFormEncoder
+ end
+
+ def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil)
+ @encoders[mime_name] = new(mime_name, param_encoder, response_parser)
+ end
+
+ register_encoder :json, response_parser: -> body { JSON.parse(body) }
+
+ WWWFormEncoder = new(:url_encoded_form, -> params { params }, nil, true)
+ end
end
module Runner
@@ -643,33 +701,85 @@ module ActionDispatch
# end
# end
#
+ # You can also test your JSON API easily by setting what the request should
+ # be encoded as:
+ #
+ # require 'test_helper'
+ #
+ # class ApiTest < ActionDispatch::IntegrationTest
+ # test 'creates articles' do
+ # assert_difference -> { Article.count } do
+ # post articles_path, params: { article: { title: 'Ahoy!' } }, as: :json
+ # end
+ #
+ # assert_response :success
+ # assert_equal({ id: Arcticle.last.id, title: 'Ahoy!' }, response.parsed_body)
+ # end
+ # end
+ #
+ # The `as` option sets the format to JSON, sets the content type to
+ # 'application/json' and encodes the parameters as JSON.
+ #
+ # Calling `parsed_body` on the response parses the response body as what
+ # the last request was encoded as. If the request wasn't encoded `as` something,
+ # it's the same as calling `body`.
+ #
+ # For any custom MIME Types you've registered, you can even add your own encoders with:
+ #
+ # ActionDispatch::IntegrationTest.register_encoder :wibble,
+ # param_encoder: -> params { params.to_wibble },
+ # response_parser: -> body { body }
+ #
+ # Where `param_encoder` defines how the params should be encoded and
+ # `response_parser` defines how the response body should be parsed through
+ # `parsed_body`.
+ #
# Consult the Rails Testing Guide for more.
class IntegrationTest < ActiveSupport::TestCase
- include Integration::Runner
- include ActionController::TemplateAssertions
- include ActionDispatch::Routing::UrlFor
+ module UrlOptions
+ extend ActiveSupport::Concern
+ def url_options
+ integration_session.url_options
+ end
+ end
- @@app = nil
+ module Behavior
+ extend ActiveSupport::Concern
- def self.app
- @@app || ActionDispatch.test_app
- end
+ include Integration::Runner
+ include ActionController::TemplateAssertions
- def self.app=(app)
- @@app = app
- end
+ included do
+ include ActionDispatch::Routing::UrlFor
+ include UrlOptions # don't let UrlFor override the url_options method
+ ActiveSupport.run_load_hooks(:action_dispatch_integration_test, self)
+ @@app = nil
+ end
- def app
- super || self.class.app
- end
+ module ClassMethods
+ def app
+ defined?(@@app) ? @@app : ActionDispatch.test_app
+ end
- def url_options
- integration_session.url_options
- end
+ def app=(app)
+ @@app = app
+ end
- def document_root_element
- html_document.root
+ def register_encoder(*args)
+ Integration::Session::RequestEncoder.register_encoder(*args)
+ end
+ end
+
+ def app
+ super || self.class.app
+ end
+
+ def document_root_element
+ html_document.root
+ end
end
+
+ include Behavior
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index eca0439909..1ecd7d14a7 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -1,6 +1,5 @@
require 'action_dispatch/middleware/cookies'
require 'action_dispatch/middleware/flash'
-require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch
module TestProcess
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index ad1a7f7109..46523a8600 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -22,23 +22,23 @@ module ActionDispatch
private_class_method :default_env
def request_method=(method)
- @env['REQUEST_METHOD'] = method.to_s.upcase
+ set_header('REQUEST_METHOD', method.to_s.upcase)
end
def host=(host)
- @env['HTTP_HOST'] = host
+ set_header('HTTP_HOST', host)
end
def port=(number)
- @env['SERVER_PORT'] = number.to_i
+ set_header('SERVER_PORT', number.to_i)
end
def request_uri=(uri)
- @env['REQUEST_URI'] = uri
+ set_header('REQUEST_URI', uri)
end
def path=(path)
- @env['PATH_INFO'] = path
+ set_header('PATH_INFO', path)
end
def action=(action_name)
@@ -46,24 +46,24 @@ module ActionDispatch
end
def if_modified_since=(last_modified)
- @env['HTTP_IF_MODIFIED_SINCE'] = last_modified
+ set_header('HTTP_IF_MODIFIED_SINCE', last_modified)
end
def if_none_match=(etag)
- @env['HTTP_IF_NONE_MATCH'] = etag
+ set_header('HTTP_IF_NONE_MATCH', etag)
end
def remote_addr=(addr)
- @env['REMOTE_ADDR'] = addr
+ set_header('REMOTE_ADDR', addr)
end
def user_agent=(user_agent)
- @env['HTTP_USER_AGENT'] = user_agent
+ set_header('HTTP_USER_AGENT', user_agent)
end
def accept=(mime_types)
- @env.delete('action_dispatch.request.accepts')
- @env['HTTP_ACCEPT'] = Array(mime_types).collect(&:to_s).join(",")
+ delete_header('action_dispatch.request.accepts')
+ set_header('HTTP_ACCEPT', Array(mime_types).collect(&:to_s).join(","))
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index 4b79a90242..9d4b73a43d 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -18,5 +18,11 @@ module ActionDispatch
# Was there a server-side error?
alias_method :error?, :server_error?
+
+ attr_writer :response_parser # :nodoc:
+
+ def parsed_body
+ @parsed_body ||= @response_parser.call(body)
+ end
end
end
diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb
index 3a1410d492..d8f86630b1 100644
--- a/actionpack/lib/action_pack/gem_version.rb
+++ b/actionpack/lib/action_pack/gem_version.rb
@@ -6,9 +6,9 @@ module ActionPack
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta1.1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 1ef10ff956..1e1d6f5429 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -1,5 +1,3 @@
-require File.expand_path('../../../load_paths', __FILE__)
-
$:.unshift(File.dirname(__FILE__) + '/lib')
$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
$:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers')
@@ -36,7 +34,6 @@ require 'action_dispatch'
require 'active_support/dependencies'
require 'active_model'
require 'active_record'
-require 'action_controller/caching'
require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
@@ -60,16 +57,14 @@ ActiveSupport::Deprecation.debug = true
# Disable available locale checks to avoid warnings running the test suite.
I18n.enforce_available_locales = false
-# Register danish language for testing
-I18n.backend.store_translations 'da', {}
-I18n.backend.store_translations 'pt-BR', {}
-
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
SharedTestRoutes.draw do
- get ':controller(/:action)'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action)'
+ end
end
module ActionDispatch
@@ -118,7 +113,9 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
self.app = build_app
app.routes.draw do
- get ':controller(/:action)'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action)'
+ end
end
class DeadEndRoutes < ActionDispatch::Routing::RouteSet
@@ -254,24 +251,6 @@ end
class ::ApplicationController < ActionController::Base
end
-class Workshop
- extend ActiveModel::Naming
- include ActiveModel::Conversion
- attr_accessor :id
-
- def initialize(id)
- @id = id
- end
-
- def persisted?
- id.present?
- end
-
- def to_s
- id.to_s
- end
-end
-
module ActionDispatch
class DebugExceptions
private
@@ -375,37 +354,15 @@ module RoutingTestHelpers
end
end
-class MetalRenderingController < ActionController::Metal
- include AbstractController::Rendering
- include ActionController::Rendering
- include ActionController::Renderers
-end
-
class ResourcesController < ActionController::Base
def index() head :ok end
alias_method :show, :index
end
-class ThreadsController < ResourcesController; end
-class MessagesController < ResourcesController; end
class CommentsController < ResourcesController; end
-class ReviewsController < ResourcesController; end
-
class AccountsController < ResourcesController; end
-class AdminController < ResourcesController; end
-class ProductsController < ResourcesController; end
class ImagesController < ResourcesController; end
-module Backoffice
- class ProductsController < ResourcesController; end
- class ImagesController < ResourcesController; end
-
- module Admin
- class ProductsController < ResourcesController; end
- class ImagesController < ResourcesController; end
- end
-end
-
# Skips the current run on Rubinius using Minitest::Assertions#skip
def rubinius_skip(message = '')
skip message if RUBY_ENGINE == 'rbx'
diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb
index 579ce0ed29..57a67a48b5 100644
--- a/actionpack/test/assertions/response_assertions_test.rb
+++ b/actionpack/test/assertions/response_assertions_test.rb
@@ -35,7 +35,7 @@ module ActionDispatch
end
end
- def test_assert_response_fixnum
+ def test_assert_response_integer
@response = FakeResponse.new 400
assert_response 400
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 899d92f815..db71aa2160 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -177,7 +177,10 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
set.draw do
get 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one
get 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
process :redirect_to_named_route
assert_raise(ActiveSupport::TestCase::Assertion) do
@@ -201,7 +204,10 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
with_routing do |set|
set.draw do
get 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
process :redirect_to_index
# redirection is <{"action"=>"index", "controller"=>"admin/admin/inner_module"}>
@@ -215,7 +221,10 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
with_routing do |set|
set.draw do
get '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
process :redirect_to_top_level_named_route
# assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return
@@ -231,7 +240,10 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
set.draw do
# this controller exists in the admin namespace as well which is the only difference from previous test
get '/user/:id', :to => 'user#index', :as => :top_level
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
process :redirect_to_top_level_named_route
# assert_redirected_to top_level_url('foo') would pass because of exact match early return
diff --git a/actionpack/test/controller/api/with_cookies_test.rb b/actionpack/test/controller/api/with_cookies_test.rb
new file mode 100644
index 0000000000..4491dc9002
--- /dev/null
+++ b/actionpack/test/controller/api/with_cookies_test.rb
@@ -0,0 +1,21 @@
+require 'abstract_unit'
+
+class WithCookiesController < ActionController::API
+ include ActionController::Cookies
+
+ def with_cookies
+ render plain: cookies[:foobar]
+ end
+end
+
+class WithCookiesTest < ActionController::TestCase
+ tests WithCookiesController
+
+ def test_with_cookies
+ request.cookies[:foobar] = 'bazbang'
+
+ get :with_cookies
+
+ assert_equal 'bazbang', response.body
+ end
+end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index e3f669dbb5..577a3d5800 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -175,7 +175,10 @@ class UrlOptionsTest < ActionController::TestCase
with_routing do |set|
set.draw do
get 'from_view', :to => 'url_options#from_view', :as => :from_view
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :from_view, params: { route: "from_view_url" }
@@ -209,7 +212,10 @@ class DefaultUrlOptionsTest < ActionController::TestCase
with_routing do |set|
set.draw do
get 'from_view', :to => 'default_url_options#from_view', :as => :from_view
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :from_view, params: { route: "from_view_url" }
@@ -226,7 +232,10 @@ class DefaultUrlOptionsTest < ActionController::TestCase
scope("/:locale") do
resources :descriptions
end
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :from_view, params: { route: "description_path(1)" }
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index d19b3810c2..7faf3cd8c6 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -172,6 +172,9 @@ class FunctionalCachingController < CachingController
def fragment_cached_without_digest
end
+
+ def fragment_cached_with_options
+ end
end
class FunctionalFragmentCachingTest < ActionController::TestCase
@@ -215,6 +218,18 @@ CACHED
assert_equal "<p>ERB</p>", @store.read("views/nodigest")
end
+ def test_fragment_caching_with_options
+ time = Time.now
+ get :fragment_cached_with_options
+ assert_response :success
+ expected_body = "<body>\n<p>ERB</p>\n</body>\n"
+
+ assert_equal expected_body, @response.body
+ Time.stub(:now, time + 11) do
+ assert_nil @store.read("views/with_options")
+ end
+ end
+
def test_render_inline_before_fragment_caching
get :inline_fragment_cached
assert_response :success
@@ -369,14 +384,14 @@ class CollectionCacheController < ActionController::Base
render 'index'
end
- def index_explicit_render
+ def index_explicit_render_in_controller
@customers = [Customer.new('david', 1)]
- render partial: 'customers/customer', collection: @customers
+ render partial: 'customers/customer', collection: @customers, cached: true
end
def index_with_comment
@customers = [Customer.new('david', 1)]
- render partial: 'customers/commented_customer', collection: @customers, as: :customer
+ render partial: 'customers/commented_customer', collection: @customers, as: :customer, cached: true
end
end
@@ -387,12 +402,13 @@ class AutomaticCollectionCacheTest < ActionController::TestCase
@controller.perform_caching = true
@controller.partial_rendered_times = 0
@controller.cache_store = ActiveSupport::Cache::MemoryStore.new
- ActionView::PartialRenderer.collection_cache = @controller.cache_store
+ ActionView::PartialRenderer.collection_cache = ActiveSupport::Cache::MemoryStore.new
end
def test_collection_fetches_cached_views
get :index
assert_equal 1, @controller.partial_rendered_times
+ assert_customer_cached 'david/1', 'david, 1'
get :index
assert_equal 1, @controller.partial_rendered_times
@@ -400,13 +416,16 @@ class AutomaticCollectionCacheTest < ActionController::TestCase
def test_preserves_order_when_reading_from_cache_plus_rendering
get :index, params: { id: 2 }
- get :index_ordered
+ assert_equal 1, @controller.partial_rendered_times
+ assert_select ':root', 'david, 2'
+ get :index_ordered
+ assert_equal 3, @controller.partial_rendered_times
assert_select ':root', "david, 1\n david, 2\n david, 3"
end
def test_explicit_render_call_with_options
- get :index_explicit_render
+ get :index_explicit_render_in_controller
assert_select ':root', "david, 1"
end
@@ -418,6 +437,12 @@ class AutomaticCollectionCacheTest < ActionController::TestCase
get :index_with_comment
assert_equal 1, @controller.partial_rendered_times
end
+
+ private
+ def assert_customer_cached(key, content)
+ assert_match content,
+ ActionView::PartialRenderer.collection_cache.read("views/#{key}/7c228ab609f0baf0b1f2367469210937")
+ end
end
class FragmentCacheKeyTestController < CachingController
diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb
index 081288ef21..f87077dd86 100644
--- a/actionpack/test/controller/flash_hash_test.rb
+++ b/actionpack/test/controller/flash_hash_test.rb
@@ -57,10 +57,10 @@ module ActionDispatch
def test_to_session_value
@hash['foo'] = 'bar'
- assert_equal({'flashes' => {'foo' => 'bar'}}, @hash.to_session_value)
+ assert_equal({ 'discard' => [], 'flashes' => { 'foo' => 'bar' } }, @hash.to_session_value)
@hash.now['qux'] = 1
- assert_equal({'flashes' => {'foo' => 'bar'}}, @hash.to_session_value)
+ assert_equal({ 'flashes' => { 'foo' => 'bar' }, 'discard' => [] }, @hash.to_session_value)
@hash.discard('foo')
assert_equal(nil, @hash.to_session_value)
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index b063d769a4..eef48e8480 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -323,7 +323,9 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- get ':action', :to => FlashIntegrationTest::TestController
+ ActiveSupport::Deprecation.silence do
+ get ':action', :to => FlashIntegrationTest::TestController
+ end
end
@app = self.class.build_app(set) do |middleware|
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
index 22f1cc7c22..03a9c9ae78 100644
--- a/actionpack/test/controller/force_ssl_test.rb
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -322,3 +322,12 @@ class RedirectToSSLTest < ActionController::TestCase
assert_equal 'ihaz', response.body
end
end
+
+class ForceSSLControllerLevelTest < ActionController::TestCase
+ def test_no_redirect_websocket_ssl_request
+ request.env['rack.url_scheme'] = 'wss'
+ request.env['Upgrade'] = 'websocket'
+ get :cheeseburger
+ assert_response 200
+ end
+end
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index feb882a2b3..ef85e141a0 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -207,6 +207,22 @@ class HelperTest < ActiveSupport::TestCase
assert methods.include?(:foobar)
end
+ def test_helper_proxy_in_instance
+ methods = AllHelpersController.new.helpers.methods
+
+ # Action View
+ assert_includes methods, :pluralize
+
+ # abc_helper.rb
+ assert_includes methods, :bare_a
+
+ # fun/games_helper.rb
+ assert_includes methods, :stratego
+
+ # fun/pdf_helper.rb
+ assert_includes methods, :foobar
+ end
+
def test_helper_proxy_config
AllHelpersController.config.my_var = 'smth'
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index d0a1d1285f..34fb3b1003 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -35,7 +35,7 @@ class SessionTest < ActiveSupport::TestCase
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
assert_called_with @session, :process, [:put, path, params: args, headers: headers] do
@session.stub :redirect?, false do
- @session.request_via_redirect(:put, path, params: args, headers: headers)
+ assert_deprecated { @session.request_via_redirect(:put, path, params: args, headers: headers) }
end
end
end
@@ -54,7 +54,7 @@ class SessionTest < ActiveSupport::TestCase
value_series = [true, true, false]
assert_called @session, :follow_redirect!, times: 2 do
@session.stub :redirect?, ->{ value_series.shift } do
- @session.request_via_redirect(:get, path, params: args, headers: headers)
+ assert_deprecated { @session.request_via_redirect(:get, path, params: args, headers: headers) }
end
end
end
@@ -63,7 +63,9 @@ class SessionTest < ActiveSupport::TestCase
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
@session.stub :redirect?, false do
@session.stub :status, 200 do
- assert_equal 200, @session.request_via_redirect(:get, path, params: args, headers: headers)
+ assert_deprecated do
+ assert_equal 200, @session.request_via_redirect(:get, path, params: args, headers: headers)
+ end
end
end
end
@@ -390,7 +392,7 @@ class IntegrationTestUsesCorrectClass < ActionDispatch::IntegrationTest
reset!
%w( get post head patch put delete ).each do |verb|
- assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
+ assert_nothing_raised { __send__(verb, '/') }
end
end
end
@@ -730,8 +732,10 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
set.draw do
get 'moved' => redirect('/method')
- match ':action', :to => controller, :via => [:get, :post], :as => :action
- get 'get/:action', :to => controller, :as => :get_action
+ ActiveSupport::Deprecation.silence do
+ match ':action', :to => controller, :via => [:get, :post], :as => :action
+ get 'get/:action', :to => controller, :as => :get_action
+ end
end
self.singleton_class.include(set.url_helpers)
@@ -1105,7 +1109,12 @@ class IntegrationRequestsWithoutSetup < ActionDispatch::IntegrationTest
def test_request
with_routing do |routes|
- routes.draw { get ':action' => FooController }
+ routes.draw do
+ ActiveSupport::Deprecation.silence do
+ get ':action' => FooController
+ end
+ end
+
get '/ok'
assert_response 200
@@ -1126,3 +1135,93 @@ class IntegrationRequestsWithSessionSetup < ActionDispatch::IntegrationTest
assert_equal({"user_name"=>"david"}, cookies.to_hash)
end
end
+
+class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest
+ class FooController < ActionController::Base
+ def foos_json
+ render json: params.permit(:foo)
+ end
+
+ def foos_wibble
+ render plain: 'ok'
+ end
+ end
+
+ def test_standard_json_encoding_works
+ with_routing do |routes|
+ routes.draw do
+ ActiveSupport::Deprecation.silence do
+ post ':action' => FooController
+ end
+ end
+
+ post '/foos_json.json', params: { foo: 'fighters' }.to_json,
+ headers: { 'Content-Type' => 'application/json' }
+
+ assert_response :success
+ assert_equal({ 'foo' => 'fighters' }, response.parsed_body)
+ end
+ end
+
+ def test_encoding_as_json
+ post_to_foos as: :json do
+ assert_response :success
+ assert_match 'foos_json.json', request.path
+ assert_equal 'application/json', request.content_type
+ assert_equal({ 'foo' => 'fighters' }, request.request_parameters)
+ assert_equal({ 'foo' => 'fighters' }, response.parsed_body)
+ end
+ end
+
+ def test_encoding_as_without_mime_registration
+ assert_raise ArgumentError do
+ ActionDispatch::IntegrationTest.register_encoder :wibble
+ end
+ end
+
+ def test_registering_custom_encoder
+ Mime::Type.register 'text/wibble', :wibble
+
+ ActionDispatch::IntegrationTest.register_encoder(:wibble,
+ param_encoder: -> params { params })
+
+ post_to_foos as: :wibble do
+ assert_response :success
+ assert_match 'foos_wibble.wibble', request.path
+ assert_equal 'text/wibble', request.content_type
+ assert_equal Hash.new, request.request_parameters # Unregistered MIME Type can't be parsed.
+ assert_equal 'ok', response.parsed_body
+ end
+ ensure
+ Mime::Type.unregister :wibble
+ end
+
+ def test_parsed_body_without_as_option
+ with_routing do |routes|
+ routes.draw do
+ ActiveSupport::Deprecation.silence do
+ get ':action' => FooController
+ end
+ end
+
+ get '/foos_json.json', params: { foo: 'heyo' }
+
+ assert_equal({ 'foo' => 'heyo' }, response.parsed_body)
+ end
+ end
+
+ private
+ def post_to_foos(as:)
+ with_routing do |routes|
+ routes.draw do
+ ActiveSupport::Deprecation.silence do
+ post ':action' => FooController
+ end
+ end
+
+ post "/foos_#{as}", params: { foo: 'fighters' }, as: as
+
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 2ef9734269..5977124594 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -152,7 +152,6 @@ module ActionController
def thread_locals
tc.assert_equal 'aaron', Thread.current[:setting]
- tc.assert_not_equal Thread.current.object_id, Thread.current[:originating_thread]
response.headers['Content-Type'] = 'text/event-stream'
%w{ hello world }.each do |word|
@@ -206,7 +205,7 @@ module ActionController
def overfill_buffer_and_die
logger = ActionController::Base.logger || Logger.new($stdout)
response.stream.on_error do
- logger.warn 'Error while streaming'
+ logger.warn 'Error while streaming.'
error_latch.count_down
end
@@ -247,7 +246,8 @@ module ActionController
def assert_stream_closed
assert response.stream.closed?, 'stream should be closed'
- assert response.sent?, 'stream should be sent'
+ assert response.committed?, 'response should be committed'
+ assert response.sent?, 'response should be sent'
end
def capture_log_output
@@ -261,6 +261,14 @@ module ActionController
end
end
+ def setup
+ super
+
+ def @controller.new_controller_thread
+ Thread.new { yield }
+ end
+ end
+
def test_set_cookie
get :set_cookie
assert_equal({'hello' => 'world'}, @response.cookies)
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 6ae33be3c8..57cf2dafdf 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -183,6 +183,12 @@ class ACLogSubscriberTest < ActionController::TestCase
assert_equal "test_value", @controller.last_payload[:test_key]
end
+ def test_process_action_headers
+ get :show
+ wait
+ assert_equal "Rails Testing", @controller.last_payload[:headers]['User-Agent']
+ end
+
def test_process_action_with_filter_parameters
@request.env["action_dispatch.parameter_filter"] = [:lifo, :amount]
diff --git a/actionpack/test/controller/metal/renderers_test.rb b/actionpack/test/controller/metal/renderers_test.rb
index 007866a559..247e872674 100644
--- a/actionpack/test/controller/metal/renderers_test.rb
+++ b/actionpack/test/controller/metal/renderers_test.rb
@@ -1,6 +1,12 @@
require 'abstract_unit'
require 'active_support/core_ext/hash/conversions'
+class MetalRenderingController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+ include ActionController::Renderers
+end
+
class MetalRenderingJsonController < MetalRenderingController
class Model
def to_json(options = {})
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 76e2d3ff43..993f4001de 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -74,6 +74,14 @@ class RespondToController < ActionController::Base
end
end
+ def missing_templates
+ respond_to do |type|
+ # This test requires a block that is empty
+ type.json { }
+ type.xml
+ end
+ end
+
def using_defaults_with_type_list
respond_to(:html, :xml)
end
@@ -160,7 +168,14 @@ class RespondToController < ActionController::Base
end
end
- def variant_with_implicit_rendering
+ def variant_with_implicit_template_rendering
+ # This has exactly one variant template defined in the file system (+mobile.html.erb),
+ # which raises the regular MissingTemplate error for other variants.
+ end
+
+ def variant_without_implicit_template_rendering
+ # This differs from the above in that it does not have any templates defined in the file
+ # system, which triggers the ImplicitRender (204 No Content) behavior.
end
def variant_with_format_and_custom_render
@@ -272,6 +287,8 @@ class RespondToController < ActionController::Base
end
class RespondToControllerTest < ActionController::TestCase
+ NO_CONTENT_WARNING = "No template found for RespondToController#variant_without_implicit_template_rendering, rendering head :no_content"
+
def setup
super
@request.host = "www.example.com"
@@ -615,31 +632,77 @@ class RespondToControllerTest < ActionController::TestCase
end
end
+ def test_missing_templates
+ get :missing_templates, format: :json
+ assert_response :no_content
+ get :missing_templates, format: :xml
+ assert_response :no_content
+ end
+
def test_invalid_variant
+ assert_raises(ActionController::UnknownFormat) do
+ get :variant_with_implicit_template_rendering, params: { v: :invalid }
+ end
+ end
+
+ def test_variant_not_set_regular_unknown_format
+ assert_raises(ActionController::UnknownFormat) do
+ get :variant_with_implicit_template_rendering
+ end
+ end
+
+ def test_variant_with_implicit_template_rendering
+ get :variant_with_implicit_template_rendering, params: { v: :mobile }
+ assert_equal "text/html", @response.content_type
+ assert_equal "mobile", @response.body
+ end
+
+ def test_variant_without_implicit_rendering_from_browser
+ assert_raises(ActionController::UnknownFormat) do
+ get :variant_without_implicit_template_rendering, params: { v: :does_not_matter }
+ end
+ end
+
+ def test_variant_variant_not_set_and_without_implicit_rendering_from_browser
+ assert_raises(ActionController::UnknownFormat) do
+ get :variant_without_implicit_template_rendering
+ end
+ end
+
+ def test_variant_without_implicit_rendering_from_xhr
logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
old_logger, ActionController::Base.logger = ActionController::Base.logger, logger
- get :variant_with_implicit_rendering, params: { v: :invalid }
+ get :variant_without_implicit_template_rendering, xhr: true, params: { v: :does_not_matter }
assert_response :no_content
- assert_equal 1, logger.logged(:info).select{ |s| s =~ /No template found/ }.size, "Implicit head :no_content not logged"
+
+ assert_equal 1, logger.logged(:info).select{ |s| s == NO_CONTENT_WARNING }.size, "Implicit head :no_content not logged"
ensure
ActionController::Base.logger = old_logger
end
- def test_variant_not_set_regular_template_missing
- get :variant_with_implicit_rendering
+ def test_variant_without_implicit_rendering_from_api
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ old_logger, ActionController::Base.logger = ActionController::Base.logger, logger
+
+ get :variant_without_implicit_template_rendering, format: 'json', params: { v: :does_not_matter }
assert_response :no_content
+
+ assert_equal 1, logger.logged(:info).select{ |s| s == NO_CONTENT_WARNING }.size, "Implicit head :no_content not logged"
+ ensure
+ ActionController::Base.logger = old_logger
end
- def test_variant_with_implicit_rendering
- get :variant_with_implicit_rendering, params: { v: :implicit }
+ def test_variant_variant_not_set_and_without_implicit_rendering_from_xhr
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ old_logger, ActionController::Base.logger = ActionController::Base.logger, logger
+
+ get :variant_without_implicit_template_rendering, xhr: true
assert_response :no_content
- end
- def test_variant_with_implicit_template_rendering
- get :variant_with_implicit_rendering, params: { v: :mobile }
- assert_equal "text/html", @response.content_type
- assert_equal "mobile", @response.body
+ assert_equal 1, logger.logged(:info).select { |s| s == NO_CONTENT_WARNING }.size, "Implicit head :no_content not logged"
+ ensure
+ ActionController::Base.logger = old_logger
end
def test_variant_with_format_and_custom_render
@@ -778,24 +841,3 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "phone", @response.body
end
end
-
-class RespondToWithBlockOnDefaultRenderController < ActionController::Base
- def show
- default_render do
- render body: 'default_render yielded'
- end
- end
-end
-
-class RespondToWithBlockOnDefaultRenderControllerTest < ActionController::TestCase
- def setup
- super
- @request.host = "www.example.com"
- end
-
- def test_default_render_uses_block_when_no_template_exists
- get :show
- assert_equal "default_render yielded", @response.body
- assert_equal "text/plain", @response.content_type
- end
-end
diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
index c226fa57ee..ee3c498b1c 100644
--- a/actionpack/test/controller/new_base/bare_metal_test.rb
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -40,6 +40,22 @@ module BareMetalTest
end
end
+ class BareEmptyController < ActionController::Metal
+ def index
+ self.response_body = nil
+ end
+ end
+
+ class BareEmptyTest < ActiveSupport::TestCase
+ test "response body is nil" do
+ controller = BareEmptyController.new
+ controller.set_request!(ActionDispatch::Request.empty)
+ controller.set_response!(BareController.make_response!(controller.request))
+ controller.index
+ assert_equal nil, controller.response_body
+ end
+ end
+
class HeadController < ActionController::Metal
include ActionController::Head
diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb
index a9dcdde4b8..0b3a26807d 100644
--- a/actionpack/test/controller/new_base/content_type_test.rb
+++ b/actionpack/test/controller/new_base/content_type_test.rb
@@ -43,7 +43,9 @@ module ContentType
test "default response is text/plain and UTF8" do
with_routing do |set|
set.draw do
- get ':controller', :action => 'index'
+ ActiveSupport::Deprecation.silence do
+ get ':controller', :action => 'index'
+ end
end
get "/content_type/base"
diff --git a/actionpack/test/controller/new_base/render_body_test.rb b/actionpack/test/controller/new_base/render_body_test.rb
index f4a3db8b41..c65c245773 100644
--- a/actionpack/test/controller/new_base/render_body_test.rb
+++ b/actionpack/test/controller/new_base/render_body_test.rb
@@ -85,7 +85,7 @@ module RenderBody
test "rendering body from an action with default options renders the body with the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
get "/render_body/simple"
assert_body "hello david"
@@ -95,7 +95,7 @@ module RenderBody
test "rendering body from an action with default options renders the body without the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
get "/render_body/with_layout"
diff --git a/actionpack/test/controller/new_base/render_html_test.rb b/actionpack/test/controller/new_base/render_html_test.rb
index e9ea57e329..bfed136496 100644
--- a/actionpack/test/controller/new_base/render_html_test.rb
+++ b/actionpack/test/controller/new_base/render_html_test.rb
@@ -88,7 +88,7 @@ module RenderHtml
test "rendering text from an action with default options renders the text with the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
get "/render_html/simple"
assert_body "hello david"
@@ -98,7 +98,7 @@ module RenderHtml
test "rendering text from an action with default options renders the text without the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
get "/render_html/with_layout"
diff --git a/actionpack/test/controller/new_base/render_plain_test.rb b/actionpack/test/controller/new_base/render_plain_test.rb
index 0881442bd0..94afe7bcfe 100644
--- a/actionpack/test/controller/new_base/render_plain_test.rb
+++ b/actionpack/test/controller/new_base/render_plain_test.rb
@@ -80,7 +80,7 @@ module RenderPlain
test "rendering text from an action with default options renders the text with the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
get "/render_plain/simple"
assert_body "hello david"
@@ -90,7 +90,7 @@ module RenderPlain
test "rendering text from an action with default options renders the text without the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
get "/render_plain/with_layout"
diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb
index b06ce5db40..0d4c7cdb0a 100644
--- a/actionpack/test/controller/new_base/render_template_test.rb
+++ b/actionpack/test/controller/new_base/render_template_test.rb
@@ -177,7 +177,7 @@ module RenderTemplate
class TestWithLayout < Rack::TestCase
test "rendering with implicit layout" do
with_routing do |set|
- set.draw { get ':controller', :action => :index }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', :action => :index } }
get "/render_template/with_layout"
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
index 963f2c2f5c..1fb852a2c4 100644
--- a/actionpack/test/controller/new_base/render_test.rb
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -57,7 +57,9 @@ module Render
test "render with blank" do
with_routing do |set|
set.draw do
- get ":controller", :action => 'index'
+ ActiveSupport::Deprecation.silence do
+ get ":controller", :action => 'index'
+ end
end
get "/render/blank_render"
@@ -70,7 +72,9 @@ module Render
test "rendering more than once raises an exception" do
with_routing do |set|
set.draw do
- get ":controller", :action => 'index'
+ ActiveSupport::Deprecation.silence do
+ get ":controller", :action => 'index'
+ end
end
assert_raises(AbstractController::DoubleRenderError) do
diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb
index 048458178c..d4111d432c 100644
--- a/actionpack/test/controller/new_base/render_text_test.rb
+++ b/actionpack/test/controller/new_base/render_text_test.rb
@@ -83,7 +83,7 @@ module RenderText
test "rendering text from an action with default options renders the text with the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
ActiveSupport::Deprecation.silence do
get "/render_text/simple"
@@ -96,7 +96,7 @@ module RenderText
test "rendering text from an action with default options renders the text without the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
ActiveSupport::Deprecation.silence do
get "/render_text/with_layout"
diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb
index bd43ff7697..17c62dc3fe 100644
--- a/actionpack/test/controller/parameters/accessors_test.rb
+++ b/actionpack/test/controller/parameters/accessors_test.rb
@@ -4,6 +4,8 @@ require 'active_support/core_ext/hash/transform_values'
class ParametersAccessorsTest < ActiveSupport::TestCase
setup do
+ ActionController::Parameters.permit_all_parameters = false
+
@params = ActionController::Parameters.new(
person: {
age: '32',
@@ -129,9 +131,87 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
assert_not @params[:person].values_at(:name).first.permitted?
end
- test "equality with another hash works" do
+ test "equality with a hash is deprecated" do
hash1 = { foo: :bar }
params1 = ActionController::Parameters.new(hash1)
- assert(params1 == hash1)
+ assert_deprecated("will be removed in Rails 5.1") do
+ assert(params1 == hash1)
+ end
+ end
+
+ test "is equal to Parameters instance with same params" do
+ params1 = ActionController::Parameters.new(a: 1, b: 2)
+ params2 = ActionController::Parameters.new(a: 1, b: 2)
+ assert(params1 == params2)
+ end
+
+ test "is equal to Parameters instance with same permitted params" do
+ params1 = ActionController::Parameters.new(a: 1, b: 2).permit(:a)
+ params2 = ActionController::Parameters.new(a: 1, b: 2).permit(:a)
+ assert(params1 == params2)
+ end
+
+ test "is equal to Parameters instance with same different source params, but same permitted params" do
+ params1 = ActionController::Parameters.new(a: 1, b: 2).permit(:a)
+ params2 = ActionController::Parameters.new(a: 1, c: 3).permit(:a)
+ assert(params1 == params2)
+ assert(params2 == params1)
+ end
+
+ test 'is not equal to an unpermitted Parameters instance with same params' do
+ params1 = ActionController::Parameters.new(a: 1).permit(:a)
+ params2 = ActionController::Parameters.new(a: 1)
+ assert(params1 != params2)
+ assert(params2 != params1)
+ end
+
+ test "is not equal to Parameters instance with different permitted params" do
+ params1 = ActionController::Parameters.new(a: 1, b: 2).permit(:a, :b)
+ params2 = ActionController::Parameters.new(a: 1, b: 2).permit(:a)
+ assert(params1 != params2)
+ assert(params2 != params1)
+ end
+
+ test "equality with simple types works" do
+ assert(@params != 'Hello')
+ assert(@params != 42)
+ assert(@params != false)
+ end
+
+ test "inspect shows both class name, parameters and permitted flag" do
+ assert_equal(
+ '<ActionController::Parameters {"person"=>{"age"=>"32", '\
+ '"name"=>{"first"=>"David", "last"=>"Heinemeier Hansson"}, ' \
+ '"addresses"=>[{"city"=>"Chicago", "state"=>"Illinois"}]}} permitted: false>',
+ @params.inspect
+ )
+ end
+
+ test "inspect prints updated permitted flag in the output" do
+ assert_match(/permitted: false/, @params.inspect)
+
+ @params.permit!
+
+ assert_match(/permitted: true/, @params.inspect)
+ end
+
+ if Hash.method_defined?(:dig)
+ test "#dig delegates the dig method to its values" do
+ assert_equal "David", @params.dig(:person, :name, :first)
+ assert_equal "Chicago", @params.dig(:person, :addresses, 0, :city)
+ end
+
+ test "#dig converts hashes to parameters" do
+ assert_kind_of ActionController::Parameters, @params.dig(:person)
+ assert_kind_of ActionController::Parameters, @params.dig(:person, :addresses, 0)
+ assert @params.dig(:person, :addresses).all? do |value|
+ value.is_a?(ActionController::Parameters)
+ end
+ end
+ else
+ test "ActionController::Parameters does not respond to #dig on Ruby 2.2" do
+ assert_not ActionController::Parameters.method_defined?(:dig)
+ assert_not @params.respond_to?(:dig)
+ 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 efaf8a96c3..c5bfb10b53 100644
--- a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb
+++ b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb
@@ -12,12 +12,6 @@ class AlwaysPermittedParametersTest < ActiveSupport::TestCase
ActionController::Parameters.always_permitted_parameters = %w( controller action )
end
- test "shows deprecations warning on NEVER_UNPERMITTED_PARAMS" do
- assert_deprecated do
- 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
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 3299f2d9d0..b75eb0e3bf 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -27,6 +27,27 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
end
+ def walk_permitted params
+ params.each do |k,v|
+ case v
+ when ActionController::Parameters
+ walk_permitted v
+ when Array
+ v.each { |x| walk_permitted v }
+ end
+ end
+ end
+
+ test 'iteration should not impact permit' do
+ hash = {"foo"=>{"bar"=>{"0"=>{"baz"=>"hello", "zot"=>"1"}}}}
+ params = ActionController::Parameters.new(hash)
+
+ walk_permitted params
+
+ sanitized = params[:foo].permit(bar: [:baz])
+ assert_equal({"0"=>{"baz"=>"hello"}}, sanitized[:bar].to_unsafe_h)
+ end
+
test 'if nothing is permitted, the hash becomes empty' do
params = ActionController::Parameters.new(id: '1234')
permitted = params.permit
@@ -339,4 +360,13 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert @params.include? 'person'
assert_not @params.include? :gorilla
end
+
+ test "scalar values should be filtered when array or hash is specified" do
+ params = ActionController::Parameters.new(foo: "bar")
+
+ assert params.permit(:foo).has_key?(:foo)
+ refute params.permit(foo: []).has_key?(:foo)
+ refute params.permit(foo: [:bar]).has_key?(:foo)
+ refute params.permit(foo: :bar).has_key?(:foo)
+ end
end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 0b184eace9..4af3c628d0 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -1,5 +1,23 @@
require 'abstract_unit'
+class Workshop
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ attr_accessor :id
+
+ def initialize(id)
+ @id = id
+ end
+
+ def persisted?
+ id.present?
+ end
+
+ def to_s
+ id.to_s
+ end
+end
+
class RedirectController < ActionController::Base
# empty method not used anywhere to ensure methods like
# `status` and `location` aren't called on `redirect_to` calls
@@ -176,7 +194,6 @@ class RedirectTest < ActionController::TestCase
assert_equal "http://www.example.com", redirect_to_url
end
-
def test_relative_url_redirect_with_status
get :relative_url_redirect_with_status
assert_response 302
@@ -286,7 +303,10 @@ class RedirectTest < ActionController::TestCase
with_routing do |set|
set.draw do
resources :workshops
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :redirect_to_existing_record
@@ -310,7 +330,7 @@ class RedirectTest < ActionController::TestCase
error = assert_raise(ArgumentError) do
get :redirect_to_params
end
- assert_equal "Generating an URL from non sanitized request parameters is insecure!", error.message
+ assert_equal ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE, error.message
end
def test_redirect_to_with_block
@@ -328,7 +348,9 @@ class RedirectTest < ActionController::TestCase
def test_redirect_to_with_block_and_accepted_options
with_routing do |set|
set.draw do
- get ':controller/:action'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :redirect_to_with_block_and_options
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index d1b9586533..8d3134630d 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -16,6 +16,10 @@ class TestControllerWithExtraEtags < ActionController::Base
render plain: "stale" if stale?(etag: %w(1 2 3), template: false)
end
+ def strong
+ render plain: "stale" if stale?(strong_etag: 'strong')
+ end
+
def with_template
if stale? template: 'test/hello_world'
render plain: 'stale'
@@ -26,6 +30,9 @@ end
class ImplicitRenderTestController < ActionController::Base
def empty_action
end
+
+ def empty_action_with_template
+ end
end
class TestController < ActionController::Base
@@ -270,6 +277,18 @@ class ExpiresInRenderTest < ActionController::TestCase
response.body
end
+ def test_dynamic_render_with_absolute_path
+ file = Tempfile.new('name')
+ file.write "secrets!"
+ file.flush
+ assert_raises ActionView::MissingTemplate do
+ get :dynamic_render, params: { id: file.path }
+ end
+ ensure
+ file.close
+ file.unlink
+ end
+
def test_dynamic_render
assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb'))
assert_raises ActionView::MissingTemplate do
@@ -370,7 +389,7 @@ class LastModifiedRenderTest < ActionController::TestCase
def test_request_not_modified_but_etag_differs
@request.if_modified_since = @last_modified
- @request.if_none_match = "234"
+ @request.if_none_match = '"234"'
get :conditional_hello
assert_response :success
end
@@ -399,7 +418,7 @@ class LastModifiedRenderTest < ActionController::TestCase
def test_request_not_modified_but_etag_differs_with_record
@request.if_modified_since = @last_modified
- @request.if_none_match = "234"
+ @request.if_none_match = '"234"'
get :conditional_hello_with_record
assert_response :success
end
@@ -427,7 +446,7 @@ class LastModifiedRenderTest < ActionController::TestCase
def test_request_not_modified_but_etag_differs_with_collection_of_records
@request.if_modified_since = @last_modified
- @request.if_none_match = "234"
+ @request.if_none_match = '"234"'
get :conditional_hello_with_collection_of_records
assert_response :success
end
@@ -462,8 +481,26 @@ end
class EtagRenderTest < ActionController::TestCase
tests TestControllerWithExtraEtags
+ def test_strong_etag
+ @request.if_none_match = strong_etag(['strong', 'ab', :cde, [:f]])
+ get :strong
+ assert_response :not_modified
+
+ @request.if_none_match = '*'
+ get :strong
+ assert_response :not_modified
+
+ @request.if_none_match = '"strong"'
+ get :strong
+ assert_response :ok
+
+ @request.if_none_match = weak_etag(['strong', 'ab', :cde, [:f]])
+ get :strong
+ assert_response :ok
+ end
+
def test_multiple_etags
- @request.if_none_match = etag(["123", 'ab', :cde, [:f]])
+ @request.if_none_match = weak_etag(["123", 'ab', :cde, [:f]])
get :fresh
assert_response :not_modified
@@ -473,7 +510,7 @@ class EtagRenderTest < ActionController::TestCase
end
def test_array
- @request.if_none_match = etag([%w(1 2 3), 'ab', :cde, [:f]])
+ @request.if_none_match = weak_etag([%w(1 2 3), 'ab', :cde, [:f]])
get :array
assert_response :not_modified
@@ -497,7 +534,7 @@ class EtagRenderTest < ActionController::TestCase
begin
File.write path, 'foo'
- ActionView::Digestor.cache.clear
+ ActionView::LookupContext::DetailsKey.clear
request.if_none_match = etag
get :with_template
@@ -508,9 +545,14 @@ class EtagRenderTest < ActionController::TestCase
end
end
- def etag(record)
- %(W/"#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record))}")
- end
+ private
+ def weak_etag(record)
+ "W/#{strong_etag record}"
+ end
+
+ def strong_etag(record)
+ %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record))}")
+ end
end
class MetalRenderTest < ActionController::TestCase
@@ -522,13 +564,38 @@ class MetalRenderTest < ActionController::TestCase
end
end
+class ActionControllerBaseRenderTest < ActionController::TestCase
+ def test_direct_render_to_string
+ ac = ActionController::Base.new()
+ assert_equal "Hello world!", ac.render_to_string(template: 'test/hello_world')
+ end
+end
+
class ImplicitRenderTest < ActionController::TestCase
tests ImplicitRenderTestController
- def test_implicit_no_content_response
- get :empty_action
+ def test_implicit_no_content_response_as_browser
+ assert_raises(ActionController::UnknownFormat) do
+ get :empty_action
+ end
+ end
+
+ def test_implicit_no_content_response_as_xhr
+ get :empty_action, xhr: true
assert_response :no_content
end
+
+ def test_implicit_success_response_with_right_format
+ get :empty_action_with_template
+ assert_equal "<h1>Empty action rendered this implicitly.</h1>\n", @response.body
+ assert_response :success
+ end
+
+ def test_implicit_unknown_format_response
+ assert_raises(ActionController::UnknownFormat) do
+ get :empty_action_with_template, format: 'json'
+ end
+ end
end
class HeadRenderTest < ActionController::TestCase
@@ -582,7 +649,10 @@ class HeadRenderTest < ActionController::TestCase
with_routing do |set|
set.draw do
resources :customers
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :head_with_location_object
@@ -667,7 +737,7 @@ end
class HttpCacheForeverTest < ActionController::TestCase
class HttpCacheForeverController < ActionController::Base
def cache_me_forever
- http_cache_forever(public: params[:public], version: params[:version] || 'v1') do
+ http_cache_forever(public: params[:public]) do
render plain: 'hello'
end
end
@@ -677,20 +747,24 @@ class HttpCacheForeverTest < ActionController::TestCase
def test_cache_with_public
get :cache_me_forever, params: {public: true}
+ assert_response :ok
assert_equal "max-age=#{100.years}, public", @response.headers["Cache-Control"]
assert_not_nil @response.etag
+ assert @response.weak_etag?
end
def test_cache_with_private
get :cache_me_forever
+ assert_response :ok
assert_equal "max-age=#{100.years}, private", @response.headers["Cache-Control"]
assert_not_nil @response.etag
- assert_response :success
+ assert @response.weak_etag?
end
def test_cache_response_code_with_if_modified_since
get :cache_me_forever
- assert_response :success
+ assert_response :ok
+
@request.if_modified_since = @response.headers['Last-Modified']
get :cache_me_forever
assert_response :not_modified
@@ -698,21 +772,10 @@ class HttpCacheForeverTest < ActionController::TestCase
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
+ assert_response :ok
- 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'}
+ get :cache_me_forever
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 f0fd7ddc5e..137236c496 100644
--- a/actionpack/test/controller/render_xml_test.rb
+++ b/actionpack/test/controller/render_xml_test.rb
@@ -72,7 +72,10 @@ class RenderXmlTest < ActionController::TestCase
with_routing do |set|
set.draw do
resources :customers
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :render_with_object_location
diff --git a/actionpack/test/controller/renderer_test.rb b/actionpack/test/controller/renderer_test.rb
index 16d24fa82a..372c09bc23 100644
--- a/actionpack/test/controller/renderer_test.rb
+++ b/actionpack/test/controller/renderer_test.rb
@@ -87,6 +87,14 @@ class RendererTest < ActiveSupport::TestCase
assert_equal "<p>1\n<br />2</p>", render[inline: '<%= simple_format "1\n2" %>']
end
+ test 'rendering with user specified defaults' do
+ ApplicationController.renderer.defaults.merge!({ hello: 'hello', https: true })
+ renderer = ApplicationController.renderer.new
+ content = renderer.render inline: '<%= request.ssl? %>'
+
+ assert_equal 'true', content
+ end
+
private
def render
@render ||= ApplicationController.renderer.method(:render)
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 1984ad8825..d3f2ec6aa1 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -133,7 +133,11 @@ class PerFormTokensController < ActionController::Base
self.per_form_csrf_tokens = true
def index
- render inline: "<%= form_tag (params[:form_path] || '/per_form_tokens/post_one'), method: (params[:form_method] || :post) %>"
+ render inline: "<%= form_tag (params[:form_path] || '/per_form_tokens/post_one'), method: params[:form_method] %>"
+ end
+
+ def button_to
+ render inline: "<%= button_to 'Button', (params[:form_path] || '/per_form_tokens/post_one'), method: params[:form_method] %>"
end
def post_one
@@ -403,6 +407,37 @@ module RequestForgeryProtectionTests
end
end
+ def test_should_warn_on_not_same_origin_js
+ old_logger = ActionController::Base.logger
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ ActionController::Base.logger = logger
+
+ begin
+ assert_cross_origin_blocked { get :same_origin_js }
+
+ assert_equal 1, logger.logged(:warn).size
+ assert_match(/<script> tag on another site requested protected JavaScript/, logger.logged(:warn).last)
+ ensure
+ ActionController::Base.logger = old_logger
+ end
+ end
+
+ def test_should_not_warn_if_csrf_logging_disabled_and_not_same_origin_js
+ old_logger = ActionController::Base.logger
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ ActionController::Base.logger = logger
+ ActionController::Base.log_warning_on_csrf_failure = false
+
+ begin
+ assert_cross_origin_blocked { get :same_origin_js }
+
+ assert_equal 0, logger.logged(:warn).size
+ ensure
+ ActionController::Base.logger = old_logger
+ ActionController::Base.log_warning_on_csrf_failure = 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
session[:_csrf_token] = @token
@@ -652,15 +687,9 @@ class PerFormTokensControllerTest < ActionController::TestCase
def test_accepts_token_for_correct_path_and_method
get :index
- form_token = nil
- assert_select 'input[name=custom_authenticity_token]' do |elts|
- form_token = elts.first['value']
- assert_not_nil form_token
- end
+ form_token = assert_presence_and_fetch_form_csrf_token
- actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
- expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
- assert_equal expected, actual
+ assert_matches_session_token_on_server form_token
# This is required because PATH_INFO isn't reset between requests.
@request.env['PATH_INFO'] = '/per_form_tokens/post_one'
@@ -673,15 +702,9 @@ class PerFormTokensControllerTest < ActionController::TestCase
def test_rejects_token_for_incorrect_path
get :index
- form_token = nil
- assert_select 'input[name=custom_authenticity_token]' do |elts|
- form_token = elts.first['value']
- assert_not_nil form_token
- end
+ form_token = assert_presence_and_fetch_form_csrf_token
- actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
- expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
- assert_equal expected, actual
+ assert_matches_session_token_on_server form_token
# This is required because PATH_INFO isn't reset between requests.
@request.env['PATH_INFO'] = '/per_form_tokens/post_two'
@@ -693,15 +716,9 @@ class PerFormTokensControllerTest < ActionController::TestCase
def test_rejects_token_for_incorrect_method
get :index
- form_token = nil
- assert_select 'input[name=custom_authenticity_token]' do |elts|
- form_token = elts.first['value']
- assert_not_nil form_token
- end
+ form_token = assert_presence_and_fetch_form_csrf_token
- actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
- expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
- assert_equal expected, actual
+ assert_matches_session_token_on_server form_token
# This is required because PATH_INFO isn't reset between requests.
@request.env['PATH_INFO'] = '/per_form_tokens/post_one'
@@ -710,6 +727,50 @@ class PerFormTokensControllerTest < ActionController::TestCase
end
end
+ def test_rejects_token_for_incorrect_method_button_to
+ get :button_to, params: { form_method: 'delete' }
+
+ form_token = assert_presence_and_fetch_form_csrf_token
+
+ assert_matches_session_token_on_server form_token, 'delete'
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one'
+ assert_raises(ActionController::InvalidAuthenticityToken) do
+ patch :post_one, params: { custom_authenticity_token: form_token }
+ end
+ end
+
+ test "Accepts proper token for implicit post method on button_to tag" do
+ get :button_to
+
+ form_token = assert_presence_and_fetch_form_csrf_token
+
+ assert_matches_session_token_on_server form_token, 'post'
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one'
+ assert_nothing_raised do
+ post :post_one, params: { custom_authenticity_token: form_token }
+ end
+ end
+
+ %w{delete post patch}.each do |verb|
+ test "Accepts proper token for #{verb} method on button_to tag" do
+ get :button_to, params: { form_method: verb }
+
+ form_token = assert_presence_and_fetch_form_csrf_token
+
+ assert_matches_session_token_on_server form_token, verb
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one'
+ assert_nothing_raised do
+ send verb, :post_one, params: { custom_authenticity_token: form_token }
+ end
+ end
+ end
+
def test_accepts_global_csrf_token
get :index
@@ -726,15 +787,9 @@ class PerFormTokensControllerTest < ActionController::TestCase
def test_ignores_params
get :index, params: {form_path: '/per_form_tokens/post_one?foo=bar'}
- form_token = nil
- assert_select 'input[name=custom_authenticity_token]' do |elts|
- form_token = elts.first['value']
- assert_not_nil form_token
- end
+ form_token = assert_presence_and_fetch_form_csrf_token
- actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
- expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
- assert_equal expected, actual
+ assert_matches_session_token_on_server form_token
# This is required because PATH_INFO isn't reset between requests.
@request.env['PATH_INFO'] = '/per_form_tokens/post_one?foo=baz'
@@ -747,11 +802,20 @@ class PerFormTokensControllerTest < ActionController::TestCase
def test_ignores_trailing_slash_during_generation
get :index, params: {form_path: '/per_form_tokens/post_one/'}
- form_token = nil
- assert_select 'input[name=custom_authenticity_token]' do |elts|
- form_token = elts.first['value']
- assert_not_nil form_token
+ form_token = assert_presence_and_fetch_form_csrf_token
+
+ # This is required because PATH_INFO isn't reset between requests.
+ @request.env['PATH_INFO'] = '/per_form_tokens/post_one'
+ assert_nothing_raised do
+ post :post_one, params: {custom_authenticity_token: form_token}
end
+ assert_response :success
+ end
+
+ def test_ignores_origin_during_generation
+ get :index, params: {form_path: 'https://example.com/per_form_tokens/post_one/'}
+
+ form_token = assert_presence_and_fetch_form_csrf_token
# This is required because PATH_INFO isn't reset between requests.
@request.env['PATH_INFO'] = '/per_form_tokens/post_one'
@@ -764,11 +828,7 @@ class PerFormTokensControllerTest < ActionController::TestCase
def test_ignores_trailing_slash_during_validation
get :index
- form_token = nil
- assert_select 'input[name=custom_authenticity_token]' do |elts|
- form_token = elts.first['value']
- assert_not_nil form_token
- end
+ form_token = assert_presence_and_fetch_form_csrf_token
# This is required because PATH_INFO isn't reset between requests.
@request.env['PATH_INFO'] = '/per_form_tokens/post_one/'
@@ -781,12 +841,7 @@ class PerFormTokensControllerTest < ActionController::TestCase
def test_method_is_case_insensitive
get :index, params: {form_method: "POST"}
- form_token = nil
- assert_select 'input[name=custom_authenticity_token]' do |elts|
- form_token = elts.first['value']
- assert_not_nil form_token
- end
-
+ form_token = assert_presence_and_fetch_form_csrf_token
# This is required because PATH_INFO isn't reset between requests.
@request.env['PATH_INFO'] = '/per_form_tokens/post_one/'
assert_nothing_raised do
@@ -794,4 +849,19 @@ class PerFormTokensControllerTest < ActionController::TestCase
end
assert_response :success
end
+
+ private
+ def assert_presence_and_fetch_form_csrf_token
+ assert_select 'input[name="custom_authenticity_token"]' do |input|
+ form_csrf_token = input.first['value']
+ assert_not_nil form_csrf_token
+ return form_csrf_token
+ end
+ end
+
+ def assert_matches_session_token_on_server(form_token, method = 'post')
+ actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
+ expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', method)
+ assert_equal expected, actual
+ end
end
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index f42bef883f..c088e5a043 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -131,20 +131,22 @@ class RescueController < ActionController::Base
def missing_template
end
- def io_error_in_view
- begin
- raise IOError.new('this is io error')
- rescue
- raise ActionView::TemplateError.new(nil)
- end
+ def exception_with_more_specific_handler_for_wrapper
+ raise RecordInvalid
+ rescue
+ raise NotAuthorized
end
- def zero_division_error_in_view
- begin
- raise ZeroDivisionError.new('this is zero division error')
- rescue
- raise ActionView::TemplateError.new(nil)
- end
+ def exception_with_more_specific_handler_for_cause
+ raise NotAuthorized
+ rescue
+ raise RecordInvalid
+ end
+
+ def exception_with_no_handler_for_wrapper
+ raise RecordInvalid
+ rescue
+ raise RangeError
end
protected
@@ -233,17 +235,6 @@ class ControllerInheritanceRescueControllerTest < ActionController::TestCase
end
class RescueControllerTest < ActionController::TestCase
-
- def test_io_error_in_view
- get :io_error_in_view
- assert_equal 'io error', @response.body
- end
-
- def test_zero_division_error_in_view
- get :zero_division_error_in_view
- assert_equal 'action_view templater error', @response.body
- end
-
def test_rescue_handler
get :not_authorized
assert_response :forbidden
@@ -258,7 +249,6 @@ class RescueControllerTest < ActionController::TestCase
get :record_invalid
end
end
-
def test_rescue_handler_with_argument_as_string
assert_called_with @controller, :show_errors, [Exception] do
get :record_invalid_raise_as_string
@@ -296,11 +286,25 @@ class RescueControllerTest < ActionController::TestCase
get :resource_unavailable
assert_equal "RescueController::ResourceUnavailable", @response.body
end
-
def test_block_rescue_handler_with_argument_as_string
get :resource_unavailable_raise_as_string
assert_equal "RescueController::ResourceUnavailableToRescueAsString", @response.body
end
+
+ test 'rescue when wrapper has more specific handler than cause' do
+ get :exception_with_more_specific_handler_for_wrapper
+ assert_response :forbidden
+ end
+
+ test 'rescue when cause has more specific handler than wrapper' do
+ get :exception_with_more_specific_handler_for_cause
+ assert_response :unprocessable_entity
+ end
+
+ test 'rescue when cause has handler, but wrapper doesnt' do
+ get :exception_with_no_handler_for_wrapper
+ assert_response :unprocessable_entity
+ end
end
class RescueTest < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 4490abf7b2..8e38af5025 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -3,8 +3,22 @@ require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/with_options'
require 'active_support/core_ext/array/extract_options'
-class ResourcesTest < ActionController::TestCase
+class AdminController < ResourcesController; end
+class MessagesController < ResourcesController; end
+class ProductsController < ResourcesController; end
+class ThreadsController < ResourcesController; end
+
+module Backoffice
+ class ProductsController < ResourcesController; end
+ class ImagesController < ResourcesController; end
+
+ module Admin
+ class ProductsController < ResourcesController; end
+ class ImagesController < ResourcesController; end
+ end
+end
+class ResourcesTest < ActionController::TestCase
def test_default_restful_routes
with_restful_routing :messages do
assert_simply_restful_for :messages
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index a39fede5b9..03bf8f8295 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -15,7 +15,9 @@ class UriReservedCharactersRoutingTest < ActiveSupport::TestCase
def setup
@set = ActionDispatch::Routing::RouteSet.new
@set.draw do
- get ':controller/:action/:variable/*additional'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:variable/*additional'
+ end
end
safe, unsafe = %w(: @ & = + $ , ;), %w(^ ? # [ ])
@@ -300,7 +302,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_default_setup
- rs.draw { get '/:controller(/:action(/:id))' }
+ rs.draw { ActiveSupport::Deprecation.silence { get '/:controller(/:action(/:id))' } }
assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content"))
assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list"))
assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10"))
@@ -323,7 +325,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_colon_first
rs.draw do
- get '/:controller/:action/:id', action: 'index', id: nil
+ ActiveSupport::Deprecation.silence do
+ get '/:controller/:action/:id', action: 'index', id: nil
+ end
+
get ':url', controller: 'content', action: 'translate'
end
@@ -331,7 +336,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_route_with_regexp_for_action
- rs.draw { get '/:controller/:action', action: /auth[-|_].+/ }
+ rs.draw { ActiveSupport::Deprecation.silence { get '/:controller/:action', action: /auth[-|_].+/ } }
assert_equal({ action: 'auth_google', controller: 'content' }, rs.recognize_path('/content/auth_google'))
assert_equal({ action: 'auth-facebook', controller: 'content' }, rs.recognize_path('/content/auth-facebook'))
@@ -342,8 +347,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_regexp_for_controller
rs.draw do
- get ':controller/:admintoken(/:action(/:id))', :controller => /admin\/.+/
- get '/:controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:admintoken(/:action(/:id))', :controller => /admin\/.+/
+ get '/:controller(/:action(/:id))'
+ end
end
assert_equal({:controller => "admin/user", :admintoken => "foo", :action => "index"},
@@ -357,7 +364,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_regexp_and_captures_for_controller
rs.draw do
- get '/:controller(/:action(/:id))', :controller => /admin\/(accounts|users)/
+ ActiveSupport::Deprecation.silence do
+ get '/:controller(/:action(/:id))', :controller => /admin\/(accounts|users)/
+ end
end
assert_equal({:controller => "admin/accounts", :action => "index"}, rs.recognize_path("/admin/accounts"))
assert_equal({:controller => "admin/users", :action => "index"}, rs.recognize_path("/admin/users"))
@@ -366,11 +375,13 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_regexp_and_dot
rs.draw do
- get ':controller/:action/:file',
- :controller => /admin|user/,
- :action => /upload|download/,
- :defaults => {:file => nil},
- :constraints => {:file => %r{[^/]+(\.[^/]+)?}}
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:file',
+ :controller => /admin|user/,
+ :action => /upload|download/,
+ :defaults => {:file => nil},
+ :constraints => {:file => %r{[^/]+(\.[^/]+)?}}
+ end
end
# Without a file extension
assert_equal '/user/download/file',
@@ -457,7 +468,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_named_route_without_hash
rs.draw do
- get ':controller/:action/:id', :as => 'normal'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id', :as => 'normal'
+ end
end
end
@@ -509,7 +522,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
rs.draw do
get 'page/:year/:month/:day/:title' => 'page#show', :as => 'article',
:year => /\d+/, :month => /\d+/, :day => /\d+/
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
routes = setup_for_named_route
@@ -519,7 +535,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_changing_controller
- rs.draw { get ':controller/:action/:id' }
+ rs.draw { ActiveSupport::Deprecation.silence { get ':controller/:action/:id' } }
get URI('http://test.host/admin/user/index/10')
@@ -530,7 +546,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_paths_escaped
rs.draw do
get 'file/*path' => 'content#show_file', :as => 'path'
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
# No + to space in URI escaping, only for query params.
@@ -555,7 +574,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_non_controllers_cannot_be_matched
rs.draw do
- get ':controller/:action/:id'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_raise(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") }
end
@@ -593,8 +614,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_backwards
rs.draw do
- get 'page/:id(/:action)' => 'pages#show'
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get 'page/:id(/:action)' => 'pages#show'
+ get ':controller(/:action(/:id))'
+ end
end
get URI('http://test.host/pages/show')
@@ -603,10 +626,13 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
assert_equal '/pages/boo', url_for(rs, { :controller => 'pages', :action => 'boo' })
end
- def test_route_with_fixnum_default
+ def test_route_with_integer_default
rs.draw do
get 'page(/:id)' => 'content#show_page', :id => 1
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal '/page', url_for(rs, { :controller => 'content', :action => 'show_page' })
@@ -623,7 +649,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_text_default
rs.draw do
get 'page/:id' => 'content#show_page', :id => 1
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal '/page/foo', url_for(rs, { :controller => 'content', :action => 'show_page', :id => 'foo' })
@@ -638,7 +667,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_action_expiry
- rs.draw { get ':controller(/:action(/:id))' }
+ rs.draw { ActiveSupport::Deprecation.silence { get ':controller(/:action(/:id))' } }
get URI('http://test.host/content/show')
assert_equal '/content', controller.url_for(:controller => 'content', :only_path => true)
end
@@ -661,7 +690,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
:defaults => { :year => nil },
:constraints => { :year => /\d{4}/ }
)
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal '/test', url_for(rs, { :controller => 'post', :action => 'show' })
@@ -673,7 +705,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_set_to_nil_forgets
rs.draw do
get 'pages(/:year(/:month(/:day)))' => 'content#list_pages', :month => nil, :day => nil
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal '/pages/2005',
@@ -720,7 +755,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_named_route_method
rs.draw do
get 'categories' => 'content#categories', :as => 'categories'
- get ':controller(/:action(/:id))'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
assert_equal '/categories', url_for(rs, { :controller => 'content', :action => 'categories' })
@@ -736,7 +774,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
rs.draw do
get 'journal' => 'content#list_journal',
:date => nil, :user_id => nil
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal '/journal', url_for(rs, {
@@ -776,10 +817,12 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_subpath_recognized
rs.draw do
- get '/books/:id/edit' => 'subpath_books#edit'
- get '/items/:id/:action' => 'subpath_books'
- get '/posts/new/:action' => 'subpath_books'
- get '/posts/:id' => 'subpath_books#show'
+ ActiveSupport::Deprecation.silence do
+ get '/books/:id/edit' => 'subpath_books#edit'
+ get '/items/:id/:action' => 'subpath_books'
+ get '/posts/new/:action' => 'subpath_books'
+ get '/posts/:id' => 'subpath_books#show'
+ end
end
hash = rs.recognize_path "/books/17/edit"
@@ -801,9 +844,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_subpath_generated
rs.draw do
- get '/books/:id/edit' => 'subpath_books#edit'
- get '/items/:id/:action' => 'subpath_books'
- get '/posts/new/:action' => 'subpath_books'
+ ActiveSupport::Deprecation.silence do
+ get '/books/:id/edit' => 'subpath_books#edit'
+ get '/items/:id/:action' => 'subpath_books'
+ get '/posts/new/:action' => 'subpath_books'
+ end
end
assert_equal "/books/7/edit", url_for(rs, { :controller => "subpath_books", :id => 7, :action => "edit" })
@@ -827,8 +872,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
get 'ca' => 'ca#aa'
get 'cb' => 'cb#ab'
get 'cc' => 'cc#ac'
- get ':controller/:action/:id'
- get ':controller/:action/:id.:format'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
+ end
end
hash = rs.recognize_path "/cc"
@@ -839,8 +887,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
rs.draw do
get 'cb' => 'cb#ab'
get 'cc' => 'cc#ac'
- get ':controller/:action/:id'
- get ':controller/:action/:id.:format'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
+ end
end
hash = rs.recognize_path "/cc"
@@ -871,29 +922,34 @@ class RouteSetTest < ActiveSupport::TestCase
@default_route_set ||= begin
set = ActionDispatch::Routing::RouteSet.new
set.draw do
- get '/:controller(/:action(/:id))'
+
+ ActiveSupport::Deprecation.silence do
+ get '/:controller(/:action(/:id))'
+ end
end
set
end
end
def test_generate_extras
- set.draw { get ':controller/(:action(/:id))' }
+ set.draw { ActiveSupport::Deprecation.silence { 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(&:to_s).sort
end
def test_extra_keys
- set.draw { get ':controller/:action/:id' }
+ set.draw { ActiveSupport::Deprecation.silence { 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(&:to_s).sort
end
def test_generate_extras_not_first
set.draw do
- get ':controller/:action/:id.:format'
- get ':controller/:action/:id'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
+ end
end
path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal "/foo/bar/15", path
@@ -902,8 +958,10 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_not_first
set.draw do
- get ':controller/:action/:id.:format'
- get ':controller/:action/:id'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
+ end
end
assert_equal "/foo/bar/15?this=hello",
url_for(set, { :controller => "foo", :action => "bar", :id => 15, :this => "hello" })
@@ -911,8 +969,10 @@ class RouteSetTest < ActiveSupport::TestCase
def test_extra_keys_not_first
set.draw do
- get ':controller/:action/:id.:format'
- get ':controller/:action/:id'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
+ end
end
extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal %w(that this), extras.map(&:to_s).sort
@@ -1044,7 +1104,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_draw_default_route
set.draw do
- get '/:controller/:action/:id'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal 1, set.routes.size
@@ -1059,7 +1121,10 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_with_parameter_shell
set.draw do
get 'page/:id' => 'pages#show', :id => /\d+/
- get '/:controller(/:action(/:id))'
+
+ ActiveSupport::Deprecation.silence do
+ get '/:controller(/:action(/:id))'
+ end
end
assert_equal({:controller => 'pages', :action => 'index'}, request_path_params('/pages'))
@@ -1314,7 +1379,9 @@ class RouteSetTest < ActiveSupport::TestCase
@set = make_set false
set.draw do
- get ':controller/:id/:action'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:id/:action'
+ end
end
get URI('http://test.host/people/7/show')
@@ -1327,7 +1394,10 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do
get 'about' => "welcome#about"
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:id/:action'
+ end
end
get URI('http://test.host/welcom/get/7')
@@ -1338,7 +1408,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_generate
- set.draw { get ':controller/:action/:id' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller/:action/:id' } }
args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" }
assert_equal "/foo/bar/7?x=y", url_for(set, args)
@@ -1349,7 +1419,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_path_prefix
set.draw do
scope "my" do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
end
@@ -1360,7 +1432,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_blank_path_prefix
set.draw do
scope "" do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
end
@@ -1372,9 +1446,11 @@ class RouteSetTest < ActiveSupport::TestCase
@set = make_set false
set.draw do
- get "/connection/manage(/:action)" => 'connection/manage#index'
- get "/connection/connection" => "connection/connection#index"
- get '/connection' => 'connection#index', :as => 'family_connection'
+ ActiveSupport::Deprecation.silence do
+ get "/connection/manage(/:action)" => 'connection/manage#index'
+ get "/connection/connection" => "connection/connection#index"
+ get '/connection' => 'connection#index', :as => 'family_connection'
+ end
end
assert_equal({ :controller => 'connection/manage',
@@ -1392,7 +1468,9 @@ class RouteSetTest < ActiveSupport::TestCase
@set = make_set false
set.draw do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
get URI('http://test.host/books/show/10')
@@ -1407,7 +1485,10 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do
get 'show_weblog/:parameter' => 'weblog#show'
- get ':controller(/:action(/:id))'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
get URI('http://test.host/weblog/show/1')
@@ -1435,7 +1516,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_expiry_determination_should_consider_values_with_to_param
@set = make_set false
- set.draw { get 'projects/:project_id/:controller/:action' }
+ set.draw { ActiveSupport::Deprecation.silence { get 'projects/:project_id/:controller/:action' } }
get URI('http://test.host/projects/1/weblog/show')
@@ -1612,7 +1693,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_assign_route_options_with_anchor_chars
set.draw do
- get '/cars/:action/:person/:car/', :controller => 'cars'
+ ActiveSupport::Deprecation.silence do
+ get '/cars/:action/:person/:car/', :controller => 'cars'
+ end
end
assert_equal '/cars/buy/1/2', url_for(set, { :controller => 'cars', :action => 'buy', :person => '1', :car => '2' })
@@ -1622,7 +1705,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_segmentation_of_dot_path
set.draw do
- get '/books/:action.rss', :controller => 'books'
+ ActiveSupport::Deprecation.silence do
+ get '/books/:action.rss', :controller => 'books'
+ end
end
assert_equal '/books/list.rss', url_for(set, { :controller => 'books', :action => 'list' })
@@ -1632,7 +1717,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_segmentation_of_dynamic_dot_path
set.draw do
- get '/books(/:action(.:format))', :controller => 'books'
+ ActiveSupport::Deprecation.silence do
+ get '/books(/:action(.:format))', :controller => 'books'
+ end
end
assert_equal '/books/list.rss', url_for(set, { :controller => 'books', :action => 'list', :format => 'rss' })
@@ -1647,7 +1734,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_slashes_are_implied
- set.draw { get("/:controller(/:action(/:id))") }
+ set.draw { ActiveSupport::Deprecation.silence { get("/:controller(/:action(/:id))") } }
assert_equal '/content', url_for(set, { :controller => 'content', :action => 'index' })
assert_equal '/content/list', url_for(set, { :controller => 'content', :action => 'list' })
@@ -1738,7 +1825,9 @@ class RouteSetTest < ActiveSupport::TestCase
:constraints => { :page => /\d+/ },
:defaults => { :page => 1 }
- get ':controller/:action/:id'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal '/ibocorp', url_for(set, { :controller => 'ibocorp', :action => "show", :page => 1 })
@@ -1761,7 +1850,11 @@ class RouteSetTest < ActiveSupport::TestCase
:day => nil, :month => nil
get "blog/show/:id", :controller => "blog", :action => "show", :id => /\d+/
- get "blog/:controller/:action(/:id)"
+
+ ActiveSupport::Deprecation.silence do
+ get "blog/:controller/:action(/:id)"
+ end
+
get "*anything", :controller => "blog", :action => "unknown_request"
end
@@ -1850,13 +1943,20 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
get 'news(.:format)' => "news#index"
- get 'comment/:id(/:action)' => "comments#show"
- get 'ws/:controller(/:action(/:id))', :ws => true
- get 'account(/:action)' => "account#subscription"
- get 'pages/:page_id/:controller(/:action(/:id))'
- get ':controller/ping', :action => 'ping'
+ ActiveSupport::Deprecation.silence do
+ get 'comment/:id(/:action)' => "comments#show"
+ get 'ws/:controller(/:action(/:id))', :ws => true
+ get 'account(/:action)' => "account#subscription"
+ get 'pages/:page_id/:controller(/:action(/:id))'
+ get ':controller/ping', :action => 'ping'
+ end
+
get 'こんにちは/世界', :controller => 'news', :action => 'index'
- match ':controller(/:action(/:id))(.:format)', :via => :all
+
+ ActiveSupport::Deprecation.silence do
+ match ':controller(/:action(/:id))(.:format)', :via => :all
+ end
+
root :to => "news#index"
}
@@ -1964,11 +2064,11 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
def test_extras
params = {:controller => 'people'}
assert_equal [], @routes.extra_keys(params)
- assert_equal({:controller => 'people'}, params)
+ assert_equal({:controller => 'people', :action => 'index'}, params)
params = {:controller => 'people', :foo => 'bar'}
assert_equal [:foo], @routes.extra_keys(params)
- assert_equal({:controller => 'people', :foo => 'bar'}, params)
+ assert_equal({:controller => 'people', :action => 'index', :foo => 'bar'}, params)
params = {:controller => 'people', :action => 'create', :person => { :name => 'Josh'}}
assert_equal [:person], @routes.extra_keys(params)
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 2820425c31..9df70dacbf 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -11,6 +11,8 @@ class SendFileController < ActionController::Base
include ActionController::Testing
layout "layouts/standard" # to make sure layouts don't interfere
+ before_action :file, only: :file_from_before_action
+
attr_writer :options
def options
@options ||= {}
@@ -20,6 +22,10 @@ class SendFileController < ActionController::Base
send_file(file_path, options)
end
+ def file_from_before_action
+ raise 'No file sent from before action.'
+ end
+
def test_send_file_headers_bang
options = {
:type => Mime[:png],
@@ -192,6 +198,15 @@ class SendFileTest < ActionController::TestCase
assert_nil @controller.headers['Content-Disposition']
end
+ def test_send_file_from_before_action
+ response = nil
+ assert_nothing_raised { response = process('file_from_before_action') }
+ assert_not_nil response
+
+ assert_kind_of String, response.body
+ assert_equal file_data, response.body
+ end
+
%w(file data).each do |method|
define_method "test_send_#{method}_status" do
@controller.options = { :stream => false, :status => 500 }
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index b9caddcdb7..ea59156f65 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -137,6 +137,10 @@ XML
head :created, location: 'created resource'
end
+ def render_cookie
+ render plain: cookies["foo"]
+ end
+
def delete_cookie
cookies.delete("foo")
render plain: 'ok'
@@ -150,6 +154,10 @@ XML
render html: '<body class="foo"></body>'.html_safe
end
+ def boom
+ raise 'boom!'
+ end
+
private
def generate_url(opts)
@@ -163,7 +171,9 @@ XML
@request.delete_header 'PATH_INFO'
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
end
end
@@ -547,7 +557,7 @@ XML
assert_equal 'created', flash[:notice]
end
- def test_params_passing_with_fixnums
+ def test_params_passing_with_integer
get :test_params, params: {
page: { name: "Page name", month: 4, year: 2004, day: 6 }
}
@@ -559,7 +569,7 @@ XML
)
end
- def test_params_passing_with_fixnums_when_not_html_request
+ def test_params_passing_with_integers_when_not_html_request
get :test_params, params: { format: 'json', count: 999 }
parsed_params = ::JSON.parse(@response.body)
assert_equal(
@@ -668,7 +678,10 @@ XML
with_routing do |set|
set.draw do
get 'file/*path', to: 'test_case_test/test#test_params'
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :test_params, params: { path: ['hello', 'world'] }
@@ -829,6 +842,12 @@ XML
assert_equal 'bar', cookies['foo']
end
+ def test_cookies_should_be_escaped_properly
+ cookies['foo'] = '+'
+ get :render_cookie
+ assert_equal '+', @response.body
+ end
+
def test_should_detect_if_cookie_is_deleted
cookies['foo'] = 'bar'
get :delete_cookie
@@ -966,6 +985,26 @@ XML
assert_redirected_to 'created resource'
end
end
+
+ def test_exception_in_action_reaches_test
+ assert_raise(RuntimeError) do
+ process :boom, method: "GET"
+ end
+ end
+
+ def test_request_state_is_cleared_after_exception
+ assert_raise(RuntimeError) do
+ process :boom,
+ method: "GET",
+ params: { q: 'test1' }
+ end
+
+ process :test_query_string,
+ method: "GET",
+ params: { q: 'test2' }
+
+ assert_equal "q=test2", @response.body
+ end
end
class ResponseDefaultHeadersTest < ActionController::TestCase
@@ -998,7 +1037,9 @@ class ResponseDefaultHeadersTest < ActionController::TestCase
@request.env['PATH_INFO'] = nil
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
end
end
@@ -1125,7 +1166,9 @@ class AnonymousControllerTest < ActionController::TestCase
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
end
end
diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb
index dfc2712e3e..a6ca5fc868 100644
--- a/actionpack/test/controller/url_for_integration_test.rb
+++ b/actionpack/test/controller/url_for_integration_test.rb
@@ -52,12 +52,15 @@ module ActionPack
get 'news(.:format)' => "news#index"
- get 'comment/:id(/:action)' => "comments#show"
- get 'ws/:controller(/:action(/:id))', :ws => true
- get 'account(/:action)' => "account#subscription"
- get 'pages/:page_id/:controller(/:action(/:id))'
- get ':controller/ping', :action => 'ping'
- get ':controller(/:action(/:id))(.:format)'
+ ActiveSupport::Deprecation.silence {
+ get 'comment/:id(/:action)' => "comments#show"
+ get 'ws/:controller(/:action(/:id))', :ws => true
+ get 'account(/:action)' => "account#subscription"
+ get 'pages/:page_id/:controller(/:action(/:id))'
+ get ':controller/ping', :action => 'ping'
+ get ':controller(/:action(/:id))(.:format)'
+ }
+
root :to => "news#index"
}
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index 67212fea38..b4d2088c0a 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -4,7 +4,13 @@ module AbstractController
module Testing
class UrlForTest < ActionController::TestCase
class W
- include ActionDispatch::Routing::RouteSet.new.tap { |r| r.draw { get ':controller(/:action(/:id(.:format)))' } }.url_helpers
+ include ActionDispatch::Routing::RouteSet.new.tap { |r|
+ r.draw {
+ ActiveSupport::Deprecation.silence {
+ get ':controller(/:action(/:id(.:format)))'
+ }
+ }
+ }.url_helpers
end
def teardown
@@ -260,7 +266,7 @@ module AbstractController
w = Class.new {
config = ActionDispatch::Routing::RouteSet::Config.new '/subdir'
r = ActionDispatch::Routing::RouteSet.new(config)
- r.draw { get ':controller(/:action(/:id(.:format)))' }
+ r.draw { ActiveSupport::Deprecation.silence { get ':controller(/:action(/:id(.:format)))' } }
include r.url_helpers
}
add_host!(w)
@@ -315,7 +321,10 @@ module AbstractController
with_routing do |set|
set.draw do
get 'home/sweet/home/:user', :to => 'home#index', :as => :home
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
# We need to create a new class in order to install the new named route.
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index 5f2abc9606..bc0d215530 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -20,7 +20,9 @@ class UrlRewriterTests < ActionController::TestCase
@rewriter = Rewriter.new(@request) #.new(@request, @params)
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
end
end
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index 6d377c4691..daf17558aa 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -99,7 +99,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_parsing_json_doesnot_rescue_exception
req = Class.new(ActionDispatch::Request) do
def params_parsers
- { Mime[:json] => Proc.new { |data| raise Interrupt } }
+ { json: Proc.new { |data| raise Interrupt } }
end
def content_length; get_header('rack.input').length; end
diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb
index 5ba76d9ab9..7b707df7f6 100644
--- a/actionpack/test/dispatch/callbacks_test.rb
+++ b/actionpack/test/dispatch/callbacks_test.rb
@@ -37,13 +37,19 @@ class DispatcherTest < ActiveSupport::TestCase
def test_to_prepare_and_cleanup_delegation
prepared = cleaned = false
- ActionDispatch::Callbacks.to_prepare { prepared = true }
- ActionDispatch::Callbacks.to_prepare { cleaned = true }
+ assert_deprecated do
+ ActionDispatch::Callbacks.to_prepare { prepared = true }
+ ActionDispatch::Callbacks.to_prepare { cleaned = true }
+ end
- ActionDispatch::Reloader.prepare!
+ assert_deprecated do
+ ActionDispatch::Reloader.prepare!
+ end
assert prepared
- ActionDispatch::Reloader.cleanup!
+ assert_deprecated do
+ ActionDispatch::Reloader.cleanup!
+ end
assert cleaned
end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index dfcef14344..c7194cde4a 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -520,7 +520,7 @@ class CookiesTest < ActionController::TestCase
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]
+ assert_nil @controller.send(:cookies).signed[:non_existent_attribute]
end
def test_encrypted_cookie_using_default_serializer
@@ -638,7 +638,7 @@ class CookiesTest < ActionController::TestCase
def test_accessing_nonexistent_encrypted_cookie_should_not_raise_invalid_message
get :set_encrypted_cookie
- assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute]
+ assert_nil @controller.send(:cookies).encrypted[:non_existent_attribute]
end
def test_setting_invalid_encrypted_cookie_should_return_nil_when_accessing_it
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index 159bf10545..5a39db145e 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -27,37 +27,37 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
env['action_dispatch.show_detailed_exceptions'] = @detailed
req = ActionDispatch::Request.new(env)
case req.path
- when "/pass"
+ when %r{/pass}
[404, { "X-Cascade" => "pass" }, self]
- when "/not_found"
+ when %r{/not_found}
raise AbstractController::ActionNotFound
- when "/runtime_error"
+ when %r{/runtime_error}
raise RuntimeError
- when "/method_not_allowed"
+ when %r{/method_not_allowed}
raise ActionController::MethodNotAllowed
- when "/unknown_http_method"
+ when %r{/unknown_http_method}
raise ActionController::UnknownHttpMethod
- when "/not_implemented"
+ when %r{/not_implemented}
raise ActionController::NotImplemented
- when "/unprocessable_entity"
+ when %r{/unprocessable_entity}
raise ActionController::InvalidAuthenticityToken
- when "/not_found_original_exception"
+ when %r{/not_found_original_exception}
begin
raise AbstractController::ActionNotFound.new
rescue
raise ActionView::Template::Error.new('template')
end
- when "/missing_template"
+ when %r{/missing_template}
raise ActionView::MissingTemplate.new(%w(foo), 'foo/index', %w(foo), false, 'mailer')
- when "/bad_request"
+ when %r{/bad_request}
raise ActionController::BadRequest
- when "/missing_keys"
+ when %r{/missing_keys}
raise ActionController::UrlGenerationError, "No route matches"
- when "/parameter_missing"
+ when %r{/parameter_missing}
raise ActionController::ParameterMissing, :missing_param_key
- when "/original_syntax_error"
+ when %r{/original_syntax_error}
eval 'broke_syntax =' # `eval` need for raise native SyntaxError at runtime
- when "/syntax_error_into_view"
+ when %r{/syntax_error_into_view}
begin
eval 'broke_syntax ='
rescue Exception
@@ -67,7 +67,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
{})
raise ActionView::Template::Error.new(template)
end
- when "/framework_raises"
+ when %r{/framework_raises}
method_that_raises
else
raise "puke!"
@@ -75,13 +75,6 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
end
end
- class BoomerAPI < Boomer
- def call(env)
- env['action_dispatch.show_detailed_exceptions'] = @detailed
- raise "puke!"
- end
- end
-
RoutesApp = Struct.new(:routes).new(SharedTestRoutes)
ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false), RoutesApp)
DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp)
@@ -212,61 +205,60 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_match(/ActionController::ParameterMissing/, body)
end
- test "rescue with json error for API request" do
+ test "rescue with JSON error for JSON API request" do
@app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api)
- get "/", headers: { 'action_dispatch.show_exceptions' => true }
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }, as: :json
assert_response 500
assert_no_match(/<header>/, body)
assert_no_match(/<body>/, body)
assert_equal "application/json", response.content_type
assert_match(/RuntimeError: puke/, body)
- get "/not_found", headers: { 'action_dispatch.show_exceptions' => true }
+ get "/not_found", headers: { 'action_dispatch.show_exceptions' => true }, as: :json
assert_response 404
assert_no_match(/<body>/, body)
assert_equal "application/json", response.content_type
assert_match(/#{AbstractController::ActionNotFound.name}/, body)
- get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true }
+ get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true }, as: :json
assert_response 405
assert_no_match(/<body>/, body)
assert_equal "application/json", response.content_type
assert_match(/ActionController::MethodNotAllowed/, body)
- get "/unknown_http_method", headers: { 'action_dispatch.show_exceptions' => true }
+ get "/unknown_http_method", headers: { 'action_dispatch.show_exceptions' => true }, as: :json
assert_response 405
assert_no_match(/<body>/, body)
assert_equal "application/json", response.content_type
assert_match(/ActionController::UnknownHttpMethod/, body)
- get "/bad_request", headers: { 'action_dispatch.show_exceptions' => true }
+ get "/bad_request", headers: { 'action_dispatch.show_exceptions' => true }, as: :json
assert_response 400
assert_no_match(/<body>/, body)
assert_equal "application/json", response.content_type
assert_match(/ActionController::BadRequest/, body)
- get "/parameter_missing", headers: { 'action_dispatch.show_exceptions' => true }
+ get "/parameter_missing", headers: { 'action_dispatch.show_exceptions' => true }, as: :json
assert_response 400
assert_no_match(/<body>/, body)
assert_equal "application/json", response.content_type
assert_match(/ActionController::ParameterMissing/, body)
end
- test "rescue with json on API request returns only allowed formats or json as a fallback" do
+ test "rescue with HTML format for HTML API request" do
@app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api)
- get "/index.json", headers: { 'action_dispatch.show_exceptions' => true }
- assert_response 500
- assert_equal "application/json", response.content_type
- assert_match(/RuntimeError: puke/, body)
-
get "/index.html", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 500
- assert_no_match(/<header>/, body)
- assert_no_match(/<body>/, body)
- assert_equal "application/json", response.content_type
- assert_match(/RuntimeError: puke/, body)
+ assert_match(/<header>/, body)
+ assert_match(/<body>/, body)
+ assert_equal "text/html", response.content_type
+ assert_match(/puke/, body)
+ end
+
+ test "rescue with XML format for XML API requests" do
+ @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api)
get "/index.xml", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 500
@@ -274,6 +266,25 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_match(/RuntimeError: puke/, body)
end
+ test "rescue with JSON format as fallback if API request format is not supported" do
+ begin
+ Mime::Type.register 'text/wibble', :wibble
+
+ ActionDispatch::IntegrationTest.register_encoder(:wibble,
+ param_encoder: -> params { params })
+
+ @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api)
+
+ get "/index", headers: { 'action_dispatch.show_exceptions' => true }, as: :wibble
+ assert_response 500
+ assert_equal "application/json", response.content_type
+ assert_match(/RuntimeError: puke/, body)
+
+ ensure
+ Mime::Type.unregister :wibble
+ end
+ end
+
test "does not show filtered parameters" do
@app = DevelopmentApp
diff --git a/actionpack/test/dispatch/executor_test.rb b/actionpack/test/dispatch/executor_test.rb
new file mode 100644
index 0000000000..28bb232ecd
--- /dev/null
+++ b/actionpack/test/dispatch/executor_test.rb
@@ -0,0 +1,134 @@
+require 'abstract_unit'
+
+class ExecutorTest < ActiveSupport::TestCase
+ class MyBody < Array
+ def initialize(&block)
+ @on_close = block
+ end
+
+ def foo
+ "foo"
+ end
+
+ def bar
+ "bar"
+ end
+
+ def close
+ @on_close.call if @on_close
+ end
+ end
+
+ def test_returned_body_object_always_responds_to_close
+ body = call_and_return_body
+ assert_respond_to body, :close
+ end
+
+ def test_returned_body_object_always_responds_to_close_even_if_called_twice
+ body = call_and_return_body
+ assert_respond_to body, :close
+ body.close
+
+ body = call_and_return_body
+ assert_respond_to body, :close
+ body.close
+ end
+
+ def test_returned_body_object_behaves_like_underlying_object
+ body = call_and_return_body do
+ b = MyBody.new
+ b << "hello"
+ b << "world"
+ [200, { "Content-Type" => "text/html" }, b]
+ end
+ assert_equal 2, body.size
+ assert_equal "hello", body[0]
+ assert_equal "world", body[1]
+ assert_equal "foo", body.foo
+ assert_equal "bar", body.bar
+ end
+
+ def test_it_calls_close_on_underlying_object_when_close_is_called_on_body
+ close_called = false
+ body = call_and_return_body do
+ b = MyBody.new do
+ close_called = true
+ end
+ [200, { "Content-Type" => "text/html" }, b]
+ end
+ body.close
+ assert close_called
+ end
+
+ def test_returned_body_object_responds_to_all_methods_supported_by_underlying_object
+ body = call_and_return_body do
+ [200, { "Content-Type" => "text/html" }, MyBody.new]
+ end
+ assert_respond_to body, :size
+ assert_respond_to body, :each
+ assert_respond_to body, :foo
+ assert_respond_to body, :bar
+ end
+
+ def test_run_callbacks_are_called_before_close
+ running = false
+ executor.to_run { running = true }
+
+ body = call_and_return_body
+ assert running
+
+ running = false
+ body.close
+ assert !running
+ end
+
+ def test_complete_callbacks_are_called_on_close
+ completed = false
+ executor.to_complete { completed = true }
+
+ body = call_and_return_body
+ assert !completed
+
+ body.close
+ assert completed
+ end
+
+ def test_complete_callbacks_are_called_on_exceptions
+ completed = false
+ executor.to_complete { completed = true }
+
+ begin
+ call_and_return_body do
+ raise "error"
+ end
+ rescue
+ end
+
+ assert completed
+ end
+
+ def test_callbacks_execute_in_shared_context
+ result = false
+ executor.to_run { @in_shared_context = true }
+ executor.to_complete { result = @in_shared_context }
+
+ call_and_return_body.close
+ assert result
+ assert !defined?(@in_shared_context) # it's not in the test itself
+ end
+
+ private
+ def call_and_return_body(&block)
+ app = middleware(block || proc { [200, {}, 'response'] })
+ _, _, body = app.call({'rack.input' => StringIO.new('')})
+ body
+ end
+
+ def middleware(inner_app)
+ ActionDispatch::Executor.new(inner_app, executor)
+ end
+
+ def executor
+ @executor ||= Class.new(ActiveSupport::Executor)
+ end
+end
diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb
index e4475f4233..de57c4be1d 100644
--- a/actionpack/test/dispatch/live_response_test.rb
+++ b/actionpack/test/dispatch/live_response_test.rb
@@ -65,7 +65,7 @@ module ActionController
latch = Concurrent::CountDownLatch.new
t = Thread.new {
- @response.stream.each do
+ @response.each do
latch.count_down
end
}
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index df27e41997..69098326b9 100644
--- a/actionpack/test/dispatch/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -178,6 +178,19 @@ module ActionDispatch
mapper.mount as: "exciting"
end
end
+
+ def test_scope_does_not_destructively_mutate_default_options
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+
+ frozen = { foo: :bar }.freeze
+
+ assert_nothing_raised do
+ mapper.scope(defaults: frozen) do
+ # pass
+ end
+ end
+ end
end
end
end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 149e37bf3d..672b272590 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -49,7 +49,7 @@ class MimeTypeTest < ActiveSupport::TestCase
test "parse application with trailing star" do
accept = "application/*"
- expect = [Mime[:html], Mime[:js], Mime[:xml], Mime[:rss], Mime[:atom], Mime[:yaml], Mime[:url_encoded_form], Mime[:json], Mime[:pdf], Mime[:zip]]
+ expect = [Mime[:html], Mime[:js], Mime[:xml], Mime[:rss], Mime[:atom], Mime[:yaml], Mime[:url_encoded_form], Mime[:json], Mime[:pdf], Mime[:zip], Mime[:gzip]]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index d75e31db62..b8f0ffb64a 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -166,7 +166,7 @@ module TestGenerationPrefix
assert_equal "/pure-awesomeness/blog/posts/1", last_response.body
end
- test "[ENGINE] url_helpers from engine have higher priotity than application's url_helpers" do
+ test "[ENGINE] url_helpers from engine have higher priority than application's url_helpers" do
get "/awesome/blog/conflicting_url"
assert_equal "engine", last_response.body
end
@@ -319,14 +319,14 @@ module TestGenerationPrefix
path = engine_object.polymorphic_url(Post.new, :host => "www.example.com")
assert_equal "http://www.example.com/awesome/blog/posts/1", path
end
-
+
private
def verify_redirect(url, status = 301)
assert_equal status, last_response.status
assert_equal url, last_response.headers["Location"]
assert_equal expected_redirect_body(url), last_response.body
end
-
+
def expected_redirect_body(url)
%(<html><body>You are being <a href="#{url}">redirected</a>.</body></html>)
end
@@ -450,14 +450,14 @@ module TestGenerationPrefix
get "/absolute_custom_redirect"
verify_redirect "http://example.org/foo"
end
-
+
private
def verify_redirect(url, status = 301)
assert_equal status, last_response.status
assert_equal url, last_response.headers["Location"]
assert_equal expected_redirect_body(url), last_response.body
end
-
+
def expected_redirect_body(url)
%(<html><body>You are being <a href="#{url}">redirected</a>.</body></html>)
end
diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb
index 62e8197e20..fe8a4a3a17 100644
--- a/actionpack/test/dispatch/reloader_test.rb
+++ b/actionpack/test/dispatch/reloader_test.rb
@@ -4,15 +4,17 @@ class ReloaderTest < ActiveSupport::TestCase
Reloader = ActionDispatch::Reloader
teardown do
- Reloader.reset_callbacks :prepare
- Reloader.reset_callbacks :cleanup
+ ActiveSupport::Reloader.reset_callbacks :prepare
+ ActiveSupport::Reloader.reset_callbacks :complete
end
def test_prepare_callbacks
a = b = c = nil
- Reloader.to_prepare { |*args| a = b = c = 1 }
- Reloader.to_prepare { |*args| b = c = 2 }
- Reloader.to_prepare { |*args| c = 3 }
+ assert_deprecated do
+ Reloader.to_prepare { |*args| a = b = c = 1 }
+ Reloader.to_prepare { |*args| b = c = 2 }
+ Reloader.to_prepare { |*args| c = 3 }
+ end
# Ensure to_prepare callbacks are not run when defined
assert_nil a || b || c
@@ -60,9 +62,15 @@ class ReloaderTest < ActiveSupport::TestCase
def test_condition_specifies_when_to_reload
i, j = 0, 0, 0, 0
- Reloader.to_prepare { |*args| i += 1 }
- Reloader.to_cleanup { |*args| j += 1 }
- app = Reloader.new(lambda { |env| [200, {}, []] }, lambda { i < 3 })
+ assert_deprecated do
+ Reloader.to_prepare { |*args| i += 1 }
+ Reloader.to_cleanup { |*args| j += 1 }
+ end
+
+ x = Class.new(ActiveSupport::Reloader)
+ x.check = lambda { i < 3 }
+
+ app = Reloader.new(lambda { |env| [200, {}, []] }, x)
5.times do
resp = app.call({})
resp[2].close
@@ -109,7 +117,9 @@ class ReloaderTest < ActiveSupport::TestCase
def test_cleanup_callbacks_are_called_when_body_is_closed
cleaned = false
- Reloader.to_cleanup { cleaned = true }
+ assert_deprecated do
+ Reloader.to_cleanup { cleaned = true }
+ end
body = call_and_return_body
assert !cleaned
@@ -120,7 +130,9 @@ class ReloaderTest < ActiveSupport::TestCase
def test_prepare_callbacks_arent_called_when_body_is_closed
prepared = false
- Reloader.to_prepare { prepared = true }
+ assert_deprecated do
+ Reloader.to_prepare { prepared = true }
+ end
body = call_and_return_body
prepared = false
@@ -131,31 +143,43 @@ class ReloaderTest < ActiveSupport::TestCase
def test_manual_reloading
prepared = cleaned = false
- Reloader.to_prepare { prepared = true }
- Reloader.to_cleanup { cleaned = true }
+ assert_deprecated do
+ Reloader.to_prepare { prepared = true }
+ Reloader.to_cleanup { cleaned = true }
+ end
- Reloader.prepare!
+ assert_deprecated do
+ Reloader.prepare!
+ end
assert prepared
assert !cleaned
prepared = cleaned = false
- Reloader.cleanup!
- assert !prepared
+ assert_deprecated do
+ Reloader.cleanup!
+ end
+ assert prepared
assert cleaned
end
def test_prepend_prepare_callback
i = 10
- Reloader.to_prepare { i += 1 }
- Reloader.to_prepare(:prepend => true) { i = 0 }
+ assert_deprecated do
+ Reloader.to_prepare { i += 1 }
+ Reloader.to_prepare(:prepend => true) { i = 0 }
+ end
- Reloader.prepare!
+ assert_deprecated do
+ Reloader.prepare!
+ end
assert_equal 1, i
end
def test_cleanup_callbacks_are_called_on_exceptions
cleaned = false
- Reloader.to_cleanup { cleaned = true }
+ assert_deprecated do
+ Reloader.to_cleanup { cleaned = true }
+ end
begin
call_and_return_body do
@@ -169,8 +193,11 @@ class ReloaderTest < ActiveSupport::TestCase
private
def call_and_return_body(&block)
+ x = Class.new(ActiveSupport::Reloader)
+ x.check = lambda { true }
+
@response ||= 'response'
- @reloader ||= Reloader.new(block || proc {[200, {}, @response]})
+ @reloader ||= Reloader.new(block || proc {[200, {}, @response]}, x)
@reloader.call({'rack.input' => StringIO.new('')})[2]
end
end
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index a3992ad008..a07138b55e 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -37,9 +37,9 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
)
end
- test "parses json params for application/vnd.api+json" do
+ test "does not parse unregistered media types such as application/vnd.api+json" do
assert_parses(
- {"person" => {"name" => "David"}},
+ {},
"{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/vnd.api+json' }
)
end
@@ -103,7 +103,9 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- post ':action', :to => ::JsonParamsParsingTest::TestController
+ ActiveSupport::Deprecation.silence do
+ post ':action', :to => ::JsonParamsParsingTest::TestController
+ end
end
yield
end
@@ -143,13 +145,6 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
)
end
- test "parses json params for application/vnd.api+json" do
- assert_parses(
- {"user" => {"username" => "sikachu"}, "username" => "sikachu"},
- "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/vnd.api+json' }
- )
- end
-
test "parses json with non-object JSON content" do
assert_parses(
{"user" => {"_json" => "string content" }, "_json" => "string content" },
@@ -157,6 +152,34 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
)
end
+ test "parses json params after custom json mime type registered" do
+ begin
+ Mime::Type.unregister :json
+ Mime::Type.register "application/json", :json, %w(application/vnd.api+json)
+ assert_parses(
+ {"user" => {"username" => "meinac"}, "username" => "meinac"},
+ "{\"username\": \"meinac\"}", { 'CONTENT_TYPE' => 'application/json' }
+ )
+ ensure
+ Mime::Type.unregister :json
+ Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
+ end
+ end
+
+ test "parses json params after custom json mime type registered with synonym" do
+ begin
+ Mime::Type.unregister :json
+ Mime::Type.register "application/json", :json, %w(application/vnd.api+json)
+ assert_parses(
+ {"user" => {"username" => "meinac"}, "username" => "meinac"},
+ "{\"username\": \"meinac\"}", { 'CONTENT_TYPE' => 'application/vnd.api+json' }
+ )
+ ensure
+ Mime::Type.unregister :json
+ Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
+ end
+ end
+
private
def assert_parses(expected, actual, headers = {})
with_test_routing(UsersController) do
@@ -170,7 +193,9 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing(controller)
with_routing do |set|
set.draw do
- post ':action', :to => controller
+ ActiveSupport::Deprecation.silence do
+ post ':action', :to => controller
+ end
end
yield
end
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index b36fbd3c76..bab4413b2a 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -159,7 +159,9 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
test "does not raise EOFError on GET request with multipart content-type" do
with_routing do |set|
set.draw do
- get ':action', controller: 'multipart_params_parsing_test/test'
+ ActiveSupport::Deprecation.silence do
+ get ':action', controller: 'multipart_params_parsing_test/test'
+ end
end
headers = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x" }
get "/parse", headers: headers
@@ -188,7 +190,9 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- post ':action', :controller => 'multipart_params_parsing_test/test'
+ ActiveSupport::Deprecation.silence do
+ post ':action', :controller => 'multipart_params_parsing_test/test'
+ end
end
yield
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index bc6716525e..f04022a544 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -144,7 +144,9 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
test "ambiguous query string returns a bad request" do
with_routing do |set|
set.draw do
- get ':action', :to => ::QueryStringParsingTest::TestController
+ ActiveSupport::Deprecation.silence do
+ get ':action', :to => ::QueryStringParsingTest::TestController
+ end
end
get "/parse", headers: { "QUERY_STRING" => "foo[]=bar&foo[4]=bar" }
@@ -156,7 +158,9 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
def assert_parses(expected, actual)
with_routing do |set|
set.draw do
- get ':action', :to => ::QueryStringParsingTest::TestController
+ ActiveSupport::Deprecation.silence do
+ get ':action', :to => ::QueryStringParsingTest::TestController
+ end
end
@app = self.class.build_app(set) do |middleware|
middleware.use(EarlyParse)
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
index 7dcbcc5c21..e022e7e21e 100644
--- a/actionpack/test/dispatch/request/session_test.rb
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -114,5 +114,31 @@ module ActionDispatch
}.new
end
end
+
+ class SessionIntegrationTest < ActionDispatch::IntegrationTest
+ class MySessionApp
+ def call(env)
+ request = Rack::Request.new(env)
+ request.session['hello'] = 'Hello from MySessionApp!'
+ [ 200, {}, ['Hello from MySessionApp!'] ]
+ end
+ end
+
+ Router = ActionDispatch::Routing::RouteSet.new
+ Router.draw do
+ get '/mysessionapp' => MySessionApp.new
+ end
+
+ def app
+ @app ||= RoutedRackApp.new(Router)
+ end
+
+ def test_session_follows_rack_api_contract_1
+ get '/mysessionapp'
+ assert_response :ok
+ assert_equal 'Hello from MySessionApp!', @response.body
+ assert_equal 'Hello from MySessionApp!', session['hello']
+ end
+ end
end
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 365edf849a..b9f8c52378 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -140,7 +140,9 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- post ':action', to: ::UrlEncodedParamsParsingTest::TestController
+ ActiveSupport::Deprecation.silence do
+ post ':action', to: ::UrlEncodedParamsParsingTest::TestController
+ end
end
yield
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 0edad72fd9..8a5d85ab84 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -358,6 +358,17 @@ class RequestPort < BaseRequestTest
request = stub_request 'HTTP_HOST' => 'www.example.org:8080'
assert_equal ':8080', request.port_string
end
+
+ test "server port" do
+ request = stub_request 'SERVER_PORT' => '8080'
+ assert_equal 8080, request.server_port
+
+ request = stub_request 'SERVER_PORT' => '80'
+ assert_equal 80, request.server_port
+
+ request = stub_request 'SERVER_PORT' => ''
+ assert_equal 0, request.server_port
+ end
end
class RequestPath < BaseRequestTest
@@ -417,6 +428,11 @@ class RequestPath < BaseRequestTest
end
class RequestHost < BaseRequestTest
+ test "host without specifying port" do
+ request = stub_request 'HTTP_HOST' => 'rubyonrails.org'
+ assert_equal "rubyonrails.org", request.host_with_port
+ end
+
test "host with default port" do
request = stub_request 'HTTP_HOST' => 'rubyonrails.org:80'
assert_equal "rubyonrails.org", request.host_with_port
@@ -427,6 +443,21 @@ class RequestHost < BaseRequestTest
assert_equal "rubyonrails.org:81", request.host_with_port
end
+ test "raw without specifying port" do
+ request = stub_request 'HTTP_HOST' => 'rubyonrails.org'
+ assert_equal "rubyonrails.org", request.raw_host_with_port
+ end
+
+ test "raw host with default port" do
+ request = stub_request 'HTTP_HOST' => 'rubyonrails.org:80'
+ assert_equal "rubyonrails.org:80", request.raw_host_with_port
+ end
+
+ test "raw host with non default port" do
+ request = stub_request 'HTTP_HOST' => 'rubyonrails.org:81'
+ assert_equal "rubyonrails.org:81", request.raw_host_with_port
+ end
+
test "proxy request" do
request = stub_request 'HTTP_HOST' => 'glu.ttono.us:80'
assert_equal "glu.ttono.us", request.host_with_port
@@ -1152,36 +1183,41 @@ class RequestParameterFilter < BaseRequestTest
end
class RequestEtag < BaseRequestTest
- test "if_none_match_etags none" do
+ test "always matches *" do
+ request = stub_request('HTTP_IF_NONE_MATCH' => '*')
+
+ assert_equal '*', request.if_none_match
+ assert_equal ['*'], request.if_none_match_etags
+
+ assert request.etag_matches?('"strong"')
+ assert request.etag_matches?('W/"weak"')
+ assert_not request.etag_matches?(nil)
+ end
+
+ test "doesn't match absent If-None-Match" do
request = stub_request
assert_equal nil, request.if_none_match
assert_equal [], request.if_none_match_etags
- assert !request.etag_matches?("foo")
- assert !request.etag_matches?(nil)
- end
-
- test "if_none_match_etags single" do
- header = 'the-etag'
- request = stub_request('HTTP_IF_NONE_MATCH' => header)
- assert_equal header, request.if_none_match
- assert_equal [header], request.if_none_match_etags
- assert request.etag_matches?("the-etag")
+ assert_not request.etag_matches?("foo")
+ assert_not request.etag_matches?(nil)
end
- test "if_none_match_etags quoted single" do
+ test "matches opaque ETag validators without unquoting" do
header = '"the-etag"'
request = stub_request('HTTP_IF_NONE_MATCH' => header)
assert_equal header, request.if_none_match
- assert_equal ['the-etag'], request.if_none_match_etags
- assert request.etag_matches?("the-etag")
+ assert_equal ['"the-etag"'], request.if_none_match_etags
+
+ assert request.etag_matches?('"the-etag"')
+ assert_not request.etag_matches?("the-etag")
end
test "if_none_match_etags multiple" do
header = 'etag1, etag2, "third etag", "etag4"'
- expected = ['etag1', 'etag2', 'third etag', 'etag4']
+ expected = ['etag1', 'etag2', '"third etag"', '"etag4"']
request = stub_request('HTTP_IF_NONE_MATCH' => header)
assert_equal header, request.if_none_match
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 8b3849cb7a..aa90433505 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -37,6 +37,39 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal "closed stream", e.message
end
+ def test_each_isnt_called_if_str_body_is_written
+ # Controller writes and reads response body
+ each_counter = 0
+ @response.body = Object.new.tap {|o| o.singleton_class.send(:define_method, :each) { |&block| each_counter += 1; block.call 'foo' } }
+ @response['X-Foo'] = @response.body
+
+ assert_equal 1, each_counter, "#each was not called once"
+
+ # Build response
+ status, headers, body = @response.to_a
+
+ assert_equal 200, status
+ assert_equal "foo", headers['X-Foo']
+ assert_equal "foo", body.each.to_a.join
+
+ # Show that #each was not called twice
+ assert_equal 1, each_counter, "#each was not called once"
+ end
+
+ def test_set_header_after_read_body_during_action
+ @response.body
+
+ # set header after the action reads back @response.body
+ @response['x-header'] = "Best of all possible worlds."
+
+ # the response can be built.
+ status, headers, body = @response.to_a
+ assert_equal 200, status
+ assert_equal "", body.body
+
+ assert_equal "Best of all possible worlds.", headers['x-header']
+ end
+
def test_read_body_during_action
@response.body = "Hello, World!"
@@ -189,7 +222,7 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal({"user_name" => "david", "login" => nil}, @response.cookies)
end
- test "read cache control" do
+ test "read ETag and Cache-Control" do
resp = ActionDispatch::Response.new.tap { |response|
response.cache_control[:public] = true
response.etag = '123'
@@ -197,6 +230,9 @@ class ResponseTest < ActiveSupport::TestCase
}
resp.to_a
+ assert resp.etag?
+ assert resp.weak_etag?
+ assert_not resp.strong_etag?
assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.etag)
assert_equal({:public => true}, resp.cache_control)
@@ -204,6 +240,20 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.headers['ETag'])
end
+ test "read strong ETag" do
+ resp = ActionDispatch::Response.new.tap { |response|
+ response.cache_control[:public] = true
+ response.strong_etag = '123'
+ response.body = 'Hello'
+ }
+ resp.to_a
+
+ assert resp.etag?
+ assert_not resp.weak_etag?
+ assert resp.strong_etag?
+ assert_equal('"202cb962ac59075b964b07152d234b70"', resp.etag)
+ end
+
test "read charset and content type" do
resp = ActionDispatch::Response.new.tap { |response|
response.charset = 'utf-16'
@@ -445,4 +495,20 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type'])
end
+
+ test "strong ETag validator" do
+ @app = lambda { |env|
+ ActionDispatch::Response.new.tap { |resp|
+ resp.strong_etag = '123'
+ resp.body = 'Hello'
+ resp.request = ActionDispatch::Request.empty
+ }.to_a
+ }
+
+ get '/'
+ assert_response :ok
+
+ assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
+ assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag)
+ end
end
diff --git a/actionpack/test/dispatch/routing/concerns_test.rb b/actionpack/test/dispatch/routing/concerns_test.rb
index 7ef513b0c8..6934271846 100644
--- a/actionpack/test/dispatch/routing/concerns_test.rb
+++ b/actionpack/test/dispatch/routing/concerns_test.rb
@@ -1,5 +1,7 @@
require 'abstract_unit'
+class ReviewsController < ResourcesController; end
+
class RoutingConcernsTest < ActionDispatch::IntegrationTest
class Reviewable
def self.call(mapper, options = {})
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index 7382c267c7..5aafcb23c2 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -17,10 +17,10 @@ module ActionDispatch
@set = ActionDispatch::Routing::RouteSet.new
end
- def draw(options = {}, &block)
+ def draw(options = nil, &block)
@set.draw(&block)
inspector = ActionDispatch::Routing::RoutesInspector.new(@set.routes)
- inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n")
+ inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options).split("\n")
end
def test_displaying_routes_for_engines
@@ -133,7 +133,9 @@ module ActionDispatch
def test_inspect_routes_shows_dynamic_action_route
output = draw do
- get 'api/:action' => 'api'
+ ActiveSupport::Deprecation.silence do
+ get 'api/:action' => 'api'
+ end
end
assert_equal [
@@ -144,7 +146,9 @@ module ActionDispatch
def test_inspect_routes_shows_controller_and_action_only_route
output = draw do
- get ':controller/:action'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
assert_equal [
@@ -155,7 +159,9 @@ module ActionDispatch
def test_inspect_routes_shows_controller_and_action_route_with_constraints
output = draw do
- get ':controller(/:action(/:id))', :id => /\d+/
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))', :id => /\d+/
+ end
end
assert_equal [
@@ -164,7 +170,7 @@ module ActionDispatch
], output
end
- def test_rake_routes_shows_route_with_defaults
+ def test_rails_routes_shows_route_with_defaults
output = draw do
get 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'}
end
@@ -175,7 +181,7 @@ module ActionDispatch
], output
end
- def test_rake_routes_shows_route_with_constraints
+ def test_rails_routes_shows_route_with_constraints
output = draw do
get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
end
@@ -186,7 +192,7 @@ module ActionDispatch
], output
end
- def test_rake_routes_shows_routes_with_dashes
+ def test_rails_routes_shows_routes_with_dashes
output = draw do
get 'about-us' => 'pages#about_us'
get 'our-work/latest'
@@ -209,7 +215,7 @@ module ActionDispatch
], output
end
- def test_rake_routes_shows_route_with_rack_app
+ def test_rails_routes_shows_route_with_rack_app
output = draw do
get 'foo/:id' => MountedRackApp, :id => /[A-Z]\d{5}/
end
@@ -220,7 +226,7 @@ module ActionDispatch
], output
end
- def test_rake_routes_shows_named_route_with_mounted_rack_app
+ def test_rails_routes_shows_named_route_with_mounted_rack_app
output = draw do
mount MountedRackApp => '/foo'
end
@@ -231,7 +237,7 @@ module ActionDispatch
], output
end
- def test_rake_routes_shows_overridden_named_route_with_mounted_rack_app_with_name
+ def test_rails_routes_shows_overridden_named_route_with_mounted_rack_app_with_name
output = draw do
mount MountedRackApp => '/foo', as: 'blog'
end
@@ -242,7 +248,7 @@ module ActionDispatch
], output
end
- def test_rake_routes_shows_route_with_rack_app_nested_with_dynamic_constraints
+ def test_rails_routes_shows_route_with_rack_app_nested_with_dynamic_constraints
constraint = Class.new do
def inspect
"( my custom constraint )"
@@ -261,7 +267,7 @@ module ActionDispatch
], output
end
- def test_rake_routes_dont_show_app_mounted_in_assets_prefix
+ def test_rails_routes_dont_show_app_mounted_in_assets_prefix
output = draw do
get '/sprockets' => MountedRackApp
end
@@ -269,7 +275,7 @@ module ActionDispatch
assert_no_match(/\/sprockets/, output.first)
end
- def test_rake_routes_shows_route_defined_in_under_assets_prefix
+ def test_rails_routes_shows_route_defined_in_under_assets_prefix
output = draw do
scope '/sprockets' do
get '/foo' => 'foo#bar'
@@ -297,7 +303,7 @@ module ActionDispatch
end
def test_routes_can_be_filtered
- output = draw(filter: 'posts') do
+ output = draw('posts') do
resources :articles
resources :posts
end
@@ -313,13 +319,35 @@ module ActionDispatch
" DELETE /posts/:id(.:format) posts#destroy"], output
end
+ def test_routes_can_be_filtered_with_namespaced_controllers
+ output = draw('admin/posts') do
+ resources :articles
+ namespace :admin do
+ resources :posts
+ end
+ end
+
+ assert_equal [" Prefix Verb URI Pattern Controller#Action",
+ " admin_posts GET /admin/posts(.:format) admin/posts#index",
+ " POST /admin/posts(.:format) admin/posts#create",
+ " new_admin_post GET /admin/posts/new(.:format) admin/posts#new",
+ "edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit",
+ " admin_post GET /admin/posts/:id(.:format) admin/posts#show",
+ " PATCH /admin/posts/:id(.:format) admin/posts#update",
+ " PUT /admin/posts/:id(.:format) admin/posts#update",
+ " DELETE /admin/posts/:id(.:format) admin/posts#destroy"], output
+ end
+
+
def test_regression_route_with_controller_regexp
output = draw do
- get ':controller(/:action)', controller: /api\/[^\/]+/, format: false
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action)', controller: /api\/[^\/]+/, format: false
+ end
end
assert_equal ["Prefix Verb URI Pattern Controller#Action",
- " GET /:controller(/:action) (?-mix:api\\/[^\\/]+)#:action"], output
+ " GET /:controller(/:action) :controller#:action"], output
end
def test_inspect_routes_shows_resources_route_when_assets_disabled
@@ -336,18 +364,18 @@ module ActionDispatch
end
def test_routes_with_undefined_filter
- output = draw(:filter => 'Rails::MissingController') do
+ output = draw(controller: 'Rails::MissingController') do
get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
end
assert_equal [
- "The controller Rails::MissingController does not exist!",
+ "No routes were found for this controller",
"For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
], output
end
def test_no_routes_matched_filter
- output = draw(:filter => 'rails/dummy') do
+ output = draw('rails/dummy') do
get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
end
@@ -358,7 +386,7 @@ module ActionDispatch
end
def test_no_routes_were_defined
- output = draw(:filter => 'Rails::DummyController') { }
+ output = draw('Rails::DummyController') {}
assert_equal [
"You don't have any routes defined!",
@@ -369,6 +397,29 @@ module ActionDispatch
], output
end
+ def test_displaying_routes_for_internal_engines
+ engine = Class.new(Rails::Engine) do
+ def self.inspect
+ "Blog::Engine"
+ end
+ end
+ engine.routes.draw do
+ get '/cart', to: 'cart#show'
+ post '/cart', to: 'cart#create'
+ patch '/cart', to: 'cart#update'
+ end
+
+ output = draw do
+ get '/custom/assets', to: 'custom_assets#show'
+ mount engine => "/blog", as: "blog", internal: true
+ end
+
+ assert_equal [
+ " Prefix Verb URI Pattern Controller#Action",
+ "custom_assets GET /custom/assets(.:format) custom_assets#show",
+ ], output
+ end
+
end
end
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 5ead9357ae..75fdc9469f 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -116,7 +116,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_raise(ArgumentError) do
draw do
namespace :admin do
- get '/:controller(/:action(/:id(.:format)))'
+ ActiveSupport::Deprecation.silence do
+ get '/:controller(/:action(/:id(.:format)))'
+ end
end
end
end
@@ -125,7 +127,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_namespace_without_controller_segment
draw do
namespace :admin do
- get 'hello/:controllers/:action'
+ ActiveSupport::Deprecation.silence do
+ get 'hello/:controllers/:action'
+ end
end
end
get '/admin/hello/foo/new'
@@ -427,7 +431,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get 'global/hide_notice'
get 'global/export', :action => :export, :as => :export_request
get '/export/:id/:file', :action => :export, :as => :export_download, :constraints => { :file => /.*/ }
- get 'global/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get 'global/:action'
+ end
end
end
@@ -450,7 +457,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_local
draw do
- get "/local/:action", :controller => "local"
+ ActiveSupport::Deprecation.silence do
+ get "/local/:action", :controller => "local"
+ end
end
get '/local/dashboard'
@@ -1506,7 +1515,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_not_matching_shorthand_with_dynamic_parameters
draw do
- get ':controller/:action/admin'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/admin'
+ end
end
get '/finances/overview/admin'
@@ -1542,7 +1553,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_scoped_controller_with_namespace_and_action
draw do
namespace :account do
- get ':action/callback', :action => /twitter|github/, :controller => "callbacks", :as => :callback
+ ActiveSupport::Deprecation.silence do
+ get ':action/callback', :action => /twitter|github/, :controller => "callbacks", :as => :callback
+ end
end
end
@@ -1837,7 +1850,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_url_generator_for_generic_route
draw do
- get "whatever/:controller(/:action(/:id))"
+ ActiveSupport::Deprecation.silence do
+ get "whatever/:controller(/:action(/:id))"
+ end
end
get '/whatever/foo/bar'
@@ -1849,7 +1864,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_url_generator_for_namespaced_generic_route
draw do
- get "whatever/:controller(/:action(/:id))", :id => /\d+/
+ ActiveSupport::Deprecation.silence do
+ get "whatever/:controller(/:action(/:id))", :id => /\d+/
+ end
end
get '/whatever/foo/bar/show'
@@ -3125,12 +3142,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
assert_raise(ArgumentError) do
- assert_deprecated do
- draw { controller("/feeds") { get '/feeds/:service', :to => :show } }
- end
- end
-
- assert_raise(ArgumentError) do
draw { resources :feeds, :controller => '/feeds' }
end
end
@@ -3599,6 +3610,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/?id=1', root_path(params)
end
+ def test_dynamic_controller_segments_are_deprecated
+ assert_deprecated do
+ draw do
+ get '/:controller', action: 'index'
+ end
+ end
+ end
+
+ def test_dynamic_action_segments_are_deprecated
+ assert_deprecated do
+ draw do
+ get '/pages/:action', controller: 'pages'
+ end
+ end
+ end
+
private
def draw(&block)
@@ -3964,16 +3991,6 @@ class TestUnicodePaths < ActionDispatch::IntegrationTest
end
class TestMultipleNestedController < ActionDispatch::IntegrationTest
- module ::Foo
- module Bar
- class BazController < ActionController::Base
- def index
- render :inline => "<%= url_for :controller => '/pooh', :action => 'index' %>"
- end
- end
- end
- end
-
Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw do
namespace :foo do
@@ -3985,7 +4002,18 @@ class TestMultipleNestedController < ActionDispatch::IntegrationTest
end
end
- include Routes.url_helpers
+ module ::Foo
+ module Bar
+ class BazController < ActionController::Base
+ include Routes.url_helpers
+
+ def index
+ render :inline => "<%= url_for :controller => '/pooh', :action => 'index' %>"
+ end
+ end
+ end
+ end
+
APP = build_app Routes
def app; APP end
@@ -4122,7 +4150,11 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
app.draw do
ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
get '/foo' => ok, as: :foo
- get '/post(/:action(/:id))' => ok, as: :posts
+
+ ActiveSupport::Deprecation.silence do
+ get '/post(/:action(/:id))' => ok, as: :posts
+ end
+
get '/:foo/:foo_type/bars/:id' => ok, as: :bar
get '/projects/:id.:format' => ok, as: :project
get '/pages/:id' => ok, as: :page
@@ -4292,11 +4324,16 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest
test "invalid UTF-8 encoding returns a 400 Bad Request" do
with_routing do |set|
- set.draw do
- get "/bar/:id", :to => redirect("/foo/show/%{id}")
- get "/foo/show(/:id)", :to => "test_invalid_urls/foo#show"
- get "/foo(/:action(/:id))", :controller => "test_invalid_urls/foo"
- get "/:controller(/:action(/:id))"
+ ActiveSupport::Deprecation.silence do
+ set.draw do
+ get "/bar/:id", :to => redirect("/foo/show/%{id}")
+ get "/foo/show(/:id)", :to => "test_invalid_urls/foo#show"
+
+ ActiveSupport::Deprecation.silence do
+ get "/foo(/:action(/:id))", :controller => "test_invalid_urls/foo"
+ get "/:controller(/:action(/:id))"
+ end
+ end
end
get "/%E2%EF%BF%BD%A6"
@@ -4627,7 +4664,9 @@ class TestErrorsInController < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new
Routes.draw do
- get '/:controller(/:action)'
+ ActiveSupport::Deprecation.silence do
+ get '/:controller(/:action)'
+ end
end
APP = build_app Routes
@@ -4717,3 +4756,72 @@ class TestPartialDynamicPathSegments < ActionDispatch::IntegrationTest
assert_equal(params, request.path_parameters)
end
end
+
+class TestPathParameters < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ scope module: 'test_path_parameters' do
+ scope ':locale', locale: /en|ar/ do
+ root to: 'home#index'
+ get '/about', to: 'pages#about'
+ end
+ end
+
+ get ':controller(/:action/(:id))'
+ end
+ end
+
+ class HomeController < ActionController::Base
+ include Routes.url_helpers
+
+ def index
+ render inline: "<%= root_path %>"
+ end
+ end
+
+ class PagesController < ActionController::Base
+ include Routes.url_helpers
+
+ def about
+ render inline: "<%= root_path(locale: :ar) %> | <%= url_for(locale: :ar) %>"
+ end
+ end
+
+ APP = build_app Routes
+ def app; APP end
+
+ def test_path_parameters_are_not_mutated
+ get '/en/about'
+ assert_equal "/ar | /ar/about", @response.body
+ end
+end
+
+class TestInternalRoutingParams < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ get '/test_internal/:internal' => 'internal#internal'
+ end
+ end
+
+ class ::InternalController < ActionController::Base
+ def internal
+ head :ok
+ end
+ end
+
+ APP = build_app Routes
+
+ def app
+ APP
+ end
+
+ def test_paths_with_partial_dynamic_segments_are_recognised
+ get '/test_internal/123'
+ assert_equal 200, response.status
+
+ assert_equal(
+ { controller: 'internal', action: 'internal', internal: '123' },
+ request.path_parameters
+ )
+ end
+end
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
index dbb996973d..769de1a1e0 100644
--- a/actionpack/test/dispatch/session/cache_store_test.rb
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -164,7 +164,9 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- get ':action', :to => ::CacheStoreTest::TestController
+ ActiveSupport::Deprecation.silence do
+ get ':action', :to => ::CacheStoreTest::TestController
+ end
end
@app = self.class.build_app(set) do |middleware|
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index f07e215e3a..09cb1d925f 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -345,7 +345,9 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def with_test_route_set(options = {})
with_routing do |set|
set.draw do
- get ':action', :to => ::CookieStoreTest::TestController
+ ActiveSupport::Deprecation.silence do
+ get ':action', :to => ::CookieStoreTest::TestController
+ end
end
options = { :key => SessionKey }.merge!(options)
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 3fed9bad4f..18cb227dad 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -187,7 +187,9 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- get ':action', :to => ::MemCacheStoreTest::TestController
+ ActiveSupport::Deprecation.silence do
+ get ':action', :to => ::MemCacheStoreTest::TestController
+ end
end
@app = self.class.build_app(set) do |middleware|
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
index c66a0e6a7a..668b2b6cfe 100644
--- a/actionpack/test/dispatch/ssl_test.rb
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -7,7 +7,7 @@ class SSLTest < ActionDispatch::IntegrationTest
def build_app(headers: {}, ssl_options: {})
headers = HEADERS.merge(headers)
- ActionDispatch::SSL.new lambda { |env| [200, headers, []] }, ssl_options
+ ActionDispatch::SSL.new lambda { |env| [200, headers, []] }, ssl_options.reverse_merge(hsts: { subdomains: true })
end
end
@@ -39,6 +39,13 @@ class RedirectSSLTest < SSLTest
assert_equal redirect[:body].join, @response.body
end
+ test 'exclude can avoid redirect' do
+ excluding = { exclude: -> request { request.path =~ /healthcheck/ } }
+
+ assert_not_redirected 'http://example.org/healthcheck', redirect: excluding
+ assert_redirected from: 'http://example.org/', redirect: excluding
+ end
+
test 'https is not redirected' do
assert_not_redirected 'https://example.org'
end
@@ -98,15 +105,16 @@ end
class StrictTransportSecurityTest < SSLTest
EXPECTED = 'max-age=15552000'
+ EXPECTED_WITH_SUBDOMAINS = 'max-age=15552000; includeSubDomains'
- def assert_hsts(expected, url: 'https://example.org', hsts: {}, headers: {})
+ def assert_hsts(expected, url: 'https://example.org', hsts: { subdomains: true }, headers: {})
self.app = build_app ssl_options: { hsts: hsts }, headers: headers
get url
assert_equal expected, response.headers['Strict-Transport-Security']
end
test 'enabled by default' do
- assert_hsts EXPECTED
+ assert_hsts EXPECTED_WITH_SUBDOMAINS
end
test 'not sent with http:// responses' do
@@ -126,11 +134,15 @@ class StrictTransportSecurityTest < SSLTest
end
test ':expires sets max-age' do
- assert_hsts 'max-age=500', hsts: { expires: 500 }
+ assert_deprecated do
+ assert_hsts 'max-age=500', hsts: { expires: 500 }
+ end
end
test ':expires supports AS::Duration arguments' do
- assert_hsts 'max-age=31557600', hsts: { expires: 1.year }
+ assert_deprecated do
+ assert_hsts 'max-age=31557600', hsts: { expires: 1.year }
+ end
end
test 'include subdomains' do
@@ -142,11 +154,15 @@ class StrictTransportSecurityTest < SSLTest
end
test 'opt in to browser preload lists' do
- assert_hsts "#{EXPECTED}; preload", hsts: { preload: true }
+ assert_deprecated do
+ assert_hsts "#{EXPECTED}; preload", hsts: { preload: true }
+ end
end
test 'opt out of browser preload lists' do
- assert_hsts EXPECTED, hsts: { preload: false }
+ assert_deprecated do
+ assert_hsts EXPECTED, hsts: { preload: false }
+ end
end
end
diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb
index 51c469a61a..3c19cbd68a 100644
--- a/actionpack/test/dispatch/test_request_test.rb
+++ b/actionpack/test/dispatch/test_request_test.rb
@@ -88,6 +88,33 @@ class TestRequestTest < ActiveSupport::TestCase
assert_equal 'GoogleBot', req.user_agent
end
+ test "setter methods" do
+ req = ActionDispatch::TestRequest.create({})
+ get = 'GET'
+
+ [
+ 'request_method=', 'host=', 'request_uri=', 'path=', 'if_modified_since=', 'if_none_match=',
+ 'remote_addr=', 'user_agent=', 'accept='
+ ].each do |method|
+ req.send(method, get)
+ end
+
+ req.port = 8080
+ req.accept = 'hello goodbye'
+
+ assert_equal(get, req.get_header('REQUEST_METHOD'))
+ assert_equal(get, req.get_header('HTTP_HOST'))
+ assert_equal(8080, req.get_header('SERVER_PORT'))
+ assert_equal(get, req.get_header('REQUEST_URI'))
+ assert_equal(get, req.get_header('PATH_INFO'))
+ assert_equal(get, req.get_header('HTTP_IF_MODIFIED_SINCE'))
+ assert_equal(get, req.get_header('HTTP_IF_NONE_MATCH'))
+ assert_equal(get, req.get_header('REMOTE_ADDR'))
+ assert_equal(get, req.get_header('HTTP_USER_AGENT'))
+ assert_nil(req.get_header('action_dispatch.request.accepts'))
+ assert_equal('hello goodbye', req.get_header('HTTP_ACCEPT'))
+ end
+
private
def assert_cookies(expected, cookie_jar)
assert_equal(expected, cookie_jar.instance_variable_get("@cookies"))
diff --git a/actionpack/test/fixtures/collection_cache/index.html.erb b/actionpack/test/fixtures/collection_cache/index.html.erb
index 521b1450df..853e501ab4 100644
--- a/actionpack/test/fixtures/collection_cache/index.html.erb
+++ b/actionpack/test/fixtures/collection_cache/index.html.erb
@@ -1 +1 @@
-<%= render @customers %> \ No newline at end of file
+<%= render partial: 'customers/customer', collection: @customers, cached: true %>
diff --git a/actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb b/actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb
new file mode 100644
index 0000000000..951c761995
--- /dev/null
+++ b/actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb
@@ -0,0 +1,3 @@
+<body>
+<%= cache 'with_options', skip_digest: true, expires_in: 10 do %><p>ERB</p><% end %>
+</body>
diff --git a/actionpack/test/fixtures/implicit_render_test/empty_action_with_mobile_variant.html+mobile.erb b/actionpack/test/fixtures/implicit_render_test/empty_action_with_mobile_variant.html+mobile.erb
new file mode 100644
index 0000000000..ded99ba52d
--- /dev/null
+++ b/actionpack/test/fixtures/implicit_render_test/empty_action_with_mobile_variant.html+mobile.erb
@@ -0,0 +1 @@
+mobile
diff --git a/actionpack/test/fixtures/implicit_render_test/empty_action_with_template.html.erb b/actionpack/test/fixtures/implicit_render_test/empty_action_with_template.html.erb
new file mode 100644
index 0000000000..dd294f8cf6
--- /dev/null
+++ b/actionpack/test/fixtures/implicit_render_test/empty_action_with_template.html.erb
@@ -0,0 +1 @@
+<h1>Empty action rendered this implicitly.</h1>
diff --git a/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb b/actionpack/test/fixtures/respond_to/variant_with_implicit_template_rendering.html+mobile.erb
index 317801ad30..317801ad30 100644
--- a/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb
+++ b/actionpack/test/fixtures/respond_to/variant_with_implicit_template_rendering.html+mobile.erb
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index 15d51e5d6c..75caf56d32 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
module ActionDispatch
module Journey
class TestRouter < ActiveSupport::TestCase
- attr_reader :routes, :mapper
+ attr_reader :mapper, :routes, :route_set, :router
def setup
@app = Routing::RouteSet::Dispatcher.new({})
@@ -15,36 +15,36 @@ module ActionDispatch
end
def test_dashes
- mapper.get '/foo-bar-baz', to: 'foo#bar'
+ get '/foo-bar-baz', to: 'foo#bar'
env = rails_env 'PATH_INFO' => '/foo-bar-baz'
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_unicode
- mapper.get '/ほげ', to: 'foo#bar'
+ get '/ほげ', to: 'foo#bar'
#match the escaped version of /ほげ
env = rails_env 'PATH_INFO' => '/%E3%81%BB%E3%81%92'
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_regexp_first_precedence
- mapper.get "/whois/:domain", :domain => /\w+\.[\w\.]+/, to: "foo#bar"
- mapper.get "/whois/:id(.:format)", to: "foo#baz"
+ get "/whois/:domain", :domain => /\w+\.[\w\.]+/, to: "foo#bar"
+ get "/whois/:id(.:format)", to: "foo#baz"
env = rails_env 'PATH_INFO' => '/whois/example.com'
list = []
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
list << r
end
assert_equal 2, list.length
@@ -55,7 +55,7 @@ module ActionDispatch
end
def test_required_parts_verified_are_anchored
- mapper.get "/foo/:id", :id => /\d/, anchor: false, to: "foo#bar"
+ get "/foo/:id", :id => /\d/, anchor: false, to: "foo#bar"
assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { })
@@ -63,7 +63,7 @@ module ActionDispatch
end
def test_required_parts_are_verified_when_building
- mapper.get "/foo/:id", :id => /\d+/, anchor: false, to: "foo#bar"
+ get "/foo/:id", :id => /\d+/, anchor: false, to: "foo#bar"
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { })
assert_equal '/foo/10', path
@@ -74,7 +74,7 @@ module ActionDispatch
end
def test_only_required_parts_are_verified
- mapper.get "/foo(/:id)", :id => /\d/, :to => "foo#bar"
+ get "/foo(/:id)", :id => /\d/, :to => "foo#bar"
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { })
assert_equal '/foo/10', path
@@ -88,8 +88,7 @@ module ActionDispatch
def test_knows_what_parts_are_missing_from_named_route
route_name = "gorby_thunderhorse"
- mapper = ActionDispatch::Routing::Mapper.new @route_set
- mapper.get "/foo/:id", :as => route_name, :id => /\d+/, :to => "foo#bar"
+ get "/foo/:id", :as => route_name, :id => /\d+/, :to => "foo#bar"
error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(route_name, { }, { })
@@ -109,19 +108,16 @@ module ActionDispatch
end
def test_X_Cascade
- mapper.get "/messages(.:format)", to: "foo#bar"
- resp = @router.serve(rails_env({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' }))
+ get "/messages(.:format)", to: "foo#bar"
+ resp = router.serve(rails_env({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' }))
assert_equal ['Not Found'], resp.last
assert_equal 'pass', resp[1]['X-Cascade']
assert_equal 404, resp.first
end
def test_clear_trailing_slash_from_script_name_on_root_unanchored_routes
- route_set = Routing::RouteSet.new
- mapper = Routing::Mapper.new route_set
-
app = lambda { |env| [200, {}, ['success!']] }
- mapper.get '/weblog', :to => app
+ get '/weblog', :to => app
env = rack_env('SCRIPT_NAME' => '', 'PATH_INFO' => '/weblog')
resp = route_set.call env
@@ -130,38 +126,38 @@ module ActionDispatch
end
def test_defaults_merge_correctly
- mapper.get '/foo(/:id)', to: "foo#bar", id: nil
+ get '/foo(/:id)', to: "foo#bar", id: nil
env = rails_env 'PATH_INFO' => '/foo/10'
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
assert_equal({:id => '10', :controller => "foo", :action => "bar"}, params)
end
env = rails_env 'PATH_INFO' => '/foo'
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
assert_equal({:id => nil, :controller => "foo", :action => "bar"}, params)
end
end
def test_recognize_with_unbound_regexp
- mapper.get "/foo", anchor: false, to: "foo#bar"
+ get "/foo", anchor: false, to: "foo#bar"
env = rails_env 'PATH_INFO' => '/foo/bar'
- @router.recognize(env) { |*_| }
+ router.recognize(env) { |*_| }
assert_equal '/foo', env.env['SCRIPT_NAME']
assert_equal '/bar', env.env['PATH_INFO']
end
def test_bound_regexp_keeps_path_info
- mapper.get "/foo", to: "foo#bar"
+ get "/foo", to: "foo#bar"
env = rails_env 'PATH_INFO' => '/foo'
before = env.env['SCRIPT_NAME']
- @router.recognize(env) { |*_| }
+ router.recognize(env) { |*_| }
assert_equal before, env.env['SCRIPT_NAME']
assert_equal '/foo', env.env['PATH_INFO']
@@ -174,41 +170,41 @@ module ActionDispatch
"/messages/:id/edit(.:format)",
"/messages/:id(.:format)"
].each do |path|
- mapper.get path, to: "foo#bar"
+ get path, to: "foo#bar"
end
env = rails_env 'PATH_INFO' => '/messages/unknown/path'
yielded = false
- @router.recognize(env) do |*whatever|
+ router.recognize(env) do |*whatever|
yielded = true
end
assert_not yielded
end
def test_required_part_in_recall
- mapper.get "/messages/:a/:b", to: "foo#bar"
+ get "/messages/:a/:b", to: "foo#bar"
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :a => 'a' }, { :b => 'b' })
assert_equal "/messages/a/b", path
end
def test_splat_in_recall
- mapper.get "/*path", to: "foo#bar"
+ get "/*path", to: "foo#bar"
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar" }, { :path => 'b' })
assert_equal "/b", path
end
def test_recall_should_be_used_when_scoring
- mapper.get "/messages/:action(/:id(.:format))", to: 'foo#bar'
- mapper.get "/messages/:id(.:format)", to: 'bar#baz'
+ get "/messages/:action(/:id(.:format))", to: 'foo#bar'
+ get "/messages/:id(.:format)", to: 'bar#baz'
path, _ = @formatter.generate(nil, { :controller => "foo", :id => 10 }, { :action => 'index' })
assert_equal "/messages/index/10", path
end
def test_nil_path_parts_are_ignored
- mapper.get "/:controller(/:action(.:format))", to: "tasks#lol"
+ get "/:controller(/:action(.:format))", to: "tasks#lol"
params = { :controller => "tasks", :format => nil }
extras = { :action => 'lol' }
@@ -220,14 +216,14 @@ module ActionDispatch
def test_generate_slash
params = [ [:controller, "tasks"],
[:action, "show"] ]
- mapper.get "/", Hash[params]
+ get "/", Hash[params]
path, _ = @formatter.generate(nil, Hash[params], {})
assert_equal '/', path
end
def test_generate_calls_param_proc
- mapper.get '/:controller(/:action)', to: "foo#bar"
+ get '/:controller(/:action)', to: "foo#bar"
parameterized = []
params = [ [:controller, "tasks"],
@@ -243,7 +239,7 @@ module ActionDispatch
end
def test_generate_id
- mapper.get '/:controller(/:action)', to: 'foo#bar'
+ get '/:controller(/:action)', to: 'foo#bar'
path, params = @formatter.generate(
nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {})
@@ -252,7 +248,7 @@ module ActionDispatch
end
def test_generate_escapes
- mapper.get '/:controller(/:action)', to: "foo#bar"
+ get '/:controller(/:action)', to: "foo#bar"
path, _ = @formatter.generate(nil,
{ :controller => "tasks",
@@ -262,7 +258,7 @@ module ActionDispatch
end
def test_generate_escapes_with_namespaced_controller
- mapper.get '/:controller(/:action)', to: "foo#bar"
+ get '/:controller(/:action)', to: "foo#bar"
path, _ = @formatter.generate(
nil, { :controller => "admin/tasks",
@@ -272,7 +268,7 @@ module ActionDispatch
end
def test_generate_extra_params
- mapper.get '/:controller(/:action)', to: "foo#bar"
+ get '/:controller(/:action)', to: "foo#bar"
path, params = @formatter.generate(
nil, { :id => 1,
@@ -285,7 +281,7 @@ module ActionDispatch
end
def test_generate_missing_keys_no_matches_different_format_keys
- mapper.get '/:controller/:action/:name', to: "foo#bar"
+ get '/:controller/:action/:name', to: "foo#bar"
primarty_parameters = {
:id => 1,
:controller => "tasks",
@@ -311,7 +307,7 @@ module ActionDispatch
end
def test_generate_uses_recall_if_needed
- mapper.get '/:controller(/:action(/:id))', to: "foo#bar"
+ get '/:controller(/:action(/:id))', to: "foo#bar"
path, params = @formatter.generate(
nil,
@@ -322,7 +318,7 @@ module ActionDispatch
end
def test_generate_with_name
- mapper.get '/:controller(/:action)', to: 'foo#bar', as: 'tasks'
+ get '/:controller(/:action)', to: 'foo#bar', as: 'tasks'
path, params = @formatter.generate(
"tasks",
@@ -338,13 +334,13 @@ module ActionDispatch
'/content/show/10' => { :controller => 'content', :action => 'show', :id => "10" },
}.each do |request_path, expected|
define_method("test_recognize_#{expected.keys.map(&:to_s).join('_')}") do
- mapper.get "/:controller(/:action(/:id))", to: 'foo#bar'
+ get "/:controller(/:action(/:id))", to: 'foo#bar'
route = @routes.first
env = rails_env 'PATH_INFO' => request_path
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
assert_equal route, r
assert_equal({ :action => "bar" }.merge(expected), params)
called = true
@@ -359,13 +355,13 @@ module ActionDispatch
:splat => ['/segment/a/b%20c+d', { :segment => 'segment', :splat => 'a/b c+d' }]
}.each do |name, (request_path, expected)|
define_method("test_recognize_#{name}") do
- mapper.get '/:segment/*splat', to: 'foo#bar'
+ get '/:segment/*splat', to: 'foo#bar'
env = rails_env 'PATH_INFO' => request_path
called = false
route = @routes.first
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected.merge(:controller=>"foo", :action=>"bar"), params)
called = true
@@ -376,7 +372,7 @@ module ActionDispatch
end
def test_namespaced_controller
- mapper.get "/:controller(/:action(/:id))", { :controller => /.+?/ }
+ get "/:controller(/:action(/:id))", { :controller => /.+?/ }
route = @routes.first
env = rails_env 'PATH_INFO' => '/admin/users/show/10'
@@ -387,7 +383,7 @@ module ActionDispatch
:id => '10'
}
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected, params)
called = true
@@ -396,13 +392,13 @@ module ActionDispatch
end
def test_recognize_literal
- mapper.get "/books(/:action(.:format))", controller: "books"
+ get "/books(/:action(.:format))", controller: "books"
route = @routes.first
env = rails_env 'PATH_INFO' => '/books/list.rss'
expected = { :controller => 'books', :action => 'list', :format => 'rss' }
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected, params)
called = true
@@ -412,7 +408,7 @@ module ActionDispatch
end
def test_recognize_head_route
- mapper.match "/books(/:action(.:format))", via: 'head', to: 'foo#bar'
+ match "/books(/:action(.:format))", via: 'head', to: 'foo#bar'
env = rails_env(
'PATH_INFO' => '/books/list.rss',
@@ -420,7 +416,7 @@ module ActionDispatch
)
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
@@ -428,13 +424,13 @@ module ActionDispatch
end
def test_recognize_head_request_as_get_route
- mapper.get "/books(/:action(.:format))", to: 'foo#bar'
+ get "/books(/:action(.:format))", to: 'foo#bar'
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => "HEAD"
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
@@ -442,13 +438,13 @@ module ActionDispatch
end
def test_recognize_cares_about_get_verbs
- mapper.match "/books(/:action(.:format))", to: "foo#bar", via: :get
+ match "/books(/:action(.:format))", to: "foo#bar", via: :get
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => "POST"
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
@@ -456,13 +452,13 @@ module ActionDispatch
end
def test_recognize_cares_about_post_verbs
- mapper.match "/books(/:action(.:format))", to: "foo#bar", via: :post
+ match "/books(/:action(.:format))", to: "foo#bar", via: :post
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => "POST"
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
@@ -470,14 +466,14 @@ module ActionDispatch
end
def test_multi_verb_recognition
- mapper.match "/books(/:action(.:format))", to: "foo#bar", via: [:post, :get]
+ match "/books(/:action(.:format))", to: "foo#bar", via: [:post, :get]
%w( POST GET ).each do |verb|
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => verb
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
@@ -488,7 +484,7 @@ module ActionDispatch
"REQUEST_METHOD" => 'PUT'
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
@@ -497,6 +493,18 @@ module ActionDispatch
private
+ def get *args
+ ActiveSupport::Deprecation.silence do
+ mapper.get(*args)
+ end
+ end
+
+ def match *args
+ ActiveSupport::Deprecation.silence do
+ mapper.match(*args)
+ end
+ end
+
def rails_env env, klass = ActionDispatch::Request
klass.new(rack_env(env))
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index d85681e0d1..9d669c7cd8 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,274 +1,19 @@
-* Fix stripping the digest from the automatically generated img tag alt
- attribute when assets are handled by Sprockets >=3.0.
+* `select_tag`'s `include_blank` option for generation for blank option tag, now adds an empty space label,
+ when the value as well as content for option tag are empty, so that we confirm with html specification.
+ Ref: https://www.w3.org/TR/html5/forms.html#the-option-element.
- *Bart de Water*
+ Generation of option before:
-* Create a new `ActiveSupport::SafeBuffer` instance when `content_for` is flushed.
-
- Fixes #19890
-
- *Yoong Kang Lim*
-
-* Fix `collection_radio_buttons` hidden_field name and make it appear
- before the actual input radio tags to make the real value override
- the hidden when passed.
-
- Fixes #22773.
-
- *Santiago Pastorino*
-
-* `ActionView::TestCase::Controller#params` returns an instance of
- `ActionController::Parameters`.
-
- *Justin Coyne*
-
-* Fix regression in `submit_tag` when a symbol is used as label argument.
-
- *Yuuji Yaginuma*
-
-
-## Rails 5.0.0.beta1 (December 18, 2015) ##
-
-* `I18n.translate` helper will wrap the missing translation keys
- in a <span> tag only if `debug_missing_translation` configuration
- be true. Default value is `true`. For example in `application.rb`:
-
- # in order to turn off missing key wrapping
- config.action_view.debug_missing_translation = false
-
- *Sameer Rahmani*
-
-* Respect value of `:object` if `:object` is false when rendering.
-
- Fixes #22260.
-
- *Yuichiro Kaneko*
-
-* Generate `week_field` input values using a 1-based index and not a 0-based index
- as per the W3 spec: http://www.w3.org/TR/html-markup/datatypes.html#form.data.week
-
- *Christoph Geschwind*
-
-* Allow `host` option in `javascript_include_tag` and `stylesheet_link_tag` helpers
-
- *Grzegorz Witek*
-
-* Restrict `url_for :back` to valid, non-JavaScript URLs. GH#14444
-
- *Damien Burke*
-
-* Allow `date_select` helper selected option to accept hash like the default options.
-
- *Lecky Lao*
-
-* Collection input propagates input's `id` to the label's `for` attribute when
- using html options as the last element of collection.
-
- *Vasiliy Ermolovich*
-
-* Add a `hidden_field` on the `collection_radio_buttons` to avoid raising a error
- when the only input on the form is the `collection_radio_buttons`.
-
- *Mauro George*
-
-* `url_for` does not modify its arguments when generating polymorphic URLs.
-
- *Bernerd Schaefer*
-
-* `number_to_currency` and `number_with_delimiter` now accept a custom `delimiter_pattern` option
- to handle placement of delimiter, to support currency formats like INR.
-
- Example:
-
- number_to_currency(1230000, delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/, unit: '₹', format: "%u %n")
- # => '₹ 12,30,000.00'
-
- *Vipul A M*
-
-* Make `disable_with` the default behavior for submit tags. Disables the
- button on submit to prevent double submits.
-
- *Justin Schiff*
-
-* Add a break_sequence option to word_wrap so you can specify a custom break.
-
- *Mauricio Gomez*
-
-* Add wildcard matching to explicit dependencies.
-
- Turns:
-
- ```erb
- <% # Template Dependency: recordings/threads/events/subscribers_changed %>
- <% # Template Dependency: recordings/threads/events/completed %>
- <% # Template Dependency: recordings/threads/events/uncompleted %>
- ```
-
- Into:
-
- ```erb
- <% # Template Dependency: recordings/threads/events/* %>
- ```
-
- *Kasper Timm Hansen*
-
-* Allow defining explicit collection caching using a `# Template Collection: ...`
- directive inside templates.
-
- *Dov Murik*
-
-* Asset helpers raise `ArgumentError` when `nil` is passed as a source.
-
- *Anton Kolomiychuk*
-
-* Always attach the template digest to the cache key for collection caching
- even when `virtual_path` is not available from the view context.
- Which could happen if the rendering was done directly in the controller
- and not in a template.
-
- Fixes #20535.
-
- *Roque Pinel*
-
-* Improve detection of partial templates eligible for collection caching,
- now allowing multi-line comments at the beginning of the template file.
-
- *Dov Murik*
-
-* Raise an `ArgumentError` when a false value for `include_blank` is passed to a
- required select field (to comply with the HTML5 spec).
-
- *Grey Baker*
-
-* Do not put partial name to `local_assigns` when rendering without
- an object or a collection.
-
- *Henrik Nygren*
-
-* Remove `:rescue_format` option for `translate` helper since it's no longer
- supported by I18n.
-
- *Bernard Potocki*
-
-* `translate` should handle `raise` flag correctly in case of both main and default
- translation is missing.
-
- Fixes #19967.
-
- *Bernard Potocki*
-
-* Load the `default_form_builder` from the controller on initialization, which overrides
- the global config if it is present.
-
- *Kevin McPhillips*
-
-* Accept lambda as `child_index` option in `fields_for` method.
-
- *Karol Galanciak*
-
-* `translate` allows `default: [[]]` again for a default value of `[]`.
-
- Fixes #19640.
-
- *Adam Prescott*
-
-* `translate` should accept nils as members of the `:default`
- parameter without raising a translation missing error.
-
- Fixes #19419.
-
- *Justin Coyne*
-
-* `number_to_percentage` does not crash with `Float::NAN` or `Float::INFINITY`
- as input when `precision: 0` is used.
-
- Fixes #19227.
-
- *Yves Senn*
-
-* Fixed the translation helper method to accept different default values types
- besides String.
-
- *Ulisses Almeida*
-
-* Collection rendering automatically caches and fetches multiple partials.
-
- Collections rendered as:
-
- ```ruby
- <%= render @notifications %>
- <%= render partial: 'notifications/notification', collection: @notifications, as: :notification %>
+ ```html
+ <option value=""></option>
```
- will now read several partials from cache at once, if the template starts with a cache call:
+ Generation of option after:
- ```ruby
- # notifications/_notification.html.erb
- <% cache notification do %>
- <%# ... %>
- <% end %>
+ ```html
+ <option value="" label=" "></option>
```
- *Kasper Timm Hansen*
-
-* Fixed a dependency tracker bug that caused template dependencies not
- count layouts as dependencies for partials.
-
- *Juho Leinonen*
-
-* Extracted `ActionView::Helpers::RecordTagHelper` to external gem
- (`record_tag_helper`) and added removal notices.
-
- *Todd Bealmear*
-
-* Allow to pass a string value to `size` option in `image_tag` and `video_tag`.
-
- This makes the behavior more consistent with `width` or `height` options.
-
- *Mehdi Lahmam*
-
-* Partial template name does no more have to be a valid Ruby identifier.
-
- 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.
-
- *Akira Matsuda*
-
-* Change the default template handler from `ERB` to `Raw`.
-
- Files without a template handler in their extension will be rendered using the raw
- handler instead of ERB.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `AbstractController::Base::parent_prefixes`.
-
- *Rafael Mendonça França*
-
-* Default translations that have a lower precedence than a html safe default,
- but are not themselves safe, should not be marked as html_safe.
-
- *Justin Coyne*
-
-* Make possible to use blocks with short version of `render "partial"` helper.
-
- *Nikolay Shebanov*
-
-* Add a `hidden_field` on the `file_field` to avoid raising an error when the only
- input on the form is the `file_field`.
-
- *Mauro George*
-
-* Add an explicit error message, in `ActionView::PartialRenderer` for partial
- `rendering`, when the value of option `as` has invalid characters.
-
- *Angelo Capilleri*
-
-* Allow entries without a link tag in `AtomFeedHelper`.
-
- *Daniel Gomez de Souza*
+ *Vipul A M*
-Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionview/CHANGELOG.md) for previous changes.
+Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actionview/CHANGELOG.md) for previous changes.
diff --git a/actionview/Rakefile b/actionview/Rakefile
index 93be50721d..f0f9bda3e4 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -3,6 +3,8 @@ require 'rake/testtask'
desc "Default Task"
task :default => :test
+task :package
+
# Run the unit tests
desc "Run all unit tests"
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index 612e94021d..75c5045ec0 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -9,11 +9,11 @@ Gem::Specification.new do |s|
s.required_ruby_version = '>= 2.2.2'
- s.license = 'MIT'
+ s.license = 'MIT'
- s.author = 'David Heinemeier Hansson'
- s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.author = 'David Heinemeier Hansson'
+ s.email = 'david@loudthinking.com'
+ s.homepage = 'http://rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
s.require_path = 'lib'
@@ -24,7 +24,7 @@ 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.2'
- s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5'
+ s.add_dependency 'rails-dom-testing', '~> 2.0'
s.add_development_dependency 'actionpack', version
s.add_development_dependency 'activemodel', version
diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb
index 5a4c3ea3fe..7731773040 100644
--- a/actionview/lib/action_view/dependency_tracker.rb
+++ b/actionview/lib/action_view/dependency_tracker.rb
@@ -7,18 +7,20 @@ module ActionView
def self.find_dependencies(name, template, view_paths = nil)
tracker = @trackers[template.handler]
- return [] unless tracker.present?
+ return [] unless tracker
- if tracker.respond_to?(:supports_view_paths?) && tracker.supports_view_paths?
- tracker.call(name, template, view_paths)
- else
- tracker.call(name, template)
- end
+ tracker.call(name, template, view_paths)
end
def self.register_tracker(extension, tracker)
handler = Template.handler_for_extension(extension)
- @trackers[handler] = tracker
+ if tracker.respond_to?(:supports_view_paths?)
+ @trackers[handler] = tracker
+ else
+ @trackers[handler] = lambda { |name, template, _|
+ tracker.call(name, template)
+ }
+ end
end
def self.remove_tracker(handler)
@@ -151,11 +153,11 @@ module ActionView
def resolve_directories(wildcard_dependencies)
return [] unless @view_paths
- wildcard_dependencies.each_with_object([]) do |query, templates|
- @view_paths.find_all_with_query(query).each do |template|
- templates << "#{File.dirname(query)}/#{File.basename(template).split('.').first}"
+ wildcard_dependencies.flat_map { |query, templates|
+ @view_paths.find_all_with_query(query).map do |template|
+ "#{File.dirname(query)}/#{File.basename(template).split('.').first}"
end
- end
+ }.sort
end
def explicit_dependencies
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 6f2f9ca53c..f3c29d663c 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -4,16 +4,7 @@ require 'monitor'
module ActionView
class Digestor
- cattr_reader(:cache)
- @@cache = Concurrent::Map.new
- @@digest_monitor = Monitor.new
-
- class PerRequestDigestCacheExpiry < Struct.new(:app) # :nodoc:
- def call(env)
- ActionView::Digestor.cache.clear
- app.call(env)
- end
- end
+ @@digest_mutex = Mutex.new
class << self
# Supported options:
@@ -22,106 +13,108 @@ module ActionView
# * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
# * <tt>dependencies</tt> - An array of dependent views
# * <tt>partial</tt> - Specifies whether the template is a partial
- def digest(options)
- options.assert_valid_keys(:name, :finder, :dependencies, :partial)
-
- cache_key = ([ options[:name], options[:finder].details_key.hash ].compact + Array.wrap(options[:dependencies])).join('.')
+ def digest(name:, finder:, dependencies: [])
+ dependencies ||= []
+ cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join('.')
# this is a correctly done double-checked locking idiom
# (Concurrent::Map's lookups have volatile semantics)
- @@cache[cache_key] || @@digest_monitor.synchronize do
- @@cache.fetch(cache_key) do # re-check under lock
- compute_and_store_digest(cache_key, options)
+ finder.digest_cache[cache_key] || @@digest_mutex.synchronize do
+ finder.digest_cache.fetch(cache_key) do # re-check under lock
+ partial = name.include?("/_")
+ root = tree(name, finder, partial)
+ dependencies.each do |injected_dep|
+ root.children << Injected.new(injected_dep, nil, nil)
+ end
+ finder.digest_cache[cache_key] = root.digest(finder)
end
end
end
- private
- def compute_and_store_digest(cache_key, options) # called under @@digest_monitor lock
- klass = if options[:partial] || options[:name].include?("/_")
- # Prevent re-entry or else recursive templates will blow the stack.
- # There is no need to worry about other threads seeing the +false+ value,
- # as they will then have to wait for this thread to let go of the @@digest_monitor lock.
- pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion
- PartialDigestor
- else
- Digestor
- end
-
- @@cache[cache_key] = stored_digest = klass.new(options).digest
- ensure
- # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache
- @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest
- end
- end
+ def logger
+ ActionView::Base.logger || NullLogger
+ end
- attr_reader :name, :finder, :options
+ # Create a dependency tree for template named +name+.
+ def tree(name, finder, partial = false, seen = {})
+ logical_name = name.gsub(%r|/_|, "/")
- def initialize(options)
- @name, @finder = options.values_at(:name, :finder)
- @options = options.except(:name, :finder)
- end
+ options = {}
+ options[:formats] = [finder.rendered_format] if finder.rendered_format
- def digest
- Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest|
- logger.try :debug, " Cache digest for #{template.inspect}: #{digest}"
- end
- rescue ActionView::MissingTemplate
- logger.try :error, " Couldn't find template for digesting: #{name}"
- ''
- end
+ if finder.disable_cache { finder.exists?(logical_name, [], partial, [], options) }
+ template = finder.disable_cache { finder.find(logical_name, [], partial, [], options) }
+ finder.rendered_format ||= template.formats.first
- def dependencies
- DependencyTracker.find_dependencies(name, template, finder.view_paths)
- rescue ActionView::MissingTemplate
- logger.try :error, " '#{name}' file doesn't exist, so no dependencies"
- []
- end
+ if node = seen[template.identifier] # handle cycles in the tree
+ node
+ else
+ node = seen[template.identifier] = Node.create(name, logical_name, template, partial)
- def nested_dependencies
- dependencies.collect do |dependency|
- dependencies = PartialDigestor.new(name: dependency, finder: finder).nested_dependencies
- dependencies.any? ? { dependency => dependencies } : dependency
+ deps = DependencyTracker.find_dependencies(name, template, finder.view_paths)
+ deps.uniq { |n| n.gsub(%r|/_|, "/") }.each do |dep_file|
+ node.children << tree(dep_file, finder, true, seen)
+ end
+ node
+ end
+ else
+ logger.error " '#{name}' file doesn't exist, so no dependencies"
+ logger.error " Couldn't find template for digesting: #{name}"
+ seen[name] ||= Missing.new(name, logical_name, nil)
+ end
end
end
- private
- def logger
- ActionView::Base.logger
+ class Node
+ attr_reader :name, :logical_name, :template, :children
+
+ def self.create(name, logical_name, template, partial)
+ klass = partial ? Partial : Node
+ klass.new(name, logical_name, template, [])
end
- def logical_name
- name.gsub(%r|/_|, "/")
+ def initialize(name, logical_name, template, children = [])
+ @name = name
+ @logical_name = logical_name
+ @template = template
+ @children = children
end
- def partial?
- false
+ def digest(finder, stack = [])
+ Digest::MD5.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}")
end
- def template
- @template ||= finder.disable_cache { finder.find(logical_name, [], partial?) }
+ def dependency_digest(finder, stack)
+ children.map do |node|
+ if stack.include?(node)
+ false
+ else
+ finder.digest_cache[node.name] ||= begin
+ stack.push node
+ node.digest(finder, stack).tap { stack.pop }
+ end
+ end
+ end.join("-")
end
- def source
- template.source
+ def to_dep_map
+ children.any? ? { name => children.map(&:to_dep_map) } : name
end
+ end
- def dependency_digest
- template_digests = dependencies.collect do |template_name|
- Digestor.digest(name: template_name, finder: finder, partial: true)
- end
+ class Partial < Node; end
- (template_digests + injected_dependencies).join("-")
- end
+ class Missing < Node
+ def digest(finder, _ = []) '' end
+ end
- def injected_dependencies
- Array.wrap(options[:dependencies])
- end
- end
+ class Injected < Node
+ def digest(finder, _ = []) name end
+ end
- class PartialDigestor < Digestor # :nodoc:
- def partial?
- true
+ class NullLogger
+ def self.debug(_); end
+ def self.error(_); end
end
end
end
diff --git a/actionview/lib/action_view/flows.rb b/actionview/lib/action_view/flows.rb
index bc61920848..4b912f0b2b 100644
--- a/actionview/lib/action_view/flows.rb
+++ b/actionview/lib/action_view/flows.rb
@@ -37,9 +37,8 @@ module ActionView
end
# Try to get stored content. If the content
- # is not available and we are inside the layout
- # fiber, we set that we are waiting for the given
- # key and yield.
+ # is not available and we're inside the layout fiber,
+ # then it will begin waiting for the given key and yield.
def get(key)
return super if @content.key?(key)
@@ -60,8 +59,8 @@ module ActionView
end
# Appends the contents for the given key. This is called
- # by provides and resumes back to the fiber if it is
- # the key it is waiting for.
+ # by providing and resuming back to the fiber,
+ # if that's the key it's waiting for.
def append!(key, value)
super
@fiber.resume if @waiting_for == key
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index 23d5319579..5fc4f3f1b9 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -6,9 +6,9 @@ module ActionView
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta1.1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb
index dba70e284e..c875f5870f 100644
--- a/actionview/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb
@@ -132,7 +132,7 @@ module ActionView
end
private
- # Delegate to xml builder, first wrapping the element in a xhtml
+ # Delegate to xml builder, first wrapping the element in an xhtml
# namespaced div element if the method and arguments indicate
# that an xhtml_block? is desired.
def method_missing(method, *arguments, &block)
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 18b2102d73..4eaaa239e2 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -41,11 +41,11 @@ module ActionView
#
# ==== \Template digest
#
- # The template digest that's added to the cache key is computed by taking an md5 of the
+ # The template digest that's added to the cache key is computed by taking an MD5 of the
# contents of the entire template file. This ensures that your caches will automatically
# expire when you change the template file.
#
- # Note that the md5 is taken of the entire template file, not just what's within the
+ # Note that the MD5 is taken of the entire template file, not just what's within the
# cache do/end call. So it's possible that changing something outside of that call will
# still expire the cache.
#
@@ -118,7 +118,7 @@ module ActionView
#
# If you use a helper method, for example, inside a cached block and
# you then update that helper, you'll have to bump the cache as well.
- # It doesn't really matter how you do it, but the md5 of the template file
+ # It doesn't really matter how you do it, but the MD5 of the template file
# must change. One recommendation is to simply be explicit in a comment, like:
#
# <%# Helper Dependency Updated: May 6, 2012 at 6pm %>
@@ -126,47 +126,33 @@ module ActionView
#
# Now all you have to do is change that timestamp when the helper method changes.
#
- # === Automatic Collection Caching
+ # === Collection Caching
#
- # When rendering collections such as:
+ # When rendering a collection of objects that each use the same partial, a `cached`
+ # option can be passed.
+ # For collections rendered such:
#
- # <%= render @notifications %>
- # <%= render partial: 'notifications/notification', collection: @notifications %>
+ # <%= render partial: 'notifications/notification', collection: @notifications, cached: true %>
#
- # If the notifications/_notification partial starts with a cache call as:
+ # The `cached: true` will make Action View's rendering read several templates
+ # from cache at once instead of one call per template.
#
- # <% 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:
+ # Templates in the collection not already cached are written to cache.
#
- # <%= render @notifications, cache: false %>
+ # Works great alongside individual template fragment caching.
+ # For instance if the template the collection renders is cached like:
#
- # === Explicit Collection Caching
- #
- # If the partial template doesn't start with a clean cache call as
- # mentioned above, you can still benefit from collection caching by
- # adding a special comment format anywhere in the template, like:
- #
- # <%# Template Collection: notification %>
- # <% my_helper_that_calls_cache(some_arg, notification) do %>
- # <%= notification.name %>
+ # # notifications/_notification.html.erb
+ # <% cache notification do %>
+ # <%# ... %>
# <% end %>
#
- # The pattern used to match these is <tt>/# Template Collection: (\S+)/</tt>,
- # so it's important that you type it out just so.
- # You can only declare one collection in a partial template file.
+ # Any collection renders will find those cached templates when attempting
+ # to read multiple templates at once.
def cache(name = {}, options = {}, &block)
if controller.respond_to?(:perform_caching) && controller.perform_caching
- safe_concat(fragment_for(cache_fragment_name(name, options), options, &block))
+ name_options = options.slice(:skip_digest, :virtual_path)
+ safe_concat(fragment_for(cache_fragment_name(name, name_options), options, &block))
else
yield
end
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 233e613e97..04dcf01bb7 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -3,6 +3,7 @@ require 'action_view/helpers/tag_helper'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/date/conversions'
require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/object/with_options'
module ActionView
@@ -226,8 +227,10 @@ module ActionView
# for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>.
# Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds)
# or the given prompt string.
- # * <tt>:with_css_classes</tt> - Set to true if you want assign different styles for 'select' tags. This option
- # automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second' for your 'select' tags.
+ # * <tt>:with_css_classes</tt> - Set to true or a hash of strings. Use true if you want to assign generic styles for
+ # select tags. This automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second'. A hash of
+ # strings for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, <tt>:second</tt>
+ # will extend the select type with the given value. Use +html_options+ to modify every select tag in the set.
# * <tt>:use_hidden</tt> - Set to true if you only want to generate hidden input tags.
#
# If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
@@ -994,7 +997,7 @@ module ActionView
:name => input_name_from_type(type)
}.merge!(@html_options)
select_options[:disabled] = 'disabled' if @options[:disabled]
- select_options[:class] = [select_options[:class], type].compact.join(' ') if @options[:with_css_classes]
+ select_options[:class] = css_class_attribute(type, select_options[:class], @options[:with_css_classes]) if @options[:with_css_classes]
select_html = "\n"
select_html << content_tag("option".freeze, '', :value => '') + "\n" if @options[:include_blank]
@@ -1004,6 +1007,20 @@ module ActionView
(content_tag("select".freeze, select_html.html_safe, select_options) + "\n").html_safe
end
+ # Builds the css class value for the select element
+ # css_class_attribute(:year, 'date optional', { year: 'my-year' })
+ # => "date optional my-year"
+ def css_class_attribute(type, html_options_class, options) # :nodoc:
+ css_class = case options
+ when Hash
+ options[type.to_sym]
+ else
+ type
+ end
+
+ [html_options_class, css_class].compact.join(' ')
+ end
+
# Builds a prompt option tag with supplied options or from default options.
# prompt_option_tag(:month, prompt: 'Select month')
# => "<option value="">Select month</option>"
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index c1015ffe89..be5010cd9c 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -1066,7 +1066,7 @@ module ActionView
# Returns a text_field of type "time".
#
# The default value is generated by trying to call +strftime+ with "%T.%L"
- # on the objects's value. It is still possible to override that
+ # on the object's value. It is still possible to override that
# by passing the "value" option.
#
# === Options
@@ -1118,6 +1118,10 @@ module ActionView
# # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
#
def datetime_field(object_name, method, options = {})
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ datetime_field is deprecated and will be removed in Rails 5.1.
+ Use datetime_local_field instead.
+ MESSAGE
Tags::DatetimeField.new(object_name, method, self, options).render
end
@@ -1922,8 +1926,6 @@ module ActionView
@object_name.to_s.humanize
end
- model = model.downcase
-
defaults = []
defaults << :"helpers.submit.#{object_name}.#{key}"
defaults << :"helpers.submit.#{key}"
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 430051379d..b277efd7b6 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -268,10 +268,11 @@ module ActionView
# for more information.)
#
# You can also supply an array of ActiveSupport::TimeZone objects
- # as +priority_zones+, so that they will be listed above the rest of the
- # (long) list. (You can use ActiveSupport::TimeZone.us_zones as a convenience
- # for obtaining a list of the US time zones, or a Regexp to select the zones
- # of your choice)
+ # as +priority_zones+ so that they will be listed above the rest of the
+ # (long) list. You can use ActiveSupport::TimeZone.us_zones for a list
+ # of US time zones, ActiveSupport::TimeZone.country_zones(country_code)
+ # for another country's time zones, or a Regexp to select the zones of
+ # your choice.
#
# Finally, this method supports a <tt>:default</tt> option, which selects
# a default ActiveSupport::TimeZone if the object's time zone is +nil+.
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 55dac74d00..82f2fd30c7 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -134,13 +134,15 @@ module ActionView
if options.include?(:include_blank)
include_blank = options.delete(:include_blank)
+ options_for_blank_options_tag = { value: '' }
if include_blank == true
include_blank = ''
+ options_for_blank_options_tag[:label] = ' '
end
if include_blank
- option_tags = content_tag("option".freeze, include_blank, value: '').safe_concat(option_tags)
+ option_tags = content_tag("option".freeze, include_blank, options_for_blank_options_tag).safe_concat(option_tags)
end
end
@@ -691,6 +693,10 @@ module ActionView
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def datetime_field_tag(name, value = nil, options = {})
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ datetime_field_tag is deprecated and will be removed in Rails 5.1.
+ Use datetime_local_field_tag instead.
+ MESSAGE
text_field_tag(name, value, options.merge(type: :datetime))
end
@@ -862,13 +868,13 @@ module ActionView
def extra_tags_for_form(html_options)
authenticity_token = html_options.delete("authenticity_token")
- method = html_options.delete("method").to_s
+ method = html_options.delete("method").to_s.downcase
method_tag = case method
- when /^get$/i # must be case-insensitive, but can't use downcase as might be nil
+ when 'get'
html_options["method"] = "get"
''
- when /^post$/i, "", nil
+ when 'post', ''
html_options["method"] = "post"
token_tag(authenticity_token, form_options: {
action: html_options["action"],
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index 161aa031c6..23081c5f07 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -23,7 +23,7 @@ module ActionView
end
end
- # Formats a +number+ into a US phone number (e.g., (555)
+ # Formats a +number+ into a phone number (US by default e.g., (555)
# 123-9876). You can customize the format in the +options+ hash.
#
# ==== Options
@@ -35,6 +35,8 @@ module ActionView
# end of the generated number.
# * <tt>:country_code</tt> - Sets the country code for the phone
# number.
+ # * <tt>:pattern</tt> - Specifies how the number is divided into three
+ # groups with the custom regexp to override the default format.
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
@@ -52,6 +54,11 @@ module ActionView
#
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: ".")
# # => +1.123.555.1234 x 1343
+ #
+ # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
+ # # => "(755) 6123-4567"
+ # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/))
+ # # => "133-1234-5678"
def number_to_phone(number, options = {})
return unless number
options = options.symbolize_keys
@@ -256,8 +263,6 @@ module ActionView
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
# insignificant zeros after the decimal separator (defaults to
# +true+)
- # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
- # prefix (defaults to :binary)
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb
index c0fc3b820f..d4b55423a8 100644
--- a/actionview/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionview/lib/action_view/helpers/output_safety_helper.rb
@@ -33,6 +33,36 @@ module ActionView #:nodoc:
array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe
end
+
+ # Converts the array to a comma-separated sentence where the last element is
+ # joined by the connector word. This is the html_safe-aware version of
+ # ActiveSupport's {Array#to_sentence}[http://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
+ #
+ def to_sentence(array, options = {})
+ options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
+
+ default_connectors = {
+ :words_connector => ', ',
+ :two_words_connector => ' and ',
+ :last_word_connector => ', and '
+ }
+ if defined?(I18n)
+ i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {})
+ default_connectors.merge!(i18n_connectors)
+ end
+ options = default_connectors.merge!(options)
+
+ case array.length
+ when 0
+ ''.html_safe
+ when 1
+ ERB::Util.html_escape(array[0])
+ when 2
+ safe_join([array[0], array[1]], options[:two_words_connector])
+ else
+ safe_join([safe_join(array[0...-1], options[:words_connector]), options[:last_word_connector], array[-1]])
+ end
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index 191a881de0..f9784c3483 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -120,7 +120,7 @@ module ActionView
attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
# Vendors the full, link and white list sanitizers.
- # Provided strictly for compatibility and can be removed in Rails 5.
+ # Provided strictly for compatibility and can be removed in Rails 5.1.
def sanitizer_vendor
Rails::Html::Sanitizer
end
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index 2562504896..42e7358a1d 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -154,6 +154,7 @@ module ActionView
options.each_pair do |key, value|
if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
value.each_pair do |k, v|
+ next if v.nil?
output << sep
output << prefix_tag_option(key, k, v, escape)
end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 3a4561a083..11c7daf4da 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -302,7 +302,7 @@ module ActionView
params = html_options.delete('params')
method = html_options.delete('method').to_s
- method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : ''.html_safe
+ method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : ''.freeze.html_safe
form_method = method == 'get' ? 'get' : 'post'
form_options = html_options.delete('form') || {}
@@ -312,9 +312,10 @@ module ActionView
form_options[:'data-remote'] = true if remote
request_token_tag = if form_method == 'post'
- token_tag(nil, form_options: form_options)
+ request_method = method.empty? ? 'post' : method
+ token_tag(nil, form_options: { action: url, method: request_method })
else
- ''
+ ''.freeze
end
html_options = convert_options_to_data_attributes(options, html_options)
@@ -329,8 +330,8 @@ module ActionView
inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
if params
- params.each do |param_name, value|
- inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param)
+ to_form_params(params).each do |param|
+ inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value])
end
end
content_tag('form', inner_tags, form_options)
@@ -480,7 +481,7 @@ module ActionView
option = html_options.delete(item).presence || next
"#{item.dasherize}=#{ERB::Util.url_encode(option)}"
}.compact
- extras = extras.empty? ? '' : '?' + extras.join('&')
+ extras = extras.empty? ? ''.freeze : '?' + extras.join('&')
encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@")
html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
@@ -558,29 +559,29 @@ module ActionView
def convert_options_to_data_attributes(options, html_options)
if html_options
html_options = html_options.stringify_keys
- html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options)
+ html_options['data-remote'] = 'true'.freeze if link_to_remote_options?(options) || link_to_remote_options?(html_options)
- method = html_options.delete('method')
+ method = html_options.delete('method'.freeze)
add_method_to_attributes!(html_options, method) if method
html_options
else
- link_to_remote_options?(options) ? {'data-remote' => 'true'} : {}
+ link_to_remote_options?(options) ? {'data-remote' => 'true'.freeze} : {}
end
end
def link_to_remote_options?(options)
if options.is_a?(Hash)
- options.delete('remote') || options.delete(:remote)
+ options.delete('remote'.freeze) || options.delete(:remote)
end
end
def add_method_to_attributes!(html_options, method)
- if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/
- html_options["rel"] = "#{html_options["rel"]} nofollow".lstrip
+ if method && method.to_s.downcase != "get".freeze && html_options["rel".freeze] !~ /nofollow/
+ html_options["rel".freeze] = "#{html_options["rel".freeze]} nofollow".lstrip
end
- html_options["data-method"] = method
+ html_options["data-method".freeze] = method
end
def token_tag(token=nil, form_options: {})
@@ -588,13 +589,49 @@ module ActionView
token ||= form_authenticity_token(form_options: form_options)
tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
else
- ''
+ ''.freeze
end
end
def method_tag(method)
tag('input', type: 'hidden', name: '_method', value: method.to_s)
end
+
+ # Returns an array of hashes each containing :name and :value keys
+ # suitable for use as the names and values of form input fields:
+ #
+ # to_form_params(name: 'David', nationality: 'Danish')
+ # # => [{name: :name, value: 'David'}, {name: 'nationality', value: 'Danish'}]
+ #
+ # to_form_params(country: {name: 'Denmark'})
+ # # => [{name: 'country[name]', value: 'Denmark'}]
+ #
+ # to_form_params(countries: ['Denmark', 'Sweden']})
+ # # => [{name: 'countries[]', value: 'Denmark'}, {name: 'countries[]', value: 'Sweden'}]
+ #
+ # An optional namespace can be passed to enclose key names:
+ #
+ # to_form_params({ name: 'Denmark' }, 'country')
+ # # => [{name: 'country[name]', value: 'Denmark'}]
+ def to_form_params(attribute, namespace = nil) # :nodoc:
+ params = []
+ case attribute
+ when Hash
+ attribute.each do |key, value|
+ prefix = namespace ? "#{namespace}[#{key}]" : key
+ params.push(*to_form_params(value, prefix))
+ end
+ when Array
+ array_prefix = "#{namespace}[]"
+ attribute.each do |value|
+ params.push(*to_form_params(value, array_prefix))
+ end
+ else
+ params << { name: namespace, value: attribute.to_param }
+ end
+
+ params.sort_by { |pair| pair[:name] }
+ end
end
end
end
diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb
index 9047dbdd85..5a29c68214 100644
--- a/actionview/lib/action_view/log_subscriber.rb
+++ b/actionview/lib/action_view/log_subscriber.rb
@@ -20,7 +20,23 @@ module ActionView
end
end
alias :render_partial :render_template
- alias :render_collection :render_template
+
+ def render_collection(event)
+ identifier = event.payload[:identifier] || 'templates'
+
+ info do
+ " Rendered collection of #{from_rails_root(identifier)}" \
+ " #{render_count(event.payload)} (#{event.duration.round(1)}ms)"
+ end
+ end
+
+ def start(name, id, payload)
+ if name == "render_template.action_view"
+ log_rendering_start(payload)
+ end
+
+ super
+ end
def logger
ActionView::Base.logger
@@ -38,6 +54,24 @@ module ActionView
def rails_root
@root ||= "#{Rails.root}/"
end
+
+ def render_count(payload)
+ if payload[:cache_hits]
+ "[#{payload[:cache_hits]} / #{payload[:count]} cache hits]"
+ else
+ "[#{payload[:count]} times]"
+ end
+ end
+
+ private
+
+ def log_rendering_start(payload)
+ info do
+ message = " Rendering #{from_rails_root(payload[:identifier])}"
+ message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
+ message
+ end
+ end
end
end
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 6a76d80c47..9db1460ee7 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -22,7 +22,7 @@ module ActionView
def self.register_detail(name, &block)
self.registered_details << name
- initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }
+ Accessors::DEFAULT_PROCS[name] = block
Accessors.send :define_method, :"default_#{name}", &block
Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
@@ -34,16 +34,12 @@ module ActionView
value = value.present? ? Array(value) : default_#{name}
_set_detail(:#{name}, value) if value != @details[:#{name}]
end
-
- remove_possible_method :initialize_details
- def initialize_details(details)
- #{initialize.join("\n")}
- end
METHOD
end
# Holds accessors for the registered details.
module Accessors #:nodoc:
+ DEFAULT_PROCS = {}
end
register_detail(:locale) do
@@ -59,9 +55,7 @@ module ActionView
class DetailsKey #:nodoc:
alias :eql? :equal?
- alias :object_hash :hash
- attr_reader :hash
@details_keys = Concurrent::Map.new
def self.get(details)
@@ -69,15 +63,15 @@ module ActionView
details = details.dup
details[:formats] &= Template::Types.symbols
end
- @details_keys[details] ||= new
+ @details_keys[details] ||= Concurrent::Map.new
end
def self.clear
@details_keys.clear
end
- def initialize
- @hash = object_hash
+ def self.digest_caches
+ @details_keys.values
end
end
@@ -136,6 +130,11 @@ module ActionView
end
alias :template_exists? :exists?
+ def any?(name, prefixes = [], partial = false)
+ @view_paths.exists?(*args_for_any(name, prefixes, partial))
+ end
+ alias :any_templates? :any?
+
# Adds fallbacks to the view paths. Useful in cases when you are rendering
# a :file.
def with_fallbacks
@@ -172,6 +171,32 @@ module ActionView
[user_details, details_key]
end
+ def args_for_any(name, prefixes, partial) # :nodoc:
+ name, prefixes = normalize_name(name, prefixes)
+ details, details_key = detail_args_for_any
+ [name, prefixes, partial || false, details, details_key]
+ end
+
+ def detail_args_for_any # :nodoc:
+ @detail_args_for_any ||= begin
+ details = {}
+
+ registered_details.each do |k|
+ if k == :variants
+ details[k] = :any
+ else
+ details[k] = Accessors::DEFAULT_PROCS[k].call
+ end
+ end
+
+ if @cache
+ [details, DetailsKey.get(details)]
+ else
+ [details, nil]
+ end
+ end
+ end
+
# Support legacy foo.erb names even though we now ignore .erb
# as well as incorrectly putting part of the path in the template
# name instead of the prefix.
@@ -195,15 +220,27 @@ module ActionView
include ViewPaths
def initialize(view_paths, details = {}, prefixes = [])
- @details, @details_key = {}, nil
+ @details_key = nil
@cache = true
@prefixes = prefixes
@rendered_format = nil
+ @details = initialize_details({}, details)
self.view_paths = view_paths
- initialize_details(details)
end
+ def digest_cache
+ details_key
+ end
+
+ def initialize_details(target, details)
+ registered_details.each do |k|
+ target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call
+ end
+ target
+ end
+ private :initialize_details
+
# Override formats= to expand ["*/*"] values and automatically
# add :html as fallback to :js.
def formats=(values)
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index 59d869d92d..dfb99f4ea9 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -37,16 +37,10 @@ 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.per_request_digest_cache" do |app|
ActiveSupport.on_load(:action_view) do
if app.config.consider_all_requests_local
- app.middleware.use ActionView::Digestor::PerRequestDigestCacheExpiry
+ app.executor.to_run { ActionView::LookupContext::DetailsKey.clear }
end
end
end
@@ -57,9 +51,13 @@ module ActionView
end
end
+ initializer "action_view.collection_caching", after: "action_controller.set_configs" do |app|
+ PartialRenderer.collection_cache = app.config.action_controller.cache_store
+ end
+
rake_tasks do |app|
unless app.config.api_only
- load "action_view/tasks/dependencies.rake"
+ load "action_view/tasks/cache_digests.rake"
end
end
end
diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb
index aa77a77acf..1dddf53df0 100644
--- a/actionview/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionview/lib/action_view/renderer/abstract_renderer.rb
@@ -15,7 +15,7 @@ module ActionView
# that new object is called in turn. This abstracts the setup and rendering
# into a separate classes for partials and templates.
class AbstractRenderer #:nodoc:
- delegate :find_template, :find_file, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
+ delegate :find_template, :find_file, :template_exists?, :any_templates?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
def initialize(lookup_context)
@lookup_context = lookup_context
@@ -35,8 +35,12 @@ module ActionView
end
end
- def instrument(name, options={})
- ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield }
+ def instrument(name, **options)
+ options[:identifier] ||= (@template && @template.identifier) || @path
+
+ ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
+ yield payload
+ end
end
def prepend_formats(formats)
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index bdbf03191a..13b4ec6133 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -294,7 +294,7 @@ module ActionView
def render(context, options, block)
setup(context, options, block)
- identifier = (@template = find_partial) ? @template.identifier : @path
+ @template = find_partial
@lookup_context.rendered_format ||= begin
if @template && @template.formats.present?
@@ -305,11 +305,9 @@ module ActionView
end
if @collection
- instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
- render_collection
- end
+ render_collection
else
- instrument(:partial, :identifier => identifier) do
+ instrument(:partial) do
render_partial
end
end
@@ -318,15 +316,17 @@ module ActionView
private
def render_collection
- return nil if @collection.blank?
+ instrument(:collection, count: @collection.size) do |payload|
+ return nil if @collection.blank?
- if @options.key?(:spacer_template)
- spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
- end
+ if @options.key?(:spacer_template)
+ spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
+ end
- cache_collection_render do
- @template ? collection_with_template : collection_without_template
- end.join(spacer).html_safe
+ cache_collection_render(payload) do
+ @template ? collection_with_template : collection_without_template
+ end.join(spacer).html_safe
+ end
end
def render_partial
@@ -521,7 +521,7 @@ module ActionView
def retrieve_variable(path, as)
variable = as || begin
base = path[-1] == "/".freeze ? "".freeze : File.basename(path)
- raise_invalid_identifier(path) unless base =~ /\A_?(.*)(?:\.\w+)*\z/
+ raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
$1.to_sym
end
if @collection
diff --git a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
index 1147963882..f7deba94ce 100644
--- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/object/try'
-
module ActionView
module CollectionCaching # :nodoc:
extend ActiveSupport::Concern
@@ -11,42 +9,25 @@ module ActionView
end
private
- def cache_collection_render
- return yield unless cache_collection?
+ def cache_collection_render(instrumentation_payload)
+ return yield unless @options[:cached]
keyed_collection = collection_by_cache_keys
- partial_cache = collection_cache.read_multi(*keyed_collection.keys)
+ cached_partials = collection_cache.read_multi(*keyed_collection.keys)
+ instrumentation_payload[:cache_hits] = cached_partials.size
- @collection = keyed_collection.reject { |key, _| partial_cache.key?(key) }.values
- rendered_partials = @collection.any? ? yield.dup : []
+ @collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
+ rendered_partials = @collection.empty? ? [] : yield
- fetch_or_cache_partial(partial_cache, order_by: keyed_collection.each_key) do
- rendered_partials.shift
+ index = 0
+ fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do
+ rendered_partials[index].tap { index += 1 }
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
+ hash[expanded_cache_key(item)] = item
end
end
@@ -56,12 +37,10 @@ module ActionView
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
+ order_by.map do |cache_key|
+ cached_partials.fetch(cache_key) do
yield.tap do |rendered_partial|
- collection_cache.write(key, rendered_partial, cache_options)
+ collection_cache.write(cache_key, rendered_partial)
end
end
end
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index 9d15bbfca7..1d6afb90fe 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -84,13 +84,13 @@ module ActionView
when String
begin
if layout =~ /^\//
- with_fallbacks { find_template(layout, nil, false, keys, details) }
+ with_fallbacks { find_template(layout, nil, false, [], details) }
else
- find_template(layout, nil, false, keys, details)
+ find_template(layout, nil, false, [], details)
end
rescue ActionView::MissingTemplate
all_details = @details.merge(:formats => @lookup_context.default_formats)
- raise unless template_exists?(layout, nil, false, keys, all_details)
+ raise unless template_exists?(layout, nil, false, [], all_details)
end
when Proc
resolve_layout(layout.call(formats), keys, formats)
diff --git a/actionview/lib/action_view/tasks/dependencies.rake b/actionview/lib/action_view/tasks/cache_digests.rake
index f394c319c1..045bdf5691 100644
--- a/actionview/lib/action_view/tasks/dependencies.rake
+++ b/actionview/lib/action_view/tasks/cache_digests.rake
@@ -2,13 +2,13 @@ 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: CacheDigests.template_name, finder: CacheDigests.finder).nested_dependencies
+ puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:to_dep_map)
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: CacheDigests.template_name, finder: CacheDigests.finder).dependencies
+ puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:name)
end
class CacheDigests
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 15fc2b71a3..169ee55fdc 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -130,7 +130,6 @@ module ActionView
@source = source
@identifier = identifier
@handler = handler
- @cache_name = extract_resource_cache_name
@compiled = false
@original_encoding = nil
@locals = details[:locals] || []
@@ -166,10 +165,6 @@ 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
@@ -355,23 +350,5 @@ module ActionView
ActiveSupport::Notifications.instrument("#{action}.action_view".freeze, payload, &block)
end
end
-
- EXPLICIT_COLLECTION = /# Template Collection: (?<resource_name>\w+)/
-
- def extract_resource_cache_name
- if match = @source.match(EXPLICIT_COLLECTION) || resource_cache_call_match
- match[:resource_name]
- end
- end
-
- def resource_cache_call_match
- if @handler.respond_to?(:resource_cache_call_pattern)
- @source.match(@handler.resource_cache_call_pattern)
- end
- end
-
- def inferred_cache_name
- @inferred_cache_name ||= @virtual_path.split('/'.freeze).last.sub('_'.freeze, ''.freeze)
- end
end
end
diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb
index b03b197cb5..3f38c3d2b9 100644
--- a/actionview/lib/action_view/template/error.rb
+++ b/actionview/lib/action_view/template/error.rb
@@ -59,6 +59,9 @@ module ActionView
class Error < ActionViewError #:nodoc:
SOURCE_CODE_RADIUS = 3
+ # Override to prevent #cause resetting during re-raise.
+ attr_reader :cause
+
def initialize(template, original_exception = nil)
if original_exception
ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
@@ -67,6 +70,7 @@ module ActionView
super($!.message)
set_backtrace($!.backtrace)
+ @cause = $!
@template, @sub_templates = template, nil
end
@@ -131,13 +135,13 @@ module ActionView
end
def formatted_code_for(source_code, line_counter, indent, output)
- start_value = (output == :html) ? {} : ""
+ start_value = (output == :html) ? {} : []
source_code.inject(start_value) do |result, line|
line_counter += 1
if output == :html
result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line])
else
- result << "%#{indent}s: %s\n" % [line_counter, line]
+ result << "%#{indent}s: %s" % [line_counter, line]
end
end
end
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index 1f8459c24b..85a100ed4c 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -123,31 +123,6 @@ 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 captured as :resource_name.
- #
- # <% 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
- (?:<%\#.*%>)* # optional initial comment
- \s* # followed by optional spaces or newlines
- <%\s*cache[\(\s] # followed by an ERB call to cache
- \s* # followed by optional spaces or newlines
- (?<resource_name>\w+) # capture the cache call argument as :resource_name
- [\s\)] # followed by a space or close paren
- /xm
- end
-
private
def valid_encoding(string, encoding)
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 8a675cd521..bf68e93c58 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -55,6 +55,10 @@ module ActionView
@query_cache = SmallCache.new
end
+ def inspect
+ "#<#{self.class.name}:0x#{(object_id << 1).to_s(16)} keys=#{@data.size} queries=#{@query_cache.size}>"
+ end
+
# Cache the templates returned by the block
def cache(key, name, prefix, partial, locals)
if Resolver.caching?
@@ -84,6 +88,23 @@ module ActionView
@query_cache.clear
end
+ # Get the cache size. Do not call this
+ # method. This method is not guaranteed to be here ever.
+ def size # :nodoc:
+ size = 0
+ @data.each_value do |v1|
+ v1.each_value do |v2|
+ v2.each_value do |v3|
+ v3.each_value do |v4|
+ size += v4.size
+ end
+ end
+ end
+ end
+
+ size + @query_cache.size
+ end
+
private
def canonical_no_templates(templates)
@@ -222,7 +243,7 @@ module ActionView
end
def find_template_paths(query)
- Dir[query].reject do |filename|
+ Dir[query].uniq.reject do |filename|
File.directory?(filename) ||
# deals with case-insensitive file systems.
!File.fnmatch(query, filename, File::FNM_EXTGLOB)
@@ -245,8 +266,12 @@ module ActionView
partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
query.gsub!(/:action/, partial)
- details.each do |ext, variants|
- query.gsub!(/:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
+ details.each do |ext, candidates|
+ if ext == :variants && candidates == :any
+ query.gsub!(/:#{ext}/, "*")
+ else
+ query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}")
+ end
end
File.expand_path(query, @path)
@@ -340,7 +365,11 @@ module ActionView
query = escape_entry(File.join(@path, path))
exts = EXTENSIONS.map do |ext, prefix|
- "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
+ if ext == :variants && details[ext] == :any
+ "{#{prefix}*,}"
+ else
+ "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
+ end
end.join
query + exts
diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb
index c233d06ccb..b32567cd66 100644
--- a/actionview/lib/action_view/template/types.rb
+++ b/actionview/lib/action_view/template/types.rb
@@ -1,4 +1,3 @@
-require 'set'
require 'active_support/core_ext/module/attribute_accessors'
module ActionView
diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb
index 37722013ce..717d6866c5 100644
--- a/actionview/lib/action_view/view_paths.rb
+++ b/actionview/lib/action_view/view_paths.rb
@@ -1,5 +1,3 @@
-require 'action_view/base'
-
module ActionView
module ViewPaths
extend ActiveSupport::Concern
@@ -10,7 +8,7 @@ module ActionView
self._view_paths.freeze
end
- delegate :template_exists?, :view_paths, :formats, :formats=,
+ delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=,
:locale, :locale=, :to => :lookup_context
module ClassMethods
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 79173f730f..3256d8fc4d 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -1,5 +1,3 @@
-require File.expand_path('../../../load_paths', __FILE__)
-
$:.unshift(File.dirname(__FILE__) + '/lib')
$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
$:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers')
@@ -95,12 +93,14 @@ module ActionDispatch
super
return if DrawOnce.drew
- SharedTestRoutes.draw do
- get ':controller(/:action)'
- end
+ ActiveSupport::Deprecation.silence do
+ SharedTestRoutes.draw do
+ get ':controller(/:action)'
+ end
- ActionDispatch::IntegrationTest.app.routes.draw do
- get ':controller(/:action)'
+ ActionDispatch::IntegrationTest.app.routes.draw do
+ get ':controller(/:action)'
+ end
end
DrawOnce.drew = true
diff --git a/actionview/test/actionpack/abstract/layouts_test.rb b/actionview/test/actionpack/abstract/layouts_test.rb
index 80bc665b0a..78f6e78c61 100644
--- a/actionview/test/actionpack/abstract/layouts_test.rb
+++ b/actionview/test/actionpack/abstract/layouts_test.rb
@@ -12,7 +12,9 @@ module AbstractControllerTests
abstract!
self.view_paths = [ActionView::FixtureResolver.new(
+ "some/template.erb" => "hello <%= foo %> bar",
"layouts/hello.erb" => "With String <%= yield %>",
+ "layouts/hello_locals.erb" => "With String <%= yield %>",
"layouts/hello_override.erb" => "With Override <%= yield %>",
"layouts/overwrite.erb" => "Overwrite <%= yield %>",
"layouts/with_false_layout.erb" => "False Layout <%= yield %>",
@@ -32,6 +34,14 @@ module AbstractControllerTests
end
end
+ class WithStringLocals < Base
+ layout "hello_locals"
+
+ def index
+ render :template => 'some/template', locals: { foo: "less than 3" }
+ end
+ end
+
class WithString < Base
layout "hello"
@@ -39,6 +49,10 @@ module AbstractControllerTests
render :template => ActionView::Template::Text.new("Hello string!")
end
+ def action_has_layout_false
+ render template: ActionView::Template::Text.new("Hello string!")
+ end
+
def overwrite_default
render :template => ActionView::Template::Text.new("Hello string!"), :layout => :default
end
@@ -82,7 +96,7 @@ module AbstractControllerTests
end
end
- class WithProcReturningNil < Base
+ class WithProcReturningNil < WithString
layout proc { nil }
def index
@@ -90,6 +104,14 @@ module AbstractControllerTests
end
end
+ class WithProcReturningFalse < WithString
+ layout proc { false }
+
+ def index
+ render template: ActionView::Template::Text.new("Hello false!")
+ end
+ end
+
class WithZeroArityProc < Base
layout proc { "overwrite" }
@@ -189,6 +211,14 @@ module AbstractControllerTests
end
end
+ class WithOnlyConditionalFlipped < WithOnlyConditional
+ layout "hello_override", only: :index
+ end
+
+ class WithOnlyConditionalFlippedAndInheriting < WithOnlyConditional
+ layout nil, only: :index
+ end
+
class WithExceptConditional < WithStringImpliedChild
layout "overwrite", :except => :show
@@ -201,6 +231,45 @@ module AbstractControllerTests
end
end
+ class AbstractWithString < Base
+ layout "hello"
+ abstract!
+ end
+
+ class AbstractWithStringChild < AbstractWithString
+ def index
+ render template: ActionView::Template::Text.new("Hello abstract child!")
+ end
+ end
+
+ class AbstractWithStringChildDefaultsToInherited < AbstractWithString
+ layout nil
+
+ def index
+ render template: ActionView::Template::Text.new("Hello abstract child!")
+ end
+ end
+
+ class WithConditionalOverride < WithString
+ layout "overwrite", only: :overwritten
+
+ def non_overwritten
+ render template: ActionView::Template::Text.new("Hello non overwritten!")
+ end
+
+ def overwritten
+ render template: ActionView::Template::Text.new("Hello overwritten!")
+ end
+ end
+
+ class WithConditionalOverrideFlipped < WithConditionalOverride
+ layout "hello_override", only: :non_overwritten
+ end
+
+ class WithConditionalOverrideFlippedAndInheriting < WithConditionalOverride
+ layout nil, only: :non_overwritten
+ end
+
class TestBase < ActiveSupport::TestCase
test "when no layout is specified, and no default is available, render without a layout" do
controller = Blank.new
@@ -208,6 +277,31 @@ module AbstractControllerTests
assert_equal "Hello blank!", controller.response_body
end
+ test "with locals" do
+ controller = WithStringLocals.new
+ controller.process(:index)
+ assert_equal "With String hello less than 3 bar", controller.response_body
+ end
+
+ test "cache should not grow when locals change for a string template" do
+ cache = WithString.view_paths.paths.first.instance_variable_get(:@cache)
+
+ controller = WithString.new
+ controller.process(:index) # heat the cache
+
+ size = cache.size
+
+ 10.times do |x|
+ controller = WithString.new
+ controller.define_singleton_method :index do
+ render :template => ActionView::Template::Text.new("Hello string!"), :locals => { :"x#{x}" => :omg }
+ end
+ controller.process(:index)
+ end
+
+ assert_equal size, cache.size
+ end
+
test "when layout is specified as a string, render with that layout" do
controller = WithString.new
controller.process(:index)
@@ -264,10 +358,16 @@ module AbstractControllerTests
assert_equal "Overwrite Hello proc!", controller.response_body
end
- test "when layout is specified as a proc and the proc returns nil, don't use a layout" do
+ test "when layout is specified as a proc and the proc returns nil, use inherited layout" do
controller = WithProcReturningNil.new
controller.process(:index)
- assert_equal "Hello nil!", controller.response_body
+ assert_equal "With String Hello nil!", controller.response_body
+ end
+
+ test "when layout is specified as a proc and the proc returns false, use no layout instead of inherited layout" do
+ controller = WithProcReturningFalse.new
+ controller.process(:index)
+ assert_equal "Hello false!", controller.response_body
end
test "when layout is specified as a proc without parameters it works just the same" do
@@ -328,12 +428,24 @@ module AbstractControllerTests
end
test "when a grandchild has nil layout specified, the child has an implied layout, and the " \
- "parent has specified a layout, use the child controller layout" do
+ "parent has specified a layout, use the grand child controller layout" do
controller = WithGrandChildOfImplied.new
controller.process(:index)
assert_equal "With Grand Child Hello string!", controller.response_body
end
+ test "a child inherits layout from abstract controller" do
+ controller = AbstractWithStringChild.new
+ controller.process(:index)
+ assert_equal "With String Hello abstract child!", controller.response_body
+ end
+
+ test "a child inherits layout from abstract controller2" do
+ controller = AbstractWithStringChildDefaultsToInherited.new
+ controller.process(:index)
+ assert_equal "With String Hello abstract child!", controller.response_body
+ end
+
test "raises an exception when specifying layout true" do
assert_raises ArgumentError do
Object.class_eval do
@@ -356,6 +468,30 @@ module AbstractControllerTests
assert_equal "With Implied Hello index!", controller.response_body
end
+ test "when specify an :only option which match current action name and is opposite from parent controller" do
+ controller = WithOnlyConditionalFlipped.new
+ controller.process(:show)
+ assert_equal "With Implied Hello show!", controller.response_body
+ end
+
+ test "when specify an :only option which does not match current action name and is opposite from parent controller" do
+ controller = WithOnlyConditionalFlipped.new
+ controller.process(:index)
+ assert_equal "With Override Hello index!", controller.response_body
+ end
+
+ test "when specify to inherit and an :only option which match current action name and is opposite from parent controller" do
+ controller = WithOnlyConditionalFlippedAndInheriting.new
+ controller.process(:show)
+ assert_equal "With Implied Hello show!", controller.response_body
+ end
+
+ test "when specify to inherit and an :only option which does not match current action name and is opposite from parent controller" do
+ controller = WithOnlyConditionalFlippedAndInheriting.new
+ controller.process(:index)
+ assert_equal "Overwrite Hello index!", controller.response_body
+ end
+
test "when specify an :except option which match current action name" do
controller = WithExceptConditional.new
controller.process(:show)
@@ -368,6 +504,42 @@ module AbstractControllerTests
assert_equal "Overwrite Hello index!", controller.response_body
end
+ test "when specify overwrite as an :only option which match current action name" do
+ controller = WithConditionalOverride.new
+ controller.process(:overwritten)
+ assert_equal "Overwrite Hello overwritten!", controller.response_body
+ end
+
+ test "when specify overwrite as an :only option which does not match current action name" do
+ controller = WithConditionalOverride.new
+ controller.process(:non_overwritten)
+ assert_equal "Hello non overwritten!", controller.response_body
+ end
+
+ test "when specify overwrite as an :only option which match current action name and is opposite from parent controller" do
+ controller = WithConditionalOverrideFlipped.new
+ controller.process(:overwritten)
+ assert_equal "Hello overwritten!", controller.response_body
+ end
+
+ test "when specify overwrite as an :only option which does not match current action name and is opposite from parent controller" do
+ controller = WithConditionalOverrideFlipped.new
+ controller.process(:non_overwritten)
+ assert_equal "With Override Hello non overwritten!", controller.response_body
+ end
+
+ test "when specify to inherit and overwrite as an :only option which match current action name and is opposite from parent controller" do
+ controller = WithConditionalOverrideFlippedAndInheriting.new
+ controller.process(:overwritten)
+ assert_equal "Hello overwritten!", controller.response_body
+ end
+
+ test "when specify to inherit and overwrite as an :only option which does not match current action name and is opposite from parent controller" do
+ controller = WithConditionalOverrideFlippedAndInheriting.new
+ controller.process(:non_overwritten)
+ assert_equal "Overwrite Hello non overwritten!", controller.response_body
+ end
+
test "layout for anonymous controller" do
klass = Class.new(WithString) do
def index
@@ -379,6 +551,17 @@ module AbstractControllerTests
controller.process(:index)
assert_equal "With String index", controller.response_body
end
+
+ test "when layout is disabled with #action_has_layout? returning false, render no layout" do
+ controller = WithString.new
+ controller.instance_eval do
+ def action_has_layout?
+ false
+ end
+ end
+ controller.process(:action_has_layout_false)
+ assert_equal "Hello string!", controller.response_body
+ end
end
end
end
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index bdb9e0397b..7b69ffc628 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -726,11 +726,6 @@ class RenderTest < ActionController::TestCase
assert_equal "Elastica", @response.body
end
- def test_render_process
- get :render_action_hello_world_as_string
- assert_equal "Hello world!", @controller.process(:render_action_hello_world_as_string)
- end
-
# :ported:
def test_render_from_variable
get :render_hello_world_from_variable
diff --git a/actionview/test/activerecord/debug_helper_test.rb b/actionview/test/activerecord/debug_helper_test.rb
index 03cb1d5a91..ed1c08e134 100644
--- a/actionview/test/activerecord/debug_helper_test.rb
+++ b/actionview/test/activerecord/debug_helper_test.rb
@@ -4,7 +4,10 @@ require 'nokogiri'
class DebugHelperTest < ActionView::TestCase
def test_debug
company = Company.new(name: "firebase")
- assert_match "name: firebase", debug(company)
+ output = debug(company)
+ assert_match "name: name", output
+ assert_match "value_before_type_cast: firebase", output
+ assert_match "active_record_yaml_version: 2", output
end
def test_debug_with_marshal_error
diff --git a/actionview/test/fixtures/digestor/api/comments/_comment.json.erb b/actionview/test/fixtures/digestor/api/comments/_comment.json.erb
new file mode 100644
index 0000000000..696eb13917
--- /dev/null
+++ b/actionview/test/fixtures/digestor/api/comments/_comment.json.erb
@@ -0,0 +1 @@
+{"content": "Great story!"}
diff --git a/actionview/test/fixtures/digestor/api/comments/_comments.json.erb b/actionview/test/fixtures/digestor/api/comments/_comments.json.erb
new file mode 100644
index 0000000000..c28646a283
--- /dev/null
+++ b/actionview/test/fixtures/digestor/api/comments/_comments.json.erb
@@ -0,0 +1 @@
+<%= render partial: "comments/comment", collection: commentable.comments %>
diff --git a/actionview/test/fixtures/digestor/messages/peek.html.erb b/actionview/test/fixtures/digestor/messages/peek.html.erb
new file mode 100644
index 0000000000..84885ab0bc
--- /dev/null
+++ b/actionview/test/fixtures/digestor/messages/peek.html.erb
@@ -0,0 +1,2 @@
+<%# Template Dependency: messages/message %>
+<%= render "comments/comments" %>
diff --git a/actionview/test/fixtures/digestor/messages/thread.json.erb b/actionview/test/fixtures/digestor/messages/thread.json.erb
new file mode 100644
index 0000000000..e4c1ba97cd
--- /dev/null
+++ b/actionview/test/fixtures/digestor/messages/thread.json.erb
@@ -0,0 +1 @@
+<%= render "comments/comments" %>
diff --git a/actionview/test/fixtures/test/_customer.mobile.erb b/actionview/test/fixtures/test/_customer.mobile.erb
new file mode 100644
index 0000000000..d8220afeda
--- /dev/null
+++ b/actionview/test/fixtures/test/_customer.mobile.erb
@@ -0,0 +1 @@
+Hello: <%= customer.name rescue "Anonymous" %> \ No newline at end of file
diff --git a/actionview/test/fixtures/test/_🍣.erb b/actionview/test/fixtures/test/_🍣.erb
new file mode 100644
index 0000000000..4bbe59410a
--- /dev/null
+++ b/actionview/test/fixtures/test/_🍣.erb
@@ -0,0 +1 @@
+🍣
diff --git a/actionview/test/lib/controller/fake_models.rb b/actionview/test/lib/controller/fake_models.rb
index a122fe17c9..65c68fc34a 100644
--- a/actionview/test/lib/controller/fake_models.rb
+++ b/actionview/test/lib/controller/fake_models.rb
@@ -31,27 +31,6 @@ end
class GoodCustomer < Customer
end
-class TicketType < Struct.new(:name)
- extend ActiveModel::Naming
- include ActiveModel::Conversion
- extend ActiveModel::Translation
-
- def initialize(*args)
- super
- @persisted = false
- end
-
- def persisted=(boolean)
- @persisted = boolean
- end
-
- def persisted?
- @persisted
- end
-
- attr_accessor :name
-end
-
class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost)
extend ActiveModel::Naming
include ActiveModel::Conversion
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index 4678998bdc..e67d5d0e8c 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -255,6 +255,22 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_day(16, :prompt => 'Choose day')
end
+ def test_select_day_with_generic_with_css_classes
+ expected = %(<select id="date_day" name="date[day]" class="day">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_day(16, with_css_classes: true)
+ end
+
+ def test_select_day_with_custom_with_css_classes
+ expected = %(<select id="date_day" name="date[day]" class="my-day">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_day(16, with_css_classes: { day: 'my-day' })
+ end
+
def test_select_month
expected = %(<select id="date_month" name="date[month]">\n)
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
@@ -408,6 +424,22 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_month(8, :prompt => 'Choose month')
end
+ def test_select_month_with_generic_with_css_classes
+ expected = %(<select id="date_month" name="date[month]" class="month">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_month(8, with_css_classes: true)
+ end
+
+ def test_select_month_with_custom_with_css_classes
+ expected = %(<select id="date_month" name="date[month]" class="my-month">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_month(8, with_css_classes: { month: 'my-month' })
+ end
+
def test_select_year
expected = %(<select id="date_year" name="date[year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
@@ -487,6 +519,22 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_year(nil, :start_year => 2003, :end_year => 2005, :prompt => 'Choose year')
end
+ def test_select_year_with_generic_with_css_classes
+ expected = %(<select id="date_year" name="date[year]" class="year">\n)
+ expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_year(nil, start_year: 2003, end_year: 2005, with_css_classes: true)
+ end
+
+ def test_select_year_with_custom_with_css_classes
+ expected = %(<select id="date_year" name="date[year]" class="my-year">\n)
+ expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_year(nil, start_year: 2003, end_year: 2005, with_css_classes: { year: 'my-year' })
+ end
+
def test_select_hour
expected = %(<select id="date_hour" name="date[hour]">\n)
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
@@ -559,6 +607,22 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => 'Choose hour')
end
+ def test_select_hour_with_generic_with_css_classes
+ expected = %(<select id="date_hour" name="date[hour]" class="hour">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: true)
+ end
+
+ def test_select_hour_with_custom_with_css_classes
+ expected = %(<select id="date_hour" name="date[hour]" class="my-hour">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: { hour: 'my-hour' })
+ end
+
def test_select_minute
expected = %(<select id="date_minute" name="date[minute]">\n)
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
@@ -647,6 +711,22 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => 'Choose minute')
end
+ def test_select_minute_with_generic_with_css_classes
+ expected = %(<select id="date_minute" name="date[minute]" class="minute">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: true)
+ end
+
+ def test_select_minute_with_custom_with_css_classes
+ expected = %(<select id="date_minute" name="date[minute]" class="my-minute">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: { minute: 'my-minute' })
+ end
+
def test_select_second
expected = %(<select id="date_second" name="date[second]">\n)
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
@@ -711,6 +791,22 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => 'Choose seconds')
end
+ def test_select_second_with_generic_with_css_classes
+ expected = %(<select id="date_second" name="date[second]" class="second">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: true)
+ end
+
+ def test_select_second_with_custom_with_css_classes
+ expected = %(<select id="date_second" name="date[second]" class="my-second">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), with_css_classes: { second: 'my-second' })
+ end
+
def test_select_date
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
@@ -1018,6 +1114,22 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]", :with_css_classes => true})
end
+ def test_select_date_with_custom_with_css_classes
+ expected = %(<select id="date_year" name="date[year]" class="my-year">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_month" name="date[month]" class="my-month">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_day" name="date[day]" class="my-day">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), start_year: 2003, end_year: 2005, with_css_classes: { year: 'my-year', month: 'my-month', day: 'my-day' })
+ end
+
def test_select_date_with_css_classes_option_and_html_class_option
expected = %(<select id="date_first_year" name="date[first][year]" class="datetime optional year">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
@@ -1034,6 +1146,54 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]", :with_css_classes => true}, { class: 'datetime optional' })
end
+ def test_select_date_with_custom_with_css_classes_and_html_class_option
+ expected = %(<select id="date_year" name="date[year]" class="date optional my-year">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_month" name="date[month]" class="date optional my-month">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_day" name="date[day]" class="date optional my-day">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {start_year: 2003, end_year: 2005, with_css_classes: { year: 'my-year', month: 'my-month', day: 'my-day' }}, { class: 'date optional' })
+ end
+
+ def test_select_date_with_partial_with_css_classes_and_html_class_option
+ expected = %(<select id="date_year" name="date[year]" class="date optional">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_month" name="date[month]" class="date optional my-month custom-grid">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_day" name="date[day]" class="date optional">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {start_year: 2003, end_year: 2005, with_css_classes: { month: 'my-month custom-grid' }}, { class: 'date optional' })
+ end
+
+ def test_select_date_with_html_class_option
+ expected = %(<select id="date_year" name="date[year]" class="date optional custom-grid">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_month" name="date[month]" class="date optional custom-grid">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_day" name="date[day]" class="date optional custom-grid">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005 }, { class: 'date optional custom-grid' })
+ end
+
def test_select_datetime
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
@@ -1270,6 +1430,62 @@ class DateHelperTest < ActionView::TestCase
:prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year', :hour => 'Choose hour', :minute => 'Choose minute'})
end
+ def test_select_datetime_with_generic_with_css_classes
+ expected = %(<select id="date_year" name="date[year]" class="year">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_month" name="date[month]" class="month">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_day" name="date[day]" class="day">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %(<select id="date_hour" name="date[hour]" class="hour">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_minute" name="date[minute]" class="minute">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, with_css_classes: true)
+ end
+
+ def test_select_datetime_with_custom_with_css_classes
+ expected = %(<select id="date_year" name="date[year]" class="my-year">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_month" name="date[month]" class="my-month">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_day" name="date[day]" class="my-day">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %(<select id="date_hour" name="date[hour]" class="my-hour">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_minute" name="date[minute]" class="my-minute">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, with_css_classes: { day: 'my-day', month: 'my-month', year: 'my-year', hour: 'my-hour', minute: 'my-minute' })
+ end
+
def test_select_datetime_with_custom_hours
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="">Choose year</option>\n<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
@@ -1486,6 +1702,54 @@ class DateHelperTest < ActionView::TestCase
:prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
end
+ def test_select_time_with_generic_with_css_classes
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
+ expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
+
+ expected << %(<select id="date_hour" name="date[hour]" class="hour">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_minute" name="date[minute]" class="minute">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_second" name="date[second]" class="second">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true, with_css_classes: true)
+ end
+
+ def test_select_time_with_custom_with_css_classes
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
+ expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
+
+ expected << %(<select id="date_hour" name="date[hour]" class="my-hour">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_minute" name="date[minute]" class="my-minute">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_second" name="date[second]" class="my-second">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true, with_css_classes: { hour: 'my-hour', minute: 'my-minute', second: 'my-second' })
+ end
+
def test_select_time_with_hidden
expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n)
expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
@@ -2014,6 +2278,46 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, date_select("post", "written_on", :prompt => {:year => 'Choose year', :month => 'Choose month', :day => 'Choose day'})
end
+ def test_date_select_with_generic_with_css_classes
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="year">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="month">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="day">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+
+ expected << "</select>\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", with_css_classes: true)
+ end
+
+ def test_date_select_with_custom_with_css_classes
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="my-year">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="my-month">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="my-day">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+
+ expected << "</select>\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", with_css_classes: { year: 'my-year', month: 'my-month', day: 'my-day' })
+ end
+
def test_time_select
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
@@ -2220,6 +2524,48 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, time_select("post", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute'})
end
+ def test_time_select_with_generic_with_css_classes
+ @post = Post.new
+ @post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
+ expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
+
+ expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]" class="hour">\n)
+ 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) }
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]" class="minute">\n)
+ 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_dom_equal expected, time_select("post", "written_on", with_css_classes: true)
+ end
+
+ def test_time_select_with_custom_with_css_classes
+ @post = Post.new
+ @post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
+ expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
+
+ expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]" class="my-hour">\n)
+ 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) }
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]" class="my-minute">\n)
+ 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_dom_equal expected, time_select("post", "written_on", with_css_classes: { hour: 'my-hour', minute: 'my-minute' })
+ end
+
def test_time_select_with_disabled_html_option
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
@@ -2493,6 +2839,64 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, datetime_select("post", "updated_at", :start_year=>1999, :end_year=>2009, :prompt => {:year => 'Choose year', :month => 'Choose month', :day => 'Choose day', :hour => 'Choose hour', :minute => 'Choose minute'})
end
+ def test_datetime_select_with_generic_with_css_classes
+ @post = Post.new
+ @post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="year">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="month">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="day">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_written_on_4i" name="post[written_on(4i)]" class="hour">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n}
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_written_on_5i" name="post[written_on(5i)]" class="minute">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n}
+ expected << "</select>\n"
+
+ assert_dom_equal expected, datetime_select("post", "written_on", start_year: 1999, end_year: 2009, with_css_classes: true)
+ end
+
+ def test_datetime_select_with_custom_with_css_classes
+ @post = Post.new
+ @post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="my-year">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="my-month">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="my-day">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_written_on_4i" name="post[written_on(4i)]" class="my-hour">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n}
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_written_on_5i" name="post[written_on(5i)]" class="my-minute">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n}
+ expected << "</select>\n"
+
+ assert_dom_equal expected, datetime_select("post", "written_on", start_year: 1999, end_year: 2009, with_css_classes: { year: 'my-year', month: 'my-month', day: 'my-day', hour: 'my-hour', minute: 'my-minute' })
+ end
+
def test_date_select_with_zero_value_and_no_start_year
expected = %(<select id="date_first_year" name="date[first][year]">\n)
(Date.today.year-5).upto(Date.today.year+1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index dde757b5a2..410f562f07 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -13,34 +13,18 @@ class FixtureTemplate
end
end
-class FixtureFinder
+class FixtureFinder < ActionView::LookupContext
FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor"
- attr_reader :details, :view_paths
- attr_accessor :formats
- attr_accessor :variants
-
- def initialize
- @details = {}
- @view_paths = ActionView::PathSet.new(['digestor'])
- @formats = []
- @variants = []
- end
-
- def details_key
- details.hash
- end
-
- def find(name, prefixes = [], partial = false, keys = [], options = {})
- partial_name = partial ? name.gsub(%r|/([^/]+)$|, '/_\1') : name
- format = @formats.first.to_s
- format += "+#{@variants.first}" if @variants.any?
-
- FixtureTemplate.new("digestor/#{partial_name}.#{format}.erb")
+ def initialize(details = {})
+ super(ActionView::PathSet.new(['digestor', 'digestor/api']), details, [])
+ @rendered_format = :html
end
+end
- def disable_cache(&block)
- yield
+class ActionView::Digestor::Node
+ def flatten
+ [self] + children.flat_map(&:flatten)
end
end
@@ -49,6 +33,7 @@ class TemplateDigestorTest < ActionView::TestCase
@cwd = Dir.pwd
@tmp_dir = Dir.mktmpdir
+ ActionView::LookupContext::DetailsKey.clear
FileUtils.cp_r FixtureFinder::FIXTURES_DIR, @tmp_dir
Dir.chdir @tmp_dir
end
@@ -56,7 +41,6 @@ class TemplateDigestorTest < ActionView::TestCase
def teardown
Dir.chdir @cwd
FileUtils.rm_r @tmp_dir
- ActionView::Digestor.cache.clear
end
def test_top_level_change_reflected
@@ -84,25 +68,21 @@ class TemplateDigestorTest < ActionView::TestCase
end
def test_explicit_dependency_wildcard_picks_up_added_file
- old_caching, ActionView::Resolver.caching = ActionView::Resolver.caching, false
-
- assert_digest_difference("events/index") do
- add_template("events/_uncompleted")
+ disable_resolver_caching do
+ assert_digest_difference("events/index") do
+ add_template("events/_uncompleted")
+ end
end
- ensure
- remove_template("events/_uncompleted")
- ActionView::Resolver.caching = old_caching
end
def test_explicit_dependency_wildcard_picks_up_removed_file
- old_caching, ActionView::Resolver.caching = ActionView::Resolver.caching, false
- add_template("events/_subscribers_changed")
+ disable_resolver_caching do
+ add_template("events/_subscribers_changed")
- assert_digest_difference("events/index") do
- remove_template("events/_subscribers_changed")
+ assert_digest_difference("events/index") do
+ remove_template("events/_subscribers_changed")
+ end
end
- ensure
- ActionView::Resolver.caching = old_caching
end
def test_second_level_dependency
@@ -153,12 +133,42 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
+ def test_getting_of_singly_nested_dependencies
+ singly_nested_dependencies = ["messages/header", "messages/form", "messages/message", "events/event", "comments/comment"]
+ assert_equal singly_nested_dependencies, nested_dependencies('messages/edit')
+ end
+
+ def test_getting_of_doubly_nested_dependencies
+ doubly_nested = [{"comments/comments"=>["comments/comment"]}, "messages/message"]
+ assert_equal doubly_nested, nested_dependencies('messages/peek')
+ end
+
def test_nested_template_directory
assert_digest_difference("messages/show") do
change_template("messages/actions/_move")
end
end
+ def test_nested_template_deps
+ nested_deps = ["messages/header", {"comments/comments"=>["comments/comment"]}, "messages/actions/move", "events/event", "messages/something_missing", "messages/something_missing_1", "messages/message", "messages/form"]
+ assert_equal nested_deps, nested_dependencies("messages/show")
+ end
+
+ def test_nested_template_deps_with_non_default_rendered_format
+ finder.rendered_format = nil
+ nested_deps = [{"comments/comments"=>["comments/comment"]}]
+ assert_equal nested_deps, nested_dependencies("messages/thread")
+ end
+
+ def test_template_formats_of_nested_deps_with_non_default_rendered_format
+ finder.rendered_format = nil
+ assert_equal [:json], tree_template_formats("messages/thread").uniq
+ end
+
+ def test_template_formats_of_dependencies_with_same_logical_name_and_different_rendered_format
+ assert_equal [:html], tree_template_formats("messages/show").uniq
+ end
+
def test_recursion_in_renders
assert digest("level/recursion") # assert recursion is possible
assert_not_nil digest("level/recursion") # assert digest is stored
@@ -206,13 +216,14 @@ class TemplateDigestorTest < ActionView::TestCase
def test_details_are_included_in_cache_key
# Cache the template digest.
+ @finder = FixtureFinder.new({:formats => [:html]})
old_digest = digest("events/_event")
# Change the template; the cached digest remains unchanged.
change_template("events/_event")
# The details are changed, so a new cache key is generated.
- finder.details[:foo] = "bar"
+ @finder = FixtureFinder.new
# The cache is busted.
assert_not_equal old_digest, digest("events/_event")
@@ -269,6 +280,13 @@ class TemplateDigestorTest < ActionView::TestCase
assert_not_equal digest_phone, digest_fridge_phone
end
+ def test_different_formats_with_same_logical_template_names_results_in_different_digests
+ html_digest = digest("comments/_comment", format: :html)
+ json_digest = digest("comments/_comment", format: :json)
+
+ assert_not_equal html_digest, json_digest
+ end
+
def test_digest_cache_cleanup_with_recursion
first_digest = digest("level/_recursion")
second_digest = digest("level/_recursion")
@@ -280,21 +298,17 @@ class TemplateDigestorTest < ActionView::TestCase
end
def test_digest_cache_cleanup_with_recursion_and_template_caching_off
- resolver_before = ActionView::Resolver.caching
- ActionView::Resolver.caching = false
-
- first_digest = digest("level/_recursion")
- second_digest = digest("level/_recursion")
+ disable_resolver_caching do
+ first_digest = digest("level/_recursion")
+ second_digest = digest("level/_recursion")
- assert first_digest
+ assert first_digest
- # If the cache is cleaned up correctly, subsequent digests should return the same
- assert_equal first_digest, second_digest
- ensure
- ActionView::Resolver.caching = resolver_before
+ # If the cache is cleaned up correctly, subsequent digests should return the same
+ assert_equal first_digest, second_digest
+ end
end
-
private
def assert_logged(message)
old_logger = ActionView::Base.logger
@@ -313,29 +327,44 @@ class TemplateDigestorTest < ActionView::TestCase
def assert_digest_difference(template_name, options = {})
previous_digest = digest(template_name, options)
- ActionView::Digestor.cache.clear
+ finder.digest_cache.clear
yield
- assert previous_digest != digest(template_name, options), "digest didn't change"
- ActionView::Digestor.cache.clear
+ assert_not_equal previous_digest, digest(template_name, options), "digest didn't change"
+ finder.digest_cache.clear
end
def digest(template_name, options = {})
options = options.dup
+ finder_options = options.extract!(:variants, :format)
- finder.formats = [:html]
- finder.variants = options.delete(:variants) || []
+ finder.variants = finder_options[:variants] || []
+ finder.rendered_format = finder_options[:format] if finder_options[:format]
- ActionView::Digestor.digest({ name: template_name, finder: finder }.merge(options))
+ ActionView::Digestor.digest(name: template_name, finder: finder, dependencies: (options[:dependencies] || []))
end
def dependencies(template_name)
- ActionView::Digestor.new({ name: template_name, finder: finder }).dependencies
+ tree = ActionView::Digestor.tree(template_name, finder)
+ tree.children.map(&:name)
end
def nested_dependencies(template_name)
- ActionView::Digestor.new({ name: template_name, finder: finder }).nested_dependencies
+ tree = ActionView::Digestor.tree(template_name, finder)
+ tree.children.map(&:to_dep_map)
+ end
+
+ def tree_template_formats(template_name)
+ tree = ActionView::Digestor.tree(template_name, finder)
+ tree.flatten.map(&:template).compact.flat_map(&:formats)
+ end
+
+ def disable_resolver_caching
+ old_caching, ActionView::Resolver.caching = ActionView::Resolver.caching, false
+ yield
+ ensure
+ ActionView::Resolver.caching = old_caching
end
def finder
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 034b8a4bf6..310d0ce514 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -128,8 +128,6 @@ class FormHelperTest < ActionView::TestCase
@post_delegator.title = 'Hello World'
@car = Car.new("#000FFF")
-
- @ticket_type = TicketType.new
end
Routes = ActionDispatch::Routing::RouteSet.new
@@ -138,8 +136,6 @@ class FormHelperTest < ActionView::TestCase
resources :comments
end
- resources :ticket_types
-
namespace :admin do
resources :posts do
resources :comments
@@ -1143,13 +1139,17 @@ class FormHelperTest < ActionView::TestCase
def test_datetime_field
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T00:00:00.000+0000" />}
- assert_dom_equal(expected, datetime_field("post", "written_on"))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on"))
+ end
end
def test_datetime_field_with_datetime_value
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T01:02:03.000+0000" />}
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
- assert_dom_equal(expected, datetime_field("post", "written_on"))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on"))
+ end
end
def test_datetime_field_with_extra_attrs
@@ -1158,20 +1158,26 @@ class FormHelperTest < ActionView::TestCase
min_value = DateTime.new(2000, 6, 15, 20, 45, 30)
max_value = DateTime.new(2010, 8, 15, 10, 25, 00)
step = 60
- assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value, step: step))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value, step: step))
+ end
end
def test_datetime_field_with_value_attr
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2013-06-29T13:37:00+00:00" />}
value = DateTime.new(2013,6,29,13,37)
- assert_dom_equal(expected, datetime_field("post", "written_on", value: value))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on", value: value))
+ end
end
def test_datetime_field_with_timewithzone_value
previous_time_zone, Time.zone = Time.zone, 'UTC'
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T15:30:45.000+0000" />}
@post.written_on = Time.zone.parse('2004-06-15 15:30:45')
- assert_dom_equal(expected, datetime_field("post", "written_on"))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on"))
+ end
ensure
Time.zone = previous_time_zone
end
@@ -1179,7 +1185,9 @@ class FormHelperTest < ActionView::TestCase
def test_datetime_field_with_nil_value
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" />}
@post.written_on = nil
- assert_dom_equal(expected, datetime_field("post", "written_on"))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on"))
+ end
end
def test_datetime_field_with_string_values_for_min_and_max
@@ -1187,7 +1195,9 @@ class FormHelperTest < ActionView::TestCase
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
min_value = "2000-06-15T20:45:30.000+0000"
max_value = "2010-08-15T10:25:00.000+0000"
- assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value))
+ end
end
def test_datetime_field_with_invalid_string_values_for_min_and_max
@@ -1195,7 +1205,9 @@ class FormHelperTest < ActionView::TestCase
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
min_value = "foo"
max_value = "bar"
- assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field("post", "written_on", min: min_value, max: max_value))
+ end
end
def test_datetime_local_field
@@ -1876,20 +1888,6 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
- def test_lowercase_model_name_default_submit_button_value
- form_for(@ticket_type) do |f|
- concat f.submit
- end
-
- expected =
- '<form class="new_ticket_type" id="new_ticket_type" action="/ticket_types" accept-charset="UTF-8" method="post">' +
- hidden_fields +
- '<input type="submit" name="commit" value="Create ticket type" data-disable-with="Create ticket type" />' +
- '</form>'
-
- assert_dom_equal expected, output_buffer
- end
-
def test_form_for_with_symbol_object_name
form_for(@post, as: "other_name", html: { id: "create-post" }) do |f|
concat f.label(:title, class: 'post_title')
@@ -2257,7 +2255,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form('/posts', 'new_post', 'new_post') do
- "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
+ "<input name='commit' data-disable-with='Create Post' type='submit' value='Create Post' />"
end
assert_dom_equal expected, output_buffer
@@ -2272,7 +2270,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do
- "<input name='commit' data-disable-with='Confirm post changes' type='submit' value='Confirm post changes' />"
+ "<input name='commit' data-disable-with='Confirm Post changes' type='submit' value='Confirm Post changes' />"
end
assert_dom_equal expected, output_buffer
@@ -2300,7 +2298,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', method: 'patch') do
- "<input name='commit' data-disable-with='Update your post' type='submit' value='Update your post' />"
+ "<input name='commit' data-disable-with='Update your Post' type='submit' value='Update your Post' />"
end
assert_dom_equal expected, output_buffer
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index c5b63d33f1..7a5904f151 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -798,7 +798,7 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
- def test_select_with_fixnum
+ def test_select_with_integer
@post = Post.new
@post.category = ""
assert_dom_equal(
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index 07b3fba754..5b0b708618 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -239,8 +239,8 @@ class FormTagHelperTest < ActionView::TestCase
end
def test_select_tag_with_include_blank
- actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :include_blank => true
- expected = %(<select id="places" name="places"><option value=""></option><option>Home</option><option>Work</option><option>Pub</option></select>)
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), include_blank: true
+ expected = %(<select id="places" name="places"><option value="" label=" "></option><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
@@ -269,14 +269,14 @@ class FormTagHelperTest < ActionView::TestCase
end
def test_select_tag_with_prompt_and_include_blank
- actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :prompt => "string", :include_blank => true
- expected = %(<select name="places" id="places"><option value="">string</option><option value=""></option><option>Home</option><option>Work</option><option>Pub</option></select>)
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), prompt: "string", include_blank: true
+ expected = %(<select name="places" id="places"><option value="">string</option><option value="" label=" "></option><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_with_nil_option_tags_and_include_blank
actual = select_tag "places", nil, :include_blank => true
- expected = %(<select id="places" name="places"><option value=""></option></select>)
+ expected = %(<select id="places" name="places"><option value="" label=" "></option></select>)
assert_dom_equal expected, actual
end
@@ -622,7 +622,9 @@ class FormTagHelperTest < ActionView::TestCase
def test_datetime_field_tag
expected = %{<input id="appointment" name="appointment" type="datetime" />}
- assert_dom_equal(expected, datetime_field_tag("appointment"))
+ assert_deprecated do
+ assert_dom_equal(expected, datetime_field_tag("appointment"))
+ end
end
def test_datetime_local_field_tag
diff --git a/actionview/test/template/log_subscriber_test.rb b/actionview/test/template/log_subscriber_test.rb
index 4776c18b0b..7683444bf0 100644
--- a/actionview/test/template/log_subscriber_test.rb
+++ b/actionview/test/template/log_subscriber_test.rb
@@ -35,7 +35,8 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
@view.render(:file => "test/hello_world")
wait
- assert_equal 1, @logger.logged(:info).size
+ assert_equal 2, @logger.logged(:info).size
+ assert_match(/Rendering test\/hello_world\.erb/, @logger.logged(:info).first)
assert_match(/Rendered test\/hello_world\.erb/, @logger.logged(:info).last)
end
end
@@ -45,7 +46,8 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
@view.render(:text => "TEXT")
wait
- assert_equal 1, @logger.logged(:info).size
+ assert_equal 2, @logger.logged(:info).size
+ assert_match(/Rendering text template/, @logger.logged(:info).first)
assert_match(/Rendered text template/, @logger.logged(:info).last)
end
end
@@ -55,7 +57,8 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
@view.render(:inline => "<%= 'TEXT' %>")
wait
- assert_equal 1, @logger.logged(:info).size
+ assert_equal 2, @logger.logged(:info).size
+ assert_match(/Rendering inline template/, @logger.logged(:info).first)
assert_match(/Rendered inline template/, @logger.logged(:info).last)
end
end
@@ -86,7 +89,7 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
wait
assert_equal 1, @logger.logged(:info).size
- assert_match(/Rendered test\/_customer.erb/, @logger.logged(:info).last)
+ assert_match(/Rendered collection of test\/_customer.erb \[2 times\]/, @logger.logged(:info).last)
end
end
@@ -96,7 +99,7 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
wait
assert_equal 1, @logger.logged(:info).size
- assert_match(/Rendered customers\/_customer\.html\.erb/, @logger.logged(:info).last)
+ assert_match(/Rendered collection of customers\/_customer\.html\.erb \[2 times\]/, @logger.logged(:info).last)
end
end
@@ -106,7 +109,21 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
wait
assert_equal 1, @logger.logged(:info).size
- assert_match(/Rendered collection/, @logger.logged(:info).last)
+ assert_match(/Rendered collection of templates/, @logger.logged(:info).last)
+ end
+ end
+
+ def test_render_collection_with_cached_set
+ Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do
+ def @view.view_cache_dependencies; []; end
+ def @view.fragment_cache_key(*); 'ahoy `controller` dependency'; end
+
+ @view.render(partial: 'customers/customer', collection: [ Customer.new('david'), Customer.new('mary') ], cached: true,
+ locals: { greeting: 'hi' })
+ wait
+
+ assert_equal 1, @logger.logged(:info).size
+ assert_match(/Rendered collection of customers\/_customer\.html\.erb \[0 \/ 2 cache hits\]/, @logger.logged(:info).last)
end
end
end
diff --git a/actionview/test/template/output_safety_helper_test.rb b/actionview/test/template/output_safety_helper_test.rb
index 8de0ae2f6f..b940c9dd36 100644
--- a/actionview/test/template/output_safety_helper_test.rb
+++ b/actionview/test/template/output_safety_helper_test.rb
@@ -32,4 +32,59 @@ class OutputSafetyHelperTest < ActionView::TestCase
joined = safe_join(['"a"',['<b>','<c>']], ' <br/> ')
assert_equal '&quot;a&quot; &lt;br/&gt; &lt;b&gt; &lt;br/&gt; &lt;c&gt;', joined
end
+
+ test "to_sentence should escape non-html_safe values" do
+ actual = to_sentence(%w(< > & ' "))
+ assert actual.html_safe?
+ assert_equal("&lt;, &gt;, &amp;, &#39;, and &quot;", actual)
+
+ actual = to_sentence(%w(<script>))
+ assert actual.html_safe?
+ assert_equal("&lt;script&gt;", actual)
+ end
+
+ test "to_sentence does not double escape if single value is html_safe" do
+ assert_equal("&lt;script&gt;", to_sentence([ERB::Util.html_escape("<script>")]))
+ assert_equal("&lt;script&gt;", to_sentence(["&lt;script&gt;".html_safe]))
+ assert_equal("&amp;lt;script&amp;gt;", to_sentence(["&lt;script&gt;"]))
+ end
+
+ test "to_sentence connector words are checked for html safety" do
+ assert_equal "one & two, and three", to_sentence(['one', 'two', 'three'], words_connector: ' & '.html_safe)
+ assert_equal "one & two", to_sentence(['one', 'two'], two_words_connector: ' & '.html_safe)
+ assert_equal "one, two &lt;script&gt;alert(1)&lt;/script&gt; three", to_sentence(['one', 'two', 'three'], last_word_connector: ' <script>alert(1)</script> ')
+ end
+
+ test "to_sentence should not escape html_safe values" do
+ ptag = content_tag("p") do
+ safe_join(["<marquee>shady stuff</marquee>", tag("br")])
+ end
+ url = "https://example.com"
+ expected = %(<a href="#{url}">#{url}</a> and <p>&lt;marquee&gt;shady stuff&lt;/marquee&gt;<br /></p>)
+ actual = to_sentence([link_to(url, url), ptag])
+ assert actual.html_safe?
+ assert_equal(expected, actual)
+ end
+
+ test "to_sentence handles blank strings" do
+ actual = to_sentence(['', 'two', 'three'])
+ assert actual.html_safe?
+ assert_equal ", two, and three", actual
+ end
+
+ test "to_sentence handles nil values" do
+ actual = to_sentence([nil, 'two', 'three'])
+ assert actual.html_safe?
+ assert_equal ", two, and three", actual
+ end
+
+ test "to_sentence still supports ActiveSupports Array#to_sentence arguments" do
+ assert_equal "one two, and three", to_sentence(['one', 'two', 'three'], words_connector: ' ')
+ assert_equal "one & two, and three", to_sentence(['one', 'two', 'three'], words_connector: ' & '.html_safe)
+ assert_equal "onetwo, and three", to_sentence(['one', 'two', 'three'], words_connector: nil)
+ assert_equal "one, two, and also three", to_sentence(['one', 'two', 'three'], last_word_connector: ', and also ')
+ assert_equal "one, twothree", to_sentence(['one', 'two', 'three'], last_word_connector: nil)
+ assert_equal "one, two three", to_sentence(['one', 'two', 'three'], last_word_connector: ' ')
+ assert_equal "one, two and three", to_sentence(['one', 'two', 'three'], last_word_connector: ' and ')
+ end
end
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 333e0cca11..ad93236d32 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -207,6 +207,10 @@ module RenderTestCases
assert_nothing_raised { @view.render(:partial => "test/a-in") }
end
+ def test_render_partial_with_unicode_text
+ assert_nothing_raised { @view.render(:partial => "test/🍣") }
+ 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; " +
@@ -226,13 +230,13 @@ module RenderTestCases
assert_match %r!method.*doesnt_exist!, e.message
assert_equal "", e.sub_template_message
assert_equal "1", e.line_number
- assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code.strip
+ assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code[0].strip
assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name
end
def test_render_error_indentation
e = assert_raises(ActionView::Template::Error) { @view.render(:partial => "test/raise_indentation") }
- error_lines = e.annoted_source_code.split("\n")
+ error_lines = e.annoted_source_code
assert_match %r!error\shere!, e.message
assert_equal "11", e.line_number
assert_equal " 9: <p>Ninth paragraph</p>", error_lines.second
@@ -252,7 +256,7 @@ module RenderTestCases
assert_match %r!method.*doesnt_exist!, e.message
assert_equal "", e.sub_template_message
assert_equal "1", e.line_number
- assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code.strip
+ assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code[0].strip
assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name
end
@@ -270,6 +274,11 @@ module RenderTestCases
assert_equal "Hello: davidHello: mary", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), Customer.new("mary") ])
end
+ def test_render_partial_collection_with_partial_name_containing_dot
+ assert_equal "Hello: davidHello: mary",
+ @view.render(:partial => "test/customer.mobile", :collection => [ Customer.new("david"), Customer.new("mary") ])
+ end
+
def test_render_partial_collection_as_by_string
assert_equal "david david davidmary mary mary",
@view.render(:partial => "test/customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => 'customer')
@@ -628,56 +637,59 @@ class LazyViewRenderTest < ActiveSupport::TestCase
end
end
-class CachedCollectionViewRenderTest < CachedViewRenderTest
+class CachedCollectionViewRenderTest < ActiveSupport::TestCase
class CachedCustomer < Customer; end
- teardown do
- ActionView::PartialRenderer.collection_cache.clear
- end
+ include RenderTestCases
- test "with custom key" do
- customer = Customer.new("david")
- key = cache_key([customer, 'key'], "test/_customer")
+ # Ensure view path cache is primed
+ setup do
+ view_paths = ActionController::Base.view_paths
+ assert_equal ActionView::OptimizedFileSystemResolver, view_paths.first.class
- ActionView::PartialRenderer.collection_cache.write(key, 'Hello')
+ ActionView::PartialRenderer.collection_cache = ActiveSupport::Cache::MemoryStore.new
- assert_equal "Hello",
- @view.render(partial: "test/customer", collection: [customer], cache: ->(item) { [item, 'key'] })
+ setup_view(view_paths)
end
- test "with caching with custom key and rendering with different key" do
- customer = Customer.new("david")
- key = cache_key([customer, 'key'], "test/_customer")
+ teardown do
+ GC.start
+ I18n.reload!
+ end
- ActionView::PartialRenderer.collection_cache.write(key, 'Hello')
+ test "collection caching does not cache by default" do
+ customer = Customer.new("david", 1)
+ key = cache_key(customer, "test/_customer")
- assert_equal "Hello: david",
- @view.render(partial: "test/customer", collection: [customer], cache: ->(item) { [item, 'another_key'] })
+ ActionView::PartialRenderer.collection_cache.write(key, 'Cached')
+
+ assert_not_equal "Cached",
+ @view.render(partial: "test/customer", collection: [customer])
end
- test "automatic caching with inferred cache name" do
- customer = CachedCustomer.new("david")
- key = cache_key(customer, "test/_cached_customer")
+ test "collection caching with partial that doesn't use fragment caching" do
+ customer = Customer.new("david", 1)
+ key = cache_key(customer, "test/_customer")
ActionView::PartialRenderer.collection_cache.write(key, 'Cached')
assert_equal "Cached",
- @view.render(partial: "test/cached_customer", collection: [customer])
+ @view.render(partial: "test/customer", collection: [customer], cached: true)
end
- test "automatic caching with as name" do
- customer = CachedCustomer.new("david")
- key = cache_key(customer, "test/_cached_customer_as")
+ test "collection caching with cached true" do
+ customer = CachedCustomer.new("david", 1)
+ key = cache_key(customer, "test/_cached_customer")
ActionView::PartialRenderer.collection_cache.write(key, 'Cached')
assert_equal "Cached",
- @view.render(partial: "test/cached_customer_as", collection: [customer], as: :buyer)
+ @view.render(partial: "test/cached_customer", collection: [customer], cached: true)
end
private
- def cache_key(names, virtual_path)
+ def cache_key(*names, virtual_path)
digest = ActionView::Digestor.digest name: virtual_path, finder: @view.lookup_context, dependencies: []
- @view.fragment_cache_key([ *Array(names), digest ])
+ @view.fragment_cache_key([ *names, digest ])
end
end
diff --git a/actionview/test/template/resolver_cache_test.rb b/actionview/test/template/resolver_cache_test.rb
new file mode 100644
index 0000000000..1081c13db0
--- /dev/null
+++ b/actionview/test/template/resolver_cache_test.rb
@@ -0,0 +1,7 @@
+require 'abstract_unit'
+
+class ResolverCacheTest < ActiveSupport::TestCase
+ def test_inspect_shields_cache_internals
+ assert_match %r(#<ActionView::Resolver:0x[0-9a-f]+ @cache=#<ActionView::Resolver::Cache:0x[0-9a-f]+ keys=0 queries=0>>), ActionView::Resolver.new.inspect
+ end
+end
diff --git a/actionview/test/template/resolver_patterns_test.rb b/actionview/test/template/resolver_patterns_test.rb
index 575eb9bd28..1a091bd692 100644
--- a/actionview/test/template/resolver_patterns_test.rb
+++ b/actionview/test/template/resolver_patterns_test.rb
@@ -3,17 +3,17 @@ require 'abstract_unit'
class ResolverPatternsTest < ActiveSupport::TestCase
def setup
path = File.expand_path("../../fixtures/", __FILE__)
- pattern = ":prefix/{:formats/,}:action{.:formats,}{.:handlers,}"
+ pattern = ":prefix/{:formats/,}:action{.:formats,}{+:variants,}{.:handlers,}"
@resolver = ActionView::FileSystemResolver.new(path, pattern)
end
def test_should_return_empty_list_for_unknown_path
- templates = @resolver.find_all("unknown", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
+ templates = @resolver.find_all("unknown", "custom_pattern", false, {locale: [], formats: [:html], variants: [], handlers: [:erb]})
assert_equal [], templates, "expected an empty list of templates"
end
def test_should_return_template_for_declared_path
- templates = @resolver.find_all("path", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
+ templates = @resolver.find_all("path", "custom_pattern", false, {locale: [], formats: [:html], variants: [], handlers: [:erb]})
assert_equal 1, templates.size, "expected one template"
assert_equal "Hello custom patterns!", templates.first.source
assert_equal "custom_pattern/path", templates.first.virtual_path
@@ -21,11 +21,22 @@ class ResolverPatternsTest < ActiveSupport::TestCase
end
def test_should_return_all_templates_when_ambiguous_pattern
- templates = @resolver.find_all("another", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
+ templates = @resolver.find_all("another", "custom_pattern", false, {locale: [], formats: [:html], variants: [], handlers: [:erb]})
assert_equal 2, templates.size, "expected two templates"
assert_equal "Another template!", templates[0].source
assert_equal "custom_pattern/another", templates[0].virtual_path
assert_equal "Hello custom patterns!", templates[1].source
assert_equal "custom_pattern/another", templates[1].virtual_path
end
+
+ def test_should_return_all_variants_for_any
+ templates = @resolver.find_all("hello_world", "test", false, {locale: [], formats: [:html, :text], variants: :any, handlers: [:erb]})
+ assert_equal 3, templates.size, "expected three templates"
+ assert_equal "Hello phone!", templates[0].source
+ assert_equal "test/hello_world", templates[0].virtual_path
+ assert_equal "Hello texty phone!", templates[1].source
+ assert_equal "test/hello_world", templates[1].virtual_path
+ assert_equal "Hello world!", templates[2].source
+ assert_equal "test/hello_world", templates[2].virtual_path
+ end
end
diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb
index 6f7a78ccef..f3956a31f6 100644
--- a/actionview/test/template/tag_helper_test.rb
+++ b/actionview/test/template/tag_helper_test.rb
@@ -173,4 +173,10 @@ class TagHelperTest < ActionView::TestCase
tag('a', { aria => { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: 'hello', symbol: :foo, array: [1, 2, 3], hash: { key: 'value'}, string_with_quotes: 'double"quote"party"' } })
}
end
+
+ def test_link_to_data_nil_equal
+ div_type1 = content_tag(:div, 'test', { 'data-tooltip' => nil })
+ div_type2 = content_tag(:div, 'test', { data: {tooltip: nil} })
+ assert_dom_equal div_type1, div_type2
+ end
end
diff --git a/actionview/test/template/template_test.rb b/actionview/test/template/template_test.rb
index 921011b073..533c1c3219 100644
--- a/actionview/test/template/template_test.rb
+++ b/actionview/test/template/template_test.rb
@@ -192,38 +192,6 @@ class TestERBTemplate < ActiveSupport::TestCase
assert_match(/\xFC/, e.message)
end
- def test_not_eligible_for_collection_caching_without_cache_call
- [
- "<%= 'Hello' %>",
- "<% cache_customer = 42 %>",
- "<% cache customer.name do %><% end %>",
- "<% my_cache customer do %><% end %>"
- ].each do |body|
- template = new_template(body, virtual_path: "test/foo/_customer")
- assert_not template.eligible_for_collection_caching?, "Template #{body.inspect} should not be eligible for collection caching"
- end
- end
-
- def test_eligible_for_collection_caching_with_cache_call_or_explicit
- [
- "<% cache customer do %><% end %>",
- "<% cache(customer) do %><% end %>",
- "<% cache( customer) do %><% end %>",
- "<% cache( customer ) do %><% end %>",
- "<%cache customer do %><% end %>",
- "<% cache customer do %><% end %>",
- " <% cache customer do %>\n<% end %>\n",
- "<%# comment %><% cache customer do %><% end %>",
- "<%# comment %>\n<% cache customer do %><% end %>",
- "<%# comment\n line 2\n line 3 %>\n<% cache customer do %><% end %>",
- "<%# comment 1 %>\n<%# comment 2 %>\n<% cache customer do %><% end %>",
- "<%# comment 1 %>\n<%# Template Collection: customer %>\n<% my_cache customer do %><% end %>"
- ].each do |body|
- template = new_template(body, virtual_path: "test/foo/_customer")
- assert template.eligible_for_collection_caching?, "Template #{body.inspect} should be eligible for collection caching"
- end
- end
-
def with_external_encoding(encoding)
old = Encoding.default_external
Encoding::Converter.new old, encoding if old != encoding
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 3010656166..ab56d80de3 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -71,6 +71,34 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal 'javascript:history.back()', url_for(:back)
end
+ def test_to_form_params_with_hash
+ assert_equal(
+ [{ name: :name, value: 'David' }, { name: :nationality, value: 'Danish' }],
+ to_form_params(name: 'David', nationality: 'Danish')
+ )
+ end
+
+ def test_to_form_params_with_nested_hash
+ assert_equal(
+ [{ name: 'country[name]', value: 'Denmark' }],
+ to_form_params(country: { name: 'Denmark' })
+ )
+ end
+
+ def test_to_form_params_with_array_nested_in_hash
+ assert_equal(
+ [{ name: 'countries[]', value: 'Denmark' }, { name: 'countries[]', value: 'Sweden' }],
+ to_form_params(countries: ['Denmark', 'Sweden'])
+ )
+ end
+
+ def test_to_form_params_with_namespace
+ assert_equal(
+ [{ name: 'country[name]', value: 'Denmark' }],
+ to_form_params({name: 'Denmark'}, 'country')
+ )
+ end
+
def test_button_to_with_straight_url
assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com")
end
@@ -189,8 +217,22 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_button_to_with_params
assert_dom_equal(
- %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo" value="bar" /><input type="hidden" name="baz" value="quux" /></form>},
- button_to("Hello", "http://www.example.com", params: {foo: :bar, baz: "quux"})
+ %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="baz" value="quux" /><input type="hidden" name="foo" value="bar" /></form>},
+ button_to("Hello", "http://www.example.com", params: { foo: :bar, baz: "quux" })
+ )
+ end
+
+ def test_button_to_with_nested_hash_params
+ assert_dom_equal(
+ %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo[bar]" value="baz" /></form>},
+ button_to("Hello", "http://www.example.com", params: { foo: { bar: 'baz' } })
+ )
+ end
+
+ def test_button_to_with_nested_array_params
+ assert_dom_equal(
+ %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo[]" value="bar" /></form>},
+ button_to("Hello", "http://www.example.com", params: { foo: ['bar'] })
)
end
@@ -613,7 +655,9 @@ class UrlHelperControllerTest < ActionController::TestCase
to: 'url_helper_controller_test/url_helper#show_named_route',
as: :show_named_route
- get "/:controller(/:action(/:id))"
+ ActiveSupport::Deprecation.silence do
+ get "/:controller(/:action(/:id))"
+ end
get 'url_helper_controller_test/url_helper/normalize_recall_params',
to: UrlHelperController.action(:normalize_recall),
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 8687af5eba..5e72c67aea 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,134 +1,2 @@
-## Rails 5.0.0.beta1 (December 18, 2015) ##
-* Fixed serializing `:at` option for `assert_enqueued_with`
- and `assert_performed_with`.
-
- *Wojciech Wnętrzak*
-
-* Support passing array to `assert_enqueued_jobs` in `:only` option.
-
- *Wojciech Wnętrzak*
-
-* Add job priorities to Active Job.
-
- *wvengen*
-
-* Implement a simple `AsyncJob` processor and associated `AsyncAdapter` that
- queue jobs to a `concurrent-ruby` thread pool.
-
- *Jerry D'Antonio*
-
-* Implement `provider_job_id` for `queue_classic` adapter. This requires the
- latest, currently unreleased, version of queue_classic.
-
- *Yves Senn*
-
-* `assert_enqueued_with` and `assert_performed_with` now returns the matched
- job instance for further assertions.
-
- *Jean Boussier*
-
-* Include `I18n.locale` into job serialization/deserialization and use it around
- `perform`.
-
- Fixes #20799.
-
- *Johannes Opper*
-
-* Allow `DelayedJob`, `Sidekiq`, `qu`, and `que` to report the job id back to
- `ActiveJob::Base` as `provider_job_id`.
-
- Fixes #18821.
-
- *Kevin Deisz*, *Jeroen van Baarsen*
-
-* `assert_enqueued_jobs` and `assert_performed_jobs` in block form use the
- given number as expected value. This makes the error message much easier to
- understand.
-
- *y-yagi*
-
-* A generated job now inherits from `app/jobs/application_job.rb` by default.
-
- *Jeroen van Baarsen*
-
-* 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.
+Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activejob/CHANGELOG.md) for previous changes.
diff --git a/activejob/README.md b/activejob/README.md
index 7268186c00..8becac7753 100644
--- a/activejob/README.md
+++ b/activejob/README.md
@@ -20,12 +20,7 @@ switch between them without having to rewrite your jobs.
## Usage
-Set the queue adapter for Active Job:
-
-``` ruby
-ActiveJob::Base.queue_adapter = :inline # default queue adapter
-```
-Note: To learn how to use your preferred queueing backend see its adapter
+To learn how to use your preferred queueing backend see its adapter
documentation at
[ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
diff --git a/activejob/Rakefile b/activejob/Rakefile
index d9648a7f16..e47f17b740 100644
--- a/activejob/Rakefile
+++ b/activejob/Rakefile
@@ -1,11 +1,14 @@
require 'rake/testtask'
-ACTIVEJOB_ADAPTERS = %w(async inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner test)
+#TODO: add qu back to the list after it support Rails 5.1
+ACTIVEJOB_ADAPTERS = %w(async inline delayed_job que queue_classic resque sidekiq sneakers sucker_punch backburner test)
ACTIVEJOB_ADAPTERS -= %w(queue_classic) if defined?(JRUBY_VERSION)
task default: :test
task test: 'test:default'
+task :package
+
namespace :test do
desc 'Run all adapter tests'
task :default do
diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec
index bc1671b508..e97bb40abf 100644
--- a/activejob/activejob.gemspec
+++ b/activejob/activejob.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.homepage = 'http://rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.md', 'lib/**/*']
s.require_path = 'lib'
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index f7e05f7cd3..4b9065cf50 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -32,7 +32,6 @@ module ActiveJob
autoload :Base
autoload :QueueAdapters
autoload :ConfiguredJob
- autoload :AsyncJob
autoload :TestCase
autoload :TestHelper
end
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index 33bd5b4eb3..a5c749e5e7 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -24,7 +24,7 @@ module ActiveJob
end
# Raised when an unsupported argument type is set as a job argument. We
- # currently support NilClass, Fixnum, Float, String, TrueClass, FalseClass,
+ # currently support NilClass, Integer, Fixnum, Float, String, TrueClass, FalseClass,
# Bignum, BigDecimal, and objects that can be represented as GlobalIDs (ex: Active Record).
# Raised if you set the key for a Hash something else than a string or
# a symbol. Also raised when trying to serialize an object which can't be
@@ -34,7 +34,8 @@ module ActiveJob
module Arguments
extend self
# :nodoc:
- TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum, BigDecimal ]
+ # Calls #uniq since Integer, Fixnum, and Bignum are all the same class on Ruby 2.4+
+ TYPE_WHITELIST = [ NilClass, String, Integer, Fixnum, Bignum, Float, BigDecimal, TrueClass, FalseClass ].uniq
# Serializes a set of arguments. Whitelisted types are returned
# as-is. Arrays/Hashes are serialized element by element.
diff --git a/activejob/lib/active_job/async_job.rb b/activejob/lib/active_job/async_job.rb
deleted file mode 100644
index ed7a6e8d9b..0000000000
--- a/activejob/lib/active_job/async_job.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'concurrent/map'
-require 'concurrent/scheduled_task'
-require 'concurrent/executor/thread_pool_executor'
-require 'concurrent/utility/processor_counter'
-
-module ActiveJob
- # == Active Job Async Job
- #
- # When enqueueing jobs with Async Job each job will be executed asynchronously
- # on a +concurrent-ruby+ thread pool. All job data is retained in memory.
- # Because job data is not saved to a persistent datastore there is no
- # additional infrastructure needed and jobs process quickly. The lack of
- # persistence, however, means that all unprocessed jobs will be lost on
- # application restart. Therefore in-memory queue adapters are unsuitable for
- # most production environments but are excellent for development and testing.
- #
- # Read more about Concurrent Ruby {here}[https://github.com/ruby-concurrency/concurrent-ruby].
- #
- # To use Async Job set the queue_adapter config to +:async+.
- #
- # Rails.application.config.active_job.queue_adapter = :async
- #
- # Async Job supports job queues specified with +queue_as+. Queues are created
- # automatically as needed and each has its own thread pool.
- class AsyncJob
-
- DEFAULT_EXECUTOR_OPTIONS = {
- min_threads: [2, Concurrent.processor_count].max,
- max_threads: Concurrent.processor_count * 10,
- auto_terminate: true,
- idletime: 60, # 1 minute
- max_queue: 0, # unlimited
- fallback_policy: :caller_runs # shouldn't matter -- 0 max queue
- }.freeze
-
- QUEUES = Concurrent::Map.new do |hash, queue_name| #:nodoc:
- hash.compute_if_absent(queue_name) { ActiveJob::AsyncJob.create_thread_pool }
- end
-
- class << self
- # Forces jobs to process immediately when testing the Active Job gem.
- # This should only be called from within unit tests.
- def perform_immediately! #:nodoc:
- @perform_immediately = true
- end
-
- # Allows jobs to run asynchronously when testing the Active Job gem.
- # This should only be called from within unit tests.
- def perform_asynchronously! #:nodoc:
- @perform_immediately = false
- end
-
- def create_thread_pool #:nodoc:
- if @perform_immediately
- Concurrent::ImmediateExecutor.new
- else
- Concurrent::ThreadPoolExecutor.new(DEFAULT_EXECUTOR_OPTIONS)
- end
- end
-
- def enqueue(job_data, queue: 'default') #:nodoc:
- QUEUES[queue].post(job_data) { |job| ActiveJob::Base.execute(job) }
- end
-
- def enqueue_at(job_data, timestamp, queue: 'default') #:nodoc:
- delay = timestamp - Time.current.to_f
- if delay > 0
- Concurrent::ScheduledTask.execute(delay, args: [job_data], executor: QUEUES[queue]) do |job|
- ActiveJob::Base.execute(job)
- end
- else
- enqueue(job_data, queue: queue)
- end
- end
- end
- end
-end
diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb
index 2b6149e84e..b206522a60 100644
--- a/activejob/lib/active_job/callbacks.rb
+++ b/activejob/lib/active_job/callbacks.rb
@@ -17,6 +17,11 @@ module ActiveJob
extend ActiveSupport::Concern
include ActiveSupport::Callbacks
+ class << self
+ include ActiveSupport::Callbacks
+ define_callbacks :execute
+ end
+
included do
define_callbacks :perform
define_callbacks :enqueue
@@ -121,8 +126,8 @@ module ActiveJob
set_callback(:enqueue, :after, *filters, &blk)
end
- # Defines a callback that will get called before and after the
- # job is enqueued.
+ # Defines a callback that will get called around the enqueueing
+ # of the job.
#
# class VideoProcessJob < ActiveJob::Base
# queue_as :default
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index 19b900a285..f7f882c998 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -79,7 +79,7 @@ module ActiveJob
'queue_name' => queue_name,
'priority' => priority,
'arguments' => serialize_arguments(arguments),
- 'locale' => I18n.locale
+ 'locale' => I18n.locale.to_s
}
end
@@ -108,7 +108,7 @@ module ActiveJob
self.queue_name = job_data['queue_name']
self.priority = job_data['priority']
self.serialized_arguments = job_data['arguments']
- self.locale = job_data['locale'] || I18n.locale
+ self.locale = job_data['locale'] || I18n.locale.to_s
end
private
diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb
index 22154457fd..9dc3c0fa57 100644
--- a/activejob/lib/active_job/enqueuing.rb
+++ b/activejob/lib/active_job/enqueuing.rb
@@ -36,7 +36,7 @@ module ActiveJob
#
# ==== Examples
#
- # class SiteScrapperJob < ActiveJob::Base
+ # class SiteScraperJob < ActiveJob::Base
# rescue_from(ErrorLoadingSite) do
# retry_job queue: :low_priority
# end
diff --git a/activejob/lib/active_job/execution.rb b/activejob/lib/active_job/execution.rb
index 79d232da4a..4e4acfc2c2 100644
--- a/activejob/lib/active_job/execution.rb
+++ b/activejob/lib/active_job/execution.rb
@@ -17,8 +17,10 @@ module ActiveJob
end
def execute(job_data) #:nodoc:
- job = deserialize(job_data)
- job.perform_now
+ ActiveJob::Callbacks.run_callbacks(:execute) do
+ job = deserialize(job_data)
+ job.perform_now
+ end
end
end
@@ -32,7 +34,7 @@ module ActiveJob
perform(*arguments)
end
rescue => exception
- rescue_with_handler(exception) || raise(exception)
+ rescue_with_handler(exception) || raise
end
def perform(*)
diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb
index 0bdeca76a8..0d50c27938 100644
--- a/activejob/lib/active_job/gem_version.rb
+++ b/activejob/lib/active_job/gem_version.rb
@@ -6,9 +6,9 @@ module ActiveJob
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta1.1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
index 457015b741..72e4ebf935 100644
--- a/activejob/lib/active_job/queue_adapter.rb
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -4,25 +4,25 @@ require 'active_support/core_ext/string/inflections'
module ActiveJob
# The <tt>ActiveJob::QueueAdapter</tt> module is used to load the
- # correct adapter. The default queue adapter is the +:inline+ queue.
+ # correct adapter. The default queue adapter is the +:async+ queue.
module QueueAdapter #:nodoc:
extend ActiveSupport::Concern
included do
class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false
- self.queue_adapter = :inline
+ self.queue_adapter = :async
end
# Includes the setter method for changing the active queue adapter.
module ClassMethods
# Returns the backend queue provider. The default queue adapter
- # is the +:inline+ queue. See QueueAdapters for more information.
+ # is the +:async+ queue. See QueueAdapters for more information.
def queue_adapter
_queue_adapter
end
# Specify the backend queue provider. The default queue adapter
- # is the +:inline+ queue. See QueueAdapters for more
+ # is the +:async+ queue. See QueueAdapters for more
# information.
def queue_adapter=(name_or_adapter_or_class)
self._queue_adapter = interpret_adapter(name_or_adapter_or_class)
diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb
index 2c5039ef4d..71154d8785 100644
--- a/activejob/lib/active_job/queue_adapters.rb
+++ b/activejob/lib/active_job/queue_adapters.rb
@@ -33,7 +33,8 @@ module ActiveJob
#
# ==== Async
#
- # Yes: The Queue Adapter runs the jobs in a separate or forked process.
+ # Yes: The Queue Adapter has the ability to run the job in a non-blocking manner.
+ # It either runs on a separate or forked process, or on a different thread.
#
# No: The job is run in the same process.
#
diff --git a/activejob/lib/active_job/queue_adapters/async_adapter.rb b/activejob/lib/active_job/queue_adapters/async_adapter.rb
index 3fc27f56e7..922bc4afce 100644
--- a/activejob/lib/active_job/queue_adapters/async_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/async_adapter.rb
@@ -1,22 +1,113 @@
-require 'active_job/async_job'
+require 'securerandom'
+require 'concurrent/scheduled_task'
+require 'concurrent/executor/thread_pool_executor'
+require 'concurrent/utility/processor_counter'
module ActiveJob
module QueueAdapters
# == Active Job Async adapter
#
- # When enqueueing jobs with the Async adapter the job will be executed
- # asynchronously using {AsyncJob}[http://api.rubyonrails.org/classes/ActiveJob/AsyncJob.html].
+ # The Async adapter runs jobs with an in-process thread pool.
#
- # To use +AsyncJob+ set the queue_adapter config to +:async+.
+ # This is the default queue adapter. It's well-suited for dev/test since
+ # it doesn't need an external infrastructure, but it's a poor fit for
+ # production since it drops pending jobs on restart.
#
- # Rails.application.config.active_job.queue_adapter = :async
+ # To use this adapter, set queue adapter to +:async+:
+ #
+ # config.active_job.queue_adapter = :async
+ #
+ # To configure the adapter's thread pool, instantiate the adapter and
+ # pass your own config:
+ #
+ # config.active_job.queue_adapter = ActiveJob::QueueAdapters::AsyncAdapter.new \
+ # min_threads: 1,
+ # max_threads: 2 * Concurrent.processor_count,
+ # idletime: 600.seconds
+ #
+ # The adapter uses a {Concurrent Ruby}[https://github.com/ruby-concurrency/concurrent-ruby] thread pool to schedule and execute
+ # jobs. Since jobs share a single thread pool, long-running jobs will block
+ # short-lived jobs. Fine for dev/test; bad for production.
class AsyncAdapter
+ # See {Concurrent::ThreadPoolExecutor}[http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options.
+ def initialize(**executor_options)
+ @scheduler = Scheduler.new(**executor_options)
+ end
+
def enqueue(job) #:nodoc:
- ActiveJob::AsyncJob.enqueue(job.serialize, queue: job.queue_name)
+ @scheduler.enqueue JobWrapper.new(job), queue_name: job.queue_name
end
def enqueue_at(job, timestamp) #:nodoc:
- ActiveJob::AsyncJob.enqueue_at(job.serialize, timestamp, queue: job.queue_name)
+ @scheduler.enqueue_at JobWrapper.new(job), timestamp, queue_name: job.queue_name
+ end
+
+ # Gracefully stop processing jobs. Finishes in-progress work and handles
+ # any new jobs following the executor's fallback policy (`caller_runs`).
+ # Waits for termination by default. Pass `wait: false` to continue.
+ def shutdown(wait: true) #:nodoc:
+ @scheduler.shutdown wait: wait
+ end
+
+ # Used for our test suite.
+ def immediate=(immediate) #:nodoc:
+ @scheduler.immediate = immediate
+ end
+
+ # Note that we don't actually need to serialize the jobs since we're
+ # performing them in-process, but we do so anyway for parity with other
+ # adapters and deployment environments. Otherwise, serialization bugs
+ # may creep in undetected.
+ class JobWrapper #:nodoc:
+ def initialize(job)
+ job.provider_job_id = SecureRandom.uuid
+ @job_data = job.serialize
+ end
+
+ def perform
+ Base.execute @job_data
+ end
+ end
+
+ class Scheduler #:nodoc:
+ DEFAULT_EXECUTOR_OPTIONS = {
+ min_threads: 0,
+ max_threads: Concurrent.processor_count,
+ auto_terminate: true,
+ idletime: 60, # 1 minute
+ max_queue: 0, # unlimited
+ fallback_policy: :caller_runs # shouldn't matter -- 0 max queue
+ }.freeze
+
+ attr_accessor :immediate
+
+ def initialize(**options)
+ self.immediate = false
+ @immediate_executor = Concurrent::ImmediateExecutor.new
+ @async_executor = Concurrent::ThreadPoolExecutor.new(DEFAULT_EXECUTOR_OPTIONS.merge(options))
+ end
+
+ def enqueue(job, queue_name:)
+ executor.post(job, &:perform)
+ end
+
+ def enqueue_at(job, timestamp, queue_name:)
+ delay = timestamp - Time.current.to_f
+ if delay > 0
+ Concurrent::ScheduledTask.execute(delay, args: [job], executor: executor, &:perform)
+ else
+ enqueue(job, queue_name: queue_name)
+ end
+ end
+
+ def shutdown(wait: true)
+ @async_executor.shutdown
+ @async_executor.wait_for_termination if wait
+ end
+
+ def executor
+ immediate ? @immediate_executor : @async_executor
+ end
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/inline_adapter.rb b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
index 8ad5f4de07..0496f8449e 100644
--- a/activejob/lib/active_job/queue_adapters/inline_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
@@ -2,7 +2,7 @@ module ActiveJob
module QueueAdapters
# == Active Job Inline adapter
#
- # When enqueueing jobs with the Inline adapter the job will be executed
+ # When enqueuing jobs with the Inline adapter the job will be executed
# immediately.
#
# To use the Inline set the queue_adapter config to +:inline+.
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 311109e958..163c8eb212 100644
--- a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
@@ -5,7 +5,7 @@ module ActiveJob
# == Sucker Punch adapter for Active Job
#
# Sucker Punch is a single-process Ruby asynchronous processing library.
- # This reduces the cost of of hosting on a service like Heroku along
+ # This reduces the cost of hosting on a service like Heroku along
# with the memory footprint of having to maintain additional jobs if
# hosting on a dedicated server. All queues can run within a
# single application (eg. Rails, Sinatra, etc.) process.
diff --git a/activejob/lib/active_job/railtie.rb b/activejob/lib/active_job/railtie.rb
index 6538ac1b30..a47caa4a7e 100644
--- a/activejob/lib/active_job/railtie.rb
+++ b/activejob/lib/active_job/railtie.rb
@@ -12,12 +12,21 @@ module ActiveJob
initializer "active_job.set_configs" do |app|
options = app.config.active_job
- options.queue_adapter ||= :inline
+ options.queue_adapter ||= :async
ActiveSupport.on_load(:active_job) do
options.each { |k,v| send("#{k}=", v) }
end
end
+ initializer "active_job.set_reloader_hook" do |app|
+ ActiveSupport.on_load(:active_job) do
+ ActiveJob::Callbacks.singleton_class.set_callback(:execute, :around, prepend: true) do |_, inner|
+ app.reloader.wrap do
+ inner.call
+ end
+ end
+ end
+ end
end
end
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index 44ddfa5f69..e16af1947f 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -4,325 +4,321 @@ require 'active_support/core_ext/hash/keys'
module ActiveJob
# Provides helper methods for testing Active Job
module TestHelper
- extend ActiveSupport::Concern
+ delegate :enqueued_jobs, :enqueued_jobs=,
+ :performed_jobs, :performed_jobs=,
+ to: :queue_adapter
- included do
- def before_setup # :nodoc:
- test_adapter = ActiveJob::QueueAdapters::TestAdapter.new
+ def before_setup # :nodoc:
+ test_adapter = queue_adapter_for_test
- @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
+ @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
-
- clear_enqueued_jobs
- clear_performed_jobs
- super
end
- def after_teardown # :nodoc:
- super
- @old_queue_adapters.each do |(klass, adapter)|
- klass.queue_adapter = adapter
- end
- end
+ clear_enqueued_jobs
+ clear_performed_jobs
+ super
+ end
- # Asserts that the number of enqueued jobs matches the given number.
- #
- # def test_jobs
- # assert_enqueued_jobs 0
- # HelloJob.perform_later('david')
- # assert_enqueued_jobs 1
- # HelloJob.perform_later('abdelkader')
- # assert_enqueued_jobs 2
- # end
- #
- # If a block is passed, that block should cause the specified number of
- # jobs to be enqueued.
- #
- # def test_jobs_again
- # assert_enqueued_jobs 1 do
- # HelloJob.perform_later('cristian')
- # end
- #
- # assert_enqueued_jobs 2 do
- # HelloJob.perform_later('aaron')
- # HelloJob.perform_later('rafael')
- # end
- # end
- #
- # 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(only: only)
- yield
- new_count = enqueued_jobs_size(only: only)
- assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"
- else
- actual_count = enqueued_jobs_size(only: only)
- assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
- end
+ def after_teardown # :nodoc:
+ super
+ @old_queue_adapters.each do |(klass, adapter)|
+ klass.queue_adapter = adapter
end
+ end
- # Asserts that no jobs have been enqueued.
- #
- # def test_jobs
- # assert_no_enqueued_jobs
- # HelloJob.perform_later('jeremy')
- # assert_enqueued_jobs 1
- # end
- #
- # If a block is passed, that block should not cause any job to be enqueued.
- #
- # def test_jobs_again
- # assert_no_enqueued_jobs do
- # # No job should be enqueued from this block
- # 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(only: nil, &block)
- assert_enqueued_jobs 0, only: only, &block
- end
+ # Specifies the queue adapter to use with all active job test helpers.
+ #
+ # Returns an instance of the queue adapter and defaults to
+ # <tt>ActiveJob::QueueAdapters::TestAdapter</tt>.
+ #
+ # Note: The adapter provided by this method must provide some additional
+ # methods from those expected of a standard <tt>ActiveJob::QueueAdapter</tt>
+ # in order to be used with the active job test helpers. Refer to
+ # <tt>ActiveJob::QueueAdapters::TestAdapter</tt>.
+ def queue_adapter_for_test
+ ActiveJob::QueueAdapters::TestAdapter.new
+ 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
- #
- # perform_enqueued_jobs do
- # HelloJob.perform_later('xavier')
- # end
- # assert_performed_jobs 1
- #
- # 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
- # jobs to be performed.
- #
- # def test_jobs_again
- # assert_performed_jobs 1 do
- # HelloJob.perform_later('robin')
- # end
- #
- # assert_performed_jobs 2 do
- # HelloJob.perform_later('carlos')
- # HelloJob.perform_later('sean')
- # 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
- def assert_performed_jobs(number, only: nil)
- if block_given?
- original_count = performed_jobs.size
- perform_enqueued_jobs(only: only) { yield }
- new_count = performed_jobs.size
- assert_equal number, new_count - original_count,
- "#{number} jobs expected, but #{new_count - original_count} were performed"
- else
- performed_jobs_size = performed_jobs.size
- assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed"
- end
+ # Asserts that the number of enqueued jobs matches the given number.
+ #
+ # def test_jobs
+ # assert_enqueued_jobs 0
+ # HelloJob.perform_later('david')
+ # assert_enqueued_jobs 1
+ # HelloJob.perform_later('abdelkader')
+ # assert_enqueued_jobs 2
+ # end
+ #
+ # If a block is passed, that block should cause the specified number of
+ # jobs to be enqueued.
+ #
+ # def test_jobs_again
+ # assert_enqueued_jobs 1 do
+ # HelloJob.perform_later('cristian')
+ # end
+ #
+ # assert_enqueued_jobs 2 do
+ # HelloJob.perform_later('aaron')
+ # HelloJob.perform_later('rafael')
+ # end
+ # end
+ #
+ # The number of times a specific job is enqueued can be asserted.
+ #
+ # def test_logging_job
+ # assert_enqueued_jobs 1, 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(only: only)
+ yield
+ new_count = enqueued_jobs_size(only: only)
+ assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"
+ else
+ actual_count = enqueued_jobs_size(only: only)
+ assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
end
+ end
- # Asserts that no jobs have been performed.
- #
- # def test_jobs
- # assert_no_performed_jobs
- #
- # 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.
- #
- # def test_jobs_again
- # assert_no_performed_jobs do
- # # No job should be performed from this block
- # 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(only: nil, &block)
- assert_performed_jobs 0, only: only, &block
- end
+ # Asserts that no jobs have been enqueued.
+ #
+ # def test_jobs
+ # assert_no_enqueued_jobs
+ # HelloJob.perform_later('jeremy')
+ # assert_enqueued_jobs 1
+ # end
+ #
+ # If a block is passed, that block should not cause any job to be enqueued.
+ #
+ # def test_jobs_again
+ # assert_no_enqueued_jobs do
+ # # No job should be enqueued from this block
+ # 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(only: nil, &block)
+ assert_enqueued_jobs 0, only: only, &block
+ end
- # Asserts that the job passed in the block has been enqueued with the given arguments.
- #
- # def test_assert_enqueued_with
- # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do
- # MyJob.perform_later(1,2,3)
- # end
- #
- # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) do
- # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
- # end
- # end
- def assert_enqueued_with(args = {})
- original_enqueued_jobs_count = enqueued_jobs.count
- args.assert_valid_keys(:job, :args, :at, :queue)
- serialized_args = serialize_args_for_assertion(args)
- yield
- in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count)
- matching_job = in_block_jobs.find do |job|
- serialized_args.all? { |key, value| value == job[key] }
- end
- assert matching_job, "No enqueued job found with #{args}"
- instantiate_job(matching_job)
+ # 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
+ #
+ # perform_enqueued_jobs do
+ # HelloJob.perform_later('xavier')
+ # end
+ # assert_performed_jobs 1
+ #
+ # 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
+ # jobs to be performed.
+ #
+ # def test_jobs_again
+ # assert_performed_jobs 1 do
+ # HelloJob.perform_later('robin')
+ # end
+ #
+ # assert_performed_jobs 2 do
+ # HelloJob.perform_later('carlos')
+ # HelloJob.perform_later('sean')
+ # 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
+ def assert_performed_jobs(number, only: nil)
+ if block_given?
+ original_count = performed_jobs.size
+ perform_enqueued_jobs(only: only) { yield }
+ new_count = performed_jobs.size
+ assert_equal number, new_count - original_count,
+ "#{number} jobs expected, but #{new_count - original_count} were performed"
+ else
+ performed_jobs_size = performed_jobs.size
+ assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed"
end
+ end
- # Asserts that the job passed in the block has been performed with the given arguments.
- #
- # def test_assert_performed_with
- # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do
- # MyJob.perform_later(1,2,3)
- # end
- #
- # assert_performed_with(job: MyJob, at: Date.tomorrow.noon) do
- # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
- # end
- # end
- def assert_performed_with(args = {})
- original_performed_jobs_count = performed_jobs.count
- args.assert_valid_keys(:job, :args, :at, :queue)
- serialized_args = serialize_args_for_assertion(args)
- perform_enqueued_jobs { yield }
- in_block_jobs = performed_jobs.drop(original_performed_jobs_count)
- matching_job = in_block_jobs.find do |job|
- serialized_args.all? { |key, value| value == job[key] }
- end
- assert matching_job, "No performed job found with #{args}"
- instantiate_job(matching_job)
- end
+ # Asserts that no jobs have been performed.
+ #
+ # def test_jobs
+ # assert_no_performed_jobs
+ #
+ # 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.
+ #
+ # def test_jobs_again
+ # assert_no_performed_jobs do
+ # # No job should be performed from this block
+ # end
+ # end
+ #
+ # The block form supports filtering. If the :only option is specified,
+ # then only the listed job(s) will not be performed.
+ #
+ # def test_no_logging
+ # assert_no_performed_jobs only: LoggingJob do
+ # HelloJob.perform_later('jeremy')
+ # end
+ # end
+ #
+ # Note: This assertion is simply a shortcut for:
+ #
+ # assert_performed_jobs 0, &block
+ def assert_no_performed_jobs(only: nil, &block)
+ assert_performed_jobs 0, only: only, &block
+ 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
+ # Asserts that the job passed in the block has been enqueued with the given arguments.
+ #
+ # def test_assert_enqueued_with
+ # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do
+ # MyJob.perform_later(1,2,3)
+ # end
+ #
+ # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) do
+ # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
+ # end
+ # end
+ def assert_enqueued_with(args = {})
+ original_enqueued_jobs_count = enqueued_jobs.count
+ args.assert_valid_keys(:job, :args, :at, :queue)
+ serialized_args = serialize_args_for_assertion(args)
+ yield
+ in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count)
+ matching_job = in_block_jobs.find do |job|
+ serialized_args.all? { |key, value| value == job[key] }
+ end
+ assert matching_job, "No enqueued job found with #{args}"
+ instantiate_job(matching_job)
+ end
- 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
+ # Asserts that the job passed in the block has been performed with the given arguments.
+ #
+ # def test_assert_performed_with
+ # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do
+ # MyJob.perform_later(1,2,3)
+ # end
+ #
+ # assert_performed_with(job: MyJob, at: Date.tomorrow.noon) do
+ # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
+ # end
+ # end
+ def assert_performed_with(args = {})
+ original_performed_jobs_count = performed_jobs.count
+ args.assert_valid_keys(:job, :args, :at, :queue)
+ serialized_args = serialize_args_for_assertion(args)
+ perform_enqueued_jobs { yield }
+ in_block_jobs = performed_jobs.drop(original_performed_jobs_count)
+ matching_job = in_block_jobs.find do |job|
+ serialized_args.all? { |key, value| value == job[key] }
end
+ assert matching_job, "No performed job found with #{args}"
+ instantiate_job(matching_job)
+ end
- def queue_adapter
- ActiveJob::Base.queue_adapter
+ 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
- delegate :enqueued_jobs, :enqueued_jobs=,
- :performed_jobs, :performed_jobs=,
- to: :queue_adapter
+ def queue_adapter
+ ActiveJob::Base.queue_adapter
+ end
- private
- def clear_enqueued_jobs # :nodoc:
- enqueued_jobs.clear
- end
+ private
+ def clear_enqueued_jobs # :nodoc:
+ enqueued_jobs.clear
+ end
- def clear_performed_jobs # :nodoc:
- performed_jobs.clear
- end
+ def clear_performed_jobs # :nodoc:
+ performed_jobs.clear
+ end
- def enqueued_jobs_size(only: nil) # :nodoc:
- if only
- enqueued_jobs.count { |job| Array(only).include?(job.fetch(:job)) }
- else
- enqueued_jobs.count
- end
+ def enqueued_jobs_size(only: nil) # :nodoc:
+ if only
+ enqueued_jobs.count { |job| Array(only).include?(job.fetch(:job)) }
+ else
+ enqueued_jobs.count
end
+ end
- def serialize_args_for_assertion(args) # :nodoc:
- args.dup.tap do |serialized_args|
- serialized_args[:args] = ActiveJob::Arguments.serialize(serialized_args[:args]) if serialized_args[:args]
- serialized_args[:at] = serialized_args[:at].to_f if serialized_args[:at]
- end
+ def serialize_args_for_assertion(args) # :nodoc:
+ args.dup.tap do |serialized_args|
+ serialized_args[:args] = ActiveJob::Arguments.serialize(serialized_args[:args]) if serialized_args[:args]
+ serialized_args[:at] = serialized_args[:at].to_f if serialized_args[:at]
end
+ end
- def instantiate_job(payload) # :nodoc:
- job = payload[:job].new(*payload[:args])
- job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
- job.queue_name = payload[:queue]
- job
- end
- end
+ def instantiate_job(payload) # :nodoc:
+ job = payload[:job].new(*payload[:args])
+ job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
+ job.queue_name = payload[:queue]
+ job
+ end
end
end
diff --git a/activejob/lib/rails/generators/job/job_generator.rb b/activejob/lib/rails/generators/job/job_generator.rb
index 2115fb9f71..6e43e4a269 100644
--- a/activejob/lib/rails/generators/job/job_generator.rb
+++ b/activejob/lib/rails/generators/job/job_generator.rb
@@ -17,7 +17,22 @@ module Rails # :nodoc:
def create_job_file
template 'job.rb', File.join('app/jobs', class_path, "#{file_name}_job.rb")
+
+ in_root do
+ if self.behavior == :invoke && !File.exist?(application_job_file_name)
+ template 'application_job.rb', application_job_file_name
+ end
+ end
end
+
+ private
+ def application_job_file_name
+ @application_job_file_name ||= if mountable_engine?
+ "app/jobs/#{namespaced_path}/application_job.rb"
+ else
+ 'app/jobs/application_job.rb'
+ end
+ end
end
end
end
diff --git a/activejob/lib/rails/generators/job/templates/application_job.rb b/activejob/lib/rails/generators/job/templates/application_job.rb
new file mode 100644
index 0000000000..0b113b950e
--- /dev/null
+++ b/activejob/lib/rails/generators/job/templates/application_job.rb
@@ -0,0 +1,4 @@
+<% module_namespacing do -%>
+class ApplicationJob < ActiveJob::Base
+end
+<% end -%>
diff --git a/activejob/test/adapters/async.rb b/activejob/test/adapters/async.rb
index 5fcfb89566..08eb9658cd 100644
--- a/activejob/test/adapters/async.rb
+++ b/activejob/test/adapters/async.rb
@@ -1,4 +1,2 @@
-require 'active_job/async_job'
-
ActiveJob::Base.queue_adapter = :async
-ActiveJob::AsyncJob.perform_immediately!
+ActiveJob::Base.queue_adapter.immediate = true
diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb
index eb8ad185aa..59dc3d7f78 100644
--- a/activejob/test/cases/argument_serialization_test.rb
+++ b/activejob/test/cases/argument_serialization_test.rb
@@ -62,13 +62,14 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
assert_raises ActiveJob::SerializationError do
ActiveJob::Arguments.serialize [ { :a => [{ 2 => 3 }] } ]
end
+ end
- assert_raises ActiveJob::SerializationError do
- ActiveJob::Arguments.serialize [ '_aj_globalid' => 1 ]
- end
-
- assert_raises ActiveJob::SerializationError do
- ActiveJob::Arguments.serialize [ :_aj_globalid => 1 ]
+ test 'should not allow reserved hash keys' do
+ ['_aj_globalid', :_aj_globalid, '_aj_symbol_keys', :_aj_symbol_keys,
+ '_aj_hash_with_indifferent_access', :_aj_hash_with_indifferent_access].each do |key|
+ assert_raises ActiveJob::SerializationError do
+ ActiveJob::Arguments.serialize [key => 1]
+ end
end
end
diff --git a/activejob/test/cases/async_job_test.rb b/activejob/test/cases/async_job_test.rb
deleted file mode 100644
index 2642cfc608..0000000000
--- a/activejob/test/cases/async_job_test.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require 'helper'
-require 'jobs/hello_job'
-require 'jobs/queue_as_job'
-
-class AsyncJobTest < ActiveSupport::TestCase
- def using_async_adapter?
- ActiveJob::Base.queue_adapter.is_a? ActiveJob::QueueAdapters::AsyncAdapter
- end
-
- setup do
- ActiveJob::AsyncJob.perform_asynchronously!
- end
-
- teardown do
- ActiveJob::AsyncJob::QUEUES.clear
- ActiveJob::AsyncJob.perform_immediately!
- end
-
- test "#create_thread_pool returns a thread_pool" do
- thread_pool = ActiveJob::AsyncJob.create_thread_pool
- assert thread_pool.is_a? Concurrent::ExecutorService
- assert_not thread_pool.is_a? Concurrent::ImmediateExecutor
- end
-
- test "#create_thread_pool returns an ImmediateExecutor after #perform_immediately! is called" do
- ActiveJob::AsyncJob.perform_immediately!
- thread_pool = ActiveJob::AsyncJob.create_thread_pool
- assert thread_pool.is_a? Concurrent::ImmediateExecutor
- end
-
- test "enqueuing without specifying a queue uses the default queue" do
- skip unless using_async_adapter?
- HelloJob.perform_later
- assert ActiveJob::AsyncJob::QUEUES.key? 'default'
- end
-
- test "enqueuing to a queue that does not exist creates the queue" do
- skip unless using_async_adapter?
- QueueAsJob.perform_later
- assert ActiveJob::AsyncJob::QUEUES.key? QueueAsJob::MY_QUEUE.to_s
- end
-end
diff --git a/activejob/test/cases/job_serialization_test.rb b/activejob/test/cases/job_serialization_test.rb
index 229517774e..fa94209889 100644
--- a/activejob/test/cases/job_serialization_test.rb
+++ b/activejob/test/cases/job_serialization_test.rb
@@ -2,6 +2,7 @@ require 'helper'
require 'jobs/gid_job'
require 'jobs/hello_job'
require 'models/person'
+require 'json'
class JobSerializationTest < ActiveSupport::TestCase
setup do
@@ -15,18 +16,32 @@ class JobSerializationTest < ActiveSupport::TestCase
end
test 'serialize includes current locale' do
- assert_equal :en, HelloJob.new.serialize['locale']
+ assert_equal 'en', HelloJob.new.serialize['locale']
+ end
+
+ test 'serialize and deserialize are symmetric' do
+ # Round trip a job in memory only
+ h1 = HelloJob.new
+ h1.deserialize(h1.serialize)
+
+ # Now verify it's identical to a JSON round trip.
+ # We don't want any non-native JSON elements in the job hash,
+ # like symbols.
+ payload = JSON.dump(h1.serialize)
+ h2 = HelloJob.new
+ h2.deserialize(JSON.load(payload))
+ assert_equal h1.serialize, h2.serialize
end
test 'deserialize sets locale' do
job = HelloJob.new
- job.deserialize 'locale' => :es
- assert_equal :es, job.locale
+ job.deserialize 'locale' => 'es'
+ assert_equal 'es', job.locale
end
test 'deserialize sets default locale' do
job = HelloJob.new
job.deserialize({})
- assert_equal :en, job.locale
+ assert_equal 'en', job.locale
end
end
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index f7ee763e8a..3d863f5e65 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -509,3 +509,15 @@ class PerformedJobsTest < ActiveJob::TestCase
assert_equal 2, ActiveJob::Base.queue_adapter.performed_jobs.count
end
end
+
+class OverrideQueueAdapterTest < ActiveJob::TestCase
+ class CustomQueueAdapter < ActiveJob::QueueAdapters::TestAdapter; end
+
+ def queue_adapter_for_test
+ CustomQueueAdapter.new
+ end
+
+ def test_assert_job_has_custom_queue_adapter_set
+ assert_instance_of CustomQueueAdapter, HelloJob.queue_adapter
+ end
+end
diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb
index 7e86415f48..54b6076f09 100644
--- a/activejob/test/helper.rb
+++ b/activejob/test/helper.rb
@@ -1,5 +1,3 @@
-require File.expand_path('../../../load_paths', __FILE__)
-
require 'active_job'
require 'support/job_buffer'
diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
index d8425c9706..40f27500a5 100644
--- a/activejob/test/integration/queuing_test.rb
+++ b/activejob/test/integration/queuing_test.rb
@@ -57,13 +57,13 @@ class QueuingTest < ActiveSupport::TestCase
end
test 'should supply a provider_job_id when available for immediate jobs' do
- skip unless adapter_is?(:delayed_job, :sidekiq, :qu, :que, :queue_classic)
+ skip unless adapter_is?(:async, :delayed_job, :sidekiq, :qu, :que, :queue_classic)
test_job = TestJob.perform_later @id
assert test_job.provider_job_id, 'Provider job id should be set by provider'
end
test 'should supply a provider_job_id when available for delayed jobs' do
- skip unless adapter_is?(:delayed_job, :sidekiq, :que, :queue_classic)
+ skip unless adapter_is?(:async, :delayed_job, :sidekiq, :que, :queue_classic)
delayed_test_job = TestJob.set(wait: 1.minute).perform_later @id
assert delayed_test_job.provider_job_id, 'Provider job id should by set for delayed jobs by provider'
end
diff --git a/activejob/test/support/integration/adapters/async.rb b/activejob/test/support/integration/adapters/async.rb
index 42beb12b1f..44ab98437a 100644
--- a/activejob/test/support/integration/adapters/async.rb
+++ b/activejob/test/support/integration/adapters/async.rb
@@ -1,9 +1,10 @@
module AsyncJobsManager
def setup
ActiveJob::Base.queue_adapter = :async
+ ActiveJob::Base.queue_adapter.immediate = false
end
def clear_jobs
- ActiveJob::AsyncJob::QUEUES.clear
+ ActiveJob::Base.queue_adapter.shutdown
end
end
diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb
index 262ca72327..a0ef38b0b2 100644
--- a/activejob/test/support/integration/dummy_app_template.rb
+++ b/activejob/test/support/integration/dummy_app_template.rb
@@ -2,7 +2,7 @@ if ENV['AJ_ADAPTER'] == 'delayed_job'
generate "delayed_job:active_record", "--quiet"
end
-rake("db:migrate")
+rails_command("db:migrate")
initializer 'activejob.rb', <<-CODE
require "#{File.expand_path("../jobs_manager.rb", __FILE__)}"
diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb
index 9897f76fd0..fdf25c67a1 100644
--- a/activejob/test/support/integration/test_case_helpers.rb
+++ b/activejob/test/support/integration/test_case_helpers.rb
@@ -1,4 +1,5 @@
require 'active_support/concern'
+require 'active_support/core_ext/string/inflections'
require 'support/integration/jobs_manager'
module TestCaseHelpers
@@ -28,7 +29,8 @@ module TestCaseHelpers
end
def adapter_is?(*adapter_class_symbols)
- adapter_class_symbols.map(&:to_s).include?(ActiveJob::Base.queue_adapter.class.name.split("::").last.gsub(/Adapter$/, '').underscore)
+ adapter = ActiveJob::Base.queue_adapter.class.name.demodulize.chomp('Adapter').underscore
+ adapter_class_symbols.map(&:to_s).include? adapter
end
def wait_for_jobs_to_finish_for(seconds=60)
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index e4769d2f40..206699c036 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,132 +1,2 @@
-## Rails 5.0.0.beta1 (December 18, 2015) ##
-* Validate multiple contexts on `valid?` and `invalid?` at once.
-
- Example:
-
- class Person
- include ActiveModel::Validations
-
- attr_reader :name, :title
- validates_presence_of :name, on: :create
- validates_presence_of :title, on: :update
- end
-
- person = Person.new
- person.valid?([:create, :update]) # => false
- person.errors.messages # => {:name=>["can't be blank"], :title=>["can't be blank"]}
-
- *Dmitry Polushkin*
-
-* Add case_sensitive option for confirmation validator in models.
-
- *Akshat Sharma*
-
-* Ensure `method_missing` is called for methods passed to
- `ActiveModel::Serialization#serializable_hash` that don't exist.
-
- *Jay Elaraj*
-
-* Remove `ActiveModel::Serializers::Xml` from core.
-
- *Zachary Scott*
-
-* Add `ActiveModel::Dirty#[attr_name]_previously_changed?` and
- `ActiveModel::Dirty#[attr_name]_previous_change` to improve access
- to recorded changes after the model has been saved.
-
- It makes the dirty-attributes query methods consistent before and after
- saving.
-
- *Fernando Tapia Rico*
-
-* Deprecate the `:tokenizer` option for `validates_length_of`, in favor of
- plain Ruby.
-
- *Sean Griffin*
-
-* Deprecate `ActiveModel::Errors#add_on_empty` and `ActiveModel::Errors#add_on_blank`
- with no replacement.
-
- *Wojciech Wnętrzak*
-
-* Deprecate `ActiveModel::Errors#get`, `ActiveModel::Errors#set` and
- `ActiveModel::Errors#[]=` methods that have inconsistent behavior.
-
- *Wojciech Wnętrzak*
-
-* Allow symbol as values for `tokenize` of `LengthValidator`.
-
- *Kensuke Naito*
-
-* Assigning an unknown attribute key to an `ActiveModel` instance during initialization
- will now raise `ActiveModel::AttributeAssignment::UnknownAttributeError` instead of
- `NoMethodError`.
-
- Example:
-
- User.new(foo: 'some value')
- # => ActiveModel::AttributeAssignment::UnknownAttributeError: unknown attribute 'foo' for User.
-
- *Eugene Gilburg*
-
-* Extracted `ActiveRecord::AttributeAssignment` to `ActiveModel::AttributeAssignment`
- allowing to use it for any object as an includable module.
-
- Example:
-
- 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'
-
- *Bogdan Gusiev*
-
-* Add `ActiveModel::Errors#details`
-
- To be able to return type of used validator, one can now call `details`
- on errors instance.
-
- Example:
-
- class User < ActiveRecord::Base
- validates :name, presence: true
- end
-
- user = User.new; user.valid?; user.errors.details
- => {name: [{error: :blank}]}
-
- *Wojciech Wnętrzak*
-
-* Change validates_acceptance_of to accept true by default.
-
- 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*
-
-* Change the way in which callback chains can be halted.
-
- The preferred method to halt a callback chain from now on is to explicitly
- `throw(:abort)`.
- In the past, returning `false` in an Active Model `before_` callback had
- the side effect of halting the callback chain.
- This is not recommended anymore and, depending on the value of the
- `ActiveSupport.halt_callback_chains_on_return_false` option, will
- either not work at all or display a deprecation warning.
-
- *claudiob*
-
-Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activemodel/CHANGELOG.md) for previous changes.
+Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activemodel/CHANGELOG.md) for previous changes.
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index 5a67f0a151..036815a987 100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -4,6 +4,8 @@ dir = File.dirname(__FILE__)
task :default => :test
+task :package
+
Rake::TestTask.new do |t|
t.libs << "test"
t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb")
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 53206580f0..1c3997b864 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.homepage = 'http://rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index b95c174d6e..7de259a60d 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -49,6 +49,7 @@ module ActiveModel
eager_autoload do
autoload :Errors
+ autoload :RangeError, 'active_model/errors'
autoload :StrictValidationFailed, 'active_model/errors'
autoload :UnknownAttributeError, 'active_model/errors'
end
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 9de6ea65be..a932ada45c 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -40,8 +40,8 @@ module ActiveModel
self
end
- # Returns an Array of all key attributes if any is set, regardless if
- # the object is persisted or not. Returns +nil+ if there are no key attributes.
+ # Returns an Array of all key attributes if any of the attributes is set, whether or not
+ # the object is persisted. Returns +nil+ if there are no key attributes.
#
# class Person
# include ActiveModel::Conversion
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 6e2e5afd1b..90047c3c12 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -119,6 +119,9 @@ module ActiveModel
extend ActiveSupport::Concern
include ActiveModel::AttributeMethods
+ OPTION_NOT_GIVEN = Object.new # :nodoc:
+ private_constant :OPTION_NOT_GIVEN
+
included do
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
attribute_method_suffix '_previously_changed?', '_previous_change'
@@ -174,11 +177,10 @@ module ActiveModel
end
# Handles <tt>*_changed?</tt> for +method_missing+.
- def attribute_changed?(attr, options = {}) #:nodoc:
- result = changes_include?(attr)
- result &&= options[:to] == __send__(attr) if options.key?(:to)
- result &&= options[:from] == changed_attributes[attr] if options.key?(:from)
- result
+ def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
+ !!changes_include?(attr) &&
+ (to == OPTION_NOT_GIVEN || to == __send__(attr)) &&
+ (from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
end
# Handles <tt>*_was</tt> for +method_missing+.
@@ -187,7 +189,7 @@ module ActiveModel
end
# Handles <tt>*_previously_changed?</tt> for +method_missing+.
- def attribute_previously_changed?(attr, options = {}) #:nodoc:
+ def attribute_previously_changed?(attr) #:nodoc:
previous_changes_include?(attr)
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index ef6141a51d..6f2c8c1c53 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -71,8 +71,8 @@ module ActiveModel
# end
def initialize(base)
@base = base
- @messages = Hash.new { |messages, attribute| messages[attribute] = [] }
- @details = Hash.new { |details, attribute| details[attribute] = [] }
+ @messages = apply_default_array({})
+ @details = apply_default_array({})
end
def initialize_dup(other) # :nodoc:
@@ -110,7 +110,7 @@ module ActiveModel
# person.errors.include?(:name) # => true
# person.errors.include?(:age) # => false
def include?(attribute)
- messages[attribute].present?
+ messages.key?(attribute) && messages[attribute].present?
end
alias :has_key? :include?
alias :key? :include?
@@ -160,6 +160,15 @@ module ActiveModel
#
# person.errors[:name] # => ["cannot be nil"]
# person.errors['name'] # => ["cannot be nil"]
+ #
+ # Note that, if you try to get errors of an attribute which has
+ # no errors associated with it, this method will instantiate
+ # an empty error list for it and +keys+ will return an array
+ # of error keys which includes this attribute.
+ #
+ # person.errors.keys # => []
+ # person.errors[:name] # => []
+ # person.errors.keys # => [:name]
def [](attribute)
messages[attribute.to_sym]
end
@@ -301,9 +310,9 @@ module ActiveModel
# <tt>:strict</tt> option can also be set to any other exception.
#
# person.errors.add(:name, :invalid, strict: true)
- # # => ActiveModel::StrictValidationFailed: name is invalid
+ # # => ActiveModel::StrictValidationFailed: Name is invalid
# person.errors.add(:name, :invalid, strict: NameIsInvalid)
- # # => NameIsInvalid: name is invalid
+ # # => NameIsInvalid: Name is invalid
#
# person.errors.messages # => {}
#
@@ -318,7 +327,7 @@ module ActiveModel
# # => {: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)
+ detail = normalize_detail(message, options)
message = normalize_message(attribute, message, options)
if exception = options[:strict]
exception = ActiveModel::StrictValidationFailed if exception == true
@@ -337,7 +346,7 @@ module ActiveModel
# # => {: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
+ ActiveModel::Errors#add_on_empty is deprecated and will be removed in Rails 5.1.
To achieve the same use:
@@ -359,7 +368,7 @@ module ActiveModel
# # => {: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
+ ActiveModel::Errors#add_on_blank is deprecated and will be removed in Rails 5.1.
To achieve the same use:
@@ -477,12 +486,23 @@ module ActiveModel
default: defaults,
model: @base.model_name.human,
attribute: @base.class.human_attribute_name(attribute),
- value: value
+ value: value,
+ object: @base
}.merge!(options)
I18n.translate(key, options)
end
+ def marshal_dump
+ [@base, without_default_proc(@messages), without_default_proc(@details)]
+ end
+
+ def marshal_load(array)
+ @base, @messages, @details = array
+ apply_default_array(@messages)
+ apply_default_array(@details)
+ end
+
private
def normalize_message(attribute, message, options)
case message
@@ -493,9 +513,20 @@ module ActiveModel
end
end
- def normalize_detail(attribute, message, options)
+ def normalize_detail(message, options)
{ error: message }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS))
end
+
+ def without_default_proc(hash)
+ hash.dup.tap do |new_h|
+ new_h.default_proc = nil
+ end
+ end
+
+ def apply_default_array(hash)
+ hash.default_proc = proc { |h, key| h[key] = [] }
+ hash
+ end
end
# Raised when a validation cannot be corrected by end users and are considered
@@ -516,7 +547,20 @@ module ActiveModel
class StrictValidationFailed < StandardError
end
+ # Raised when attribute values are out of range.
+ class RangeError < ::RangeError
+ end
+
# Raised when unknown attributes are supplied via mass assignment.
+ #
+ # class Person
+ # include ActiveModel::AttributeAssignment
+ # include ActiveModel::Validations
+ # end
+ #
+ # person = Person.new
+ # person.assign_attributes(name: 'Gorby')
+ # # => ActiveModel::UnknownAttributeError: unknown attribute 'name' for Person.
class UnknownAttributeError < NoMethodError
attr_reader :record, :attribute
diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb
index 4563f860c3..4a8ee915cf 100644
--- a/activemodel/lib/active_model/gem_version.rb
+++ b/activemodel/lib/active_model/gem_version.rb
@@ -6,9 +6,9 @@ module ActiveModel
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta1.1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb
index bec851594f..6ec3452478 100644
--- a/activemodel/lib/active_model/type.rb
+++ b/activemodel/lib/active_model/type.rb
@@ -47,7 +47,7 @@ module ActiveModel
register(:binary, Type::Binary)
register(:boolean, Type::Boolean)
register(:date, Type::Date)
- register(:date_time, Type::DateTime)
+ register(:datetime, Type::DateTime)
register(:decimal, Type::Decimal)
register(:float, Type::Float)
register(:immutable_string, Type::ImmutableString)
diff --git a/activemodel/lib/active_model/type/decimal.rb b/activemodel/lib/active_model/type/decimal.rb
index d19d8baada..11ea327026 100644
--- a/activemodel/lib/active_model/type/decimal.rb
+++ b/activemodel/lib/active_model/type/decimal.rb
@@ -29,12 +29,12 @@ module ActiveModel
end
end
- scale ? casted_value.round(scale) : casted_value
+ apply_scale(casted_value)
end
def convert_float_to_big_decimal(value)
if precision
- BigDecimal(value, float_precision)
+ BigDecimal(apply_scale(value), float_precision)
else
value.to_d
end
@@ -47,6 +47,14 @@ module ActiveModel
precision.to_i
end
end
+
+ def apply_scale(value)
+ if scale
+ value.round(scale)
+ else
+ value
+ end
+ end
end
end
end
diff --git a/activemodel/lib/active_model/type/integer.rb b/activemodel/lib/active_model/type/integer.rb
index 2f73ede009..eea2280b22 100644
--- a/activemodel/lib/active_model/type/integer.rb
+++ b/activemodel/lib/active_model/type/integer.rb
@@ -46,7 +46,7 @@ module ActiveModel
def ensure_in_range(value)
unless range.cover?(value)
- raise RangeError, "#{value} is out of range for #{self.class} with limit #{_limit}"
+ raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit}"
end
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 8159b9b1d3..a10d2285a2 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -304,8 +304,6 @@ module ActiveModel
# Runs all the specified validations and returns +true+ if no errors were
# added otherwise +false+.
#
- # Aliased as validate.
- #
# class Person
# include ActiveModel::Validations
#
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index c5c0cd4636..a04e5f347e 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -64,11 +64,7 @@ module ActiveModel
private
def convert_to_reader_name(method_name)
- attr_name = method_name.to_s
- if attr_name.end_with?("=")
- attr_name = attr_name[0..-2]
- end
- attr_name
+ method_name.to_s.chomp('=')
end
end
end
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index 52111e5442..a201f72ed0 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -29,8 +29,7 @@ module ActiveModel
end
module ClassMethods
- # Defines a callback that will get called right before validation
- # happens.
+ # Defines a callback that will get called right before validation.
#
# class Person
# include ActiveModel::Validations
@@ -65,8 +64,7 @@ module ActiveModel
set_callback(:validation, :before, *args, &block)
end
- # Defines a callback that will get called right after validation
- # happens.
+ # Defines a callback that will get called right after validation.
#
# class Person
# include ActiveModel::Validations
diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb
index bad9e4f9a9..d49af603bb 100644
--- a/activemodel/lib/active_model/validations/clusivity.rb
+++ b/activemodel/lib/active_model/validations/clusivity.rb
@@ -30,14 +30,15 @@ module ActiveModel
@delimiter ||= options[:in] || options[:within]
end
- # In Ruby 1.9 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
+ # In Ruby 2.2 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
# possible values in the range for equality, which is slower but more accurate.
# <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
- # endpoints, which is fast but is only accurate on Numeric, Time, or DateTime ranges.
+ # endpoints, which is fast but is only accurate on Numeric, Time, Date,
+ # or DateTime ranges.
def inclusion_method(enumerable)
if enumerable.is_a? Range
case enumerable.first
- when Numeric, Time, DateTime
+ when Numeric, Time, DateTime, Date
:cover?
else
:include?
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 910cca2f49..79297ac119 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -136,7 +136,7 @@ module ActiveModel
# * <tt>:too_long</tt> - The error message if the attribute goes over the
# maximum (default is: "is too long (maximum is %{count} characters)").
# * <tt>:too_short</tt> - The error message if the attribute goes under the
- # minimum (default is: "is too short (min is %{count} characters)").
+ # minimum (default is: "is too short (minimum is %{count} characters)").
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt>
# method and the attribute is the wrong size (default is: "is the wrong
# length (should be %{count} characters)").
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index ad7012df48..9a0a0655de 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -120,7 +120,7 @@ module ActiveModel
# * <tt>:only_integer</tt> - Specifies whether the value has to be an
# integer, e.g. an integral value (default is +false+).
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
- # +false+). Notice that for fixnum and float columns empty strings are
+ # +false+). Notice that for Integer and Float columns empty strings are
# converted to +nil+.
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
# supplied value.
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 1d2888a818..699f74ed17 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -100,7 +100,7 @@ module ActiveModel
# PresenceValidator.kind # => :presence
# UniquenessValidator.kind # => :uniqueness
def self.kind
- @kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous?
+ @kind ||= name.split('::').last.underscore.chomp('_validator').to_sym unless anonymous?
end
# Accepts options that will be made available through the +options+ reader.
@@ -163,10 +163,6 @@ 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/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index a5ac055033..c90ee7021c 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -11,7 +11,7 @@ class ErrorsTest < ActiveModel::TestCase
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
def read_attribute_for_validation(attr)
@@ -128,6 +128,13 @@ class ErrorsTest < ActiveModel::TestCase
assert !person.errors.include?(:foo)
end
+ test "include? does not add a key to messages hash" do
+ person = Person.new
+ person.errors.include?(:foo)
+
+ assert_not person.errors.messages.key?(:foo)
+ end
+
test "adding errors using conditionals with Person#validate!" do
person = Person.new
person.validate!
@@ -420,4 +427,13 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal [:name], person.errors.messages.keys
assert_equal [:name], person.errors.details.keys
end
+
+ test "errors are marshalable" do
+ errors = ActiveModel::Errors.new(Person.new)
+ errors.add(:name, :invalid)
+ serialized = Marshal.load(Marshal.dump(errors))
+
+ assert_equal errors.messages, serialized.messages
+ assert_equal errors.details, serialized.details
+ end
end
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 27fdbc739c..c589fd2c33 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -1,7 +1,4 @@
-require File.expand_path('../../../../load_paths', __FILE__)
-
require 'active_model'
-require 'active_support/core_ext/string/access'
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
diff --git a/activemodel/test/cases/type/decimal_test.rb b/activemodel/test/cases/type/decimal_test.rb
index 353dbf84ad..1950566c0e 100644
--- a/activemodel/test/cases/type/decimal_test.rb
+++ b/activemodel/test/cases/type/decimal_test.rb
@@ -52,6 +52,13 @@ module ActiveModel
assert_not type.changed?(5.0, 5.0, '5.0')
assert_not type.changed?(-5.0, -5.0, '-5.0')
end
+
+ def test_scale_is_applied_before_precision_to_prevent_rounding_errors
+ type = Decimal.new(precision: 5, scale: 3)
+
+ assert_equal BigDecimal("1.250"), type.cast(1.250473853637869)
+ assert_equal BigDecimal("1.250"), type.cast("1.250473853637869")
+ end
end
end
end
diff --git a/activemodel/test/cases/type/integer_test.rb b/activemodel/test/cases/type/integer_test.rb
index dac922db42..6603f25c9a 100644
--- a/activemodel/test/cases/type/integer_test.rb
+++ b/activemodel/test/cases/type/integer_test.rb
@@ -53,25 +53,25 @@ module ActiveModel
end
test "values below int min value are out of range" do
- assert_raises(::RangeError) do
+ assert_raises(ActiveModel::RangeError) do
Integer.new.serialize(-2147483649)
end
end
test "values above int max value are out of range" do
- assert_raises(::RangeError) do
+ assert_raises(ActiveModel::RangeError) do
Integer.new.serialize(2147483648)
end
end
test "very small numbers are out of range" do
- assert_raises(::RangeError) do
+ assert_raises(ActiveModel::RangeError) do
Integer.new.serialize(-9999999999999999999999999999999)
end
end
test "very large numbers are out of range" do
- assert_raises(::RangeError) do
+ assert_raises(ActiveModel::RangeError) do
Integer.new.serialize(9999999999999999999999999999999)
end
end
@@ -96,10 +96,10 @@ module ActiveModel
assert_equal(9223372036854775807, type.serialize(9223372036854775807))
assert_equal(-9223372036854775808, type.serialize(-9223372036854775808))
- assert_raises(::RangeError) do
+ assert_raises(ActiveModel::RangeError) do
type.serialize(-9999999999999999999999999999999)
end
- assert_raises(::RangeError) do
+ assert_raises(ActiveModel::RangeError) do
type.serialize(9999999999999999999999999999999)
end
end
diff --git a/activemodel/test/cases/type/unsigned_integer_test.rb b/activemodel/test/cases/type/unsigned_integer_test.rb
index 16301b3ac0..026cb08a06 100644
--- a/activemodel/test/cases/type/unsigned_integer_test.rb
+++ b/activemodel/test/cases/type/unsigned_integer_test.rb
@@ -9,7 +9,7 @@ module ActiveModel
end
test "minus value is out of range" do
- assert_raises(::RangeError) do
+ assert_raises(ActiveModel::RangeError) do
UnsignedInteger.new.serialize(-1)
end
end
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 86bbbe6ebe..ea4c2ee7df 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -73,7 +73,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validate_format_of_with_multiline_regexp_and_option
- assert_nothing_raised(ArgumentError) do
+ assert_nothing_raised do
Topic.validates_format_of(:title, with: /^Valid Title$/, multiline: true)
end
end
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 55d1fb4dcb..9bd44175a6 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -21,24 +21,38 @@ class InclusionValidationTest < ActiveModel::TestCase
end
def test_validates_inclusion_of_time_range
- Topic.validates_inclusion_of(:created_at, in: 1.year.ago..Time.now)
+ range_begin = 1.year.ago
+ range_end = Time.now
+ Topic.validates_inclusion_of(:created_at, in: range_begin..range_end)
assert Topic.new(title: 'aaa', created_at: 2.years.ago).invalid?
assert Topic.new(title: 'aaa', created_at: 3.months.ago).valid?
assert Topic.new(title: 'aaa', created_at: 37.weeks.from_now).invalid?
+ assert Topic.new(title: 'aaa', created_at: range_begin).valid?
+ assert Topic.new(title: 'aaa', created_at: range_end).valid?
end
def test_validates_inclusion_of_date_range
- Topic.validates_inclusion_of(:created_at, in: 1.year.until(Date.today)..Date.today)
+ range_begin = 1.year.until(Date.today)
+ range_end = Date.today
+ Topic.validates_inclusion_of(:created_at, in: range_begin..range_end)
assert Topic.new(title: 'aaa', created_at: 2.years.until(Date.today)).invalid?
assert Topic.new(title: 'aaa', created_at: 3.months.until(Date.today)).valid?
assert Topic.new(title: 'aaa', created_at: 37.weeks.since(Date.today)).invalid?
+ assert Topic.new(title: 'aaa', created_at: 1.year.until(Date.today)).valid?
+ assert Topic.new(title: 'aaa', created_at: Date.today).valid?
+ assert Topic.new(title: 'aaa', created_at: range_begin).valid?
+ assert Topic.new(title: 'aaa', created_at: range_end).valid?
end
def test_validates_inclusion_of_date_time_range
- Topic.validates_inclusion_of(:created_at, in: 1.year.until(DateTime.current)..DateTime.current)
+ range_begin = 1.year.until(DateTime.current)
+ range_end = DateTime.current
+ Topic.validates_inclusion_of(:created_at, in: range_begin..range_end)
assert Topic.new(title: 'aaa', created_at: 2.years.until(DateTime.current)).invalid?
assert Topic.new(title: 'aaa', created_at: 3.months.until(DateTime.current)).valid?
assert Topic.new(title: 'aaa', created_at: 37.weeks.since(DateTime.current)).invalid?
+ assert Topic.new(title: 'aaa', created_at: range_begin).valid?
+ assert Topic.new(title: 'aaa', created_at: range_end).valid?
end
def test_validates_inclusion_of
@@ -58,9 +72,9 @@ class InclusionValidationTest < ActiveModel::TestCase
assert_raise(ArgumentError) { Topic.validates_inclusion_of(:title, in: nil) }
assert_raise(ArgumentError) { Topic.validates_inclusion_of(:title, in: 0) }
- assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: "hi!") }
- assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: {}) }
- assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: []) }
+ assert_nothing_raised { Topic.validates_inclusion_of(:title, in: "hi!") }
+ assert_nothing_raised { Topic.validates_inclusion_of(:title, in: {}) }
+ assert_nothing_raised { Topic.validates_inclusion_of(:title, in: []) }
end
def test_validates_inclusion_of_with_allow_nil
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index ee901b75fb..11dce1df20 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -355,7 +355,7 @@ class LengthValidationTest < ActiveModel::TestCase
assert_equal ["Your essay must be at least 5 words."], t.errors[:content]
end
- def test_validates_length_of_for_fixnum
+ def test_validates_length_of_for_integer
Topic.validates_length_of(:approved, is: 4)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1)
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index f0317ad219..2a4e9f842f 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -444,4 +444,20 @@ class ValidationsTest < ActiveModel::TestCase
assert topic.invalid?
assert duped.valid?
end
+
+ def test_validation_with_message_as_proc_that_takes_a_record_as_a_parameter
+ Topic.validates_presence_of(:title, message: proc { |record| "You have failed me for the last time, #{record.author_name}." })
+
+ t = Topic.new(author_name: 'Admiral')
+ assert t.invalid?
+ assert_equal ["You have failed me for the last time, Admiral."], t.errors[:title]
+ end
+
+ def test_validation_with_message_as_proc_that_takes_record_and_data_as_a_parameters
+ Topic.validates_presence_of(:title, message: proc { |record, data| "#{data[:attribute]} is missing. You have failed me for the last time, #{record.author_name}." })
+
+ t = Topic.new(author_name: 'Admiral')
+ assert t.invalid?
+ assert_equal ["Title is missing. You have failed me for the last time, Admiral."], t.errors[:title]
+ end
end
diff --git a/activemodel/test/models/project.rb b/activemodel/test/models/project.rb
deleted file mode 100644
index 581b6dc0b3..0000000000
--- a/activemodel/test/models/project.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class Project
- include ActiveModel::DeprecatedMassAssignmentSecurity
-end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index b59c9b4635..0bd3c2e78c 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,1815 +1,35 @@
-* Improve schema_migrations insertion performance by inserting all versions
- in one INSERT SQL.
-
- *Akira Matsuda*, *Naoto Koshikawa*
-
-* Using `references` or `belongs_to` in migrations will always add index
- for the referenced column by default, without adding `index: true` option
- to generated migration file. Users can opt out of this by passing
- `index: false`.
-
- Fixes #18146.
-
- *Matthew Draper*, *Prathamesh Sonpatki*
-
-* Run `type` attributes through attributes API type-casting before
- instantiating the corresponding subclass. This makes it possible to define
- custom STI mappings.
-
- Fixes #21986.
-
- *Yves Senn*
-
-* Don't try to quote functions or expressions passed to `:default` option if
- they are passed as procs.
-
- This will generate proper query with the passed function or expression for
- the default option, instead of trying to quote it in incorrect fashion.
-
- Example:
-
- create_table :posts do |t|
- t.datetime :published_at, default: -> { 'NOW()' }
- end
-
- *Ryuta Kamizono*
-
-* Fix regression when loading fixture files with symbol keys.
-
- Fixes #22584.
-
- *Yves Senn*
-
-* Use `version` column as primary key for schema_migrations table because
- `schema_migrations` versions are guaranteed to be unique.
-
- This makes it possible to use `update_attributes` on models that do
- not have a primary key.
-
- *Richard Schneeman*
-
-* Add short-hand methods for text and blob types in MySQL.
-
- In Pg and Sqlite3, `:text` and `:binary` have variable unlimited length.
- But in MySQL, these have limited length for each types (ref #21591, #21619).
- This change adds short-hand methods for each text and blob types.
-
- Example:
-
- create_table :foos do |t|
- t.tinyblob :tiny_blob
- t.mediumblob :medium_blob
- t.longblob :long_blob
- t.tinytext :tiny_text
- t.mediumtext :medium_text
- t.longtext :long_text
- end
-
- *Ryuta Kamizono*
-
-* Take into account UTC offset when assigning string representation of
- timestamp with offset specified to attribute of time type.
-
- *Andrey Novikov*
-
-* When calling `first` with a `limit` argument, return directly from the
- `loaded?` records if available.
-
- *Ben Woosley*
-
-* Deprecate sending the `offset` argument to `find_nth`. Please use the
- `offset` method on relation instead.
-
- *Ben Woosley*
-
-## Rails 5.0.0.beta1 (December 18, 2015) ##
-
-* Order the result of `find(ids)` to match the passed array, if the relation
- has no explicit order defined.
-
- Fixes #20338.
-
- *Miguel Grazziotin*, *Matthew Draper*
-
-* Omit default limit values in dumped schema. It's tidier, and if the defaults
- change in the future, we can address that via Migration API Versioning.
-
- *Jean Boussier*
-
-* Support passing the schema name as a prefix to table name in
- `ConnectionAdapters::SchemaStatements#indexes`. Previously the prefix would
- be considered a full part of the index name, and only the schema in the
- current search path would be considered.
-
- *Grey Baker*
-
-* Ignore index name in `index_exists?` and `remove_index` when not passed a
- name to check for.
-
- *Grey Baker*
-
-* Extract support for the legacy `mysql` database adapter from core. It will
- live on in a separate gem for now, but most users should just use `mysql2`.
-
- *Abdelkader Boudih*
-
-* ApplicationRecord is a new superclass for all app models, analogous to app
- controllers subclassing ApplicationController instead of
- ActionController::Base. This gives apps a single spot to configure app-wide
- model behavior.
-
- Newly generated applications have `app/models/application_record.rb`
- present by default.
-
- *Genadi Samokovarov*
-
-* Version the API presented to migration classes, so we can change parameter
- defaults without breaking existing migrations, or forcing them to be
- rewritten through a deprecation cycle.
-
- New migrations specify the Rails version they were written for:
-
- class AddStatusToOrders < ActiveRecord::Migration[5.0]
- def change
- # ...
- end
- end
-
- *Matthew Draper*, *Ravil Bayramgalin*
-
-* Use bind params for `limit` and `offset`. This will generate significantly
- fewer prepared statements for common tasks like pagination. To support this
- change, passing a string containing a comma to `limit` has been deprecated,
- and passing an Arel node to `limit` is no longer supported.
-
- Fixes #22250.
-
- *Sean Griffin*
-
-* Introduce after_{create,update,delete}_commit callbacks.
-
- Before:
-
- after_commit :add_to_index_later, on: :create
- after_commit :update_in_index_later, on: :update
- after_commit :remove_from_index_later, on: :destroy
-
- After:
-
- after_create_commit :add_to_index_later
- after_update_commit :update_in_index_later
- after_destroy_commit :remove_from_index_later
-
- Fixes #22515.
-
- *Genadi Samokovarov*
-
-* Respect the column default values for `inheritance_column` when
- instantiating records through the base class.
-
- Fixes #17121.
-
- Example:
-
- # The schema of BaseModel has `t.string :type, default: 'SubType'`
- subtype = BaseModel.new
- assert_equals SubType, subtype.class
-
- *Kuldeep Aggarwal*
-
-* Fix `rake db:structure:dump` on Postgres when multiple schemas are used.
-
- Fixes #22346.
-
- *Nick Muerdter*, *ckoenig*
-
-* Add schema dumping support for PostgreSQL geometric data types.
-
- *Ryuta Kamizono*
-
-* Except keys of `build_record`'s argument from `create_scope` in `initialize_attributes`.
-
- Fixes #21893.
-
- *Yuichiro Kaneko*
-
-* Deprecate `connection.tables` on the SQLite3 and MySQL adapters.
- Also deprecate passing arguments to `#tables`.
- And deprecate `table_exists?`.
-
- The `#tables` method of some adapters (mysql, mysql2, sqlite3) would return
- both tables and views while others (postgresql) just return tables. To make
- their behavior consistent, `#tables` will return only tables in the future.
-
- The `#table_exists?` method would check both tables and views. To make
- their behavior consistent with `#tables`, `#table_exists?` will check only
- tables in the future.
-
- *Yuichiro Kaneko*
-
-* Improve support for non Active Record objects on `validates_associated`
-
- Skipping `marked_for_destruction?` when the associated object does not responds
- to it make easier to validate virtual associations built on top of Active Model
- objects and/or serialized objects that implement a `valid?` instance method.
-
- *Kassio Borges*, *Lucas Mazza*
-
-* Change connection management middleware to return a new response with
- a body proxy, rather than mutating the original.
-
- *Kevin Buchanan*
-
-* Make `db:migrate:status` to render `1_some.rb` format migrate files.
-
- These files are in `db/migrate`:
-
- * 1_valid_people_have_last_names.rb
- * 20150819202140_irreversible_migration.rb
- * 20150823202140_add_admin_flag_to_users.rb
- * 20150823202141_migration_tests.rb
- * 2_we_need_reminders.rb
- * 3_innocent_jointable.rb
-
- Before:
-
- $ bundle exec rake db:migrate:status
- ...
-
- Status Migration ID Migration Name
- --------------------------------------------------
- up 001 ********** NO FILE **********
- up 002 ********** NO FILE **********
- up 003 ********** NO FILE **********
- up 20150819202140 Irreversible migration
- up 20150823202140 Add admin flag to users
- up 20150823202141 Migration tests
-
- After:
-
- $ bundle exec rake db:migrate:status
- ...
-
- Status Migration ID Migration Name
- --------------------------------------------------
- up 001 Valid people have last names
- up 002 We need reminders
- up 003 Innocent jointable
- up 20150819202140 Irreversible migration
- up 20150823202140 Add admin flag to users
- up 20150823202141 Migration tests
-
- *Yuichiro Kaneko*
-
-* Define `ActiveRecord::Sanitization.sanitize_sql_for_order` and use it inside
- `preprocess_order_args`.
-
- *Yuichiro Kaneko*
-
-* Allow bigint with default nil for avoiding auto increment primary key.
-
- *Ryuta Kamizono*
-
-* Remove `DEFAULT_CHARSET` and `DEFAULT_COLLATION` in `MySQLDatabaseTasks`.
-
- We should omit the collation entirely rather than providing a default.
- Then the choice is the responsibility of the server and MySQL distribution.
-
- *Ryuta Kamizono*
-
-* Alias `ActiveRecord::Relation#left_joins` to
- `ActiveRecord::Relation#left_outer_joins`.
-
- *Takashi Kokubun*
-
-* Use advisory locking to raise a `ConcurrentMigrationError` instead of
- attempting to migrate when another migration is currently running.
-
- *Sam Davies*
-
-* Added `ActiveRecord::Relation#left_outer_joins`.
-
- Example:
-
- User.left_outer_joins(:posts)
- # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON
- "posts"."user_id" = "users"."id"
-
- *Florian Thomas*
-
-* Support passing an array to `order` for SQL parameter sanitization.
-
- *Aaron Suggs*
-
-* Avoid disabling errors on the PostgreSQL connection when enabling the
- `standard_conforming_strings` setting. Errors were previously disabled because
- the setting wasn't writable in Postgres 8.1 and didn't exist in earlier
- versions. Now Rails only supports Postgres 8.2+ we're fine to assume the
- setting exists. Disabling errors caused problems when using a connection
- pooling tool like PgBouncer because it's not guaranteed to have the same
- connection between calls to `execute` and it could leave the connection
- with errors disabled.
-
- Fixes #22101.
-
- *Harry Marr*
-
-* Set `scope.reordering_value` to `true` if `:reordering`-values are specified.
-
- Fixes #21886.
-
- *Hiroaki Izu*
-
-* Add support for bidirectional destroy dependencies.
-
- Fixes #13609.
-
- Example:
-
- class Content < ActiveRecord::Base
- has_one :position, dependent: :destroy
- end
-
- class Position < ActiveRecord::Base
- belongs_to :content, dependent: :destroy
- end
-
- *Seb Jacobs*
-
-* Includes HABTM returns correct size now. It's caused by the join dependency
- only instantiates one HABTM object because the join table hasn't a primary key.
-
- Fixes #16032.
-
- Examples:
-
- before:
-
- Project.first.salaried_developers.size # => 3
- Project.includes(:salaried_developers).first.salaried_developers.size # => 1
-
- after:
-
- Project.first.salaried_developers.size # => 3
- Project.includes(:salaried_developers).first.salaried_developers.size # => 3
-
- *Bigxiang*
-
-* Add option to index errors in nested attributes
-
- For models which have nested attributes, errors within those models will
- now be indexed if :index_errors is specified when defining a
- has_many relationship, or if its set in the global config.
-
- Example:
-
- class Guitar < ActiveRecord::Base
- has_many :tuning_pegs
- accepts_nested_attributes_for :tuning_pegs
- end
-
- class TuningPeg < ActiveRecord::Base
- belongs_to :guitar
- validates_numericality_of :pitch
- end
-
- # Old style
- guitar.errors["tuning_pegs.pitch"] = ["is not a number"]
-
- # New style (if defined globally, or set in has_many_relationship)
- guitar.errors["tuning_pegs[1].pitch"] = ["is not a number"]
-
- *Michael Probber*, *Terence Sun*
-
-* Exit with non-zero status for failed database rake tasks.
-
- *Jay Hayes*
-
-* Queries such as `Computer.joins(:monitor).group(:status).count` will now be
- interpreted as `Computer.joins(:monitor).group('computers.status').count`
- so that when `Computer` and `Monitor` have both `status` columns we don't
- have conflicts in projection.
-
- *Rafael Sales*
-
-* Add ability to default to `uuid` as primary key when generating database migrations.
-
- Example:
-
- config.generators do |g|
- g.orm :active_record, primary_key_type: :uuid
- end
-
- *Jon McCartie*
-
-* Don't cache arguments in `#find_by` if they are an `ActiveRecord::Relation`.
-
- Fixes #20817.
-
- *Hiroaki Izu*
-
-* Qualify column name inserted by `group` in calculation.
-
- Giving `group` an unqualified column name now works, even if the relation
- has `JOIN` with another table which also has a column of the name.
-
- *Soutaro Matsumoto*
-
-* Don't cache prepared statements containing an IN clause or a SQL literal, as
- these queries will change often and are unlikely to have a cache hit.
-
- *Sean Griffin*
-
-* Fix `rewhere` in a `has_many` association.
-
- Fixes #21955.
-
- *Josh Branchaud*, *Kal*
-
-* `where` raises ArgumentError on unsupported types.
-
- Fixes #20473.
-
- *Jake Worth*
-
-* Add an immutable string type to help reduce memory usage for apps which do
- not need mutation detection on strings.
-
- *Sean Griffin*
-
-* Give `ActiveRecord::Relation#update` its own deprecation warning when
- passed an `ActiveRecord::Base` instance.
-
- Fixes #21945.
-
- *Ted Johansson*
-
-* Make it possible to pass `:to_table` when adding a foreign key through
- `add_reference`.
-
- Fixes #21563.
-
- *Yves Senn*
-
-* No longer pass deprecated option `-i` to `pg_dump`.
-
- *Paul Sadauskas*
-
-* Concurrent `AR::Base#increment!` and `#decrement!` on the same record
- are all reflected in the database rather than overwriting each other.
-
- *Bogdan Gusiev*
-
-* Avoid leaking the first relation we call `first` on, per model.
-
- Fixes #21921.
-
- *Matthew Draper*, *Jean Boussier*
-
-* Remove unused `pk_and_sequence_for` in `AbstractMysqlAdapter`.
-
- *Ryuta Kamizono*
-
-* Allow fixtures files to set the model class in the YAML file itself.
-
- To load the fixtures file `accounts.yml` as the `User` model, use:
-
- _fixture:
- model_class: User
- david:
- name: David
-
- Fixes #9516.
-
- *Roque Pinel*
-
-* Don't require a database connection to load a class which uses acceptance
- validations.
+* Ensure hashes can be assigned to attributes created using `composed_of`.
+ Fixes #25210.
*Sean Griffin*
-* Correctly apply `unscope` when preloading through associations.
-
- *Jimmy Bourassa*
-
-* Fixed taking precision into count when assigning a value to timestamp attribute.
-
- Timestamp column can have less precision than ruby timestamp
- In result in how big a fraction of a second can be stored in the
- database.
-
-
- m = Model.create!
- m.created_at.usec == m.reload.created_at.usec # => false
- # due to different precision in Time.now and database column
-
- If the precision is low enough, (mysql default is 0, so it is always low
- enough by default) the value changes when model is reloaded from the
- database. This patch fixes that issue ensuring that any timestamp
- assigned as an attribute is converted to column precision under the
- attribute.
-
- *Bogdan Gusiev*
-
-* Introduce `connection.data_sources` and `connection.data_source_exists?`.
- These methods determine what relations can be used to back Active Record
- models (usually tables and views).
-
- Also deprecate `SchemaCache#tables`, `SchemaCache#table_exists?` and
- `SchemaCache#clear_table_cache!` in favor of their new data source
- counterparts.
-
- *Yves Senn*, *Matthew Draper*
-
-* Add `ActiveRecord::Base.ignored_columns` to make some columns
- invisible from Active Record.
-
- *Jean Boussier*
-
-* `ActiveRecord::Tasks::MySQLDatabaseTasks` fails if shellout to
- mysql commands (like `mysqldump`) is not successful.
-
- *Steve Mitchell*
-
-* Ensure `select` quotes aliased attributes, even when using `from`.
-
- Fixes #21488.
-
- *Sean Griffin*, *@johanlunds*
-
-* MySQL: support `unsigned` numeric data types.
-
- Example:
-
- create_table :foos do |t|
- t.unsigned_integer :quantity
- t.unsigned_bigint :total
- t.unsigned_float :percentage
- t.unsigned_decimal :price, precision: 10, scale: 2
- end
-
- The `unsigned: true` option may be used for the primary key:
-
- create_table :foos, id: :bigint, unsigned: true do |t|
- …
- end
+* Fix logging edge case where if an attribute was of the binary type and
+ was provided as a Hash.
- *Ryuta Kamizono*
+ *Jon Moss*
-* Add `#views` and `#view_exists?` methods on connection adapters.
+* Handle JSON deserialization correctly if the column default from database
+ adapter returns `''` instead of `nil`.
- *Ryuta Kamizono*
+ *Johannes Opper*
-* Correctly dump composite primary key.
+* Introduce ActiveRecord::TransactionSerializationError for catching
+ transaction serialization failures or deadlocks.
- Example:
-
- create_table :barcodes, primary_key: ["region", "code"] do |t|
- t.string :region
- t.integer :code
- end
-
- *Ryuta Kamizono*
-
-* Lookup the attribute name for `restrict_with_error` messages on the
- model class that defines the association.
-
- *kuboon*, *Ronak Jangir*
-
-* Correct query for PostgreSQL 8.2 compatibility.
-
- *Ben Murphy*, *Matthew Draper*
-
-* `bin/rake db:migrate` uses
- `ActiveRecord::Tasks::DatabaseTasks.migrations_paths` instead of
- `Migrator.migrations_paths`.
-
- *Tobias Bielohlawek*
-
-* Support dropping indexes concurrently in PostgreSQL.
-
- See http://www.postgresql.org/docs/9.4/static/sql-dropindex.html for more
- details.
-
- *Grey Baker*
+ *Erol Fornoles*
-* Deprecate passing conditions to `ActiveRecord::Relation#delete_all`
- and `ActiveRecord::Relation#destroy_all`.
+* PostgreSQL: Fix db:structure:load silent failure on SQL error
- *Wojciech Wnętrzak*
-
-* Instantiating an AR model with `ActionController::Parameters` now raises
- an `ActiveModel::ForbiddenAttributesError` if the parameters include a
- `type` field that has not been explicitly permitted. Previously, the
- `type` field was simply ignored in the same situation.
-
- *Prem Sichanugrist*
-
-* PostgreSQL, `create_schema`, `drop_schema` and `rename_table` now quote
- schema names.
-
- Fixes #21418.
+ The command line flag "-v ON_ERROR_STOP=1" should be used
+ when invoking psql to make sure errors are not suppressed.
Example:
- create_schema("my.schema")
- # CREATE SCHEMA "my.schema";
-
- *Yves Senn*
-
-* PostgreSQL, add `:if_exists` option to `#drop_schema`. This makes it
- possible to drop a schema that might exist without raising an exception if
- it doesn't.
-
- *Yves Senn*
-
-* Only try to nullify has_one target association if the record is persisted.
-
- Fixes #21223.
-
- *Agis Anastasopoulos*
-
-* Uniqueness validator raises descriptive error when running on a persisted
- record without primary key.
-
- Fixes #21304.
-
- *Yves Senn*
-
-* Add a native JSON data type support in MySQL.
-
- Example:
-
- create_table :json_data_type do |t|
- t.json :settings
- end
-
- *Ryuta Kamizono*
-
-* Descriptive error message when fixtures contain a missing column.
-
- Fixes #21201.
-
- *Yves Senn*
-
-* `ActiveRecord::Tasks::PostgreSQLDatabaseTasks` fail if shellout to
- postgresql commands (like `pg_dump`) is not successful.
-
- *Bryan Paxton*, *Nate Berkopec*
-
-* Add `ActiveRecord::Relation#in_batches` to work with records and relations
- in batches.
-
- Available options are `of` (batch size), `load`, `start`, and `finish`.
-
- Examples:
-
- Person.in_batches.each_record(&:party_all_night!)
- Person.in_batches.update_all(awesome: true)
- Person.in_batches.delete_all
- Person.in_batches.each do |relation|
- relation.delete_all
- sleep 10 # Throttles the delete queries
- end
-
- Fixes #20933.
-
- *Sina Siadat*
-
-* Added methods for PostgreSQL geometric data types to use in migrations.
-
- Example:
-
- create_table :foo do |t|
- t.line :foo_line
- t.lseg :foo_lseg
- t.box :foo_box
- t.path :foo_path
- t.polygon :foo_polygon
- t.circle :foo_circle
- end
-
- *Mehmet Emin İNAÇ*
-
-* Add `cache_key` to ActiveRecord::Relation.
-
- Example:
-
- @users = User.where("name like ?", "%Alberto%")
- @users.cache_key
- # => "/users/query-5942b155a43b139f2471b872ac54251f-3-20150714212107656125000"
-
- *Alberto Fernández-Capel*
-
-* Properly allow uniqueness validations on primary keys.
-
- Fixes #20966.
-
- *Sean Griffin*, *presskey*
-
-* Don't raise an error if an association failed to destroy when `destroy` was
- called on the parent (as opposed to `destroy!`).
-
- Fixes #20991.
-
- *Sean Griffin*
-
-* `ActiveRecord::RecordNotFound` modified to store model name, primary_key and
- id of the caller model. It allows the catcher of this exception to make
- a better decision to what to do with it.
-
- Example:
-
- class SomeAbstractController < ActionController::Base
- rescue_from ActiveRecord::RecordNotFound, with: :redirect_to_404
-
- private def redirect_to_404(e)
- return redirect_to(posts_url) if e.model == 'Post'
- raise
- end
- end
-
- *Sameer Rahmani*
-
-* Deprecate the keys for association `restrict_dependent_destroy` errors in favor
- of new key names.
-
- Previously `has_one` and `has_many` associations were using the
- `one` and `many` keys respectively. Both of these keys have special
- meaning in I18n (they are considered to be pluralizations) so by
- renaming them to `has_one` and `has_many` we make the messages more explicit
- and most importantly they don't clash with linguistical systems that need to
- validate translation keys (and their pluralizations).
-
- The `:'restrict_dependent_destroy.one'` key should be replaced with
- `:'restrict_dependent_destroy.has_one'`, and `:'restrict_dependent_destroy.many'`
- with `:'restrict_dependent_destroy.has_many'`.
-
- *Roque Pinel*, *Christopher Dell*
-
-* Fix state being carried over from previous transaction.
-
- Considering the following example where `name` is a required attribute.
- Before we had `new_record?` returning `true` for a persisted record:
-
- author = Author.create! name: 'foo'
- author.name = nil
- author.save # => false
- author.new_record? # => true
-
- Fixes #20824.
-
- *Roque Pinel*
-
-* Correctly ignore `mark_for_destruction` when `autosave` isn't set to `true`
- when validating associations.
-
- Fixes #20882.
-
- *Sean Griffin*
-
-* Fix a bug where counter_cache doesn't always work with polymorphic
- relations.
-
- Fixes #16407.
-
- *Stefan Kanev*, *Sean Griffin*
-
-* Ensure that cyclic associations with autosave don't cause duplicate errors
- to be added to the parent record.
-
- Fixes #20874.
-
- *Sean Griffin*
-
-* Ensure that `ActionController::Parameters` can still be passed to nested
- attributes.
-
- Fixes #20922.
-
- *Sean Griffin*
-
-* Deprecate force association reload by passing a truthy argument to
- association method.
-
- For collection association, you can call `#reload` on association proxy to
- force a reload:
-
- @user.posts.reload # Instead of @user.posts(true)
-
- For singular association, you can call `#reload` on the parent object to
- clear its association cache then call the association method:
-
- @user.reload.profile # Instead of @user.profile(true)
-
- Passing a truthy argument to force association to reload will be removed in
- Rails 5.1.
-
- *Prem Sichanugrist*
-
-* Replaced `ActiveSupport::Concurrency::Latch` with `Concurrent::CountDownLatch`
- from the concurrent-ruby gem.
-
- *Jerry D'Antonio*
-
-* Fix through associations using scopes having the scope merged multiple
- times.
-
- Fixes #20721.
- Fixes #20727.
-
- *Sean Griffin*
-
-* `ActiveRecord::Base.dump_schema_after_migration` applies migration tasks
- other than `db:migrate`. (eg. `db:rollback`, `db:migrate:dup`, ...)
-
- Fixes #20743.
-
- *Yves Senn*
-
-* Add alternate syntax to make `change_column_default` reversible.
-
- User can pass in `:from` and `:to` to make `change_column_default` command
- become reversible.
-
- Example:
-
- change_column_default :posts, :status, from: nil, to: "draft"
- change_column_default :users, :authorized, from: true, to: false
-
- *Prem Sichanugrist*
-
-* Prevent error when using `force_reload: true` on an unassigned polymorphic
- belongs_to association.
-
- Fixes #20426.
-
- *James Dabbs*
-
-* Correctly raise `ActiveRecord::AssociationTypeMismatch` when assigning
- a wrong type to a namespaced association.
-
- Fixes #20545.
-
- *Diego Carrion*
-
-* `validates_absence_of` respects `marked_for_destruction?`.
-
- Fixes #20449.
-
- *Yves Senn*
-
-* Include the `Enumerable` module in `ActiveRecord::Relation`
-
- *Sean Griffin*, *bogdan*
-
-* Use `Enumerable#sum` in `ActiveRecord::Relation` if a block is given.
-
- *Sean Griffin*
-
-* Let `WITH` queries (Common Table Expressions) be explainable.
-
- *Vladimir Kochnev*
-
-* Make `remove_index :table, :column` reversible.
-
- *Yves Senn*
-
-* Fixed an error which would occur in dirty checking when calling
- `update_attributes` from a getter.
-
- Fixes #20531.
-
- *Sean Griffin*
-
-* Make `remove_foreign_key` reversible. Any foreign key options must be
- specified, similar to `remove_column`.
-
- *Aster Ryan*
-
-* Add `:_prefix` and `:_suffix` options to `enum` definition.
-
- Fixes #17511, #17415.
-
- *Igor Kapkov*
-
-* Correctly handle decimal arrays with defaults in the schema dumper.
-
- Fixes #20515.
-
- *Sean Griffin*, *jmondo*
-
-* Deprecate the PostgreSQL `:point` type in favor of a new one which will return
- `Point` objects instead of an `Array`
-
- *Sean Griffin*
-
-* Ensure symbols passed to `ActiveRecord::Relation#select` are always treated
- as columns.
-
- Fixes #20360.
-
- *Sean Griffin*
-
-* Do not set `sql_mode` if `strict: :default` is specified.
-
- # config/database.yml
- production:
- adapter: mysql2
- database: foo_prod
- user: foo
- strict: :default
-
- *Ryuta Kamizono*
-
-* Allow proc defaults to be passed to the attributes API. See documentation
- for examples.
-
- *Sean Griffin*, *Kir Shatrov*
-
-* SQLite: `:collation` support for string and text columns.
-
- Example:
-
- create_table :foo do |t|
- t.string :string_nocase, collation: 'NOCASE'
- t.text :text_rtrim, collation: 'RTRIM'
- end
-
- add_column :foo, :title, :string, collation: 'RTRIM'
-
- change_column :foo, :title, :string, collation: 'NOCASE'
-
- *Akshay Vishnoi*
-
-* Allow the use of symbols or strings to specify enum values in test
- fixtures:
-
- awdr:
- title: "Agile Web Development with Rails"
- status: :proposed
-
- *George Claghorn*
-
-* Clear query cache when `ActiveRecord::Base#reload` is called.
-
- *Shane Hender, Pierre Nespo*
-
-* Include stored procedures and function on the MySQL structure dump.
-
- *Jonathan Worek*
-
-* Pass `:extend` option for `has_and_belongs_to_many` associations to the
- underlying `has_many :through`.
-
- *Jaehyun Shin*
-
-* Deprecate `Relation#uniq` use `Relation#distinct` instead.
-
- See #9683.
-
- *Yves Senn*
-
-* Allow single table inheritance instantiation to work when storing
- demodulized class names.
-
- *Alex Robbin*
-
-* Correctly pass MySQL options when using `structure_dump` or
- `structure_load`.
-
- Specifically, it fixes an issue when using SSL authentication.
-
- *Alex Coomans*
-
-* Dump indexes in `create_table` instead of `add_index`.
-
- If the adapter supports indexes in `create_table`, generated SQL is
- slightly more efficient.
-
- *Ryuta Kamizono*
-
-* Correctly dump `:options` on `create_table` for MySQL.
-
- *Ryuta Kamizono*
-
-* PostgreSQL: `:collation` support for string and text columns.
-
- Example:
-
- create_table :foos do |t|
- t.string :string_en, collation: 'en_US.UTF-8'
- t.text :text_ja, collation: 'ja_JP.UTF-8'
- end
-
- *Ryuta Kamizono*
-
-* Remove `ActiveRecord::Serialization::XmlSerializer` from core.
-
- *Zachary Scott*
-
-* Make `unscope` aware of "less than" and "greater than" conditions.
-
- *TAKAHASHI Kazuaki*
-
-* `find_by` and `find_by!` raise `ArgumentError` when called without
- arguments.
-
- *Kohei Suzuki*
-
-* Revert behavior of `db:schema:load` back to loading the full
- environment. This ensures that initializers are run.
-
- Fixes #19545.
-
- *Yves Senn*
-
-* Fix missing index when using `timestamps` with the `index` option.
-
- The `index` option used with `timestamps` should be passed to both
- `column` definitions for `created_at` and `updated_at` rather than just
- the first.
-
- *Paul Mucur*
-
-* Rename `:class` to `:anonymous_class` in association options.
-
- Fixes #19659.
-
- *Andrew White*
-
-* Autosave existing records on a has many through association when the parent
- is new.
-
- Fixes #19782.
-
- *Sean Griffin*
-
-* Fixed a bug where uniqueness validations would error on out of range values,
- even if an validation should have prevented it from hitting the database.
-
- *Andrey Voronkov*
-
-* MySQL: `:charset` and `:collation` support for string and text columns.
-
- Example:
-
- create_table :foos do |t|
- t.string :string_utf8_bin, charset: 'utf8', collation: 'utf8_bin'
- t.text :text_ascii, charset: 'ascii'
- end
-
- *Ryuta Kamizono*
-
-* Foreign key related methods in the migration DSL respect
- `ActiveRecord::Base.pluralize_table_names = false`.
-
- Fixes #19643.
-
- *Mehmet Emin İNAÇ*
-
-* Reduce memory usage from loading types on PostgreSQL.
-
- Fixes #19578.
-
- *Sean Griffin*
-
-* Add `config.active_record.warn_on_records_fetched_greater_than` option.
-
- When set to an integer, a warning will be logged whenever a result set
- larger than the specified size is returned by a query.
-
- Fixes #16463.
-
- *Jason Nochlin*
-
-* Ignore `.psqlrc` when loading database structure.
-
- *Jason Weathered*
-
-* Fix referencing wrong table aliases while joining tables of has many through
- association (only when calling calculation methods).
-
- Fixes #19276.
-
- *pinglamb*
-
-* Correctly persist a serialized attribute that has been returned to
- its default value by an in-place modification.
-
- Fixes #19467.
-
- *Matthew Draper*
-
-* 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.
-
- Fixes #19420.
-
- *Jake Waller*
-
-* Reuse the `CollectionAssociation#reader` cache when the foreign key is
- available prior to save.
-
- *Ben Woosley*
-
-* Add `config.active_record.dump_schemas` to fix `db:structure:dump`
- when using schema_search_path and PostgreSQL extensions.
-
- Fixes #17157.
-
- *Ryan Wallace*
-
-* Renaming `use_transactional_fixtures` to `use_transactional_tests` for clarity.
-
- Fixes #18864.
-
- *Brandon Weiss*
-
-* Increase pg gem version requirement to `~> 0.18`. Earlier versions of the
- pg gem are known to have problems with Ruby 2.2.
-
- *Matt Brictson*
-
-* Correctly dump `serial` and `bigserial`.
-
- *Ryuta Kamizono*
-
-* Fix default `format` value in `ActiveRecord::Tasks::DatabaseTasks#schema_file`.
-
- *James Cox*
-
-* 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.
-
- Fixes #15549.
-
- *Will Bryant*, *Aaron Patterson*
-
-* Correctly create through records when created on a has many through
- association when using `where`.
-
- Fixes #19073.
-
- *Sean Griffin*
-
-* Add `SchemaMigration.create_table` support for any unicode charsets with MySQL.
-
- *Ryuta Kamizono*
-
-* 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.
-
- If you absolutely rely on this behavior, consider patching
- `disable_referential_integrity`.
-
- *Yves Senn*
-
-* Restore aborted transaction state when `disable_referential_integrity` fails
- due to missing permissions.
-
- *Toby Ovod-Everett*, *Yves Senn*
-
-* In PostgreSQL, print a warning message if `disable_referential_integrity`
- fails due to missing permissions.
-
- *Andrey Nering*, *Yves Senn*
-
-* Allow a `:limit` option for MySQL bigint primary key support.
-
- Example:
-
- create_table :foos, id: :primary_key, limit: 8 do |t|
- end
-
- # or
-
- create_table :foos, id: false do |t|
- t.primary_key :id, limit: 8
- end
-
- *Ryuta Kamizono*
-
-* `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.)
-
- *Josef Šimánek*
-
-* Fixed `ActiveRecord::Relation#becomes!` and `changed_attributes` issues for type
- columns.
-
- Fixes #17139.
-
- *Miklos Fazekas*
-
-* Format the time string according to the precision of the time column.
-
- *Ryuta Kamizono*
-
-* Allow a `:precision` option for time type columns.
-
- *Ryuta Kamizono*
-
-* Add `ActiveRecord::Base.suppress` to prevent the receiver from being saved
- during the 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. 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
-
- *Michael Ryan*
-
-* `:time` option added for `#touch`.
-
- Fixes #18905.
-
- *Hyonjee Joo*
-
-* Deprecate passing of `start` value to `find_in_batches` and `find_each`
- in favour of `begin_at` value.
-
- *Vipul A M*
-
-* Add `foreign_key_exists?` method.
-
- *Tõnis Simo*
-
-* 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`)
-
- # Before:
-
- users.none?
- # SELECT "users".* FROM "users"
-
- users.one?
- # SELECT "users".* FROM "users"
-
- # After:
-
- users.none?
- # SELECT 1 AS one FROM "users" LIMIT 1
-
- users.one?
- # SELECT COUNT(*) FROM "users"
-
- *Eugene Gilburg*
-
-* Have `enum` perform type casting consistently with the rest of Active
- Record, such as `where`.
-
- *Sean Griffin*
-
-* `scoping` no longer pollutes the current scope of sibling classes when using
- STI.
-
- Fixes #18806.
-
- Example:
-
- StiOne.none.scoping do
- StiTwo.all
- end
-
-
- *Sean Griffin*
-
-* `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.
-
- Fixes #18664.
-
- *Yves Senn*
-
-* `find_in_batches` now accepts an `:finish` parameter that complements the `:start`
- parameter to specify where to stop batch processing.
-
- *Vipul A M*
-
-* Fix a rounding problem for PostgreSQL timestamp columns.
-
- If a timestamp column has a precision specified, it needs to
- format according to that.
-
- *Ryuta Kamizono*
-
-* Respect the database default charset for `schema_migrations` table.
-
- The charset of `version` column in `schema_migrations` table depends
- on the database default charset and collation rather than the encoding
- of the connection.
-
- *Ryuta Kamizono*
-
-* Raise `ArgumentError` when passing `nil` or `false` to `Relation#merge`.
-
- These are not valid values to merge in a relation, so it should warn users
- early.
-
- *Rafael Mendonça França*
-
-* Use `SCHEMA` instead of `DB_STRUCTURE` for specifying a structure file.
-
- This makes the `db:structure` tasks consistent with `test:load_structure`.
-
- *Dieter Komendera*
-
-* Respect custom primary keys for associations when calling `Relation#where`
-
- Fixes #18813.
-
- *Sean Griffin*
-
-* 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`.
-
- Fixes #10865.
-
- *Sean Griffin*
-
-* 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.
-
- 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.
-
- *Chris Sinjakli*
-
-* 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.
-
- This fixes the issue by skipping validations if the parent record is
- persisted, not changed, and not marked for destruction.
-
- Fixes #17621.
-
- *Eileen M. Uchitelle*, *Aaron Patterson*
-
-* Fix n+1 query problem when eager loading nil associations (fixes #18312)
-
- *Sammy Larbi*
-
-* 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.
-
- *Henrik Nygren*
-
-* Fixed `ActiveRecord::Relation#group` method when an argument is an SQL
- reserved keyword:
-
- Example:
-
- SplitTest.group(:key).count
- Property.group(:value).count
-
- *Bogdan Gusiev*
-
-* Added the `#or` method on `ActiveRecord::Relation`, allowing use of the OR
- operator to combine WHERE or HAVING clauses.
-
- Example:
-
- Post.where('id = 1').or(Post.where('id = 2'))
- # => SELECT * FROM posts WHERE (id = 1) OR (id = 2)
-
- *Sean Griffin*, *Matthew Draper*, *Gael Muller*, *Olivier El Mekki*
-
-* Don't define autosave association callbacks twice from
- `accepts_nested_attributes_for`.
-
- Fixes #18704.
-
- *Sean Griffin*
-
-* Integer types will no longer raise a `RangeError` when assigning an
- attribute, but will instead raise when going to the database.
-
- 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*
-
-* 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 #18580.
-
- *Sean Griffin*
-
-* Don't remove join dependencies in `Relation#exists?`
-
- Fixes #18632.
-
- *Sean Griffin*
-
-* Invalid values assigned to a JSON column are assumed to be `nil`.
-
- Fixes #18629.
-
- *Sean Griffin*
-
-* 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*
-
-* Introduce the `:if_exists` option for `drop_table`.
-
- Example:
-
- drop_table(:posts, if_exists: true)
-
- That would execute:
-
- DROP TABLE IF EXISTS posts
-
- If the table doesn't exist, `if_exists: false` (the default) raises an
- exception whereas `if_exists: true` does nothing.
-
- *Cody Cutrer*, *Stefan Kanev*, *Ryuta Kamizono*
-
-* Don't run SQL if attribute value is not changed for update_attribute method.
-
- *Prathamesh Sonpatki*
-
-* `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
-
- ActiveRecord::Base.time_zone_aware_types = [:datetime]
-
- A deprecation warning will be emitted if you have a `:time` column, and have
- not explicitly opted out.
-
- Fixes #3145.
-
- *Sean Griffin*
-
-* Tests now run after_commit callbacks. You no longer have to declare
- `uses_transaction ‘test name’` to test the results of an after_commit.
-
- 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`.
-
- *arthurnn*, *Ravil Bayramgalin*, *Matthew Draper*
-
-* `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*
-
-* `attribute_will_change!` will no longer cause non-persistable attributes to
- be sent to the database.
-
- Fixes #18407.
-
- *Sean Griffin*
-
-* Remove support for the `protected_attributes` gem.
-
- *Carlos Antonio da Silva*, *Roberto Miranda*
-
-* Fix accessing of fixtures having non-string labels like Fixnum.
-
- *Prathamesh Sonpatki*
-
-* Remove deprecated support to preload instance-dependent associations.
-
- *Yves Senn*
-
-* Remove deprecated support for PostgreSQL ranges with exclusive lower bounds.
-
- *Yves Senn*
-
-* Remove deprecation when modifying a relation with cached Arel.
- This raises an `ImmutableRelation` error instead.
-
- *Yves Senn*
-
-* Added `ActiveRecord::SecureToken` in order to encapsulate generation of
- unique tokens for attributes in a model using `SecureRandom`.
-
- *Roberto Miranda*
-
-* Change the behavior of boolean columns to be closer to Ruby's semantics.
-
- Before this change we had a small set of "truthy", and all others are "falsy".
-
- Now, we have a small set of "falsy" values and all others are "truthy" matching
- Ruby's semantics.
-
- *Rafael Mendonça França*
-
-* Deprecate `ActiveRecord::Base.errors_in_transactional_callbacks=`.
-
- *Rafael Mendonça França*
-
-* Change transaction callbacks to not swallow errors.
-
- Before this change any errors raised inside a transaction callback
- were getting rescued and printed in the logs.
-
- Now these errors are not rescued anymore and just bubble up, as the other callbacks.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `sanitize_sql_hash_for_conditions`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `Reflection#source_macro`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `symbolized_base_class` and `symbolized_sti_name`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActiveRecord::Base.disable_implicit_join_references=`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated access to connection specification using a string accessor.
-
- Now all strings will be handled as a URL.
-
- *Rafael Mendonça França*
-
-* Change the default `null` value for `timestamps` to `false`.
-
- *Rafael Mendonça França*
-
-* Return an array of pools from `connection_pools`.
-
- *Rafael Mendonça França*
-
-* Return a null column from `column_for_attribute` when no column exists.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `serialized_attributes`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated automatic counter caches on `has_many :through`.
-
- *Rafael Mendonça França*
-
-* Change the way in which callback chains can be halted.
-
- The preferred method to halt a callback chain from now on is to explicitly
- `throw(:abort)`.
- In the past, returning `false` in an Active Record `before_` callback had the
- side effect of halting the callback chain.
- This is not recommended anymore and, depending on the value of the
- `ActiveSupport.halt_callback_chains_on_return_false` option, will
- either not work at all or display a deprecation warning.
-
- *claudiob*
-
-* Clear query cache on rollback.
-
- *Florian Weingarten*
-
-* Fix setting of foreign_key for through associations when building a new record.
-
- Fixes #12698.
-
- *Ivan Antropov*
-
-* Improve dumping of the primary key. If it is not a default primary key,
- correctly dump the type and options.
-
- Fixes #14169, #16599.
-
- *Ryuta Kamizono*
-
-* Format the datetime string according to the precision of the datetime field.
-
- Incompatible to rounding behavior between MySQL 5.6 and earlier.
-
- 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`:
-
- http://bugs.mysql.com/bug.php?id=68760
-
- *Ryuta Kamizono*
-
-* Allow a precision option for MySQL datetimes.
-
- *Ryuta Kamizono*
-
-* Fixed automatic `inverse_of` for models nested in a module.
-
- *Andrew McCloud*
-
-* Change `ActiveRecord::Relation#update` behavior so that it can
- be called without passing ids of the records to be updated.
-
- This change allows updating multiple records returned by
- `ActiveRecord::Relation` with callbacks and validations.
-
- # Before
- # ArgumentError: wrong number of arguments (1 for 2)
- Comment.where(group: 'expert').update(body: "Group of Rails Experts")
-
- # After
- # Comments with group expert updated with body "Group of Rails Experts"
- Comment.where(group: 'expert').update(body: "Group of Rails Experts")
-
- *Prathamesh Sonpatki*
-
-* Fix `reaping_frequency` option when the value is a string.
-
- This usually happens when it is configured using `DATABASE_URL`.
-
- *korbin*
-
-* Fix error message when trying to create an associated record and the foreign
- key is missing.
-
- Before this fix the following exception was being raised:
-
- NoMethodError: undefined method `val' for #<Arel::Nodes::BindParam:0x007fc64d19c218>
-
- Now the message is:
-
- ActiveRecord::UnknownAttributeError: unknown attribute 'foreign_key' for Model.
-
- *Rafael Mendonça França*
-
-* Fix change detection problem for PostgreSQL bytea type and
- `ArgumentError: string contains null byte` exception with pg-0.18.
-
- Fixes #17680.
-
- *Lars Kanis*
-
-* 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.
-
- Fixes #18070.
-
- *arthurnn*
-
-* `validates_size_of` / `validates_length_of` do not count records
- which are `marked_for_destruction?`.
-
- Fixes #7247.
-
- *Yves Senn*
-
-* Ensure `first!` and friends work on loaded associations.
-
- Fixes #18237.
-
- *Sean Griffin*
-
-* `eager_load` preserves readonly flag for associations.
-
- Fixes #15853.
-
- *Takashi Kokubun*
-
-* Provide `:touch` option to `save()` to accommodate saving without updating
- timestamps.
-
- Fixes #18202.
-
- *Dan Olson*
-
-* Provide a more helpful error message when an unsupported class is passed to
- `serialize`.
-
- Fixes #18224.
-
- *Sean Griffin*
-
-* Add bigint primary key support for MySQL.
-
- Example:
-
- create_table :foos, id: :bigint do |t|
- end
-
- *Ryuta Kamizono*
-
-* Support for any type of primary key.
-
- Fixes #14194.
-
- *Ryuta Kamizono*
-
-* Dump the default `nil` for PostgreSQL UUID primary key.
-
- *Ryuta Kamizono*
-
-* 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.
-
- *Sean Griffin*
-
-* Don't raise when writing an attribute with an out-of-range datetime passed
- by the user.
-
- *Grey Baker*
-
-* Replace deprecated `ActiveRecord::Tasks::DatabaseTasks#load_schema` with
- `ActiveRecord::Tasks::DatabaseTasks#load_schema_for`.
-
- *Yves Senn*
-
-* Fix bug with `ActiveRecord::Type::Numeric` that caused negative values to
- be marked as having changed when set to the same negative value.
-
- Fixes #18161.
-
- *Daniel Fox*
-
-* 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.
-
- *Matthew Draper*, *Yves Senn*
-
-* `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.
-
- Fixes #17945.
-
- *Yves Senn*
-
-* Fix undesirable RangeError by `Type::Integer`. Add `Type::UnsignedInteger`.
-
- *Ryuta Kamizono*
-
-* Add `foreign_type` option to `has_one` and `has_many` association macros.
-
- This option enables to define the column name of associated object's type for polymorphic associations.
-
- *Ulisses Almeida*, *Kassio Borges*
-
-* Remove deprecated behavior allowing nested arrays to be passed as query
- values.
-
- *Melanie Gilman*
-
-* Deprecate passing a class as a value in a query. Users should pass strings
- instead.
-
- *Melanie Gilman*
-
-* `add_timestamps` and `remove_timestamps` now properly reversible with
- options.
+ psql -v ON_ERROR_STOP=1 -q -f awesome-file.sql my-app-db
- *Noam Gagliardi-Rabinovich*
+ Fixes #23818.
-* `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.
+ *Ralin Chimev*
- *Yves Senn*
-Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activerecord/CHANGELOG.md) for previous changes.
+Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc
index a74fcf2df7..cd22f76d01 100644
--- a/activerecord/RUNNING_UNIT_TESTS.rdoc
+++ b/activerecord/RUNNING_UNIT_TESTS.rdoc
@@ -7,11 +7,11 @@ http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#setting-up-
To run a specific test:
- $ ruby -Itest test/cases/base_test.rb -n method_name
+ $ bundle exec ruby -Itest test/cases/base_test.rb -n method_name
To run a set of tests:
- $ ruby -Itest test/cases/base_test.rb
+ $ bundle exec ruby -Itest test/cases/base_test.rb
You can also run tests that depend upon a specific database backend. For
example:
@@ -41,7 +41,7 @@ parameters in +test/config.example.yml+ are.
You can override the +connections:+ parameter in either file using the +ARCONN+
(Active Record CONNection) environment variable:
- $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb
+ $ ARCONN=postgresql bundle exec ruby -Itest test/cases/base_test.rb
You can specify a custom location for the config file using the +ARCONFIG+
environment variable.
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 0564dca94a..012db35765 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -20,6 +20,8 @@ end
desc 'Run mysql2, sqlite, and postgresql tests by default'
task :default => :test
+task :package
+
desc 'Run mysql2, sqlite, and postgresql tests'
task :test do
tasks = defined?(JRUBY_VERSION) ?
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 4405da2812..881dee13e4 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.homepage = 'http://rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'examples/**/*', 'lib/**/*']
s.require_path = 'lib'
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
index a5a1f284a0..c3c46c19c5 100644
--- a/activerecord/examples/performance.rb
+++ b/activerecord/examples/performance.rb
@@ -1,4 +1,3 @@
-require File.expand_path('../../../load_paths', __FILE__)
require "active_record"
require 'benchmark/ips'
diff --git a/activerecord/examples/simple.rb b/activerecord/examples/simple.rb
index 4ed5d80eb2..5a041e8417 100644
--- a/activerecord/examples/simple.rb
+++ b/activerecord/examples/simple.rb
@@ -1,4 +1,3 @@
-require File.expand_path('../../../load_paths', __FILE__)
require 'active_record'
class Person < ActiveRecord::Base
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index ab3846ae65..baa497dc98 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -137,7 +137,6 @@ module ActiveRecord
eager_autoload do
autoload :AbstractAdapter
- autoload :ConnectionManagement, "active_record/connection_adapters/abstract/connection_pool"
end
end
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 3ff41ed81b..8bed5bca28 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -256,15 +256,16 @@ module ActiveRecord
def writer_method(name, class_name, mapping, allow_nil, converter)
define_method("#{name}=") do |part|
klass = class_name.constantize
- if part.is_a?(Hash)
- 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?
part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
end
+ if part.is_a?(Hash)
+ raise ArgumentError unless part.size == part.keys.max
+ part = klass.new(*part.sort.map(&:last))
+ end
+
if part.nil? && allow_nil
mapping.each { |key, _| self[key] = nil }
@aggregation_cache[name] = nil
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index ee0bb8fafe..c18e88e4cf 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -10,7 +10,7 @@ module ActiveRecord
end
def ==(other)
- other == to_a
+ other == records
end
def build(*args, &block)
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index f6d8e8a342..3729e22e64 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -300,10 +300,10 @@ module ActiveRecord
#
# === A word of warning
#
- # Don't create associations that have the same name as instance methods of
- # ActiveRecord::Base. Since the association adds a method with that name to
- # its model, it will override the inherited method and break things.
- # For instance, +attributes+ and +connection+ would be bad choices for association names.
+ # Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveRecord::Core] of
+ # <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to
+ # its model, using an association with the same name as one provided by <tt>ActiveRecord::Base</tt> will override the method inherited through <tt>ActiveRecord::Base</tt> and will break things.
+ # For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of <tt>ActiveRecord::Base</tt> instance methods.
#
# == Auto-generated methods
# See also Instance Public methods below for more details.
@@ -318,7 +318,7 @@ module ActiveRecord
# create_other(attributes={}) | X | | X
# create_other!(attributes={}) | X | | X
#
- # ===Collection associations (one-to-many / many-to-many)
+ # === Collection associations (one-to-many / many-to-many)
# | | | has_many
# generated methods | habtm | has_many | :through
# ----------------------------------+-------+----------+----------
@@ -504,7 +504,7 @@ module ActiveRecord
#
# == Customizing the query
#
- # \Associations are built from <tt>Relation</tt>s, and you can use the Relation syntax
+ # \Associations are built from <tt>Relation</tt> objects, and you can use the Relation syntax
# to customize them. For example, to add a condition:
#
# class Blog < ActiveRecord::Base
@@ -1326,7 +1326,8 @@ module ActiveRecord
# Specifies type of the source association used by #has_many <tt>:through</tt> queries where the source
# association is a polymorphic #belongs_to.
# [:validate]
- # If +false+, don't validate the associated objects when saving the parent object. true by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated objects or destroy them if marked for destruction,
# when saving the parent object. If false, never save or destroy the associated objects.
@@ -1456,7 +1457,8 @@ module ActiveRecord
# Specifies type of the source association used by #has_one <tt>:through</tt> queries where the source
# association is a polymorphic #belongs_to.
# [:validate]
- # If +false+, don't validate the associated object when saving the parent object. +false+ by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated object or destroy it if marked for destruction,
# when saving the parent object. If false, never save or destroy the associated object.
@@ -1580,7 +1582,8 @@ module ActiveRecord
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
# [:validate]
- # If +false+, don't validate the associated objects when saving the parent object. +false+ by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated object or destroy it if marked for destruction, when
# saving the parent object.
@@ -1593,6 +1596,8 @@ module ActiveRecord
# If true, the associated object will be touched (the updated_at/on attributes set to current time)
# when this record is either saved or destroyed. If you specify a symbol, that attribute
# will be updated with the current time in addition to the updated_at/on attribute.
+ # Please note that with touching no validation is performed and only the +after_touch+,
+ # +after_commit+ and +after_rollback+ callbacks are executed.
# [:inverse_of]
# Specifies the name of the #has_one or #has_many association on the associated
# object that is the inverse of this #belongs_to association. Does not work in
@@ -1764,7 +1769,8 @@ module ActiveRecord
# So if a Person class makes a #has_and_belongs_to_many association to Project,
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
# [:validate]
- # If +false+, don't validate the associated objects when saving the parent object. +true+ by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated objects or destroy them if marked for destruction, when
# saving the parent object.
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index d64ab64c99..62e867a353 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -217,7 +217,8 @@ module ActiveRecord
unless record.is_a?(reflection.klass)
fresh_class = reflection.class_name.safe_constantize
unless fresh_class && record.is_a?(fresh_class)
- message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\
+ "got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})"
raise ActiveRecord::AssociationTypeMismatch, message
end
end
@@ -257,7 +258,7 @@ module ActiveRecord
# Returns true if statement cache should be skipped on the association reader.
def skip_statement_cache?
- reflection.scope_chain.any?(&:any?) ||
+ reflection.has_scope? ||
scope.eager_loading? ||
klass.scope_attributes? ||
reflection.source_reflection.active_record.default_scopes.any?
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 48437a1c9e..15844de0bc 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -124,8 +124,7 @@ module ActiveRecord
scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass)
reflection = chain_head
- loop do
- break unless reflection
+ while reflection
table = reflection.alias_name
unless reflection == chain_tail
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 41698c5360..24997370b2 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -61,6 +61,7 @@ module ActiveRecord
def update_counters_on_replace(record)
if require_counter_update? && different_target?(record)
+ owner.instance_variable_set :@_after_replace_counter_called, true
record.increment!(reflection.counter_cache_column)
decrement_counters
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 346329c610..3121e70a04 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -33,6 +33,8 @@ module ActiveRecord::Associations::Builder # :nodoc:
if (@_after_create_counter_called ||= false)
@_after_create_counter_called = false
+ elsif (@_after_replace_counter_called ||= false)
+ @_after_replace_counter_called = false
elsif attribute_changed?(foreign_key) && !new_record?
if reflection.polymorphic?
model = attribute(reflection.foreign_type).try(:constantize)
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 56a8dc4e18..f25bd7ca9f 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -70,7 +70,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
def self.wrap_scope(scope, mod)
if scope
- proc { |owner| instance_exec(owner, &scope).extending(mod) }
+ if scope.arity > 0
+ proc { |owner| instance_exec(owner, &scope).extending(mod) }
+ else
+ proc { instance_exec(&scope).extending(mod) }
+ end
else
proc { extending(mod) }
end
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 b888148841..5fbd79d118 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
@@ -76,6 +76,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
left_model.retrieve_connection
end
+ def self.primary_key
+ false
+ end
}
join_model.name = "HABTM_#{association_name.to_s.camelize}"
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 473b80a658..0eaa0a4f36 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -10,9 +10,9 @@ module ActiveRecord
# HasManyAssociation => has_many
# HasManyThroughAssociation + ThroughAssociation => has_many :through
#
- # CollectionAssociation class provides common methods to the collections
+ # The CollectionAssociation class provides common methods to the collections
# defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
- # +:through association+ option.
+ # the +:through association+ option.
#
# You need to be careful with assumptions regarding the target: The proxy
# does not fetch records from the database until it needs them, but new
@@ -72,7 +72,10 @@ module ActiveRecord
pk_type = reflection.primary_key_type
ids = Array(ids).reject(&:blank?)
ids.map! { |i| pk_type.cast(i) }
- replace(klass.find(ids).index_by(&:id).values_at(*ids))
+ records = klass.where(reflection.association_primary_key => ids).index_by do |r|
+ r.send(reflection.association_primary_key)
+ end.values_at(*ids)
+ replace(records)
end
def reset
@@ -133,6 +136,14 @@ module ActiveRecord
first_nth_or_last(:forty_two, *args)
end
+ def third_to_last(*args)
+ first_nth_or_last(:third_to_last, *args)
+ end
+
+ def second_to_last(*args)
+ first_nth_or_last(:second_to_last, *args)
+ end
+
def last(*args)
first_nth_or_last(:last, *args)
end
@@ -235,9 +246,12 @@ module ActiveRecord
end
end
- # Count all records using SQL. Construct options and pass them with
- # scope to the target class's +count+.
+ # Returns the number of records. If no arguments are given, it counts all
+ # columns using SQL. If one argument is given, it counts only the passed
+ # column using SQL. If a block is given, it counts the number of records
+ # yielding a true value.
def count(column_name = nil)
+ return super if block_given?
relation = scope
if association_scope.distinct_value
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
@@ -269,7 +283,7 @@ module ActiveRecord
_options = records.extract_options!
dependent = _options[:dependent] || options[:dependent]
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
delete_or_destroy(records, dependent)
end
@@ -280,7 +294,7 @@ module ActiveRecord
# +:dependent+ option.
def destroy(*records)
return if records.empty?
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
delete_or_destroy(records, :destroy)
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index fe693cfbb6..5d1e7ffb73 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -197,6 +197,16 @@ module ActiveRecord
@association.forty_two(*args)
end
+ # Same as #first except returns only the third-to-last record.
+ def third_to_last(*args)
+ @association.third_to_last(*args)
+ end
+
+ # Same as #first except returns only the second-to-last record.
+ def second_to_last(*args)
+ @association.second_to_last(*args)
+ end
+
# Returns the last record, or the last +n+ records, from the collection.
# If the collection is empty, the first form returns +nil+, and the second
# form returns an empty array.
@@ -587,7 +597,7 @@ module ActiveRecord
# Pet.find(1)
# # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1
#
- # You can pass +Fixnum+ or +String+ values, it finds the records
+ # You can pass +Integer+ or +String+ values, it finds the records
# responding to the +id+ and executes delete on them.
#
# class Person < ActiveRecord::Base
@@ -651,7 +661,7 @@ module ActiveRecord
#
# Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
#
- # You can pass +Fixnum+ or +String+ values, it finds the records
+ # You can pass +Integer+ or +String+ values, it finds the records
# responding to the +id+ and then deletes them from the database.
#
# person.pets.size # => 3
@@ -705,12 +715,13 @@ module ActiveRecord
end
alias uniq distinct
- # Count all records using SQL.
+ # Count all records.
#
# class Person < ActiveRecord::Base
# has_many :pets
# end
#
+ # # This will perform the count using SQL.
# person.pets.count # => 3
# person.pets
# # => [
@@ -718,8 +729,13 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- def count(column_name = nil)
- @association.count(column_name)
+ #
+ # Passing a block will select all of a person's pets in SQL and then
+ # perform the count using Ruby.
+ #
+ # person.pets.count { |pet| pet.name.include?('-') } # => 2
+ def count(column_name = nil, &block)
+ @association.count(column_name, &block)
end
# Returns the size of the collection. If the collection hasn't been loaded,
@@ -969,6 +985,10 @@ module ActiveRecord
end
alias_method :to_a, :to_ary
+ def records # :nodoc:
+ load_target
+ end
+
# Adds one or more +records+ to the collection by setting their foreign keys
# to the association's primary key. Returns +self+, so several appends may be
# chained together.
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 0e4e951269..491152bbcb 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -92,8 +92,9 @@ module ActiveRecord
# associations # => [:appointments]
# joins # => []
#
- def initialize(base, associations, joins)
+ def initialize(base, associations, joins, eager_loading: true)
@alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins, base.type_caster)
+ @eager_loading = eager_loading
tree = self.class.make_tree associations
@join_root = JoinBase.new base, build(tree, base)
@join_root.children.each { |child| construct_tables! @join_root, child }
@@ -228,7 +229,7 @@ module ActiveRecord
def find_reflection(klass, name)
klass._reflect_on_association(name) or
- raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
+ raise ConfigurationError, "Can't join '#{ klass.name }' to association named '#{ name }'; perhaps you misspelled it?"
end
def build(associations, base_klass)
@@ -238,11 +239,12 @@ module ActiveRecord
reflection.check_eager_loadable!
if reflection.polymorphic?
+ next unless @eager_loading
raise EagerLoadPolymorphicError.new(reflection)
end
JoinAssociation.new reflection, build(right, reflection.klass)
- end
+ end.compact
end
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
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 be65cf318c..c5fbe0d1d1 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -54,12 +54,18 @@ module ActiveRecord
end
scope_chain_index += 1
- relation = ActiveRecord::Relation.create(
- klass,
- table,
- predicate_builder,
- )
- scope_chain_items.concat [klass.send(:build_default_scope, relation)].compact
+ klass_scope =
+ if klass.current_scope
+ klass.current_scope.clone
+ else
+ relation = ActiveRecord::Relation.create(
+ klass,
+ table,
+ predicate_builder,
+ )
+ klass.send(:build_default_scope, relation)
+ end
+ scope_chain_items.concat [klass_scope].compact
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
left.merge right
@@ -75,7 +81,7 @@ module ActiveRecord
column = klass.columns_hash[reflection.type.to_s]
binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name))
- constraint = constraint.and table[reflection.type].eq(Arel::Nodes::BindParam.new)
+ constraint = constraint.and klass.arel_attribute(reflection.type, table).eq(Arel::Nodes::BindParam.new)
end
joins << table.create_join(table, table.create_on(constraint), join_type)
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index ecf6fb8643..e64af84e1a 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -184,6 +184,7 @@ module ActiveRecord
def self.new(klass, owners, reflection, preload_scope); self; end
def self.run(preloader); end
def self.preloaded_records; []; end
+ def self.owners; []; end
end
# Returns a class containing the logic needed to load preload the data
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index e11a5cfb8a..3032bc786e 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -47,7 +47,7 @@ module ActiveRecord
# This is overridden by HABTM as the condition should be on the foreign_key column in
# the join table
def association_key
- table[association_key_name]
+ klass.arel_attribute(association_key_name, table)
end
# The name of the key on the model which declares the association
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 6c83058202..b0203909ce 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -38,12 +38,7 @@ module ActiveRecord
}
end
- record_offset = {}
- @preloaded_records.each_with_index do |record,i|
- record_offset[record] = i
- end
-
- through_records.each_with_object({}) { |(lhs,center),records_by_owner|
+ through_records.each_with_object({}) do |(lhs,center), records_by_owner|
pl_to_middle = center.group_by { |record| middle_to_pl[record] }
records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
@@ -53,13 +48,25 @@ module ActiveRecord
target_records_from_association(association)
}.compact
- rhs_records.sort_by { |rhs| record_offset[rhs] }
+ # Respect the order on `reflection_scope` if it exists, else use the natural order.
+ if reflection_scope.values[:order].present?
+ @id_map ||= id_to_index_map @preloaded_records
+ rhs_records.sort_by { |rhs| @id_map[rhs] }
+ else
+ rhs_records
+ end
end
- }
+ end
end
private
+ def id_to_index_map(ids)
+ id_map = {}
+ ids.each_with_index { |id, index| id_map[id] = index }
+ id_map
+ end
+
def reset_association(owners, association_name)
should_reset = (through_scope != through_reflection.klass.unscoped) ||
(reflection.options[:source_type] && through_reflection.collection?)
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index c7cc48ba16..f913f0852a 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -45,7 +45,7 @@ module ActiveRecord
end
def get_records
- return scope.limit(1).to_a if skip_statement_cache?
+ return scope.limit(1).records if skip_statement_cache?
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 3c4c8f10ec..9530f134d0 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -77,7 +77,11 @@ module ActiveRecord
end
def with_type(type)
- self.class.new(name, value_before_type_cast, type, original_attribute)
+ if changed_in_place?
+ with_value_from_user(value).with_type(type)
+ else
+ self.class.new(name, value_before_type_cast, type, original_attribute)
+ end
end
def type_cast(*)
@@ -108,6 +112,22 @@ module ActiveRecord
[self.class, name, value_before_type_cast, type].hash
end
+ def init_with(coder)
+ @name = coder["name"]
+ @value_before_type_cast = coder["value_before_type_cast"]
+ @type = coder["type"]
+ @original_attribute = coder["original_attribute"]
+ @value = coder["value"] if coder.map.key?("value")
+ end
+
+ def encode_with(coder)
+ coder["name"] = name
+ coder["value_before_type_cast"] = value_before_type_cast if value_before_type_cast
+ coder["type"] = type if type
+ coder["original_attribute"] = original_attribute if original_attribute
+ coder["value"] = value if defined?(@value)
+ end
+
protected
attr_reader :original_attribute
@@ -170,7 +190,7 @@ module ActiveRecord
super(name, nil, Type::Value.new)
end
- def value
+ def type_cast(*)
nil
end
@@ -185,6 +205,8 @@ module ActiveRecord
end
class Uninitialized < Attribute # :nodoc:
+ UNINITIALIZED_ORIGINAL_VALUE = Object.new
+
def initialize(name, type)
super(name, nil, type)
end
@@ -195,12 +217,20 @@ module ActiveRecord
end
end
+ def original_value
+ UNINITIALIZED_ORIGINAL_VALUE
+ end
+
def value_for_database
end
def initialized?
false
end
+
+ def with_type(type)
+ self.class.new(name, type)
+ end
end
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
end
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb
index 6dbd92ce28..4580813364 100644
--- a/activerecord/lib/active_record/attribute/user_provided_default.rb
+++ b/activerecord/lib/active_record/attribute/user_provided_default.rb
@@ -4,20 +4,25 @@ module ActiveRecord
class Attribute # :nodoc:
class UserProvidedDefault < FromUser # :nodoc:
def initialize(name, value, type, database_default)
+ @user_provided_value = value
super(name, value, type, database_default)
end
- def type_cast(value)
- if value.is_a?(Proc)
- super(value.call)
+ def value_before_type_cast
+ if user_provided_value.is_a?(Proc)
+ @memoized_value_before_type_cast ||= user_provided_value.call
else
- super
+ @user_provided_value
end
end
def with_type(type)
- self.class.new(name, value_before_type_cast, type, original_attribute)
+ self.class.new(name, user_provided_value, type, original_attribute)
end
+
+ protected
+
+ attr_reader :user_provided_value
end
end
end
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index a6d81c82b4..b96d8e9352 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -29,14 +29,6 @@ module ActiveRecord
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
end
- # Tries to assign given value to given attribute.
- # In case of an error, re-raises with the ActiveRecord constant.
- def _assign_attribute(k, v) # :nodoc:
- super
- rescue ActiveModel::UnknownAttributeError
- raise UnknownAttributeError.new(self, k)
- end
-
# Assign any deferred nested attributes after the base attributes have been set.
def assign_nested_parameter_attributes(pairs)
pairs.each { |k, v| _assign_attribute(k, v) }
@@ -46,7 +38,7 @@ module ActiveRecord
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
def assign_multiparameter_attributes(pairs)
execute_callstack_for_multiparameter_attributes(
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 423a93964e..1fb5eb28cd 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -34,30 +34,6 @@ module ActiveRecord
BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
- class AttributeMethodCache
- def initialize
- @module = Module.new
- @method_cache = Concurrent::Map.new
- end
-
- def [](name)
- @method_cache.compute_if_absent(name) do
- safe_name = name.unpack('h*'.freeze).first
- temp_method = "__temp__#{safe_name}"
- ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
- @module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
- @module.instance_method temp_method
- end
- end
-
- private
-
- # Override this method in the subclasses for method body.
- def method_body(method_name, const_name)
- raise NotImplementedError, "Subclasses must implement a method_body(method_name, const_name) method."
- end
- end
-
class GeneratedAttributeMethods < Module; end # :nodoc:
module ClassMethods
@@ -358,8 +334,6 @@ module ActiveRecord
#
# Note: +:id+ is always present.
#
- # Alias for the #read_attribute method.
- #
# class Person < ActiveRecord::Base
# belongs_to :organization
# end
@@ -384,7 +358,7 @@ module ActiveRecord
# person = Person.new
# person[:age] = '22'
# person[:age] # => 22
- # person[:age] # => Fixnum
+ # person[:age].class # => Integer
def []=(attr_name, value)
write_attribute(attr_name, value)
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 5197e21fa4..ab2ecaa7c5 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -1,8 +1,11 @@
module ActiveRecord
module AttributeMethods
module Read
- ReaderMethodCache = Class.new(AttributeMethodCache) {
- private
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ protected
+
# We want to generate the methods via module_eval rather than
# define_method, because define_method is slower on dispatch.
# Evaluating many similar methods may use more memory as the instruction
@@ -21,21 +24,6 @@ module ActiveRecord
# to allocate an object on each call to the attribute method.
# Making it frozen means that it doesn't get duped when used to
# key the @attributes in read_attribute.
- def method_body(method_name, const_name)
- <<-EOMETHOD
- def #{method_name}
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
- _read_attribute(name) { |n| missing_attribute(n, caller) }
- end
- EOMETHOD
- end
- }.new
-
- extend ActiveSupport::Concern
-
- module ClassMethods
- protected
-
def define_method_attribute(name)
safe_name = name.unpack('h*'.freeze).first
temp_method = "__temp__#{safe_name}"
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 061628725d..e160460286 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -20,7 +20,7 @@ module ActiveRecord
nil
end
else
- map(super) { |t| cast(t) }
+ map_avoiding_infinite_recursion(super) { |v| cast(v) }
end
end
@@ -34,13 +34,23 @@ module ActiveRecord
elsif value.is_a?(::Float)
value
else
- map(value) { |v| convert_time_to_time_zone(v) }
+ map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
end
end
def set_time_zone_without_conversion(value)
::Time.zone.local_to_utc(value).in_time_zone
end
+
+ def map_avoiding_infinite_recursion(value)
+ map(value) do |v|
+ if value.equal?(v)
+ nil
+ else
+ yield(v)
+ end
+ end
+ end
end
extend ActiveSupport::Concern
@@ -94,7 +104,7 @@ module ActiveRecord
To silence this deprecation warning, add the following:
- config.active_record.time_zone_aware_types << :time
+ config.active_record.time_zone_aware_types = [:datetime, :time]
MESSAGE
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index bbf2a51a0e..70c2d2f25d 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -1,19 +1,6 @@
module ActiveRecord
module AttributeMethods
module Write
- WriterMethodCache = Class.new(AttributeMethodCache) {
- private
-
- def method_body(method_name, const_name)
- <<-EOMETHOD
- def #{method_name}(value)
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
- write_attribute(name, value)
- end
- EOMETHOD
- end
- }.new
-
extend ActiveSupport::Concern
included do
@@ -39,7 +26,7 @@ module ActiveRecord
end
# Updates the attribute identified by <tt>attr_name</tt> with the
- # specified +value+. Empty strings for fixnum and float columns are
+ # specified +value+. Empty strings for Integer and Float columns are
# turned into +nil+.
def write_attribute(attr_name, value)
write_attribute_with_type_cast(attr_name, value, true)
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index be581ac2a9..720d5f8b7c 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -1,7 +1,10 @@
require 'active_record/attribute_set/builder'
+require 'active_record/attribute_set/yaml_encoder'
module ActiveRecord
class AttributeSet # :nodoc:
+ delegate :each_value, to: :attributes
+
def initialize(attributes)
@attributes = attributes
end
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb
index 3bd7c7997b..24a255efc1 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activerecord/lib/active_record/attribute_set/builder.rb
@@ -22,7 +22,7 @@ module ActiveRecord
end
class LazyAttributeHash # :nodoc:
- delegate :transform_values, :each_key, to: :materialize
+ delegate :transform_values, :each_key, :each_value, to: :materialize
def initialize(types, values, additional_types)
@types = types
diff --git a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb
new file mode 100644
index 0000000000..6208048231
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb
@@ -0,0 +1,39 @@
+module ActiveRecord
+ class AttributeSet
+ # Attempts to do more intelligent YAML dumping of an
+ # ActiveRecord::AttributeSet to reduce the size of the resulting string
+ class YAMLEncoder
+ def initialize(default_types)
+ @default_types = default_types
+ end
+
+ def encode(attribute_set, coder)
+ coder['concise_attributes'] = attribute_set.each_value.map do |attr|
+ if attr.type.equal?(default_types[attr.name])
+ attr.with_type(nil)
+ else
+ attr
+ end
+ end
+ end
+
+ def decode(coder)
+ if coder['attributes']
+ coder['attributes']
+ else
+ attributes_hash = Hash[coder['concise_attributes'].map do |attr|
+ if attr.type.nil?
+ attr = attr.with_type(default_types[attr.name])
+ end
+ [attr.name, attr]
+ end]
+ AttributeSet.new(attributes_hash)
+ end
+ end
+
+ protected
+
+ attr_reader :default_types
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 5d0405c3be..519de271c3 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -67,12 +67,14 @@ module ActiveRecord
#
# A default can also be provided.
#
+ # # db/schema.rb
# create_table :store_listings, force: true do |t|
# t.string :my_string, default: "original default"
# end
#
# StoreListing.new.my_string # => "original default"
#
+ # # app/models/store_listing.rb
# class StoreListing < ActiveRecord::Base
# attribute :my_string, :string, default: "new default"
# end
@@ -89,6 +91,7 @@ module ActiveRecord
#
# \Attributes do not need to be backed by a database column.
#
+ # # app/models/my_model.rb
# class MyModel < ActiveRecord::Base
# attribute :my_string, :string
# attribute :my_int_array, :integer, array: true
@@ -119,7 +122,7 @@ module ActiveRecord
#
# class MoneyType < ActiveRecord::Type::Integer
# def cast(value)
- # if !value.kind_of(Numeric) && value.include?('$')
+ # if !value.kind_of?(Numeric) && value.include?('$')
# price_in_dollars = value.gsub(/\$/, '').to_f
# super(price_in_dollars * 100)
# else
@@ -131,7 +134,7 @@ module ActiveRecord
# # config/initializers/types.rb
# ActiveRecord::Type.register(:money, MoneyType)
#
- # # /app/models/store_listing.rb
+ # # app/models/store_listing.rb
# class StoreListing < ActiveRecord::Base
# attribute :price_in_cents, :money
# end
@@ -154,7 +157,7 @@ module ActiveRecord
# end
#
# class MoneyType < Type::Value
- # def initialize(currency_converter)
+ # def initialize(currency_converter:)
# @currency_converter = currency_converter
# end
#
@@ -167,11 +170,13 @@ module ActiveRecord
# end
# end
#
+ # # config/initializers/types.rb
# ActiveRecord::Type.register(:money, MoneyType)
#
+ # # app/models/product.rb
# class Product < ActiveRecord::Base
# currency_converter = ConversionRatesFromTheInternet.new
- # attribute :price_in_bitcoins, :money, currency_converter
+ # attribute :price_in_bitcoins, :money, currency_converter: currency_converter
# end
#
# Product.where(price_in_bitcoins: Money.new(5, "USD"))
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index bac5a38a5d..06c7482bf9 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -22,7 +22,7 @@ module ActiveRecord
#
# == Validation
#
- # Children records are validated unless <tt>:validate</tt> is +false+.
+ # Child records are validated unless <tt>:validate</tt> is +false+.
#
# == Callbacks
#
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 4a31a1aa84..6a1a27ce41 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -13,7 +13,6 @@ require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/class/subclasses'
-require 'arel'
require 'active_record/attribute_decorators'
require 'active_record/errors'
require 'active_record/log_subscriber'
@@ -132,9 +131,6 @@ module ActiveRecord #:nodoc:
# end
# end
#
- # You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
- # or <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
- #
# == Attribute query methods
#
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
@@ -173,7 +169,8 @@ module ActiveRecord #:nodoc:
# ActiveRecord::RecordNotFound error if they do not return any records,
# like <tt>Person.find_by_last_name!</tt>.
#
- # It's also possible to use multiple attributes in the same find by separating them with "_and_".
+ # It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with
+ # "_and_".
#
# Person.find_by(user_name: user_name, password: password)
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 854f9776a3..95de6937af 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -53,9 +53,9 @@ module ActiveRecord
# end
#
# class Firm < ActiveRecord::Base
- # # Destroys the associated clients and people when the firm is destroyed
- # before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
- # before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
+ # # Disables access to the system, for associated clients and people when the firm is destroyed
+ # before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') }
+ # before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') }
# end
#
# == Inheritable callback queues
@@ -179,7 +179,7 @@ module ActiveRecord
#
# If the +before_validation+ callback throws +:abort+, the process will be
# aborted and {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will return +false+.
- # If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise a ActiveRecord::RecordInvalid exception.
+ # If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise an ActiveRecord::RecordInvalid exception.
# Nothing will be appended to the errors object.
#
# == Canceling callbacks
diff --git a/activerecord/lib/active_record/coders/json.rb b/activerecord/lib/active_record/coders/json.rb
index 75d3bfe625..cb185a881e 100644
--- a/activerecord/lib/active_record/coders/json.rb
+++ b/activerecord/lib/active_record/coders/json.rb
@@ -6,7 +6,7 @@ module ActiveRecord
end
def self.load(json)
- ActiveSupport::JSON.decode(json) unless json.nil?
+ ActiveSupport::JSON.decode(json) unless json.blank?
end
end
end
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
index 5dcc98424a..8d41c0d799 100644
--- a/activerecord/lib/active_record/collection_cache_key.rb
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -16,7 +16,7 @@ module ActiveRecord
query = collection
.unscope(:select)
- .select("COUNT(*) AS size", "MAX(#{column}) AS timestamp")
+ .select("COUNT(*) AS #{connection.quote_column_name("size")}", "MAX(#{column}) AS timestamp")
.unscope(:order)
result = connection.select_one(query)
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 ccd2899489..c341773be1 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -298,7 +298,7 @@ module ActiveRecord
def run
return unless frequency
Thread.new(frequency, pool) { |t, p|
- while true
+ loop do
sleep t
p.reap
end
@@ -618,7 +618,7 @@ module ActiveRecord
timeout_time = Time.now + (@checkout_timeout * 2)
@available.with_a_bias_for(Thread.current) do
- while true
+ loop do
synchronize do
return if collected_conns.size == @connections.size && @now_connecting == 0
remaining_timeout = timeout_time - Time.now
@@ -778,8 +778,7 @@ module ActiveRecord
end
# ConnectionHandler is a collection of ConnectionPool objects. It is used
- # for keeping separate connection pools for Active Record models that connect
- # to different databases.
+ # for keeping separate connection pools that connect to different databases.
#
# For example, suppose that you have 5 models, with the following hierarchy:
#
@@ -821,17 +820,16 @@ module ActiveRecord
# ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
# All Active Record models use this handler to determine the connection pool that they
# should use.
+ #
+ # The ConnectionHandler class is not coupled with the Active models, as it has no knowlodge
+ # about the model. The model, needs to pass a specification name to the handler,
+ # in order to lookup the correct connection pool.
class ConnectionHandler
def initialize
- # These caches are keyed by klass.name, NOT klass. Keying them by klass
- # alone would lead to memory leaks in development mode as all previous
- # instances of the class would stay in memory.
+ # These caches are keyed by spec.name (ConnectionSpecification#name).
@owner_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k|
h[k] = Concurrent::Map.new(:initial_capacity => 2)
end
- @class_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k|
- h[k] = Concurrent::Map.new
- end
end
def connection_pool_list
@@ -839,10 +837,12 @@ module ActiveRecord
end
alias :connection_pools :connection_pool_list
- def establish_connection(owner, spec)
- @class_to_pool.clear
- raise RuntimeError, "Anonymous class is not allowed." unless owner.name
- owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
+ def establish_connection(config)
+ resolver = ConnectionSpecification::Resolver.new(Base.configurations)
+ spec = resolver.spec(config)
+
+ remove_connection(spec.name)
+ owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
end
# Returns true if there are any active connections among the connection
@@ -873,18 +873,18 @@ module ActiveRecord
# active or defined connection: if it is the latter, it will be
# opened and set as the active connection for the class it was defined
# for (not necessarily the current class).
- def retrieve_connection(klass) #:nodoc:
- pool = retrieve_connection_pool(klass)
- raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
+ def retrieve_connection(spec_name) #:nodoc:
+ pool = retrieve_connection_pool(spec_name)
+ raise ConnectionNotEstablished, "No connection pool with id '#{spec_name}' found." unless pool
conn = pool.connection
- raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
+ raise ConnectionNotEstablished, "No connection for '#{spec_name}' in connection pool" unless conn
conn
end
# Returns true if a connection that's accessible to this class has
# already been opened.
- def connected?(klass)
- conn = retrieve_connection_pool(klass)
+ def connected?(spec_name)
+ conn = retrieve_connection_pool(spec_name)
conn && conn.connected?
end
@@ -892,82 +892,43 @@ module ActiveRecord
# connection and the defined connection (if they exist). The result
# can be used as an argument for establish_connection, for easily
# re-establishing the connection.
- def remove_connection(owner)
- if pool = owner_to_pool.delete(owner.name)
- @class_to_pool.clear
+ def remove_connection(spec_name)
+ if pool = owner_to_pool.delete(spec_name)
pool.automatic_reconnect = false
pool.disconnect!
pool.spec.config
end
end
- # Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
+ # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool.
# This makes retrieving the connection pool O(1) once the process is warm.
# When a connection is established or removed, we invalidate the cache.
- #
- # Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
- # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
- # #fetch is significantly slower than #[]. So in the nil case, no caching will
- # take place, but that's ok since the nil case is not the common one that we wish
- # to optimise for.
- def retrieve_connection_pool(klass)
- class_to_pool[klass.name] ||= begin
- until pool = pool_for(klass)
- klass = klass.superclass
- break unless klass <= Base
- end
-
- class_to_pool[klass.name] = pool
- end
- end
-
- private
-
- def owner_to_pool
- @owner_to_pool[Process.pid]
- end
-
- def class_to_pool
- @class_to_pool[Process.pid]
- end
-
- def pool_for(owner)
- owner_to_pool.fetch(owner.name) {
- if ancestor_pool = pool_from_any_process_for(owner)
+ def retrieve_connection_pool(spec_name)
+ owner_to_pool.fetch(spec_name) do
+ # Check if a connection was previously established in an ancestor process,
+ # which may have been forked.
+ if ancestor_pool = pool_from_any_process_for(spec_name)
# A connection was established in an ancestor process that must have
# subsequently forked. We can't reuse the connection, but we can copy
# the specification and establish a new connection with it.
- establish_connection(owner, ancestor_pool.spec).tap do |pool|
+ establish_connection(ancestor_pool.spec.to_hash).tap do |pool|
pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
end
else
- owner_to_pool[owner.name] = nil
+ owner_to_pool[spec_name] = nil
end
- }
+ end
end
- def pool_from_any_process_for(owner)
- owner_to_pool = @owner_to_pool.values.find { |v| v[owner.name] }
- owner_to_pool && owner_to_pool[owner.name]
- end
- end
+ private
- class ConnectionManagement
- def initialize(app)
- @app = app
+ def owner_to_pool
+ @owner_to_pool[Process.pid]
end
- def call(env)
- testing = env['rack.test']
-
- status, headers, body = @app.call(env)
- proxy = ::Rack::BodyProxy.new(body) do
- ActiveRecord::Base.clear_active_connections! unless testing
- end
- [status, headers, proxy]
- rescue Exception
- ActiveRecord::Base.clear_active_connections! unless testing
- raise
+ def pool_from_any_process_for(spec_name)
+ owner_to_pool = @owner_to_pool.values.find { |v| v[spec_name] }
+ owner_to_pool && owner_to_pool[spec_name]
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 7a2a1a0e33..507a925d32 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -27,10 +27,10 @@ module ActiveRecord
end
# Returns an ActiveRecord::Result instance.
- def select_all(arel, name = nil, binds = [])
+ def select_all(arel, name = nil, binds = [], preparable: nil)
arel, binds = binds_from_relation arel, binds
sql = to_sql(arel, binds)
- if arel.is_a?(String)
+ if !prepared_statements || (arel.is_a?(String) && preparable.nil?)
preparable = false
else
preparable = visitor.preparable
@@ -66,18 +66,23 @@ module ActiveRecord
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
def select_rows(sql, name = nil, binds = [])
+ exec_query(sql, name, binds).rows
end
- undef_method :select_rows
- # Executes the SQL statement in the context of this connection.
+ # Executes the SQL statement in the context of this connection and returns
+ # the raw result from the connection adapter.
+ # Note: depending on your database connector, the result returned by this
+ # method may be manually memory managed. Consider using the exec_query
+ # wrapper instead.
def execute(sql, name = nil)
+ raise NotImplementedError
end
- undef_method :execute
# Executes +sql+ statement in the context of this connection using
# +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
def exec_query(sql, name = 'SQL', binds = [], prepare: false)
+ raise NotImplementedError
end
# Executes insert +sql+ statement in the context of this connection using
@@ -121,18 +126,21 @@ module ActiveRecord
end
alias create insert
alias insert_sql insert
+ deprecate insert_sql: :insert
# Executes the update statement and returns the number of rows affected.
def update(arel, name = nil, binds = [])
exec_update(to_sql(arel, binds), name, binds)
end
alias update_sql update
+ deprecate update_sql: :update
# Executes the delete statement and returns the number of rows affected.
def delete(arel, name = nil, binds = [])
exec_delete(to_sql(arel, binds), name, binds)
end
alias delete_sql delete
+ deprecate delete_sql: :delete
# Returns +true+ when the connection adapter supports prepared statement
# caching, otherwise returns +false+
@@ -213,9 +221,7 @@ module ActiveRecord
# * You are creating a nested (savepoint) transaction
#
# The mysql2 and postgresql adapters support setting the transaction
- # 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.
+ # isolation level.
def transaction(requires_new: nil, isolation: nil, joinable: true)
if !requires_new && current_transaction.joinable?
if isolation
@@ -285,9 +291,6 @@ module ActiveRecord
exec_rollback_to_savepoint(name)
end
- def exec_rollback_to_savepoint(name = nil) #:nodoc:
- end
-
def default_sequence_name(table, column)
nil
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 5e27cfe507..0bdfd4f900 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -61,11 +61,11 @@ module ActiveRecord
@query_cache.clear
end
- def select_all(arel, name = nil, binds = [])
+ def select_all(arel, name = nil, binds = [], preparable: nil)
if @query_cache_enabled && !locked?(arel)
arel, binds = binds_from_relation arel, binds
sql = to_sql(arel, binds)
- cache_sql(sql, binds) { super(sql, name, binds) }
+ cache_sql(sql, binds) { super(sql, name, binds, preparable: preparable) }
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 7e3760d34b..860ef17dca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -82,7 +82,7 @@ module ActiveRecord
# Quotes the column name. Defaults to no quoting.
def quote_column_name(column_name)
- column_name
+ column_name.to_s
end
# Quotes the table name. Defaults to column name quoting.
@@ -146,6 +146,10 @@ module ActiveRecord
end
end
+ def quoted_time(value) # :nodoc:
+ quoted_date(value).sub(/\A2000-01-01 /, '')
+ end
+
def prepare_binds_for_database(binds) # :nodoc:
binds.map(&:value_for_database)
end
@@ -166,6 +170,7 @@ module ActiveRecord
# BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
when Numeric, ActiveSupport::Duration then value.to_s
+ when Type::Time::Value then "'#{quoted_time(value)}'"
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
when Class then "'#{value}'"
@@ -181,6 +186,7 @@ module ActiveRecord
when false then unquoted_false
# BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
+ when Type::Time::Value then quoted_time(value)
when Date, Time then quoted_date(value)
when *types_which_need_no_typecasting
value
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
index c0662f8473..3a06f75292 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
@@ -1,8 +1,8 @@
module ActiveRecord
module ConnectionAdapters
- module Savepoints #:nodoc:
- def supports_savepoints?
- true
+ module Savepoints
+ def current_savepoint_name
+ current_transaction.savepoint_name
end
def create_savepoint(name = current_savepoint_name)
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 0ba4d94e3c..6add697eeb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -53,8 +53,8 @@ module ActiveRecord
statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
end
- create_sql << "(#{statements.join(', ')}) " if statements.present?
- create_sql << "#{o.options}"
+ create_sql << "(#{statements.join(', ')})" if statements.present?
+ add_table_options!(create_sql, table_options(o))
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
create_sql
end
@@ -82,6 +82,19 @@ module ActiveRecord
"DROP CONSTRAINT #{quote_column_name(name)}"
end
+ def table_options(o)
+ table_options = {}
+ table_options[:comment] = o.comment
+ table_options[:options] = o.options
+ table_options
+ end
+
+ def add_table_options!(create_sql, options)
+ if options_sql = options[:options]
+ create_sql << " #{options_sql}"
+ end
+ end
+
def column_options(o)
column_options = {}
column_options[:null] = o.null unless o.null.nil?
@@ -92,6 +105,7 @@ module ActiveRecord
column_options[:auto_increment] = o.auto_increment
column_options[:primary_key] = o.primary_key
column_options[:collation] = o.collation
+ column_options[:comment] = o.comment
column_options
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 690e0ba957..8dbafc5a4b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -3,14 +3,14 @@ module ActiveRecord
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc:
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
@@ -51,11 +51,12 @@ 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 }
+ def defined_for?(to_table_ord = nil, to_table: nil, **options)
+ if to_table_ord
+ self.to_table == to_table_ord.to_s
else
- to_table == options_or_to_table.to_s
+ (to_table.nil? || to_table.to_s == self.to_table) &&
+ options.all? { |k, v| self.options[k].to_s == v.to_s }
end
end
@@ -182,6 +183,7 @@ module ActiveRecord
end
CODE
end
+ alias_method :numeric, :decimal
end
# Represents the schema of an SQL table in an abstract way. This class
@@ -206,17 +208,18 @@ module ActiveRecord
include ColumnMethods
attr_accessor :indexes
- attr_reader :name, :temporary, :options, :as, :foreign_keys
+ attr_reader :name, :temporary, :options, :as, :foreign_keys, :comment
- def initialize(name, temporary, options, as = nil)
+ def initialize(name, temporary = false, options = nil, as = nil, comment: nil)
@columns_hash = {}
@indexes = {}
- @foreign_keys = {}
+ @foreign_keys = []
@primary_keys = nil
@temporary = temporary
@options = options
@as = as
@name = name
+ @comment = comment
end
def primary_keys(name = nil) # :nodoc:
@@ -329,7 +332,10 @@ module ActiveRecord
end
def foreign_key(table_name, options = {}) # :nodoc:
- foreign_keys[table_name] = options
+ table_name_prefix = ActiveRecord::Base.table_name_prefix
+ table_name_suffix = ActiveRecord::Base.table_name_suffix
+ table_name = "#{table_name_prefix}#{table_name}#{table_name_suffix}"
+ foreign_keys.push([table_name, options])
end
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
@@ -372,6 +378,7 @@ module ActiveRecord
column.auto_increment = options[:auto_increment]
column.primary_key = type == :primary_key || options[:primary_key]
column.collation = options[:collation]
+ column.comment = options[:comment]
column
end
@@ -436,6 +443,7 @@ module ActiveRecord
# t.bigint
# t.float
# t.decimal
+ # t.numeric
# t.datetime
# t.timestamp
# t.time
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 a95109fdae..677a4c6bd0 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -7,15 +7,16 @@ module ActiveRecord
# Adapter level by over-writing this code inside the database specific adapters
module ColumnDumper
def column_spec(column)
- spec = prepare_column_options(column)
- (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k}: ")}
+ spec = Hash[prepare_column_options(column).map { |k, v| [k, "#{k}: #{v}"] }]
+ spec[:name] = column.name.inspect
+ spec[:type] = schema_type(column).to_s
spec
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) })
+ return {} if default_primary_key?(column)
+ spec = { id: schema_type(column).inspect }
+ spec.merge!(prepare_column_options(column).except!(:null))
end
# This can be overridden on an Adapter level basis to support other
@@ -23,9 +24,6 @@ module ActiveRecord
# PostgreSQL::ColumnDumper)
def prepare_column_options(column)
spec = {}
- spec[:name] = column.name.inspect
- spec[:type] = schema_type(column)
- spec[:null] = 'false' unless column.null
if limit = schema_limit(column)
spec[:limit] = limit
@@ -42,26 +40,38 @@ module ActiveRecord
default = schema_default(column) if column.has_default?
spec[:default] = default unless default.nil?
+ spec[:null] = 'false' unless column.null
+
if collation = schema_collation(column)
spec[:collation] = collation
end
+ spec[:comment] = column.comment.inspect if column.comment.present?
+
spec
end
# Lists the valid migration options
def migration_keys
- [:name, :limit, :precision, :scale, :default, :null, :collation]
+ [:name, :limit, :precision, :scale, :default, :null, :collation, :comment]
end
private
+ def default_primary_key?(column)
+ schema_type(column) == :integer
+ end
+
def schema_type(column)
- column.type.to_s
+ if column.bigint?
+ :bigint
+ else
+ column.type
+ end
end
def schema_limit(column)
- limit = column.limit
+ limit = column.limit unless column.bigint?
limit.inspect if limit && limit != native_database_types[column.type][:limit]
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 002f2ea8ce..eec0bc8518 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -18,6 +18,11 @@ module ActiveRecord
nil
end
+ # Returns the table comment that's stored in database metadata.
+ def table_comment(table_name)
+ nil
+ end
+
# Truncates a table alias according to the limits of the current adapter.
def table_alias_for(table_name)
table_name[0...table_alias_length].tr('.', '_')
@@ -92,7 +97,9 @@ module ActiveRecord
# Returns an array of Column objects for the table specified by +table_name+.
# See the concrete implementation for details on the expected parameter values.
- def columns(table_name) end
+ def columns(table_name)
+ raise NotImplementedError, "#columns is not implemented"
+ end
# Checks to see if a column exists in a given table.
#
@@ -110,18 +117,25 @@ module ActiveRecord
#
def column_exists?(table_name, column_name, type = nil, options = {})
column_name = column_name.to_s
- columns(table_name).any?{ |c| c.name == column_name &&
- (!type || c.type == type) &&
- (!options.key?(:limit) || c.limit == options[:limit]) &&
- (!options.key?(:precision) || c.precision == options[:precision]) &&
- (!options.key?(:scale) || c.scale == options[:scale]) &&
- (!options.key?(:default) || c.default == options[:default]) &&
- (!options.key?(:null) || c.null == options[:null]) }
+ checks = []
+ checks << lambda { |c| c.name == column_name }
+ checks << lambda { |c| c.type == type } if type
+ (migration_keys - [:name]).each do |attr|
+ checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
+ end
+
+ columns(table_name).any? { |c| checks.all? { |check| check[c] } }
end
# Returns just a table's primary key
def primary_key(table_name)
pks = primary_keys(table_name)
+ warn <<-WARNING.strip_heredoc if pks.count > 1
+ WARNING: Rails does not support composite primary key.
+
+ #{table_name} has composite primary key. Composite primary key is ignored.
+ WARNING
+
pks.first if pks.one?
end
@@ -245,8 +259,8 @@ module ActiveRecord
# SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
#
# See also TableDefinition#column for details on how to create columns.
- def create_table(table_name, options = {})
- td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
+ def create_table(table_name, comment: nil, **options)
+ td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
if options[:id] != false && !options[:as]
pk = options.fetch(:primary_key) do
@@ -274,6 +288,14 @@ module ActiveRecord
end
end
+ if supports_comments? && !supports_comments_in_create?
+ change_table_comment(table_name, comment) if comment
+
+ td.columns.each do |column|
+ change_column_comment(table_name, column.name, column.comment) if column.comment
+ end
+ end
+
result
end
@@ -320,12 +342,13 @@ module ActiveRecord
column_options = options.delete(:column_options) || {}
column_options.reverse_merge!(null: false)
+ type = column_options.delete(:type) || :integer
t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key }
create_table(join_table_name, options.merge!(id: false)) do |td|
- td.integer t1_column, column_options
- td.integer t2_column, column_options
+ td.send type, t1_column, column_options
+ td.send type, t2_column, column_options
yield td if block_given?
end
end
@@ -450,7 +473,7 @@ module ActiveRecord
# The +type+ parameter is normally one of the migrations native types,
# which is one of the following:
# <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
- # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
+ # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
# <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
# <tt>:binary</tt>, <tt>:boolean</tt>.
#
@@ -468,9 +491,9 @@ module ActiveRecord
# Allows or disallows +NULL+ values in the column. This option could
# have been named <tt>:null_allowed</tt>.
# * <tt>:precision</tt> -
- # Specifies the precision for a <tt>:decimal</tt> column.
+ # Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
# * <tt>:scale</tt> -
- # Specifies the scale for a <tt>:decimal</tt> column.
+ # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
#
# Note: The precision is the total number of significant digits
# and the scale is the number of digits that can be stored following
@@ -487,8 +510,6 @@ module ActiveRecord
# Default is (10,0).
# * PostgreSQL: <tt>:precision</tt> [1..infinity],
# <tt>:scale</tt> [0..infinity]. No default.
- # * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
- # Internal storage as strings. No default.
# * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
# but the maximum supported <tt>:precision</tt> is 16. No default.
# * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
@@ -691,7 +712,7 @@ module ActiveRecord
#
# CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
#
- # Note: only supported by MySQL. Supported: <tt>:fulltext</tt> and <tt>:spatial</tt> on MyISAM tables.
+ # Note: only supported by MySQL.
def add_index(table_name, column_name, options = {})
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
@@ -770,6 +791,7 @@ module ActiveRecord
# The reference column type. Defaults to +:integer+.
# [<tt>:index</tt>]
# Add an appropriate index. Defaults to false.
+ # See #add_index for usage of this option.
# [<tt>:foreign_key</tt>]
# Add an appropriate foreign key constraint. Defaults to false.
# [<tt>:polymorphic</tt>]
@@ -789,6 +811,14 @@ module ActiveRecord
#
# add_reference(:products, :supplier, polymorphic: true, index: true)
#
+ # ====== Create a supplier_id column with a unique index
+ #
+ # add_reference(:products, :supplier, index: { unique: true })
+ #
+ # ====== Create a supplier_id column with a named index
+ #
+ # add_reference(:products, :supplier, index: { name: "my_supplier_index" })
+ #
# ====== Create a supplier_id column and appropriate foreign key
#
# add_reference(:products, :supplier, foreign_key: true)
@@ -817,14 +847,19 @@ module ActiveRecord
#
# remove_reference(:products, :user, index: true, foreign_key: true)
#
- def remove_reference(table_name, ref_name, options = {})
- if options[:foreign_key]
+ def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
+ if foreign_key
reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
- remove_foreign_key(table_name, reference_name)
+ if foreign_key.is_a?(Hash)
+ foreign_key_options = foreign_key
+ else
+ foreign_key_options = { to_table: reference_name }
+ end
+ remove_foreign_key(table_name, **foreign_key_options)
end
remove_column(table_name, "#{ref_name}_id")
- remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
+ remove_column(table_name, "#{ref_name}_type") if polymorphic
end
alias :remove_belongs_to :remove_reference
@@ -847,7 +882,7 @@ module ActiveRecord
#
# generates:
#
- # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
#
# ====== Creating a foreign key on a specific column
#
@@ -863,7 +898,7 @@ module ActiveRecord
#
# generates:
#
- # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
#
# The +options+ hash can include the following keys:
# [<tt>:column</tt>]
@@ -955,11 +990,23 @@ module ActiveRecord
end
def dump_schema_information #:nodoc:
+ versions = ActiveRecord::SchemaMigration.order('version').pluck(:version)
+ insert_versions_sql(versions)
+ end
+
+ def insert_versions_sql(versions) # :nodoc:
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
- sql = "INSERT INTO #{sm_table} (version) VALUES "
- sql << ActiveRecord::SchemaMigration.order('version').pluck(:version).map {|v| "('#{v}')" }.join(', ')
- sql << ";\n\n"
+ if supports_multi_insert?
+ sql = "INSERT INTO #{sm_table} (version) VALUES "
+ sql << versions.map {|v| "('#{v}')" }.join(', ')
+ sql << ";\n\n"
+ sql
+ else
+ versions.map { |version|
+ "INSERT INTO #{sm_table} (version) VALUES ('#{version}');"
+ }.join "\n\n"
+ end
end
# Should not be called normally, but this operation is non-destructive.
@@ -972,6 +1019,10 @@ module ActiveRecord
ActiveRecord::InternalMetadata.create_table
end
+ def internal_string_options_for_primary_key # :nodoc:
+ { primary_key: true }
+ end
+
def assume_migrated_upto_version(version, migrations_paths)
migrations_paths = Array(migrations_paths)
version = version.to_i
@@ -992,7 +1043,7 @@ module ActiveRecord
if (duplicate = inserting.detect {|v| inserting.count(v) > 1})
raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
end
- execute "INSERT INTO #{sm_table} (version) VALUES #{inserting.map {|v| '(#{v})'}.join(', ') }"
+ execute insert_versions_sql(inserting)
end
end
@@ -1040,9 +1091,9 @@ module ActiveRecord
end
# Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
- # Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
+ # Additional options (like +:null+) are forwarded to #add_column.
#
- # add_timestamps(:suppliers, null: false)
+ # add_timestamps(:suppliers, null: true)
#
def add_timestamps(table_name, options = {})
options[:null] = false if options[:null].nil?
@@ -1064,16 +1115,19 @@ module ActiveRecord
Table.new(table_name, base)
end
- def add_index_options(table_name, column_name, options = {}) #:nodoc:
- column_names = Array(column_name)
+ def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
+ if column_name.is_a?(String) && /\W/ === column_name
+ column_names = column_name
+ else
+ column_names = Array(column_name)
+ end
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
index_type = options[:type].to_s if options.key?(:type)
index_type ||= options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
- index_name ||= index_name(table_name, column: column_names)
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
+ index_name ||= index_name(table_name, index_name_options(column_names))
if options.key?(:algorithm)
algorithm = index_algorithms.fetch(options[:algorithm]) {
@@ -1087,21 +1141,30 @@ module ActiveRecord
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
end
- if index_name.length > max_index_length
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
- end
+ validate_index_length!(table_name, index_name, options.fetch(:internal, false))
+
if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false)
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
- [index_name, index_type, index_columns, index_options, algorithm, using]
+ [index_name, index_type, index_columns, index_options, algorithm, using, comment]
end
def options_include_default?(options)
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
end
+ # Changes the comment for a table or removes it if +nil+.
+ def change_table_comment(table_name, comment)
+ raise NotImplementedError, "#{self.class} does not support changing table comments"
+ end
+
+ # Changes the comment for a column or removes it if +nil+.
+ def change_column_comment(table_name, column_name, comment) #:nodoc:
+ raise NotImplementedError, "#{self.class} does not support changing column comments"
+ end
+
protected
def add_index_sort_order(option_strings, column_names, options = {})
if options.is_a?(Hash) && order = options[:order]
@@ -1118,6 +1181,8 @@ module ActiveRecord
# Overridden by the MySQL adapter for supporting index lengths
def quoted_columns_for_index(column_names, options = {})
+ return [column_names] if column_names.is_a?(String)
+
option_strings = Hash[column_names.map {|name| [name, '']}]
# add index sort order if supported
@@ -1129,6 +1194,8 @@ module ActiveRecord
end
def index_name_for_remove(table_name, options = {})
+ return options[:name] if can_remove_index_by_name?(options)
+
# if the adapter doesn't support the indexes call the best we can do
# is return the default index name for the options provided
return index_name(table_name, options) unless respond_to?(:indexes)
@@ -1136,7 +1203,7 @@ module ActiveRecord
checks = []
if options.is_a?(Hash)
- checks << lambda { |i| i.name == options[:name].to_s } if options.has_key?(:name)
+ checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
column_names = Array(options[:column]).map(&:to_s)
else
column_names = Array(options).map(&:to_s)
@@ -1183,14 +1250,22 @@ module ActiveRecord
end
private
- def create_table_definition(name, temporary = false, options = nil, as = nil)
- TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(*args)
+ TableDefinition.new(*args)
end
def create_alter_table(name)
AlterTable.new create_table_definition(name)
end
+ def index_name_options(column_names) # :nodoc:
+ if column_names.is_a?(String)
+ column_names = column_names.scan(/\w+/).join('_')
+ end
+
+ { column: column_names }
+ end
+
def foreign_key_name(table_name, options) # :nodoc:
identifier = "#{table_name}_#{options.fetch(:column)}_fk"
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
@@ -1199,8 +1274,10 @@ module ActiveRecord
end
end
- def validate_index_length!(table_name, new_name) # :nodoc:
- if new_name.length > allowed_index_name_length
+ def validate_index_length!(table_name, new_name, internal = false) # :nodoc:
+ max_index_length = internal ? index_name_length : allowed_index_name_length
+
+ if new_name.length > max_index_length
raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
end
end
@@ -1212,6 +1289,10 @@ module ActiveRecord
default_or_changes
end
end
+
+ def can_remove_index_by_name?(options)
+ options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
+ end
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 14d04a6388..ca795cb1ad 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -167,8 +167,13 @@ module ActiveRecord
def commit_transaction
transaction = @stack.last
- transaction.before_commit_records
- @stack.pop
+
+ begin
+ transaction.before_commit_records
+ ensure
+ @stack.pop
+ end
+
transaction.commit
transaction.commit_records
end
@@ -183,7 +188,10 @@ module ActiveRecord
transaction = begin_transaction options
yield
rescue Exception => error
- rollback_transaction if transaction
+ if transaction
+ rollback_transaction
+ after_failure_actions(transaction, error)
+ end
raise
ensure
unless error
@@ -209,7 +217,16 @@ module ActiveRecord
end
private
+
NULL_TRANSACTION = NullTransaction.new
+
+ # Deallocate invalidated prepared statements outside of the transaction
+ def after_failure_actions(transaction, error)
+ return unless transaction.is_a?(RealTransaction)
+ return unless error.is_a?(ActiveRecord::PreparedStatementCacheExpired)
+ @connection.clear_cache!
+ end
+
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index d9b42d4283..d4b9e301bc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,5 +1,4 @@
require 'active_record/type'
-require 'active_support/core_ext/benchmark'
require 'active_record/connection_adapters/determine_if_preparable_visitor'
require 'active_record/connection_adapters/schema_cache'
require 'active_record/connection_adapters/sql_type_metadata'
@@ -28,7 +27,6 @@ module ActiveRecord
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
autoload :ConnectionHandler
- autoload :ConnectionManagement
end
autoload_under 'abstract' do
@@ -69,6 +67,7 @@ module ActiveRecord
include QueryCache
include ActiveSupport::Callbacks
include ColumnDumper
+ include Savepoints
SIMPLE_INT = /\A\d+\z/
@@ -106,8 +105,15 @@ module ActiveRecord
@config = config
@pool = nil
@schema_cache = SchemaCache.new self
- @visitor = nil
- @prepared_statements = false
+ @quoted_column_names, @quoted_table_names = {}, {}
+ @visitor = arel_visitor
+
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
+ @prepared_statements = true
+ @visitor.extend(DetermineIfPreparableVisitor)
+ else
+ @prepared_statements = false
+ end
end
class Version
@@ -143,8 +149,12 @@ module ActiveRecord
end
end
+ def arel_visitor # :nodoc:
+ Arel::Visitors::ToSql.new(self)
+ end
+
def valid_type?(type)
- true
+ false
end
def schema_creation
@@ -238,6 +248,11 @@ module ActiveRecord
false
end
+ # Does this adapter support expression indices?
+ def supports_expression_index?
+ false
+ end
+
# Does this adapter support explain?
def supports_explain?
false
@@ -279,6 +294,21 @@ module ActiveRecord
false
end
+ # Does this adapter support metadata comments on database objects (tables, columns, indexes)?
+ def supports_comments?
+ false
+ end
+
+ # Can comments for tables, columns, and indexes be specified in create/alter table statements?
+ def supports_comments_in_create?
+ false
+ end
+
+ # Does this adapter support multi-value insert?
+ def supports_multi_insert?
+ true
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
@@ -380,12 +410,6 @@ module ActiveRecord
@connection
end
- def create_savepoint(name = nil)
- end
-
- def release_savepoint(name = nil)
- end
-
def case_sensitive_comparison(table, attribute, column, value)
if value.nil?
table[attribute].eq(value)
@@ -398,7 +422,7 @@ module ActiveRecord
if can_perform_case_insensitive_comparison_for?(column)
table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new))
else
- case_sensitive_comparison(table, attribute, column, value)
+ table[attribute].eq(Arel::Nodes::BindParam.new)
end
end
@@ -407,10 +431,6 @@ module ActiveRecord
end
private :can_perform_case_insensitive_comparison_for?
- def current_savepoint_name
- current_transaction.savepoint_name
- end
-
# Check the connection back in to the connection pool
def close
pool.checkin self
@@ -422,8 +442,8 @@ module ActiveRecord
end
end
- def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
- Column.new(name, default, sql_type_metadata, null, default_function, collation)
+ def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc:
+ Column.new(name, default, sql_type_metadata, null, table_name, default_function, collation)
end
def lookup_cast_type(sql_type) # :nodoc:
@@ -434,6 +454,24 @@ module ActiveRecord
visitor.accept(node, collector).value
end
+ def combine_bind_parameters(
+ from_clause: [],
+ join_clause: [],
+ where_clause: [],
+ having_clause: [],
+ limit: nil,
+ offset: nil
+ ) # :nodoc:
+ result = from_clause + join_clause + where_clause + having_clause
+ if limit
+ result << limit
+ end
+ if offset
+ result << offset
+ end
+ result
+ end
+
protected
def initialize_type_map(m) # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 3e84786be0..718a6c5b91 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,5 +1,8 @@
require 'active_record/connection_adapters/abstract_adapter'
+require 'active_record/connection_adapters/statement_pool'
require 'active_record/connection_adapters/mysql/column'
+require 'active_record/connection_adapters/mysql/explain_pretty_printer'
+require 'active_record/connection_adapters/mysql/quoting'
require 'active_record/connection_adapters/mysql/schema_creation'
require 'active_record/connection_adapters/mysql/schema_definitions'
require 'active_record/connection_adapters/mysql/schema_dumper'
@@ -10,17 +13,21 @@ require 'active_support/core_ext/string/strip'
module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
+ include MySQL::Quoting
include MySQL::ColumnDumper
- include Savepoints
def update_table_definition(table_name, base) # :nodoc:
MySQL::Table.new(table_name, base)
end
- def schema_creation
+ def schema_creation # :nodoc:
MySQL::SchemaCreation.new(self)
end
+ def arel_visitor # :nodoc:
+ Arel::Visitors::MySQL.new(self)
+ end
+
##
# :singleton-method:
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
@@ -31,25 +38,17 @@ module ActiveRecord
class_attribute :emulate_booleans
self.emulate_booleans = true
- LOST_CONNECTION_ERROR_MESSAGES = [
- "Server shutdown in progress",
- "Broken pipe",
- "Lost connection to MySQL server during query",
- "MySQL server has gone away" ]
-
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
-
NATIVE_DATABASE_TYPES = {
primary_key: "int auto_increment PRIMARY KEY",
string: { name: "varchar", limit: 255 },
- text: { name: "text" },
+ text: { name: "text", limit: 65535 },
integer: { name: "int", limit: 4 },
float: { name: "float" },
decimal: { name: "decimal" },
datetime: { name: "datetime" },
time: { name: "time" },
date: { name: "date" },
- binary: { name: "blob" },
+ binary: { name: "blob", limit: 65535 },
boolean: { name: "tinyint", limit: 1 },
json: { name: "json" },
}
@@ -57,35 +56,38 @@ module ActiveRecord
INDEX_TYPES = [:fulltext, :spatial]
INDEX_USINGS = [:btree, :hash]
- # FIXME: Make the first parameter more similar for the two adapters
+ class StatementPool < ConnectionAdapters::StatementPool
+ private def dealloc(stmt)
+ stmt[:stmt].close
+ end
+ end
+
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
- @quoted_column_names, @quoted_table_names = {}, {}
- @visitor = Arel::Visitors::MySQL.new self
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
- @prepared_statements = true
- @visitor.extend(DetermineIfPreparableVisitor)
- else
- @prepared_statements = false
+ if version < '5.0.0'
+ raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.0."
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
+
+ def internal_string_options_for_primary_key # :nodoc:
+ super.tap { |options|
+ options[:collation] = collation.sub(/\A[^_]+/, 'utf8') if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
+ }
end
- def version
+ def version #:nodoc:
@version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
end
+ def mariadb? # :nodoc:
+ full_version =~ /mariadb/i
+ end
+
# Returns true, since this connection adapter supports migrations.
def supports_migrations?
true
@@ -99,18 +101,20 @@ module ActiveRecord
true
end
+ # Returns true, since this connection adapter supports prepared statement
+ # caching.
+ def supports_statement_cache?
+ true
+ end
+
# Technically MySQL allows to create indexes with the sort order syntax
# but at the moment (5.5) it doesn't yet implement them
def supports_index_sort_order?
true
end
- # MySQL 4 technically support transaction isolation, but it is affected by a bug
- # where the transaction level gets persisted for the whole session:
- #
- # http://bugs.mysql.com/bug.php?id=39170
def supports_transaction_isolation?
- version >= '5.0.0'
+ true
end
def supports_explain?
@@ -126,25 +130,27 @@ module ActiveRecord
end
def supports_views?
- version >= '5.0.0'
+ true
end
def supports_datetime_with_precision?
- version >= '5.6.4'
+ if mariadb?
+ version >= '5.3.0'
+ else
+ version >= '5.6.4'
+ end
end
- # 5.0.0 definitely supports it, possibly supported by earlier versions but
- # not sure
def supports_advisory_locks?
- version >= '5.0.0'
+ true
end
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
- select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
+ select_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1
end
def release_advisory_lock(lock_name) # :nodoc:
- select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
+ select_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1
end
def native_database_types
@@ -163,8 +169,8 @@ module ActiveRecord
raise NotImplementedError
end
- def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
- MySQL::Column.new(field, default, sql_type_metadata, null, default_function, collation)
+ def new_column(*args) #:nodoc:
+ MySQL::Column.new(*args)
end
# Must return the MySQL error number from the exception, if the exception has an
@@ -173,48 +179,6 @@ module ActiveRecord
raise NotImplementedError
end
- # QUOTING ==================================================
-
- def _quote(value) # :nodoc:
- if value.is_a?(Type::Binary::Data)
- "x'#{value.hex}'"
- else
- super
- end
- end
-
- def quote_column_name(name) #:nodoc:
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
- end
-
- def quote_table_name(name) #:nodoc:
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
- end
-
- def quoted_true
- QUOTED_TRUE
- end
-
- def unquoted_true
- 1
- end
-
- def quoted_false
- QUOTED_FALSE
- end
-
- def unquoted_false
- 0
- end
-
- def quoted_date(value)
- if supports_datetime_with_precision?
- super
- else
- super.sub(/\.\d{6}\z/, '')
- end
- end
-
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity #:nodoc:
@@ -228,6 +192,14 @@ module ActiveRecord
end
end
+ # CONNECTION MANAGEMENT ====================================
+
+ # Clears the prepared statements cache.
+ def clear_cache!
+ reload_type_map
+ @statements.clear
+ end
+
#--
# DATABASE STATEMENTS ======================================
#++
@@ -238,77 +210,7 @@ module ActiveRecord
result = exec_query(sql, 'EXPLAIN', binds)
elapsed = Time.now - start
- ExplainPrettyPrinter.new.pp(result, elapsed)
- end
-
- class ExplainPrettyPrinter # :nodoc:
- # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
- # MySQL shell:
- #
- # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
- # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
- # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
- # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
- # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
- # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
- # 2 rows in set (0.00 sec)
- #
- # This is an exercise in Ruby hyperrealism :).
- def pp(result, elapsed)
- widths = compute_column_widths(result)
- separator = build_separator(widths)
-
- pp = []
-
- pp << separator
- pp << build_cells(result.columns, widths)
- pp << separator
-
- result.rows.each do |row|
- pp << build_cells(row, widths)
- end
-
- pp << separator
- pp << build_footer(result.rows.length, elapsed)
-
- pp.join("\n") + "\n"
- end
-
- private
-
- def compute_column_widths(result)
- [].tap do |widths|
- result.columns.each_with_index do |column, i|
- cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
- widths << cells_in_column.map(&:length).max
- end
- end
- end
-
- def build_separator(widths)
- padding = 1
- '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
- end
-
- def build_cells(items, widths)
- cells = []
- items.each_with_index do |item, i|
- item = 'NULL' if item.nil?
- justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
- cells << item.to_s.send(justifier, widths[i])
- end
- '| ' + cells.join(' | ') + ' |'
- end
-
- def build_footer(nrows, elapsed)
- rows_label = nrows == 1 ? 'row' : 'rows'
- "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
- end
- end
-
- def clear_cache!
- super
- reload_type_map
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
end
# Executes the SQL statement in the context of this connection.
@@ -376,9 +278,9 @@ module ActiveRecord
# create_database 'matt_development', charset: :big5
def create_database(name, options = {})
if options[:collation]
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}"
else
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
end
end
@@ -387,7 +289,7 @@ module ActiveRecord
# Example:
# drop_database('sebastian_development')
def drop_database(name) #:nodoc:
- execute "DROP DATABASE IF EXISTS `#{name}`"
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
end
def current_database
@@ -445,8 +347,7 @@ module ActiveRecord
def data_source_exists?(table_name)
return false unless table_name.present?
- schema, name = table_name.to_s.split('.', 2)
- schema, name = @config[:database], schema unless name # A table was provided without a schema
+ schema, name = extract_schema_qualified_name(table_name)
sql = "SELECT table_name FROM information_schema.tables "
sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
@@ -461,8 +362,7 @@ module ActiveRecord
def view_exists?(view_name) # :nodoc:
return false unless view_name.present?
- schema, name = view_name.to_s.split('.', 2)
- schema, name = @config[:database], schema unless name # A view was provided without a schema
+ schema, name = extract_schema_qualified_name(view_name)
sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
@@ -483,7 +383,7 @@ module ActiveRecord
mysql_index_type = row[:Index_type].downcase.to_sym
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using, row[:Index_comment].presence)
end
indexes.last.columns << row[:Column_name]
@@ -496,21 +396,28 @@ module ActiveRecord
# Returns an array of +Column+ objects for the table specified by +table_name+.
def columns(table_name) # :nodoc:
- sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
- execute_and_free(sql, 'SCHEMA') do |result|
- each_hash(result).map do |field|
- type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
- new_column(field[:Field], nil, type_metadata, field[:Null] == "YES", field[:Default], field[:Collation])
- else
- new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
- end
+ table_name = table_name.to_s
+ column_definitions(table_name).map do |field|
+ type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
+ if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
+ default, default_function = nil, field[:Default]
+ else
+ default, default_function = field[:Default], nil
end
+ new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
end
end
- def create_table(table_name, options = {}) #:nodoc:
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
+ def table_comment(table_name) # :nodoc:
+ select_value(<<-SQL.strip_heredoc, 'SCHEMA')
+ SELECT table_comment
+ FROM information_schema.tables
+ WHERE table_name=#{quote(table_name)}
+ SQL
+ end
+
+ def create_table(table_name, **options) #:nodoc:
+ super(table_name, options: 'ENGINE=InnoDB', **options)
end
def bulk_change_table(table_name, operations) #:nodoc:
@@ -593,11 +500,21 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
+ index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
+ execute add_sql_comment!(sql, comment)
+ end
+
+ def add_sql_comment!(sql, comment) # :nodoc:
+ sql << " COMMENT #{quote(comment)}" if comment
+ sql
end
def foreign_keys(table_name)
+ raise ArgumentError unless table_name.present?
+
+ schema, name = extract_schema_qualified_name(table_name)
+
fk_info = select_all <<-SQL.strip_heredoc
SELECT fk.referenced_table_name as 'to_table'
,fk.referenced_column_name as 'primary_key'
@@ -605,8 +522,8 @@ module ActiveRecord
,fk.constraint_name as 'name'
FROM information_schema.key_column_usage fk
WHERE fk.referenced_column_name is not null
- AND fk.table_schema = '#{@config[:database]}'
- AND fk.table_name = '#{table_name}'
+ AND fk.table_schema = #{quote(schema)}
+ AND fk.table_name = #{quote(name)}
SQL
create_table_info = create_table_info(table_name)
@@ -632,7 +549,12 @@ module ActiveRecord
raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
# strip AUTO_INCREMENT
- raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+ raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+
+ # strip COMMENT
+ raw_table_options.sub!(/ COMMENT='.+'/, '')
+
+ raw_table_options
end
# Maps logical Rails types to MySQL-specific data types.
@@ -660,8 +582,7 @@ module ActiveRecord
# SHOW VARIABLES LIKE 'name'
def show_variable(name)
- variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
- variables.first['Value'] unless variables.empty?
+ select_value("SELECT @@#{name}", 'SCHEMA')
rescue ActiveRecord::StatementInvalid
nil
end
@@ -669,8 +590,7 @@ module ActiveRecord
def primary_keys(table_name) # :nodoc:
raise ArgumentError unless table_name.present?
- schema, name = table_name.to_s.split('.', 2)
- schema, name = @config[:database], schema unless name # A table was provided without a schema
+ schema, name = extract_schema_qualified_name(table_name)
select_values(<<-SQL.strip_heredoc, 'SCHEMA')
SELECT column_name
@@ -683,20 +603,17 @@ module ActiveRecord
end
def case_sensitive_comparison(table, attribute, column, value)
- if value.nil? || column.case_sensitive?
- super
- else
+ if !value.nil? && column.collation && !column.case_sensitive?
table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
+ else
+ super
end
end
- def case_insensitive_comparison(table, attribute, column, value)
- if column.case_sensitive?
- super
- else
- table[attribute].eq(Arel::Nodes::BindParam.new)
- end
+ def can_perform_case_insensitive_comparison_for?(column)
+ column.case_sensitive?
end
+ private :can_perform_case_insensitive_comparison_for?
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
@@ -746,7 +663,7 @@ module ActiveRecord
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.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
m.alias_type %r(year)i, 'integer'
m.alias_type %r(bit)i, 'binary'
@@ -790,7 +707,7 @@ module ActiveRecord
case length
when Hash
column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
- when Fixnum
+ when Integer
column_names.each {|name| option_strings[name] += "(#{length})"}
end
end
@@ -810,12 +727,22 @@ module ActiveRecord
column_names.map {|name| quote_column_name(name) + option_strings[name]}
end
+ # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
+ ER_DUP_ENTRY = 1062
+ ER_NO_REFERENCED_ROW_2 = 1452
+ ER_DATA_TOO_LONG = 1406
+ ER_LOCK_DEADLOCK = 1213
+
def translate_exception(exception, message)
case error_number(exception)
- when 1062
+ when ER_DUP_ENTRY
RecordNotUnique.new(message)
- when 1452
+ when ER_NO_REFERENCED_ROW_2
InvalidForeignKey.new(message)
+ when ER_DATA_TOO_LONG
+ ValueTooLong.new(message)
+ when ER_LOCK_DEADLOCK
+ TransactionSerializationError.new(message)
else
super
end
@@ -901,10 +828,6 @@ module ActiveRecord
subselect.from subsubselect.as('__active_record_temp')
end
- def mariadb?
- full_version =~ /mariadb/i
- end
-
def supports_rename_index?
mariadb? ? false : version >= '5.7.6'
end
@@ -917,7 +840,7 @@ module ActiveRecord
# Increase timeout so the server doesn't disconnect us.
wait_timeout = @config[:wait_timeout]
- wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
defaults = [':default', :default].to_set
@@ -925,9 +848,19 @@ module ActiveRecord
# Make MySQL reject illegal values rather than truncating or blanking them, see
# http://dev.mysql.com/doc/refman/5.7/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') || defaults.include?(@config[:strict])
- variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
+ if sql_mode = variables.delete('sql_mode')
+ sql_mode = quote(sql_mode)
+ elsif !defaults.include?(strict_mode?)
+ if strict_mode?
+ sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
+ else
+ sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
+ sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
+ sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
+ end
+ sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
end
+ sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
# NAMES does not have an equals sign, see
# http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
@@ -949,7 +882,13 @@ module ActiveRecord
end.compact.join(', ')
# ...and send them all in one query
- @connection.query "SET #{encoding} #{variable_assignments}"
+ @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
+ end
+
+ def column_definitions(table_name) # :nodoc:
+ execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
+ each_hash(result)
+ end
end
def extract_foreign_key_action(structure, name, action) # :nodoc:
@@ -969,8 +908,14 @@ module ActiveRecord
create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
end
- def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
- MySQL::TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(*args) # :nodoc:
+ MySQL::TableDefinition.new(*args)
+ end
+
+ def extract_schema_qualified_name(string) # :nodoc:
+ schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
+ schema, name = @config[:database], schema unless name
+ [schema, name]
end
def integer_to_sql(limit) # :nodoc:
@@ -980,7 +925,6 @@ module ActiveRecord
when 3; 'mediumint'
when nil, 4; 'int'
when 5..8; 'bigint'
- when 11; 'int(11)' # backward compatibility with Rails 2.0
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
end
end
@@ -1016,8 +960,8 @@ module ActiveRecord
class MysqlString < Type::String # :nodoc:
def serialize(value)
case value
- when true then "1"
- when false then "0"
+ when true then MySQL::Quoting::QUOTED_TRUE
+ when false then MySQL::Quoting::QUOTED_FALSE
else super
end
end
@@ -1026,8 +970,8 @@ module ActiveRecord
def cast_value(value)
case value
- when true then "1"
- when false then "0"
+ when true then MySQL::Quoting::QUOTED_TRUE
+ when false then MySQL::Quoting::QUOTED_FALSE
else super
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 81de7c03fb..28f0c8686a 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -1,11 +1,9 @@
-require 'set'
-
module ActiveRecord
# :stopdoc:
module ConnectionAdapters
# An abstract definition of a column in a table.
class Column
- attr_reader :name, :null, :sql_type_metadata, :default, :default_function, :collation
+ attr_reader :name, :default, :sql_type_metadata, :null, :table_name, :default_function, :collation, :comment
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
@@ -15,14 +13,15 @@ module ActiveRecord
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
# +sql_type_metadata+ is various information about the type of the column
# +null+ determines if this column allows +NULL+ values.
- def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
+ def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil)
@name = name.freeze
+ @table_name = table_name
@sql_type_metadata = sql_type_metadata
@null = null
@default = default
@default_function = default_function
@collation = collation
- @table_name = nil
+ @comment = comment
end
def has_default?
@@ -30,7 +29,7 @@ module ActiveRecord
end
def bigint?
- /bigint/ === sql_type
+ /\Abigint\b/ === sql_type
end
# Returns the human name of the column name.
@@ -54,7 +53,7 @@ module ActiveRecord
protected
def attributes_for_hash
- [self.class, name, default, sql_type_metadata, null, default_function, collation]
+ [self.class, name, default, sql_type_metadata, null, table_name, default_function, collation]
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 f633892dee..346916337e 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -3,16 +3,20 @@ require 'uri'
module ActiveRecord
module ConnectionAdapters
class ConnectionSpecification #:nodoc:
- attr_reader :config, :adapter_method
+ attr_reader :name, :config, :adapter_method
- def initialize(config, adapter_method)
- @config, @adapter_method = config, adapter_method
+ def initialize(name, config, adapter_method)
+ @name, @config, @adapter_method = name, config, adapter_method
end
def initialize_dup(original)
@config = original.config.dup
end
+ def to_hash
+ @config.merge(name: @name)
+ end
+
# Expands a connection string into a hash.
class ConnectionUrlResolver # :nodoc:
@@ -33,7 +37,7 @@ module ActiveRecord
def initialize(url)
raise "Database URL cannot be empty" if url.blank?
@uri = uri_parser.parse(url)
- @adapter = @uri.scheme.tr('-', '_')
+ @adapter = @uri.scheme && @uri.scheme.tr('-', '_')
@adapter = "postgresql" if @adapter == "postgres"
if @uri.opaque
@@ -179,7 +183,12 @@ module ActiveRecord
end
adapter_method = "#{spec[:adapter]}_connection"
- ConnectionSpecification.new(spec, adapter_method)
+
+ unless ActiveRecord::Base.respond_to?(adapter_method)
+ raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
+ end
+
+ ConnectionSpecification.new(spec.delete(:name) || "primary", spec, adapter_method)
end
private
@@ -224,7 +233,7 @@ module ActiveRecord
#
def resolve_symbol_connection(spec)
if config = configurations[spec.to_s]
- resolve_connection(config)
+ resolve_connection(config).merge("name" => spec.to_s)
else
raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}")
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
new file mode 100644
index 0000000000..13c9b6cbd9
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -0,0 +1,125 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ module DatabaseStatements
+ # Returns an ActiveRecord::Result instance.
+ def select_all(arel, name = nil, binds = [], preparable: nil)
+ result = if ExplainRegistry.collect? && prepared_statements
+ unprepared_statement { super }
+ else
+ super
+ end
+ @connection.next_result while @connection.more_results?
+ result
+ end
+
+ # Returns a record hash with the column names as keys and column values
+ # as values.
+ def select_one(arel, name = nil, binds = [])
+ arel, binds = binds_from_relation(arel, binds)
+ @connection.query_options.merge!(as: :hash)
+ select_result(to_sql(arel, binds), name, binds) do |result|
+ @connection.next_result while @connection.more_results?
+ result.first
+ end
+ ensure
+ @connection.query_options.merge!(as: :array)
+ end
+
+ # Returns an array of arrays containing the field values.
+ # Order is the same as that returned by +columns+.
+ def select_rows(sql, name = nil, binds = [])
+ select_result(sql, name, binds) do |result|
+ @connection.next_result while @connection.more_results?
+ result.to_a
+ end
+ end
+
+ # Executes the SQL statement in the context of this connection.
+ def execute(sql, name = nil)
+ if @connection
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
+ # made since we established the connection
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
+ end
+
+ super
+ end
+
+ def exec_query(sql, name = 'SQL', binds = [], prepare: false)
+ if without_prepared_statement?(binds)
+ execute_and_free(sql, name) do |result|
+ ActiveRecord::Result.new(result.fields, result.to_a) if result
+ end
+ else
+ exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result|
+ ActiveRecord::Result.new(result.fields, result.to_a) if result
+ end
+ end
+ end
+
+ def exec_delete(sql, name, binds)
+ if without_prepared_statement?(binds)
+ execute_and_free(sql, name) { @connection.affected_rows }
+ else
+ exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
+ end
+ end
+ alias :exec_update :exec_delete
+
+ protected
+
+ def last_inserted_id(result)
+ @connection.last_id
+ end
+
+ private
+
+ def select_result(sql, name = nil, binds = [])
+ if without_prepared_statement?(binds)
+ execute_and_free(sql, name) { |result| yield result }
+ else
+ exec_stmt_and_free(sql, name, binds, cache_stmt: true) { |_, result| yield result }
+ end
+ end
+
+ def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
+ if @connection
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
+ # made since we established the connection
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
+ end
+
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
+
+ log(sql, name, binds) do
+ if cache_stmt
+ cache = @statements[sql] ||= {
+ stmt: @connection.prepare(sql)
+ }
+ stmt = cache[:stmt]
+ else
+ stmt = @connection.prepare(sql)
+ end
+
+ begin
+ result = stmt.execute(*type_casted_binds)
+ rescue Mysql2::Error => e
+ if cache_stmt
+ @statements.delete(sql)
+ else
+ stmt.close
+ end
+ raise e
+ end
+
+ ret = yield stmt, result
+ result.free if result
+ stmt.close unless cache_stmt
+ ret
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb
new file mode 100644
index 0000000000..1820853196
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb
@@ -0,0 +1,70 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ class ExplainPrettyPrinter # :nodoc:
+ # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
+ # MySQL shell:
+ #
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # 2 rows in set (0.00 sec)
+ #
+ # This is an exercise in Ruby hyperrealism :).
+ def pp(result, elapsed)
+ widths = compute_column_widths(result)
+ separator = build_separator(widths)
+
+ pp = []
+
+ pp << separator
+ pp << build_cells(result.columns, widths)
+ pp << separator
+
+ result.rows.each do |row|
+ pp << build_cells(row, widths)
+ end
+
+ pp << separator
+ pp << build_footer(result.rows.length, elapsed)
+
+ pp.join("\n") + "\n"
+ end
+
+ private
+
+ def compute_column_widths(result)
+ [].tap do |widths|
+ result.columns.each_with_index do |column, i|
+ cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
+ widths << cells_in_column.map(&:length).max
+ end
+ end
+ end
+
+ def build_separator(widths)
+ padding = 1
+ '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
+ end
+
+ def build_cells(items, widths)
+ cells = []
+ items.each_with_index do |item, i|
+ item = 'NULL' if item.nil?
+ justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
+ cells << item.to_s.send(justifier, widths[i])
+ end
+ '| ' + cells.join(' | ') + ' |'
+ end
+
+ def build_footer(nrows, elapsed)
+ rows_label = nrows == 1 ? 'row' : 'rows'
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
new file mode 100644
index 0000000000..fbab654112
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
@@ -0,0 +1,51 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ module Quoting # :nodoc:
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
+
+ def quote_column_name(name)
+ @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
+ end
+
+ def quote_table_name(name)
+ @quoted_table_names[name] ||= super.gsub('.', '`.`')
+ end
+
+ def quoted_true
+ QUOTED_TRUE
+ end
+
+ def unquoted_true
+ 1
+ end
+
+ def quoted_false
+ QUOTED_FALSE
+ end
+
+ def unquoted_false
+ 0
+ end
+
+ def quoted_date(value)
+ if supports_datetime_with_precision?
+ super
+ else
+ super.sub(/\.\d{6}\z/, '')
+ end
+ end
+
+ private
+
+ def _quote(value)
+ if value.is_a?(Type::Binary::Data)
+ "x'#{value.hex}'"
+ else
+ super
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
index 1e2c859af9..fd2dc2aee8 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
@@ -2,6 +2,9 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
class SchemaCreation < AbstractAdapter::SchemaCreation
+ delegate :add_sql_comment!, to: :@conn
+ private :add_sql_comment!
+
private
def visit_DropForeignKey(name)
@@ -22,6 +25,10 @@ module ActiveRecord
add_column_position!(change_column_sql, column_options(o.column))
end
+ def add_table_options!(create_sql, options)
+ add_sql_comment!(super, options[:comment])
+ end
+
def column_options(o)
column_options = super
column_options[:charset] = o.charset
@@ -29,13 +36,15 @@ module ActiveRecord
end
def add_column_options!(sql, options)
- if options[:charset]
- sql << " CHARACTER SET #{options[:charset]}"
+ if charset = options[:charset]
+ sql << " CHARACTER SET #{charset}"
end
- if options[:collation]
- sql << " COLLATE #{options[:collation]}"
+
+ if collation = options[:collation]
+ sql << " COLLATE #{collation}"
end
- super
+
+ add_sql_comment!(super, options[:comment])
end
def add_column_position!(sql, options)
@@ -44,12 +53,13 @@ module ActiveRecord
elsif options[:after]
sql << " AFTER #{quote_column_name(options[:after])}"
end
+
sql
end
def index_in_create(table_name, column_name, options)
- index_name, index_type, index_columns, _, _, index_using = @conn.add_index_options(table_name, column_name, options)
- "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns}) "
+ index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options)
+ add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})", comment)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
index 9dee3172f4..2ba9657f24 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -3,18 +3,13 @@ module ActiveRecord
module MySQL
module ColumnDumper
def column_spec_for_primary_key(column)
- spec = {}
if column.bigint?
- spec[:id] = ':bigint'
+ spec = { id: :bigint.inspect }
spec[:default] = schema_default(column) || 'nil' unless column.auto_increment?
- spec[:unsigned] = 'true' if column.unsigned?
- elsif column.auto_increment?
- spec[:unsigned] = 'true' if column.unsigned?
- return if spec.empty?
else
- spec[:id] = column.type.inspect
- spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
+ spec = super
end
+ spec[:unsigned] = 'true' if column.unsigned?
spec
end
@@ -30,24 +25,24 @@ module ActiveRecord
private
+ def default_primary_key?(column)
+ super && column.auto_increment?
+ end
+
def schema_type(column)
if column.sql_type == 'tinyblob'
- 'blob'
+ :blob
else
super
end
end
- def schema_limit(column)
- super unless column.type == :boolean
- end
-
def schema_precision(column)
super unless /time/ === column.sql_type && column.precision == 0
end
def schema_collation(column)
- if column.collation && table_name = column.instance_variable_get(:@table_name)
+ if column.collation && table_name = column.table_name
@table_collation_cache ||= {}
@table_collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"]
column.collation.inspect if column.collation != @table_collation_cache[table_name]
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index c3c5b660fd..22d35f1db5 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,7 +1,9 @@
require 'active_record/connection_adapters/abstract_mysql_adapter'
+require 'active_record/connection_adapters/mysql/database_statements'
gem 'mysql2', '>= 0.3.18', '< 0.5'
require 'mysql2'
+raise 'mysql2 0.4.3 is not supported. Please upgrade to 0.4.4+' if Mysql2::VERSION == '0.4.3'
module ActiveRecord
module ConnectionHandling # :nodoc:
@@ -16,7 +18,7 @@ module ActiveRecord
if config[:flags].kind_of? Array
config[:flags].push "FOUND_ROWS".freeze
else
- config[:flags] |= Mysql2::Client::FOUND_ROWS
+ config[:flags] |= Mysql2::Client::FOUND_ROWS
end
end
@@ -35,14 +37,28 @@ module ActiveRecord
class Mysql2Adapter < AbstractMysqlAdapter
ADAPTER_NAME = 'Mysql2'.freeze
+ include MySQL::DatabaseStatements
+
def initialize(connection, logger, connection_options, config)
super
- @prepared_statements = false
+ @prepared_statements = false unless config.key?(:prepared_statements)
configure_connection
end
def supports_json?
- version >= '5.7.8'
+ !mariadb? && version >= '5.7.8'
+ end
+
+ def supports_comments?
+ true
+ end
+
+ def supports_comments_in_create?
+ true
+ end
+
+ def supports_savepoints?
+ true
end
# HELPER METHODS ===========================================
@@ -95,61 +111,6 @@ module ActiveRecord
end
end
- #--
- # DATABASE STATEMENTS ======================================
- #++
-
- # Returns a record hash with the column names as keys and column values
- # as values.
- def select_one(arel, name = nil, binds = [])
- arel, binds = binds_from_relation(arel, binds)
- execute(to_sql(arel, binds), name).each(as: :hash) do |row|
- @connection.next_result while @connection.more_results?
- return row
- end
- end
-
- # Returns an array of arrays containing the field values.
- # Order is the same as that returned by +columns+.
- def select_rows(sql, name = nil, binds = [])
- result = execute(sql, name)
- @connection.next_result while @connection.more_results?
- result.to_a
- end
-
- # Executes the SQL statement in the context of this connection.
- def execute(sql, name = nil)
- if @connection
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
- # made since we established the connection
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
- end
-
- super
- end
-
- def exec_query(sql, name = 'SQL', binds = [], prepare: false)
- result = execute(sql, name)
- @connection.next_result while @connection.more_results?
- ActiveRecord::Result.new(result.fields, result.to_a)
- end
-
- alias exec_without_stmt exec_query
-
- def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
- execute to_sql(sql, binds), name
- end
-
- def exec_delete(sql, name, binds)
- execute to_sql(sql, binds), name
- @connection.affected_rows
- end
- alias :exec_update :exec_delete
-
- def last_inserted_id(result)
- @connection.last_id
- end
-
private
def connect
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index bfa03fa136..3ad1911a28 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -8,7 +8,6 @@ module ActiveRecord
def serial?
return unless default_function
- table_name = @table_name || '(?<table_name>.+)'
%r{\Anextval\('"?#{table_name}_#{name}_seq"?'::regclass\)\z} === default_function
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 6c15facf3b..6f2e03b370 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -4,44 +4,7 @@ module ActiveRecord
module DatabaseStatements
def explain(arel, binds = [])
sql = "EXPLAIN #{to_sql(arel, binds)}"
- ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
- end
-
- class ExplainPrettyPrinter # :nodoc:
- # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
- # PostgreSQL shell:
- #
- # QUERY PLAN
- # ------------------------------------------------------------------------------
- # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
- # Join Filter: (posts.user_id = users.id)
- # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
- # Index Cond: (id = 1)
- # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
- # Filter: (posts.user_id = 1)
- # (6 rows)
- #
- def pp(result)
- header = result.columns.first
- lines = result.rows.map(&:first)
-
- # We add 2 because there's one char of padding at both sides, note
- # the extra hyphens in the example above.
- width = [header, *lines].map(&:length).max + 2
-
- pp = []
-
- pp << header.center(width).rstrip
- pp << '-' * width
-
- pp += lines.map {|line| " #{line}"}
-
- nrows = result.rows.length
- rows_label = nrows == 1 ? 'row' : 'rows'
- pp << "(#{nrows} #{rows_label})"
-
- pp.join("\n") + "\n"
- end
+ PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
end
def select_value(arel, name = nil, binds = [])
@@ -128,6 +91,8 @@ module ActiveRecord
# Executes an SQL statement, returning a PGresult object on success
# or raising a PGError exception otherwise.
+ # Note: the PGresult object is manually memory managed; if you don't
+ # need it specifically, you many want consider the exec_query wrapper.
def execute(sql, name = nil)
log(sql, name) do
@connection.async_exec(sql)
@@ -153,7 +118,7 @@ module ActiveRecord
alias :exec_update :exec_delete
def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
- unless pk
+ if pk.nil?
# Extract the table from the insert sql. Yuck.
table_ref = extract_table_ref_from_insert_sql(sql)
pk = primary_key(table_ref) if table_ref
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb
new file mode 100644
index 0000000000..789b88912c
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb
@@ -0,0 +1,42 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ class ExplainPrettyPrinter # :nodoc:
+ # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
+ # PostgreSQL shell:
+ #
+ # QUERY PLAN
+ # ------------------------------------------------------------------------------
+ # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
+ # Join Filter: (posts.user_id = users.id)
+ # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
+ # Index Cond: (id = 1)
+ # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
+ # Filter: (posts.user_id = 1)
+ # (6 rows)
+ #
+ def pp(result)
+ header = result.columns.first
+ lines = result.rows.map(&:first)
+
+ # We add 2 because there's one char of padding at both sides, note
+ # the extra hyphens in the example above.
+ width = [header, *lines].map(&:length).max + 2
+
+ pp = []
+
+ pp << header.center(width).rstrip
+ pp << '-' * width
+
+ pp += lines.map {|line| " #{line}"}
+
+ nrows = result.rows.length
+ rows_label = nrows == 1 ? 'row' : 'rows'
+ pp << "(#{nrows} #{rows_label})"
+
+ pp.join("\n") + "\n"
+ end
+ end
+ end
+ 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 eeccb09bdf..838cb63281 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
@@ -1,3 +1,5 @@
+require 'ipaddr'
+
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
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 2163674019..dcc12ae2a4 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:
- class_attribute :precision
-
def type
:money
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
index 7427a25ad5..4da240edb2 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
@@ -14,6 +14,8 @@ module ActiveRecord
def cast(value)
case value
when ::String
+ return if value.blank?
+
if value[0] == '(' && value[-1] == ')'
value = value[1...-1]
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index c1c77a967e..6414459cd1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -27,8 +27,8 @@ module ActiveRecord
# - schema_name."table.name"
# - "schema.name".table_name
# - "schema.name"."table.name"
- def quote_table_name(name)
- Utils.extract_schema_qualified_name(name.to_s).quoted
+ def quote_table_name(name) # :nodoc:
+ @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted
end
# Quotes schema names for use in SQL queries.
@@ -41,8 +41,8 @@ module ActiveRecord
end
# Quotes column names for use in SQL queries.
- def quote_column_name(name) #:nodoc:
- PGconn.quote_ident(name.to_s)
+ def quote_column_name(name) # :nodoc:
+ @quoted_column_names[name] ||= PGconn.quote_ident(super)
end
# Quote date/time values for use in SQL input.
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
index cc7721ddd8..a1e10fd364 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
@@ -3,16 +3,9 @@ module ActiveRecord
module PostgreSQL
module ColumnDumper
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] = schema_default(column) || 'nil'
- else
- spec[:id] = column.type.inspect
- spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
+ spec = super
+ if schema_type(column) == :uuid
+ spec[:default] ||= 'nil'
end
spec
end
@@ -31,13 +24,17 @@ module ActiveRecord
private
+ def default_primary_key?(column)
+ schema_type(column) == :serial
+ end
+
def schema_type(column)
return super unless column.serial?
if column.bigint?
- 'bigserial'
+ :bigserial
else
- 'serial'
+ :serial
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 67e727d8ed..f6860b9aba 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/string/strip'
+
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
@@ -172,7 +174,11 @@ module ActiveRecord
table = Utils.extract_schema_qualified_name(table_name.to_s)
result = query(<<-SQL, 'SCHEMA')
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment,
+ (SELECT COUNT(*) FROM pg_opclass o
+ JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c
+ ON o.oid = c.oid WHERE o.opcdefault = 'f')
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -190,43 +196,61 @@ module ActiveRecord
indkey = row[2].split(" ").map(&:to_i)
inddef = row[3]
oid = row[4]
+ comment = row[5]
+ opclass = row[6]
- columns = Hash[query(<<-SQL, "SCHEMA")]
- SELECT a.attnum, a.attname
- FROM pg_attribute a
- WHERE a.attrelid = #{oid}
- AND a.attnum IN (#{indkey.join(",")})
- SQL
+ using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
- column_names = columns.values_at(*indkey).compact
+ if indkey.include?(0) || opclass > 0
+ columns = expressions
+ else
+ columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
+ SELECT a.attnum, a.attname
+ FROM pg_attribute a
+ WHERE a.attrelid = #{oid}
+ AND a.attnum IN (#{indkey.join(",")})
+ SQL
- unless column_names.empty?
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
- desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
- orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
- where = inddef.scan(/WHERE (.+)$/).flatten[0]
- using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
-
- IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
+ orders = Hash[
+ expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
+ ]
end
+
+ IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence)
end.compact
end
# Returns the list of all column definitions for a table.
- def columns(table_name)
- # Limit, precision, and scale are all handled by the superclass.
- column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation|
+ def columns(table_name) # :nodoc:
+ table_name = table_name.to_s
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation, comment|
oid = oid.to_i
fmod = fmod.to_i
type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
default_value = extract_value_from_default(default)
default_function = extract_default_function(default_value, default)
- new_column(column_name, default_value, type_metadata, !notnull, default_function, collation)
+ new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation, comment: comment.presence)
end
end
- def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
- PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function, collation)
+ def new_column(*args) # :nodoc:
+ PostgreSQLColumn.new(*args)
+ end
+
+ # Returns a comment stored in database for given table
+ def table_comment(table_name) # :nodoc:
+ name = Utils.extract_schema_qualified_name(table_name.to_s)
+ if name.identifier
+ select_value(<<-SQL.strip_heredoc, 'SCHEMA')
+ SELECT pg_catalog.obj_description(c.oid, 'pg_class')
+ FROM pg_catalog.pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relname = #{quote(name.identifier)}
+ AND c.relkind IN ('r') -- (r)elation/table
+ AND n.nspname = #{name.schema ? quote(name.schema) : 'ANY (current_schemas(false))'}
+ SQL
+ end
end
# Returns the current database name.
@@ -325,7 +349,7 @@ module ActiveRecord
select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA')
else
- @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
+ @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
end
end
end
@@ -340,7 +364,7 @@ module ActiveRecord
end
if @logger && pk && !sequence
- @logger.warn "#{table} has primary key #{pk} with no default sequence"
+ @logger.warn "#{table} has primary key #{pk} with no default sequence."
end
if pk && sequence
@@ -445,6 +469,7 @@ module ActiveRecord
def add_column(table_name, column_name, type, options = {}) #:nodoc:
clear_cache!
super
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
@@ -466,6 +491,7 @@ module ActiveRecord
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)
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
end
# Changes the default value of a table column.
@@ -494,6 +520,18 @@ module ActiveRecord
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
end
+ # Adds comment for given table column or drops it if +comment+ is a +nil+
+ def change_column_comment(table_name, column_name, comment) # :nodoc:
+ clear_cache!
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
+ end
+
+ # Adds comment for given table or drops it if +comment+ is a +nil+
+ def change_table_comment(table_name, comment) # :nodoc:
+ clear_cache!
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
+ end
+
# Renames a column in a table.
def rename_column(table_name, column_name, new_column_name) #:nodoc:
clear_cache!
@@ -502,8 +540,10 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
+ execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}").tap do
+ execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
+ end
end
def remove_index(table_name, options = {}) #:nodoc:
@@ -601,7 +641,7 @@ module ActiveRecord
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.")
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.")
end
else
super(type, limit, precision, scale)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 2de6fbfaf0..ddfc560747 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -5,6 +5,7 @@ require 'pg'
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/explain_pretty_printer"
require "active_record/connection_adapters/postgresql/oid"
require "active_record/connection_adapters/postgresql/quoting"
require "active_record/connection_adapters/postgresql/referential_integrity"
@@ -15,8 +16,6 @@ require "active_record/connection_adapters/postgresql/type_metadata"
require "active_record/connection_adapters/postgresql/utils"
require "active_record/connection_adapters/statement_pool"
-require 'ipaddr'
-
module ActiveRecord
module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects
@@ -118,12 +117,15 @@ module ActiveRecord
include PostgreSQL::SchemaStatements
include PostgreSQL::DatabaseStatements
include PostgreSQL::ColumnDumper
- include Savepoints
def schema_creation # :nodoc:
PostgreSQL::SchemaCreation.new self
end
+ def arel_visitor # :nodoc:
+ Arel::Visitors::PostgreSQL.new(self)
+ end
+
# Returns true, since this connection adapter supports prepared statement
# caching.
def supports_statement_cache?
@@ -138,6 +140,10 @@ module ActiveRecord
true
end
+ def supports_expression_index?
+ true
+ end
+
def supports_transaction_isolation?
true
end
@@ -158,6 +164,14 @@ module ActiveRecord
postgresql_version >= 90200
end
+ def supports_comments?
+ true
+ end
+
+ def supports_savepoints?
+ true
+ end
+
def index_algorithms
{ concurrently: 'CONCURRENTLY' }
end
@@ -194,14 +208,6 @@ module ActiveRecord
def initialize(connection, logger, connection_parameters, config)
super(connection, logger, config)
- @visitor = Arel::Visitors::PostgreSQL.new self
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
- @prepared_statements = true
- @visitor.extend(DetermineIfPreparableVisitor)
- else
- @prepared_statements = false
- end
-
@connection_parameters = connection_parameters
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@@ -211,10 +217,10 @@ module ActiveRecord
connect
add_pg_encoders
@statements = StatementPool.new @connection,
- self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
+ self.class.type_cast_config_to_integer(config[:statement_limit])
- if postgresql_version < 80200
- raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
+ if postgresql_version < 90100
+ raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
end
add_pg_decoders
@@ -296,9 +302,8 @@ module ActiveRecord
true
end
- # Returns true if pg > 9.1
def supports_extensions?
- postgresql_version >= 90100
+ true
end
# Range datatypes weren't introduced until PostgreSQL 9.2
@@ -398,8 +403,10 @@ module ActiveRecord
protected
# See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
+ VALUE_LIMIT_VIOLATION = "22001"
FOREIGN_KEY_VIOLATION = "23503"
UNIQUE_VIOLATION = "23505"
+ SERIALIZATION_FAILURE = "40001"
def translate_exception(exception, message)
return exception unless exception.respond_to?(:result)
@@ -409,6 +416,10 @@ module ActiveRecord
RecordNotUnique.new(message)
when FOREIGN_KEY_VIOLATION
InvalidForeignKey.new(message)
+ when VALUE_LIMIT_VIOLATION
+ ValueTooLong.new(message)
+ when SERIALIZATION_FAILURE
+ TransactionSerializationError.new(message)
else
super
end
@@ -598,25 +609,41 @@ module ActiveRecord
@connection.exec_prepared(stmt_key, type_casted_binds)
end
rescue ActiveRecord::StatementInvalid => e
- pgerror = e.cause
+ raise unless is_cached_plan_failure?(e)
- # Get the PG code for the failure. Annoyingly, the code for
- # prepared statements whose return value may have changed is
- # FEATURE_NOT_SUPPORTED. Check here for more details:
- # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
- begin
- code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
- rescue
- raise e
- end
- if FEATURE_NOT_SUPPORTED == code
+ # Nothing we can do if we are in a transaction because all commands
+ # will raise InFailedSQLTransaction
+ if in_transaction?
+ raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
+ else
+ # outside of transactions we can simply flush this query and retry
@statements.delete sql_key(sql)
retry
- else
- raise e
end
end
+ # Annoyingly, the code for prepared statements whose return value may
+ # have changed is FEATURE_NOT_SUPPORTED.
+ #
+ # This covers various different error types so we need to do additional
+ # work to classify the exception definitively as a
+ # ActiveRecord::PreparedStatementCacheExpired
+ #
+ # Check here for more details:
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
+ CACHED_PLAN_HEURISTIC = 'cached plan must not change result type'.freeze
+ def is_cached_plan_failure?(e)
+ pgerror = e.cause
+ code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
+ rescue
+ false
+ end
+
+ def in_transaction?
+ open_transactions > 0
+ end
+
# Returns the statement identifier for the client side cache
# of statements
def sql_key(sql)
@@ -645,12 +672,6 @@ module ActiveRecord
# connected server's characteristics.
def connect
@connection = PGconn.connect(@connection_parameters)
-
- # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
- # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
- # should know about this but can't detect it there, so deal with it here.
- OID::Money.precision = (postgresql_version >= 80300) ? 19 : 10
-
configure_connection
rescue ::PG::Error => error
if error.message.include?("does not exist")
@@ -702,7 +723,7 @@ module ActiveRecord
# Returns the list of a table's column names, data types, and default values.
#
# The underlying query is roughly:
- # SELECT column.name, column.type, default.value
+ # SELECT column.name, column.type, default.value, column.comment
# FROM column LEFT JOIN default
# ON column.table_id = default.table_id
# AND column.num = default.column_num
@@ -722,7 +743,8 @@ module ActiveRecord
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
(SELECT c.collname FROM pg_collation c, pg_type t
- WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation)
+ WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation),
+ col_description(a.attrelid, a.attnum) AS comment
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
@@ -736,8 +758,8 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
- PostgreSQL::TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(*args) # :nodoc:
+ PostgreSQL::TableDefinition.new(*args)
end
def can_perform_case_insensitive_comparison_for?(column)
@@ -805,7 +827,7 @@ module ActiveRecord
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(:datetime, 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)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb
new file mode 100644
index 0000000000..a946f5ebd0
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb
@@ -0,0 +1,19 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module SQLite3
+ class ExplainPrettyPrinter # :nodoc:
+ # Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles
+ # the output of the SQLite shell:
+ #
+ # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
+ # 0|1|1|SCAN TABLE posts (~100000 rows)
+ #
+ def pp(result)
+ result.rows.map do |row|
+ row.join('|')
+ end.join("\n") + "\n"
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
new file mode 100644
index 0000000000..d5a181d3e2
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -0,0 +1,48 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module SQLite3
+ module Quoting # :nodoc:
+ def quote_string(s)
+ @connection.class.quote(s)
+ end
+
+ def quote_table_name_for_assignment(table, attr)
+ quote_column_name(attr)
+ end
+
+ def quote_column_name(name)
+ @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
+ end
+
+ def quoted_time(value)
+ quoted_date(value)
+ end
+
+ private
+
+ def _quote(value)
+ if value.is_a?(Type::Binary::Data)
+ "x'#{value.hex}'"
+ else
+ super
+ end
+ end
+
+ def _type_cast(value)
+ 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
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
index fe1dcbd710..70c0d28830 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
@@ -3,6 +3,13 @@ module ActiveRecord
module SQLite3
class SchemaCreation < AbstractAdapter::SchemaCreation
private
+
+ def column_options(o)
+ options = super
+ options[:null] = false if o.primary_key
+ options
+ end
+
def add_column_options!(sql, options)
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index d1893f35f5..eb2268157b 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,5 +1,7 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/connection_adapters/statement_pool'
+require 'active_record/connection_adapters/sqlite3/explain_pretty_printer'
+require 'active_record/connection_adapters/sqlite3/quoting'
require 'active_record/connection_adapters/sqlite3/schema_creation'
gem 'sqlite3', '~> 1.3.6'
@@ -7,7 +9,6 @@ require 'sqlite3'
module ActiveRecord
module ConnectionHandling # :nodoc:
- # sqlite3 adapter reuses sqlite_connection.
def sqlite3_connection(config)
# Require database.
unless config[:database]
@@ -49,7 +50,8 @@ module ActiveRecord
# * <tt>:database</tt> - Path to the database file.
class SQLite3Adapter < AbstractAdapter
ADAPTER_NAME = 'SQLite'.freeze
- include Savepoints
+
+ include SQLite3::Quoting
NATIVE_DATABASE_TYPES = {
primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
@@ -77,21 +79,15 @@ module ActiveRecord
SQLite3::SchemaCreation.new self
end
+ def arel_visitor # :nodoc:
+ Arel::Visitors::SQLite.new(self)
+ end
+
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
@active = nil
- @statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
-
- @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
- @visitor.extend(DetermineIfPreparableVisitor)
- else
- @prepared_statements = false
- end
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
end
def supports_ddl_transactions?
@@ -133,6 +129,10 @@ module ActiveRecord
true
end
+ def supports_multi_insert?
+ sqlite_version >= '3.7.11'
+ end
+
def active?
@active != false
end
@@ -154,6 +154,10 @@ module ActiveRecord
true
end
+ def valid_type?(type)
+ true
+ end
+
# Returns 62. SQLite supports index names up to 64
# characters. The rest is used by rails internally to perform
# temporary rename operations
@@ -174,65 +178,13 @@ module ActiveRecord
true
end
- # QUOTING ==================================================
-
- def _quote(value) # :nodoc:
- case value
- when Type::Binary::Data
- "x'#{value.hex}'"
- else
- super
- end
- end
-
- def _type_cast(value) # :nodoc:
- 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
- end
-
- def quote_string(s) #:nodoc:
- @connection.class.quote(s)
- end
-
- def quote_table_name_for_assignment(table, attr)
- quote_column_name(attr)
- end
-
- def quote_column_name(name) #:nodoc:
- @quoted_column_names[name] ||= %Q("#{name.to_s.gsub('"', '""')}")
- end
-
#--
# DATABASE STATEMENTS ======================================
#++
def explain(arel, binds = [])
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
- ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', []))
- end
-
- class ExplainPrettyPrinter
- # Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles
- # the output of the SQLite shell:
- #
- # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
- # 0|1|1|SCAN TABLE posts (~100000 rows)
- #
- def pp(result) # :nodoc:
- result.rows.map do |row|
- row.join('|')
- end.join("\n") + "\n"
- end
+ SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', []))
end
def exec_query(sql, name = nil, binds = [], prepare: false)
@@ -280,10 +232,6 @@ module ActiveRecord
log(sql, name) { @connection.execute(sql) }
end
- def select_rows(sql, name = nil, binds = [])
- exec_query(sql, name, binds).rows
- end
-
def begin_db_transaction #:nodoc:
log('begin transaction',nil) { @connection.transaction }
end
@@ -351,7 +299,8 @@ module ActiveRecord
end
# Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name) #:nodoc:
+ def columns(table_name) # :nodoc:
+ table_name = table_name.to_s
table_structure(table_name).map do |field|
case field["dflt_value"]
when /^null$/i
@@ -365,7 +314,7 @@ module ActiveRecord
collation = field['collation']
sql_type = field['type']
type_metadata = fetch_type_metadata(sql_type)
- new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, nil, collation)
+ new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, table_name, nil, collation)
end
end
@@ -592,7 +541,7 @@ module ActiveRecord
result = exec_query(sql, 'SCHEMA').first
if result
- # Splitting with left parantheses and picking up last will return all
+ # Splitting with left parentheses and picking up last will return all
# columns separated with comma(,).
columns_string = result["sql"].split('(').last
diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
index 57463dd749..9b0ed3e08b 100644
--- a/activerecord/lib/active_record/connection_adapters/statement_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
@@ -1,11 +1,13 @@
module ActiveRecord
module ConnectionAdapters
- class StatementPool
+ class StatementPool # :nodoc:
include Enumerable
- def initialize(max = 1000)
+ DEFAULT_STATEMENT_LIMIT = 1000
+
+ def initialize(statement_limit = nil)
@cache = Hash.new { |h,pid| h[pid] = {} }
- @max = max
+ @statement_limit = statement_limit || DEFAULT_STATEMENT_LIMIT
end
def each(&block)
@@ -25,7 +27,7 @@ module ActiveRecord
end
def []=(sql, stmt)
- while @max <= cache.size
+ while @statement_limit <= cache.size
dealloc(cache.shift.last)
end
cache[sql] = stmt
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index a8b3d03ba5..086c678af5 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -44,17 +44,18 @@ module ActiveRecord
#
# The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
# may be returned on an error.
- def establish_connection(spec = nil)
- spec ||= DEFAULT_ENV.call.to_sym
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
- spec = resolver.spec(spec)
+ def establish_connection(config = nil)
+ raise "Anonymous class is not allowed." unless name
- unless respond_to?(spec.adapter_method)
- raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
- end
+ config ||= DEFAULT_ENV.call.to_sym
+ spec_name = self == Base ? "primary" : name
+ self.connection_specification_name = spec_name
+
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
+ spec = resolver.resolve(config).symbolize_keys
+ spec[:name] = spec_name
- remove_connection
- connection_handler.establish_connection self, spec
+ connection_handler.establish_connection(spec)
end
class MergeAndResolveDefaultUrlConfig # :nodoc:
@@ -87,6 +88,16 @@ module ActiveRecord
retrieve_connection
end
+ attr_writer :connection_specification_name
+
+ # Return the specification name from the current class or its parent.
+ def connection_specification_name
+ if !defined?(@connection_specification_name) || @connection_specification_name.nil?
+ return self == Base ? "primary" : superclass.connection_specification_name
+ end
+ @connection_specification_name
+ end
+
def connection_id
ActiveRecord::RuntimeRegistry.connection_id ||= Thread.current.object_id
end
@@ -106,20 +117,28 @@ module ActiveRecord
end
def connection_pool
- connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
+ connection_handler.retrieve_connection_pool(connection_specification_name) or raise ConnectionNotEstablished
end
def retrieve_connection
- connection_handler.retrieve_connection(self)
+ connection_handler.retrieve_connection(connection_specification_name)
end
# Returns +true+ if Active Record is connected.
def connected?
- connection_handler.connected?(self)
+ connection_handler.connected?(connection_specification_name)
end
- def remove_connection(klass = self)
- connection_handler.remove_connection(klass)
+ def remove_connection(name = nil)
+ name ||= @connection_specification_name if defined?(@connection_specification_name)
+ # if removing a connection that have a pool, we reset the
+ # connection_specification_name so it will use the parent
+ # pool.
+ if connection_handler.retrieve_connection_pool(name)
+ self.connection_specification_name = nil
+ end
+
+ connection_handler.remove_connection(name)
end
def clear_cache! # :nodoc:
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 475a298467..de337b24d6 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -72,6 +72,14 @@ module ActiveRecord
##
# :singleton-method:
+ # Specifies if an error should be raised on query limit or order being
+ # ignored when doing batch queries. Useful in applications where the
+ # limit or scope being ignored is error-worthy, rather than a warning.
+ mattr_accessor :error_on_ignored_order_or_limit, instance_writer: false
+ self.error_on_ignored_order_or_limit = false
+
+ ##
+ # :singleton-method:
# Specify whether or not to use timestamps for migration versions
mattr_accessor :timestamped_migrations, instance_writer: false
self.timestamped_migrations = true
@@ -128,7 +136,7 @@ module ActiveRecord
end
def initialize_find_by_cache # :nodoc:
- @find_by_statement_cache = {}.extend(Mutex_m)
+ @find_by_statement_cache = { true => {}.extend(Mutex_m), false => {}.extend(Mutex_m) }
end
def inherited(child_class) # :nodoc:
@@ -151,7 +159,7 @@ module ActiveRecord
id = id.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `find`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
@@ -249,13 +257,18 @@ module ActiveRecord
# Returns the Arel engine.
def arel_engine # :nodoc:
@arel_engine ||=
- if Base == self || connection_handler.retrieve_connection_pool(self)
+ if Base == self || connection_handler.retrieve_connection_pool(connection_specification_name)
self
else
superclass.arel_engine
end
end
+ def arel_attribute(name, table = arel_table) # :nodoc:
+ name = attribute_alias(name) if attribute_alias?(name)
+ table[name]
+ end
+
def predicate_builder # :nodoc:
@predicate_builder ||= PredicateBuilder.new(table_metadata)
end
@@ -267,8 +280,9 @@ module ActiveRecord
private
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)
+ cache = @find_by_statement_cache[connection.prepared_statements]
+ cache[key] || cache.synchronize {
+ cache[key] ||= StatementCache.create(connection, &block)
}
end
@@ -324,7 +338,7 @@ module ActiveRecord
# post.title # => 'hello world'
def init_with(coder)
coder = LegacyYamlAdapter.convert(self.class, coder)
- @attributes = coder['attributes']
+ @attributes = self.class.yaml_encoder.decode(coder)
init_internals
@@ -390,11 +404,9 @@ module ActiveRecord
# Post.new.encode_with(coder)
# coder # => {"attributes" => {"id" => nil, ... }}
def encode_with(coder)
- # FIXME: Remove this when we better serialize attributes
- coder['raw_attributes'] = attributes_before_type_cast
- coder['attributes'] = @attributes
+ self.class.yaml_encoder.encode(@attributes, coder)
coder['new_record'] = new_record?
- coder['active_record_yaml_version'] = 1
+ coder['active_record_yaml_version'] = 2
end
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
@@ -418,7 +430,7 @@ module ActiveRecord
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
def hash
if id
- id.hash
+ [self.class, id].hash
else
super
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 903c63a7db..7be332fb97 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -152,7 +152,7 @@ module ActiveRecord
enum_values = ActiveSupport::HashWithIndifferentAccess.new
name = name.to_sym
- # def self.statuses statuses end
+ # def self.statuses() statuses end
detect_enum_conflict!(name, name.to_s.pluralize, true)
klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values }
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index e5906b6756..38e4fbec8b 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -125,6 +125,10 @@ module ActiveRecord
class InvalidForeignKey < WrappedDatabaseException
end
+ # Raised when a record cannot be inserted or updated because a value too long for a column type.
+ class ValueTooLong < StatementInvalid
+ end
+
# Raised when number of bind variables in statement given to +:condition+ key
# (for example, when using {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method)
# does not match number of expected values supplied.
@@ -139,6 +143,11 @@ module ActiveRecord
class NoDatabaseError < StatementInvalid
end
+ # Raised when Postgres returns 'cached plan must not change result type' and
+ # we cannot retry gracefully (e.g. inside a transaction)
+ class PreparedStatementCacheExpired < StatementInvalid
+ end
+
# Raised on attempt to save stale record. Record is stale when it's being saved in another query after
# instantiation, for example, when two users edit the same wiki page and one starts editing and saves
# the page before the other.
@@ -275,4 +284,19 @@ module ActiveRecord
# The mysql2 and postgresql adapters support setting the transaction isolation level.
class TransactionIsolationError < ActiveRecordError
end
+
+ # TransactionSerializationError will be raised when a transaction is rolled
+ # back by the database due to a serialization failure or a deadlock.
+ #
+ # See the following:
+ #
+ # * http://www.postgresql.org/docs/current/static/transaction-iso.html
+ # * https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html#error_er_lock_deadlock
+ class TransactionSerializationError < ActiveRecordError
+ end
+
+ # IrreversibleOrderError is raised when a relation's order is too complex for
+ # +reverse_order+ to automatically reverse.
+ class IrreversibleOrderError < ActiveRecordError
+ end
end
diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index f969556c50..e4a44244e2 100644
--- a/activerecord/lib/active_record/fixture_set/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -52,9 +52,15 @@ module ActiveRecord
end
end
+ def prepare_erb(content)
+ erb = ERB.new(content)
+ erb.filename = @file
+ erb
+ end
+
def render(content)
context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new
- ERB.new(content).result(context.get_binding)
+ prepare_erb(content).result(context.get_binding)
end
# Validate our unmarshalled data.
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index ed1bbf5dcd..51bf12d0bf 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -66,7 +66,7 @@ module ActiveRecord
# By default, +test_helper.rb+ will load all of your fixtures into your test
# database, so this test will succeed.
#
- # The testing environment will automatically load the all fixtures into the database before each
+ # The testing environment will automatically load all the fixtures into the database before each
# test. To ensure consistent data, the environment deletes the fixtures before running the load.
#
# In addition to being available in the database, the fixture's data may also be accessed by
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index b4f2f66e1c..f33456a744 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -6,9 +6,9 @@ module ActiveRecord
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta1.1"
+ 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 3b6fb70d0d..899683ee4f 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -192,7 +192,7 @@ module ActiveRecord
end
def type_condition(table = arel_table)
- sti_column = table[inheritance_column]
+ sti_column = arel_attribute(inheritance_column, table)
sti_names = ([self] + descendants).map(&:sti_name)
sti_column.in(sti_names)
diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb
index 10fee4dca2..17a5dc1d1b 100644
--- a/activerecord/lib/active_record/internal_metadata.rb
+++ b/activerecord/lib/active_record/internal_metadata.rb
@@ -5,10 +5,6 @@ module ActiveRecord
# This class is used to create a table that keeps track of values and keys such
# as which environment migrations were run in.
class InternalMetadata < ActiveRecord::Base # :nodoc:
- # Keys in mysql are limited to 191 characters, due to this no adapter can
- # use a longer key
- KEY_LIMIT = 191
-
class << self
def primary_key
"key"
@@ -18,12 +14,12 @@ module ActiveRecord
"#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}"
end
- def index_name
- "#{table_name_prefix}unique_#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}"
+ def original_table_name
+ "#{table_name_prefix}active_record_internal_metadatas#{table_name_suffix}"
end
def []=(key, value)
- first_or_initialize(key: key).update_attributes!(value: value)
+ find_or_initialize_by(key: key).update_attributes!(value: value)
end
def [](key)
@@ -34,14 +30,23 @@ module ActiveRecord
ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) }
end
+ def original_table_exists?
+ # This method will be removed in Rails 5.1
+ # Since it is only necessary when `active_record_internal_metadatas` could exist
+ ActiveSupport::Deprecation.silence { connection.table_exists?(original_table_name) }
+ end
+
# Creates an internal metadata table with columns +key+ and +value+
def create_table
+ if original_table_exists?
+ connection.rename_table(original_table_name, table_name)
+ end
unless table_exists?
- connection.create_table(table_name, id: false) do |t|
- t.column :key, :string, null: false, limit: KEY_LIMIT
- t.column :value, :string
- t.index :key, unique: true, name: index_name
+ key_options = connection.internal_string_options_for_primary_key
+ connection.create_table(table_name, id: false) do |t|
+ t.string :key, key_options
+ t.string :value
t.timestamps
end
end
diff --git a/activerecord/lib/active_record/legacy_yaml_adapter.rb b/activerecord/lib/active_record/legacy_yaml_adapter.rb
index 89dee58423..c7683f68c7 100644
--- a/activerecord/lib/active_record/legacy_yaml_adapter.rb
+++ b/activerecord/lib/active_record/legacy_yaml_adapter.rb
@@ -4,7 +4,7 @@ module ActiveRecord
return coder unless coder.is_a?(Psych::Coder)
case coder["active_record_yaml_version"]
- when 1 then coder
+ when 1, 2 then coder
else
if coder["attributes"].is_a?(AttributeSet)
Rails420.convert(klass, coder)
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 2336d23a1c..67b8efac66 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -150,7 +150,7 @@ module ActiveRecord
# The version column used for optimistic locking. Defaults to +lock_version+.
def locking_column
- reset_locking_column unless defined?(@locking_column)
+ @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
@locking_column
end
@@ -184,9 +184,16 @@ module ActiveRecord
end
end
+
+ # In de/serialize we change `nil` to 0, so that we can allow passing
+ # `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError`
+ # during update record.
class LockingType < DelegateClass(Type::Value) # :nodoc:
def deserialize(value)
- # `nil` *should* be changed to 0
+ super.to_i
+ end
+
+ def serialize(value)
super.to_i
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index b63caa4473..8e32af1c49 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -22,7 +22,11 @@ module ActiveRecord
def render_bind(attribute)
value = if attribute.type.binary? && attribute.value
- "<#{attribute.value.bytesize} bytes of binary data>"
+ if attribute.value.is_a?(Hash)
+ "<#{attribute.value_for_database.to_s.bytesize} bytes of binary data>"
+ else
+ "<#{attribute.value.bytesize} bytes of binary data>"
+ end
else
attribute.value_for_database
end
@@ -67,7 +71,7 @@ module ActiveRecord
case sql
when /\A\s*rollback/mi
RED
- when /\s*.*?select .*for update/mi, /\A\s*lock/mi
+ when /select .*for update/mi, /\A\s*lock/mi
WHITE
when /\A\s*select/i
BLUE
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 4419a7b1e7..81fe053fe1 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -156,8 +156,8 @@ module ActiveRecord
class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
def initialize(env = "production")
- msg = "You are attempting to run a destructive action against your '#{env}' database\n"
- msg << "If you are sure you want to continue, run the same command with the environment variable\n"
+ msg = "You are attempting to run a destructive action against your '#{env}' database.\n"
+ msg << "If you are sure you want to continue, run the same command with the environment variable:\n"
msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
super(msg)
end
@@ -166,13 +166,13 @@ module ActiveRecord
class EnvironmentMismatchError < ActiveRecordError
def initialize(current: nil, stored: nil)
msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
- msg << "You are running in `#{ current }` environment."
+ msg << "You are running in `#{ current }` environment. "
msg << "If you are sure you want to continue, first set the environment using:\n\n"
msg << "\tbin/rails db:environment:set"
if defined?(Rails.env)
- super("#{msg} RAILS_ENV=#{::Rails.env}")
+ super("#{msg} RAILS_ENV=#{::Rails.env}\n\n")
else
- super(msg)
+ super("#{msg}\n\n")
end
end
end
@@ -277,7 +277,7 @@ module ActiveRecord
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
# the column to a different type using the same parameters as add_column.
# * <tt>change_column_default(table_name, column_name, default)</tt>: Sets a
- # default value for +column_name+ definded by +default+ on +table_name+.
+ # default value for +column_name+ defined by +default+ on +table_name+.
# * <tt>change_column_null(table_name, column_name, null, default = nil)</tt>:
# Sets or removes a +NOT NULL+ constraint on +column_name+. The +null+ flag
# indicates whether the value can be +NULL+. See
@@ -524,23 +524,17 @@ module ActiveRecord
end
def self.[](version)
- version = version.to_s
- name = "V#{version.tr('.', '_')}"
- unless Compatibility.const_defined?(name)
- versions = Compatibility.constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete('V').tr('_', '.').inspect }
- raise "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}"
- end
- Compatibility.const_get(name)
+ Compatibility.find(version)
end
def self.current_version
- Rails.version.to_f
+ ActiveRecord::VERSION::STRING.to_f
end
MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc:
# This class is used to verify that all migrations have been run before
- # loading a web page if config.active_record.migration_error is set to :page_load
+ # loading a web page if <tt>config.active_record.migration_error</tt> is set to :page_load
class CheckPending
def initialize(app)
@app = app
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index 5d742b523b..74833f938f 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -1,7 +1,17 @@
module ActiveRecord
class Migration
module Compatibility # :nodoc: all
- V5_0 = Current
+ def self.find(version)
+ version = version.to_s
+ name = "V#{version.tr('.', '_')}"
+ unless const_defined?(name)
+ versions = constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete('V').tr('_', '.').inspect }
+ raise ArgumentError, "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}"
+ end
+ const_get(name)
+ end
+
+ V5_1 = Current
module FourTwoShared
module TableDefinition
@@ -30,6 +40,19 @@ module ActiveRecord
end
end
+ def change_table(table_name, options = {})
+ if block_given?
+ super(table_name, options) do |t|
+ class << t
+ prepend TableDefinition
+ end
+ yield t
+ end
+ else
+ super
+ end
+ end
+
def add_reference(*, **options)
options[:index] ||= false
super
@@ -44,7 +67,7 @@ module ActiveRecord
def index_exists?(table_name, column_name, options = {})
column_names = Array(column_name).map(&:to_s)
options[:name] =
- if options.key?(:name).present?
+ if options[:name].present?
options[:name].to_s
else
index_name(table_name, column: column_names)
@@ -79,6 +102,9 @@ module ActiveRecord
end
end
+ class V5_0 < V5_1
+ end
+
class V4_2 < V5_0
# 4.2 is defined as a module because it needs to be shared with
# Legacy. When the time comes, V5_0 should be defined straight
@@ -89,7 +115,7 @@ module ActiveRecord
module Legacy
include FourTwoShared
- def run(*)
+ def migrate(*)
ActiveSupport::Deprecation.warn \
"Directly inheriting from ActiveRecord::Migration is deprecated. " \
"Please specify the Rails release the migration was written for:\n" \
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 722d7b5fce..7996c32bbc 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -44,9 +44,9 @@ module ActiveRecord
##
# :singleton-method:
- # Accessor for the name of the internal metadata table. By default, the value is "active_record_internal_metadatas"
+ # Accessor for the name of the internal metadata table. By default, the value is "ar_internal_metadata"
class_attribute :internal_metadata_table_name, instance_accessor: false
- self.internal_metadata_table_name = "active_record_internal_metadatas"
+ self.internal_metadata_table_name = "ar_internal_metadata"
##
# :singleton-method:
@@ -231,6 +231,18 @@ module ActiveRecord
@explicit_sequence_name = true
end
+ # Determines if the primary key values should be selected from their
+ # corresponding sequence before the insert statement.
+ def prefetch_primary_key?
+ connection.prefetch_primary_key?(table_name)
+ end
+
+ # Returns the next value that will be used as the primary key on
+ # an insert statement.
+ def next_sequence_value
+ connection.next_sequence_value(sequence_name)
+ end
+
# Indicates whether the table associated with this class exists
def table_exists?
connection.schema_cache.data_source_exists?(table_name)
@@ -255,6 +267,10 @@ module ActiveRecord
@attribute_types ||= Hash.new(Type::Value.new)
end
+ def yaml_encoder # :nodoc:
+ @yaml_encoder ||= AttributeSet::YAMLEncoder.new(attribute_types)
+ end
+
# Returns the type of the attribute with the given name, after applying
# all modifiers. This method is the only valid source of information for
# anything related to the types of a model's attributes. This method will
@@ -363,6 +379,7 @@ module ActiveRecord
@columns = nil
@columns_hash = nil
@attribute_names = nil
+ @yaml_encoder = nil
direct_descendants.each do |descendant|
descendant.send(:reload_schema_from_cache)
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 0d5a8e6f25..fe68869143 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -195,19 +195,27 @@ module ActiveRecord
# Nested attributes for an associated collection can also be passed in
# the form of a hash of hashes instead of an array of hashes:
#
- # Member.create(name: 'joe',
- # posts_attributes: { first: { title: 'Foo' },
- # second: { title: 'Bar' } })
+ # Member.create(
+ # name: 'joe',
+ # posts_attributes: {
+ # first: { title: 'Foo' },
+ # second: { title: 'Bar' }
+ # }
+ # )
#
# has the same effect as
#
- # Member.create(name: 'joe',
- # posts_attributes: [ { title: 'Foo' },
- # { title: 'Bar' } ])
+ # Member.create(
+ # name: 'joe',
+ # posts_attributes: [
+ # { title: 'Foo' },
+ # { title: 'Bar' }
+ # ]
+ # )
#
# The keys of the hash which is the value for +:posts_attributes+ are
# ignored in this case.
- # However, it is not allowed to use +'id'+ or +:id+ for one of
+ # However, it is not allowed to use <tt>'id'</tt> or <tt>:id</tt> for one of
# such keys, otherwise the hash will be wrapped in an array and
# interpreted as an attribute hash for a single post.
#
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 0b500346bc..1ab4e0404f 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module NullRelation # :nodoc:
def exec_queries
- @records = []
+ @records = [].freeze
end
def pluck(*column_names)
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 4d661735cc..afed5e5e85 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -61,7 +61,7 @@ module ActiveRecord
# +instantiate+ instead of +new+, finder methods ensure they get new
# instances of the appropriate class for each record.
#
- # See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see
+ # See <tt>ActiveRecord::Inheritance#discriminate_class_for_record</tt> to see
# how this "single-table" inheritance mapping is implemented.
def instantiate(attributes, column_types = {})
klass = discriminate_class_for_record(attributes)
@@ -106,7 +106,7 @@ module ActiveRecord
# the existing record gets updated.
#
# By default, save always runs validations. If any of them fail the action
- # is cancelled and #save returns +false+. However, if you supply
+ # is cancelled and #save returns +false+, and the record won't be saved. However, if you supply
# validate: false, validations are bypassed altogether. See
# ActiveRecord::Validations for more information.
#
@@ -133,7 +133,7 @@ module ActiveRecord
# the existing record gets updated.
#
# By default, #save! always runs validations. If any of them fail
- # ActiveRecord::RecordInvalid gets raised. However, if you supply
+ # ActiveRecord::RecordInvalid gets raised, and the record won't be saved. However, if you supply
# validate: false, validations are bypassed altogether. See
# ActiveRecord::Validations for more information.
#
@@ -270,7 +270,7 @@ module ActiveRecord
alias update_attributes update
# Updates its receiver just like #update but calls #save! instead
- # of +save+, so an exception is raised if the record is invalid.
+ # of +save+, so an exception is raised if the record is invalid and saving will fail.
def update!(attributes)
# The following transaction covers any possible database side-effects of the
# attributes assignment. For example, setting the IDs of a child collection.
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index dcb2bd3d84..07d863d15e 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -5,7 +5,7 @@ module ActiveRecord
# Enable the query cache within the block if Active Record is configured.
# If it's not, it will execute the given block.
def cache(&block)
- if ActiveRecord::Base.connected?
+ if connected?
connection.cache(&block)
else
yield
@@ -15,7 +15,7 @@ module ActiveRecord
# Disable the query cache within the block if Active Record is configured.
# If it's not, it will execute the given block.
def uncached(&block)
- if ActiveRecord::Base.connected?
+ if connected?
connection.uncached(&block)
else
yield
@@ -23,34 +23,31 @@ module ActiveRecord
end
end
- def initialize(app)
- @app = app
- end
-
- def call(env)
+ def self.run
connection = ActiveRecord::Base.connection
enabled = connection.query_cache_enabled
connection_id = ActiveRecord::Base.connection_id
connection.enable_query_cache!
- response = @app.call(env)
- response[2] = Rack::BodyProxy.new(response[2]) do
- restore_query_cache_settings(connection_id, enabled)
- end
-
- response
- rescue Exception => e
- restore_query_cache_settings(connection_id, enabled)
- raise e
+ [enabled, connection_id]
end
- private
+ def self.complete(state)
+ enabled, connection_id = state
- def restore_query_cache_settings(connection_id, enabled)
ActiveRecord::Base.connection_id = connection_id
ActiveRecord::Base.connection.clear_query_cache
ActiveRecord::Base.connection.disable_query_cache! unless enabled
end
+ def self.install_executor_hooks(executor = ActiveSupport::Executor)
+ executor.register_hook(self)
+
+ executor.to_complete do
+ unless ActiveRecord::Base.connection.transaction_open?
+ ActiveRecord::Base.clear_active_connections!
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 1f429cfd94..53ddd95bb0 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module Querying
- delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all
- delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, to: :all
+ delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :none?, :one?, to: :all
+ delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all
delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all
delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all
delegate :find_by, :find_by!, to: :all
@@ -35,8 +35,8 @@ module ActiveRecord
#
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
# Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
- def find_by_sql(sql, binds = [])
- result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
+ def find_by_sql(sql, binds = [], preparable: nil)
+ result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
column_types = result_set.column_types.dup
columns_hash.each_key { |k| column_types.delete k }
message_bus = ActiveSupport::Notifications.instrumenter
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 38916f7376..98ea425d16 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -16,12 +16,6 @@ module ActiveRecord
config.app_generators.orm :active_record, :migration => true,
:timestamps => true
- config.app_middleware.insert_after ::ActionDispatch::Callbacks,
- ActiveRecord::QueryCache
-
- config.app_middleware.insert_after ::ActionDispatch::Callbacks,
- ActiveRecord::ConnectionAdapters::ConnectionManagement
-
config.action_dispatch.rescue_responses.merge!(
'ActiveRecord::RecordNotFound' => :not_found,
'ActiveRecord::StaleObjectError' => :conflict,
@@ -40,7 +34,7 @@ module ActiveRecord
task :load_config do
ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
- if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
+ if defined?(ENGINE_ROOT) && engine = Rails::Engine.find(ENGINE_ROOT)
if engine.paths['db/migrate'].existent
ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a
end
@@ -71,7 +65,6 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
self.default_timezone = :utc
- self.time_zone_aware_types = ActiveRecord::Base.time_zone_aware_types
end
end
@@ -153,11 +146,9 @@ end_warning
end
end
- initializer "active_record.set_reloader_hooks" do |app|
- hook = app.config.reload_classes_only_on_change ? :to_prepare : :to_cleanup
-
+ initializer "active_record.set_reloader_hooks" do
ActiveSupport.on_load(:active_record) do
- ActionDispatch::Reloader.send(hook) do
+ ActiveSupport::Reloader.before_class_unload do
if ActiveRecord::Base.connected?
ActiveRecord::Base.clear_cache!
ActiveRecord::Base.clear_reloadable_connections!
@@ -166,6 +157,12 @@ end_warning
end
end
+ initializer "active_record.set_executor_hooks" do
+ ActiveSupport.on_load(:active_record) do
+ ActiveRecord::QueryCache.install_executor_hooks
+ end
+ end
+
initializer "active_record.add_watchable_files" do |app|
path = app.paths["db"].first
config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"]
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 69a7838001..fc1b62ee96 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -22,7 +22,7 @@ db_namespace = namespace :db do
end
end
- desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV, it defaults to creating the development and test databases.'
+ desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to creating the development and test databases.'
task :create => [:load_config] do
ActiveRecord::Tasks::DatabaseTasks.create_current
end
@@ -33,7 +33,7 @@ db_namespace = namespace :db do
end
end
- desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV, it defaults to dropping the development and test databases.'
+ desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to dropping the development and test databases.'
task :drop => [:load_config, :check_protected_environments] do
db_namespace["drop:_unsafe"].invoke
end
@@ -256,7 +256,7 @@ db_namespace = namespace :db do
end
desc 'Loads a schema.rb file into the database'
- task :load => [:environment, :load_config] do
+ task :load => [:environment, :load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA'])
end
@@ -278,7 +278,7 @@ db_namespace = namespace :db do
desc 'Clears a db/schema_cache.dump file.'
task :clear => [:environment, :load_config] do
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
- FileUtils.rm(filename) if File.exist?(filename)
+ rm_f filename, verbose: false
end
end
@@ -302,7 +302,7 @@ db_namespace = namespace :db do
end
desc "Recreates the databases from the structure.sql file"
- task :load => [:environment, :load_config] do
+ task :load => [:environment, :load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['SCHEMA'])
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 320ced5afa..bf398b0d40 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -124,9 +124,24 @@ module ActiveRecord
end
end
- # Holds all the methods that are shared between MacroReflection, AssociationReflection
- # and ThroughReflection
+ # Holds all the methods that are shared between MacroReflection and ThroughReflection.
+ #
+ # AbstractReflection
+ # MacroReflection
+ # AggregateReflection
+ # AssociationReflection
+ # HasManyReflection
+ # HasOneReflection
+ # BelongsToReflection
+ # HasAndBelongsToManyReflection
+ # ThroughReflection
+ # PolymorphicReflection
+ # RuntimeReflection
class AbstractReflection # :nodoc:
+ def through_reflection?
+ false
+ end
+
def table_name
klass.table_name
end
@@ -228,18 +243,14 @@ module ActiveRecord
def alias_candidate(name)
"#{plural_name}_#{name}"
end
+
+ def chain
+ collect_join_chain
+ end
end
# Base class for AggregateReflection and AssociationReflection. Objects of
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
- #
- # MacroReflection
- # AggregateReflection
- # AssociationReflection
- # HasManyReflection
- # HasOneReflection
- # BelongsToReflection
- # ThroughReflection
class MacroReflection < AbstractReflection
# Returns the name of the macro.
#
@@ -418,7 +429,7 @@ module ActiveRecord
# A chain of reflections from this one back to the owner. For more see the explanation in
# ThroughReflection.
- def chain
+ def collect_join_chain
[self]
end
@@ -438,6 +449,10 @@ module ActiveRecord
scope ? [[scope]] : [[]]
end
+ def has_scope?
+ scope
+ end
+
def has_inverse?
inverse_name
end
@@ -483,28 +498,7 @@ module ActiveRecord
# Returns +true+ if +self+ is a +has_one+ reflection.
def has_one?; false; end
- def association_class
- case macro
- when :belongs_to
- if polymorphic?
- Associations::BelongsToPolymorphicAssociation
- else
- Associations::BelongsToAssociation
- end
- when :has_many
- if options[:through]
- Associations::HasManyThroughAssociation
- else
- Associations::HasManyAssociation
- end
- when :has_one
- if options[:through]
- Associations::HasOneThroughAssociation
- else
- Associations::HasOneAssociation
- end
- end
- end
+ def association_class; raise NotImplementedError; end
def polymorphic?
options[:polymorphic]
@@ -513,6 +507,18 @@ module ActiveRecord
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
+ def add_as_source(seed)
+ seed
+ end
+
+ def add_as_polymorphic_through(reflection, seed)
+ seed + [PolymorphicReflection.new(self, reflection)]
+ end
+
+ def add_as_through(seed)
+ seed + [self]
+ end
+
protected
def actual_source_reflection # FIXME: this is a horrible name
@@ -522,14 +528,7 @@ module ActiveRecord
private
def calculate_constructable(macro, options)
- case macro
- when :belongs_to
- !polymorphic?
- when :has_one
- !options[:through]
- else
- true
- end
+ true
end
# Attempts to find the inverse association name automatically.
@@ -622,34 +621,52 @@ module ActiveRecord
end
class HasManyReflection < AssociationReflection # :nodoc:
- def initialize(name, scope, options, active_record)
- super(name, scope, options, active_record)
- end
-
def macro; :has_many; end
def collection?; true; end
- end
- class HasOneReflection < AssociationReflection # :nodoc:
- def initialize(name, scope, options, active_record)
- super(name, scope, options, active_record)
+ def association_class
+ if options[:through]
+ Associations::HasManyThroughAssociation
+ else
+ Associations::HasManyAssociation
+ end
end
+ end
+ class HasOneReflection < AssociationReflection # :nodoc:
def macro; :has_one; end
def has_one?; true; end
- end
- class BelongsToReflection < AssociationReflection # :nodoc:
- def initialize(name, scope, options, active_record)
- super(name, scope, options, active_record)
+ def association_class
+ if options[:through]
+ Associations::HasOneThroughAssociation
+ else
+ Associations::HasOneAssociation
+ end
end
+ private
+
+ def calculate_constructable(macro, options)
+ !options[:through]
+ end
+ end
+
+ class BelongsToReflection < AssociationReflection # :nodoc:
def macro; :belongs_to; end
def belongs_to?; true; end
+ def association_class
+ if polymorphic?
+ Associations::BelongsToPolymorphicAssociation
+ else
+ Associations::BelongsToAssociation
+ end
+ end
+
def join_keys(association_klass)
key = polymorphic? ? association_primary_key(association_klass) : association_primary_key
JoinKeys.new(key, foreign_key)
@@ -658,6 +675,12 @@ module ActiveRecord
def join_id_for(owner) # :nodoc:
owner[foreign_key]
end
+
+ private
+
+ def calculate_constructable(macro, options)
+ !polymorphic?
+ end
end
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
@@ -685,6 +708,10 @@ module ActiveRecord
@source_reflection_name = delegate_reflection.options[:source]
end
+ def through_reflection?
+ true
+ end
+
def klass
@klass ||= delegate_reflection.compute_class(class_name)
end
@@ -743,25 +770,13 @@ module ActiveRecord
# # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
# <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
#
- def chain
- @chain ||= begin
- a = source_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
+ def collect_join_chain
+ collect_join_reflections [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:
- @chain = nil
delegate_reflection.clear_association_scope_cache
source_reflection.clear_association_scope_cache
through_reflection.clear_association_scope_cache
@@ -808,13 +823,19 @@ module ActiveRecord
end
end
+ def has_scope?
+ scope || options[:source_type] ||
+ source_reflection.has_scope? ||
+ through_reflection.has_scope?
+ end
+
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
def nested?
- chain.length > 2
+ source_reflection.through_reflection? || through_reflection.through_reflection?
end
# We want to use the klass from this reflection, rather than just delegate straight to
@@ -853,7 +874,7 @@ module ActiveRecord
example_options = options.dup
example_options[:source] = source_reflection_names.first
ActiveSupport::Deprecation.warn \
- "Ambiguous source reflection for through association. Please " \
+ "Ambiguous source reflection for through association. Please " \
"specify a :source directive on your declaration like:\n" \
"\n" \
" class #{active_record.name} < ActiveRecord::Base\n" \
@@ -914,6 +935,27 @@ module ActiveRecord
scope_chain
end
+ def add_as_source(seed)
+ collect_join_reflections seed
+ end
+
+ def add_as_polymorphic_through(reflection, seed)
+ collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)])
+ end
+
+ def add_as_through(seed)
+ collect_join_reflections(seed + [self])
+ end
+
+ def collect_join_reflections(seed)
+ a = source_reflection.add_as_source seed
+ if options[:source_type]
+ through_reflection.add_as_polymorphic_through self, a
+ else
+ through_reflection.add_as_through a
+ end
+ end
+
protected
def actual_source_reflection # FIXME: this is a horrible name
@@ -970,7 +1012,7 @@ module ActiveRecord
end
def constraints
- [source_type_info]
+ @reflection.constraints + [source_type_info]
end
def source_type_info
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 032b8d4c5d..7a1552856b 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -45,9 +45,9 @@ module ActiveRecord
k.name == primary_key
}]
- if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
- primary_key_value = connection.next_sequence_value(klass.sequence_name)
- values[klass.arel_table[klass.primary_key]] = primary_key_value
+ if !primary_key_value && klass.prefetch_primary_key?
+ primary_key_value = klass.next_sequence_value
+ values[arel_attribute(klass.primary_key)] = primary_key_value
end
end
@@ -94,17 +94,21 @@ module ActiveRecord
end
def substitute_values(values) # :nodoc:
- binds = values.map do |arel_attr, value|
- QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
- end
+ binds = []
+ substitutes = []
- substitutes = values.map do |(arel_attr, _)|
- [arel_attr, Arel::Nodes::BindParam.new]
+ values.each do |arel_attr, value|
+ binds.push QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
+ substitutes.push [arel_attr, Arel::Nodes::BindParam.new]
end
[substitutes, binds]
end
+ def arel_attribute(name) # :nodoc:
+ klass.arel_attribute(name, table)
+ end
+
# Initializes new record from relation while maintaining the current
# scope.
#
@@ -249,17 +253,21 @@ module ActiveRecord
# Converts relation objects to Array.
def to_a
+ records.dup
+ end
+
+ def records # :nodoc:
load
@records
end
# Serializes the relation objects Array.
def encode_with(coder)
- coder.represent_seq(nil, to_a)
+ coder.represent_seq(nil, records)
end
def as_json(options = nil) #:nodoc:
- to_a.as_json(options)
+ records.as_json(options)
end
# Returns size of the records.
@@ -271,12 +279,7 @@ module ActiveRecord
def empty?
return @records.empty? if loaded?
- if limit_value == 0
- true
- else
- c = count(:all)
- c.respond_to?(:zero?) ? c.zero? : c.empty?
- end
+ limit_value == 0 || !exists?
end
# Returns true if there are no records.
@@ -294,13 +297,13 @@ module ActiveRecord
# Returns true if there is exactly one record.
def one?
return super if block_given?
- limit_value ? to_a.one? : size == 1
+ limit_value ? records.one? : size == 1
end
# Returns true if there is more than one record.
def many?
return super if block_given?
- limit_value ? to_a.many? : size > 1
+ limit_value ? records.many? : size > 1
end
# Returns a cache key that can be used to identify the records fetched by
@@ -373,9 +376,9 @@ module ActiveRecord
stmt.table(table)
if joins_values.any?
- @klass.connection.join_to_update(stmt, arel, table[primary_key])
+ @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
else
- stmt.key = table[primary_key]
+ stmt.key = arel_attribute(primary_key)
stmt.take(arel.limit)
stmt.order(*arel.orders)
stmt.wheres = arel.constraints
@@ -414,13 +417,13 @@ module ActiveRecord
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) }
+ records.each { |record| record.update(attributes) }
else
if ActiveRecord::Base === id
id = id.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `update`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
object = find(id)
@@ -449,11 +452,11 @@ module ActiveRecord
if conditions
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
Passing conditions to destroy_all is deprecated and will be removed in Rails 5.1.
- To achieve the same use where(conditions).destroy_all
+ To achieve the same use where(conditions).destroy_all.
MESSAGE
where(conditions).destroy_all
else
- to_a.each(&:destroy).tap { reset }
+ records.each(&:destroy).tap { reset }
end
end
@@ -519,7 +522,7 @@ module ActiveRecord
if conditions
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
Passing conditions to delete_all is deprecated and will be removed in Rails 5.1.
- To achieve the same use where(conditions).delete_all
+ To achieve the same use where(conditions).delete_all.
MESSAGE
where(conditions).delete_all
else
@@ -527,7 +530,7 @@ module ActiveRecord
stmt.from(table)
if joins_values.any?
- @klass.connection.join_to_delete(stmt, arel, table[primary_key])
+ @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
else
stmt.wheres = arel.constraints
end
@@ -583,7 +586,7 @@ module ActiveRecord
def reset
@last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
@should_eager_load = @join_dependency = nil
- @records = []
+ @records = [].freeze
@offsets = {}
self
end
@@ -650,21 +653,21 @@ module ActiveRecord
def ==(other)
case other
when Associations::CollectionProxy, AssociationRelation
- self == other.to_a
+ self == other.records
when Relation
other.to_sql == to_sql
when Array
- to_a == other
+ records == other
end
end
def pretty_print(q)
- q.pp(self.to_a)
+ q.pp(self.records)
end
# Returns true if relation is blank.
def blank?
- to_a.blank?
+ records.blank?
end
def values
@@ -672,7 +675,7 @@ module ActiveRecord
end
def inspect
- entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect)
+ entries = records.take([limit_value, 11].compact.min).map!(&:inspect)
entries[10] = '...' if entries.size == 11
"#<#{self.class.name} [#{entries.join(', ')}]>"
@@ -681,14 +684,14 @@ module ActiveRecord
protected
def load_records(records)
- @records = records
+ @records = records.freeze
@loaded = true
end
private
def exec_queries
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bound_attributes)
+ @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes).freeze
preload = preload_values
preload += includes_values unless eager_loading?
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 54587ae18e..3639625722 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -2,6 +2,8 @@ require "active_record/relation/batches/batch_enumerator"
module ActiveRecord
module Batches
+ ORDER_OR_LIMIT_IGNORED_MESSAGE = "Scoped order and limit are ignored, it's forced to be batch order and batch size."
+
# Looping through a collection of records from the database
# (using the Scoping::Named::ClassMethods.all method, for example)
# is very inefficient since it will try to instantiate all the objects at once.
@@ -31,6 +33,9 @@ module ActiveRecord
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
+ # the order and limit have to be ignored due to batching.
+ #
# 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
@@ -48,13 +53,13 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_each(start: nil, finish: nil, batch_size: 1000)
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
if block_given?
- find_in_batches(start: start, finish: finish, batch_size: batch_size) do |records|
+ find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
records.each { |record| yield record }
end
else
- enum_for(:find_each, start: start, finish: finish, batch_size: batch_size) do
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
relation = self
apply_limits(relation, start, finish).size
end
@@ -83,6 +88,9 @@ module ActiveRecord
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
+ # the order and limit have to be ignored due to batching.
+ #
# 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
@@ -100,16 +108,16 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_in_batches(start: nil, finish: nil, batch_size: 1000)
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
relation = self
unless block_given?
- return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size) do
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
total = apply_limits(relation, start, finish).size
(total - 1).div(batch_size) + 1
end
end
- in_batches(of: batch_size, start: start, finish: finish, load: true) do |batch|
+ in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
yield batch.to_a
end
end
@@ -140,6 +148,8 @@ module ActiveRecord
# * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
+ # the order and limit have to be ignored due to batching.
#
# This is especially useful if you want to work with the
# ActiveRecord::Relation object instead of the array of records, or if
@@ -171,14 +181,14 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control the batch
# sizes.
- def in_batches(of: 1000, start: nil, finish: nil, load: false)
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
relation = self
unless block_given?
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
end
- if logger && (arel.orders.present? || arel.taken.present?)
- logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
+ if arel.orders.present? || arel.taken.present?
+ act_on_order_or_limit_ignored(error_on_ignore)
end
relation = relation.reorder(batch_order).limit(of)
@@ -187,7 +197,7 @@ module ActiveRecord
loop do
if load
- records = batch_relation.to_a
+ records = batch_relation.records
ids = records.map(&:id)
yielded_relation = self.where(primary_key => ids)
yielded_relation.load_records(records)
@@ -204,20 +214,30 @@ module ActiveRecord
yield yielded_relation
break if ids.length < of
- batch_relation = relation.where(table[primary_key].gt(primary_key_offset))
+ batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
end
end
private
def apply_limits(relation, start, finish)
- relation = relation.where(table[primary_key].gteq(start)) if start
- relation = relation.where(table[primary_key].lteq(finish)) if finish
+ relation = relation.where(arel_attribute(primary_key).gteq(start)) if start
+ relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish
relation
end
def batch_order
"#{quoted_table_name}.#{quoted_primary_key} ASC"
end
+
+ def act_on_order_or_limit_ignored(error_on_ignore)
+ raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order_or_limit : error_on_ignore)
+
+ if raise_error
+ raise ArgumentError.new(ORDER_OR_LIMIT_IGNORED_MESSAGE)
+ elsif logger
+ logger.warn(ORDER_OR_LIMIT_IGNORED_MESSAGE)
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
index c6e39814dd..333b3a63cf 100644
--- a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
+++ b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
@@ -35,14 +35,14 @@ module ActiveRecord
return to_enum(:each_record) unless block_given?
@relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
- relation.to_a.each { |record| yield record }
+ relation.records.each { |record| yield record }
end
end
# Delegates #delete_all, #update_all, #destroy_all methods to each batch.
#
# People.in_batches.delete_all
- # People.in_batches.destroy_all('age < 10')
+ # People.where('age < 10').in_batches.destroy_all
# People.in_batches.update_all('age = age + 1')
[:delete_all, :update_all, :destroy_all].each do |method|
define_method(method) do |*args, &block|
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index f45844a9ea..d6d92b8607 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -37,7 +37,11 @@ module ActiveRecord
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
# between databases. In invalid cases, an error from the database is thrown.
def count(column_name = nil)
- calculate(:count, column_name)
+ if block_given?
+ to_a.count { |*block_args| yield(*block_args) }
+ else
+ calculate(:count, column_name)
+ end
end
# Calculates the average value on a given column. Returns +nil+ if there's
@@ -89,7 +93,7 @@ module ActiveRecord
#
# There are two basic forms of output:
#
- # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
+ # * Single aggregate value: The single value is type cast to Integer for COUNT, Float
# for AVG, and the given column's type for everything else.
#
# * Grouped values: This returns an ordered hash of the values and groups them. It
@@ -155,15 +159,7 @@ module ActiveRecord
# See also #ids.
#
def pluck(*column_names)
- column_names.map! do |column_name|
- if column_name.is_a?(Symbol) && attribute_alias?(column_name)
- attribute_alias(column_name)
- else
- column_name.to_s
- end
- end
-
- if loaded? && (column_names - @klass.column_names).empty?
+ if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
return @records.pluck(*column_names)
end
@@ -172,7 +168,7 @@ module ActiveRecord
else
relation = spawn
relation.select_values = column_names.map { |cn|
- columns_hash.key?(cn) ? arel_table[cn] : cn
+ @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
}
result = klass.connection.select_all(relation.arel, nil, bound_attributes)
result.cast_values(klass.attribute_types)
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index e4e5d63006..2484cb3264 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,4 +1,3 @@
-require 'set'
require 'active_support/concern'
module ActiveRecord
@@ -36,8 +35,9 @@ module ActiveRecord
# may vary depending on the klass of a relation, so we create a subclass of Relation
# for each different klass, and the delegations are compiled into that subclass only.
- delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
- :[], :&, :|, :+, :-, :sample, :shuffle, :reverse, :compact, to: :to_a
+ delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
+ :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
+ :shuffle, :split, to: :records
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :to => :klass
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 3cbb12a09d..d255cad91b 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -42,10 +42,10 @@ module ActiveRecord
# Person.find_by(name: 'Spartacus', rating: 4)
# # returns the first item or nil.
#
- # Person.where(name: 'Spartacus', rating: 4).first_or_initialize
+ # Person.find_or_initialize_by(name: 'Spartacus', rating: 4)
# # returns the first item or returns a new instance (requires you call .save to persist against the database).
#
- # Person.where(name: 'Spartacus', rating: 4).first_or_create
+ # Person.find_or_create_by(name: 'Spartacus', rating: 4)
# # returns the first item or creates it and returns it.
#
# ==== Alternatives for #find
@@ -145,15 +145,21 @@ module ActiveRecord
#
# [#<Person id:4>, #<Person id:3>, #<Person id:2>]
def last(limit = nil)
- if limit
- if order_values.empty? && primary_key
- order(arel_table[primary_key].desc).limit(limit).reverse
- else
- to_a.last(limit)
- end
- else
- find_last
- end
+ return find_last(limit) if loaded? || limit_value
+
+ result = limit(limit || 1)
+ result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
+ result = result.reverse_order!
+
+ limit ? result.reverse : result.first
+ rescue ActiveRecord::IrreversibleOrderError
+ ActiveSupport::Deprecation.warn(<<-WARNING.squish)
+ Finding a last element by loading the relation when SQL ORDER
+ can not be reversed is deprecated.
+ Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case.
+ Please call `to_a.last` if you still want to load the relation.
+ WARNING
+ find_last(limit)
end
# Same as #last but raises ActiveRecord::RecordNotFound if no record
@@ -242,6 +248,38 @@ module ActiveRecord
find_nth! 41
end
+ # Find the third-to-last record.
+ # If no order is defined it will order by primary key.
+ #
+ # Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people
+ # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
+ # Person.where(["user_name = :u", { u: user_name }]).third_to_last
+ def third_to_last
+ find_nth_from_last 3
+ end
+
+ # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
+ # is found.
+ def third_to_last!
+ find_nth_from_last 3 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ end
+
+ # Find the second-to-last record.
+ # If no order is defined it will order by primary key.
+ #
+ # Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people
+ # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
+ # Person.where(["user_name = :u", { u: user_name }]).second_to_last
+ def second_to_last
+ find_nth_from_last 2
+ end
+
+ # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
+ # is found.
+ def second_to_last!
+ find_nth_from_last 2 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ end
+
# Returns true if a record exists in the table that matches the +id+ or
# conditions given, or false otherwise. The argument can take six forms:
#
@@ -274,13 +312,13 @@ module ActiveRecord
conditions = conditions.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `exists?`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
return false if !conditions
- relation = apply_join_dependency(self, construct_join_dependency)
+ relation = apply_join_dependency(self, construct_join_dependency(eager_loading: false))
return false if ActiveRecord::NullRelation === relation
relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1)
@@ -295,10 +333,12 @@ module ActiveRecord
end
connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
+ rescue RangeError
+ false
end
# This method is called whenever no records are found with either a single
- # id or multiple ids and raises a ActiveRecord::RecordNotFound exception.
+ # id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
#
# The error message is different depending on whether a single id or
# multiple ids are provided. If multiple ids are provided, then the number
@@ -354,21 +394,13 @@ module ActiveRecord
end
end
- def construct_join_dependency(joins = [])
+ def construct_join_dependency(joins = [], eager_loading: true)
including = eager_load_values + includes_values
- ActiveRecord::Associations::JoinDependency.new(@klass, including, joins)
+ ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, eager_loading: eager_loading)
end
def construct_relation_for_association_calculations
- from = arel.froms.first
- if Arel::Table === from
- 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
- # possible for `from` to be a list?
- apply_join_dependency(self, construct_join_dependency(from))
- end
+ apply_join_dependency(self, construct_join_dependency(joins_values))
end
def apply_join_dependency(relation, join_dependency)
@@ -429,7 +461,7 @@ module ActiveRecord
id = id.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `find`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
@@ -468,7 +500,7 @@ module ActiveRecord
def find_some_ordered(ids)
ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
- result = except(:limit, :offset).where(primary_key => ids).to_a
+ result = except(:limit, :offset).where(primary_key => ids).records
if result.size == ids.size
pk_type = @klass.type_for_attribute(primary_key)
@@ -484,25 +516,24 @@ module ActiveRecord
if loaded?
@records.first
else
- @take ||= limit(1).to_a.first
+ @take ||= limit(1).records.first
end
end
def find_nth(index, offset = nil)
+ # TODO: once the offset argument is removed we rely on offset_index
+ # within find_nth_with_limit, rather than pass it in via
+ # find_nth_with_limit_and_offset
+ if offset
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing an offset argument to find_nth is deprecated,
+ please use Relation#offset instead.
+ MSG
+ end
if loaded?
@records[index]
else
- # TODO: once the offset argument is removed we rely on offset_index
- # within find_nth_with_limit, rather than pass it in via
- # find_nth_with_limit_and_offset
- if offset
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing an offset argument to find_nth is deprecated,
- please use Relation#offset instead.
- MSG
- else
- offset = offset_index
- end
+ offset ||= offset_index
@offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first
end
end
@@ -515,7 +546,7 @@ module ActiveRecord
# TODO: once the offset argument is removed from find_nth,
# find_nth_with_limit_and_offset can be merged into this method
relation = if order_values.empty? && primary_key
- order(arel_table[primary_key].asc)
+ order(arel_attribute(primary_key).asc)
else
self
end
@@ -524,16 +555,22 @@ module ActiveRecord
relation.limit(limit).to_a
end
- def find_last
+ def find_nth_from_last(index)
if loaded?
- @records.last
+ @records[-index]
else
- @last ||=
- if limit_value
- to_a.last
- else
- reverse_order.limit(1).to_a.first
- end
+ relation = if order_values.empty? && primary_key
+ order(arel_attribute(primary_key).asc)
+ else
+ self
+ end
+
+ relation.to_a[-index]
+ # TODO: can be made more performant on large result sets by
+ # for instance, last(index)[-index] (which would require
+ # refactoring the last(n) finder method to make test suite pass),
+ # or by using a combination of reverse_order, limit, and offset,
+ # e.g., reverse_order.offset(index-1).first
end
end
@@ -547,5 +584,9 @@ module ActiveRecord
find_nth_with_limit(index, limit)
end
end
+
+ def find_last(limit)
+ limit ? records.last(limit) : records.last
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 0f88791d92..ecce949370 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -5,6 +5,7 @@ module ActiveRecord
require 'active_record/relation/predicate_builder/base_handler'
require 'active_record/relation/predicate_builder/basic_object_handler'
require 'active_record/relation/predicate_builder/class_handler'
+ require 'active_record/relation/predicate_builder/polymorphic_array_handler'
require 'active_record/relation/predicate_builder/range_handler'
require 'active_record/relation/predicate_builder/relation_handler'
@@ -22,6 +23,7 @@ module ActiveRecord
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new(self))
register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
+ register_handler(PolymorphicArrayValue, PolymorphicArrayHandler.new(self))
end
def build_from_hash(attributes)
@@ -40,10 +42,7 @@ module ActiveRecord
#
# For polymorphic relationships, find the foreign key and type:
# PriceEstimate.where(estimate_of: treasure)
- if table.associated_with?(column)
- value = AssociationQueryValue.new(table.associated_table(column), value)
- end
-
+ value = AssociationQueryHandler.value_for(table, column, value) if table.associated_with?(column)
build(table.arel_attribute(column), value)
end
@@ -137,9 +136,11 @@ module ActiveRecord
end
def convert_dot_notation_to_hash(attributes)
- dot_notation = attributes.keys.select { |s| s.include?(".".freeze) }
+ dot_notation = attributes.select do |k, v|
+ k.include?(".".freeze) && !v.is_a?(Hash)
+ end
- dot_notation.each do |key|
+ dot_notation.each_key do |key|
table_name, column_name = key.split(".".freeze)
value = attributes.delete(key)
attributes[table_name] ||= {}
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
index e81be63cd3..413cb9fd84 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
@@ -1,6 +1,17 @@
module ActiveRecord
class PredicateBuilder
class AssociationQueryHandler # :nodoc:
+ def self.value_for(table, column, value)
+ associated_table = table.associated_table(column)
+ klass = if associated_table.polymorphic_association? && ::Array === value && value.first.is_a?(Base)
+ PolymorphicArrayValue
+ else
+ AssociationQueryValue
+ end
+
+ klass.new(associated_table, value)
+ end
+
def initialize(predicate_builder)
@predicate_builder = predicate_builder
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
new file mode 100644
index 0000000000..b6c6240343
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
@@ -0,0 +1,57 @@
+module ActiveRecord
+ class PredicateBuilder
+ class PolymorphicArrayHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ table = value.associated_table
+ queries = value.type_to_ids_mapping.map do |type, ids|
+ { table.association_foreign_type.to_s => type, table.association_foreign_key.to_s => ids }
+ end
+
+ predicates = queries.map { |query| predicate_builder.build_from_hash(query) }
+
+ if predicates.size > 1
+ type_and_ids_predicates = predicates.map { |type_predicate, id_predicate| Arel::Nodes::Grouping.new(type_predicate.and(id_predicate)) }
+ type_and_ids_predicates.inject(&:or)
+ else
+ predicates.first
+ end
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+
+ class PolymorphicArrayValue # :nodoc:
+ attr_reader :associated_table, :values
+
+ def initialize(associated_table, values)
+ @associated_table = associated_table
+ @values = values
+ end
+
+ def type_to_ids_mapping
+ default_hash = Hash.new { |hsh, key| hsh[key] = [] }
+ values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) }
+ end
+
+ private
+
+ def primary_key(value)
+ associated_table.association_primary_key(base_class(value))
+ end
+
+ def base_class(value)
+ value.class.base_class
+ end
+
+ def convert_to_id(value)
+ value._read_attribute(primary_key(value))
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb
index 063150958a..8a910a82fe 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb
@@ -3,7 +3,7 @@ module ActiveRecord
class RelationHandler # :nodoc:
def call(attribute, value)
if value.select_values.empty?
- value = value.select(value.klass.arel_table[value.klass.primary_key])
+ value = value.select(value.arel_attribute(value.klass.primary_key))
end
attribute.in(value.arel)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 716b1e8505..8a87015e44 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -99,22 +99,28 @@ module ActiveRecord
end
def bound_attributes
- result = from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds
if limit_value && !string_containing_comma?(limit_value)
- result << Attribute.with_cast_value(
+ limit_bind = Attribute.with_cast_value(
"LIMIT".freeze,
connection.sanitize_limit(limit_value),
Type::Value.new,
)
end
if offset_value
- result << Attribute.with_cast_value(
+ offset_bind = Attribute.with_cast_value(
"OFFSET".freeze,
offset_value.to_i,
Type::Value.new,
)
end
- result
+ connection.combine_bind_parameters(
+ from_clause: from_clause.binds,
+ join_clause: arel.bind_values,
+ where_clause: where_clause.binds,
+ having_clause: having_clause.binds,
+ limit: limit_bind,
+ offset: offset_bind,
+ )
end
FROZEN_EMPTY_HASH = {}.freeze
@@ -652,9 +658,13 @@ module ActiveRecord
# present). Neither relation may have a #limit, #offset, or #distinct set.
#
# Post.where("id = 1").or(Post.where("author_id = 3"))
- # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'author_id = 3'))
+ # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
#
def or(other)
+ unless other.is_a? Relation
+ raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
+ end
+
spawn.or!(other)
end
@@ -998,12 +1008,6 @@ module ActiveRecord
self.send(unscope_code, result)
end
- def association_for_table(table_name)
- table_name = table_name.to_s
- @klass._reflect_on_association(table_name) ||
- @klass._reflect_on_association(table_name.singularize)
- end
-
def build_from
opts = from_clause.value
name = from_clause.name
@@ -1093,8 +1097,8 @@ module ActiveRecord
def arel_columns(columns)
columns.map do |field|
- if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_clause.value
- arel_table[field]
+ if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value
+ arel_attribute(field)
elsif Symbol === field
connection.quote_table_name(field.to_s)
else
@@ -1104,14 +1108,23 @@ module ActiveRecord
end
def reverse_sql_order(order_query)
- order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
+ if order_query.empty?
+ return [arel_attribute(primary_key).desc] if primary_key
+ raise IrreversibleOrderError,
+ "Relation has no current order and table has no primary key to be used as default order"
+ end
order_query.flat_map do |o|
case o
+ when Arel::Attribute
+ o.desc
when Arel::Nodes::Ordering
o.reverse
when String
- o.to_s.split(',').map! do |s|
+ if does_not_support_reverse?(o)
+ raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically"
+ end
+ o.split(',').map! do |s|
s.strip!
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
end
@@ -1121,6 +1134,13 @@ module ActiveRecord
end
end
+ def does_not_support_reverse?(order)
+ #uses sql function with multiple arguments
+ order =~ /\([^()]*,[^()]*\)/ ||
+ # uses "nulls first" like construction
+ order =~ /nulls (first|last)\Z/i
+ end
+
def build_order(arel)
orders = order_values.uniq
orders.reject!(&:blank?)
@@ -1156,12 +1176,10 @@ module ActiveRecord
order_args.map! do |arg|
case arg
when Symbol
- arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
- table[arg].asc
+ arel_attribute(arg).asc
when Hash
arg.map { |field, dir|
- field = klass.attribute_alias(field) if klass.attribute_alias?(field)
- table[field].send(dir.downcase)
+ arel_attribute(field).send(dir.downcase)
}
else
arg
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 67d7f83cb4..d5c18a2a4a 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -29,7 +29,7 @@ module ActiveRecord
# This is mainly intended for sharing common conditions between multiple associations.
def merge(other)
if other.is_a?(Array)
- to_a & other
+ records & other
elsif other
spawn.merge!(other)
else
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 2c2d6cfa47..89396b518c 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -158,8 +158,9 @@ module ActiveRecord
end
end
+ ARRAY_WITH_EMPTY_STRING = ['']
def non_empty_predicates
- predicates - ['']
+ predicates - ARRAY_WITH_EMPTY_STRING
end
def wrap_sql_literal(node)
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 2bfc5ff7ae..a9e1fd0dad 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -60,7 +60,7 @@ module ActiveRecord
end
# Accepts an array, or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for a ORDER clause.
+ # them into a valid SQL fragment for an ORDER clause.
#
# sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
# # => "field(id, 1,3,2)"
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 65005bd44b..d769376d1a 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -50,10 +50,6 @@ module ActiveRecord
def header(stream)
define_params = @version ? "version: #{@version}" : ""
- if stream.respond_to?(:external_encoding) && stream.external_encoding
- stream.puts "# encoding: #{stream.external_encoding.name}"
- end
-
stream.puts <<HEADER
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
@@ -104,10 +100,7 @@ HEADER
end
def table(table, stream)
- columns = @connection.columns(table).map do |column|
- column.instance_variable_set(:@table_name, table)
- column
- end
+ columns = @connection.columns(table)
begin
tbl = StringIO.new
@@ -126,7 +119,7 @@ HEADER
tbl.print ", primary_key: #{pk.inspect}" unless pk == 'id'
pkcol = columns.detect { |c| c.name == pk }
pkcolspec = @connection.column_spec_for_primary_key(pkcol)
- if pkcolspec
+ if pkcolspec.present?
pkcolspec.each do |key, value|
tbl.print ", #{key}: #{value}"
end
@@ -141,6 +134,10 @@ HEADER
table_options = @connection.table_options(table)
tbl.print ", options: #{table_options.inspect}" unless table_options.blank?
+ if comment = @connection.table_comment(table).presence
+ tbl.print ", comment: #{comment.inspect}"
+ end
+
tbl.puts " do |t|"
# then dump all non-primary key columns
@@ -178,7 +175,7 @@ HEADER
tbl.puts
end
- indexes(table, tbl)
+ indexes_in_create(table, tbl)
tbl.puts " end"
tbl.puts
@@ -194,31 +191,47 @@ HEADER
stream
end
+ # Keep it for indexing materialized views
def indexes(table, stream)
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
- statement_parts = [
- "t.index #{index.columns.inspect}",
- "name: #{index.name.inspect}",
- ]
- statement_parts << 'unique: true' if index.unique
-
- index_lengths = (index.lengths || []).compact
- statement_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
-
- index_orders = index.orders || {}
- statement_parts << "order: #{index.orders.inspect}" if index_orders.any?
- statement_parts << "where: #{index.where.inspect}" if index.where
- statement_parts << "using: #{index.using.inspect}" if index.using
- statement_parts << "type: #{index.type.inspect}" if index.type
-
- " #{statement_parts.join(', ')}"
+ table_name = remove_prefix_and_suffix(index.table).inspect
+ " add_index #{([table_name]+index_parts(index)).join(', ')}"
end
stream.puts add_index_statements.sort.join("\n")
+ stream.puts
+ end
+ end
+
+ def indexes_in_create(table, stream)
+ if (indexes = @connection.indexes(table)).any?
+ index_statements = indexes.map do |index|
+ " t.index #{index_parts(index).join(', ')}"
+ end
+ stream.puts index_statements.sort.join("\n")
end
end
+ def index_parts(index)
+ index_parts = [
+ index.columns.inspect,
+ "name: #{index.name.inspect}",
+ ]
+ index_parts << 'unique: true' if index.unique
+
+ index_lengths = (index.lengths || []).compact
+ index_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
+
+ index_orders = index.orders || {}
+ index_parts << "order: #{index.orders.inspect}" if index_orders.any?
+ index_parts << "where: #{index.where.inspect}" if index.where
+ index_parts << "using: #{index.using.inspect}" if index.using
+ index_parts << "type: #{index.type.inspect}" if index.type
+ index_parts << "comment: #{index.comment.inspect}" if index.comment
+ index_parts
+ end
+
def foreign_keys(table, stream)
if (foreign_keys = @connection.foreign_keys(table)).any?
add_foreign_key_statements = foreign_keys.map do |foreign_key|
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index ee4c71f304..b6cb233e03 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -16,31 +16,22 @@ module ActiveRecord
"#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
end
- def index_name
- "#{table_name_prefix}unique_#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
- end
-
def table_exists?
ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) }
end
- def create_table(limit=nil)
+ def create_table
unless table_exists?
- version_options = {null: false}
- version_options[:limit] = limit if limit
+ version_options = connection.internal_string_options_for_primary_key
connection.create_table(table_name, id: false) do |t|
- t.column :version, :string, version_options
- t.index :version, unique: true, name: index_name
+ t.string :version, version_options
end
end
end
def drop_table
- if table_exists?
- connection.remove_index table_name, name: index_name
- connection.drop_table(table_name)
- end
+ connection.drop_table table_name, if_exists: true
end
def normalize_migration_number(number)
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index f6b6768ce3..9eab59ac78 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -115,7 +115,8 @@ module ActiveRecord
base_rel ||= relation
evaluate_default_scope do
default_scopes.inject(base_rel) do |default_scope, scope|
- default_scope.merge(base_rel.scoping { scope.call })
+ scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call)
+ default_scope.merge(base_rel.instance_exec(&scope))
end
end
end
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 103569c84d..5395bd6076 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -151,6 +151,7 @@ module ActiveRecord
"a class method with the same name."
end
+ valid_scope_name?(name)
extension = Module.new(&block) if block
if body.respond_to?(:to_proc)
@@ -169,6 +170,15 @@ module ActiveRecord
end
end
end
+
+ protected
+
+ def valid_scope_name?(name)
+ if respond_to?(name, true)
+ logger.warn "Creating scope :#{name}. " \
+ "Overwriting existing method #{self.name}.#{name}."
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index f6b0efb88a..6c896ccea6 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -106,7 +106,7 @@ module ActiveRecord
sql = query_builder.sql_for bind_values, connection
- klass.find_by_sql sql, bind_values
+ klass.find_by_sql(sql, bind_values, preparable: true)
end
alias :call :execute
end
diff --git a/activerecord/lib/active_record/suppressor.rb b/activerecord/lib/active_record/suppressor.rb
index b3644bf569..d9acb1a1dc 100644
--- a/activerecord/lib/active_record/suppressor.rb
+++ b/activerecord/lib/active_record/suppressor.rb
@@ -30,14 +30,19 @@ module ActiveRecord
module ClassMethods
def suppress(&block)
+ previous_state = SuppressorRegistry.suppressed[name]
SuppressorRegistry.suppressed[name] = true
yield
ensure
- SuppressorRegistry.suppressed[name] = false
+ SuppressorRegistry.suppressed[name] = previous_state
end
end
- def create_or_update(*args) # :nodoc:
+ def save(*) # :nodoc:
+ SuppressorRegistry.suppressed[self.class.name] ? true : super
+ end
+
+ def save!(*) # :nodoc:
SuppressorRegistry.suppressed[self.class.name] ? true : super
end
end
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index f9bb1cf5e0..a1326aa359 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -22,7 +22,11 @@ module ActiveRecord
end
def arel_attribute(column_name)
- arel_table[column_name]
+ if klass
+ klass.arel_attribute(column_name, arel_table)
+ else
+ arel_table[column_name]
+ end
end
def type(column_name)
@@ -40,7 +44,8 @@ module ActiveRecord
def associated_table(table_name)
return self if table_name == arel_table.name
- association = klass._reflect_on_association(table_name)
+ association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.singularize)
+
if association && !association.polymorphic?
association_klass = association.klass
arel_table = association_klass.arel_table.alias(table_name)
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 8f52e9068a..e3e665e149 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -107,8 +107,9 @@ module ActiveRecord
def create(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).create
+ $stdout.puts "Created database '#{configuration['database']}'"
rescue DatabaseAlreadyExists
- $stderr.puts "#{configuration['database']} already exists"
+ $stderr.puts "Database '#{configuration['database']}' already exists"
rescue Exception => error
$stderr.puts error
$stderr.puts "Couldn't create database for #{configuration.inspect}"
@@ -116,7 +117,11 @@ module ActiveRecord
end
def create_all
+ old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
each_local_configuration { |configuration| create configuration }
+ if old_pool
+ ActiveRecord::Base.connection_handler.establish_connection(old_pool.spec.to_hash)
+ end
end
def create_current(environment = env)
@@ -129,11 +134,12 @@ module ActiveRecord
def drop(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).drop
+ $stdout.puts "Dropped database '#{configuration['database']}'"
rescue ActiveRecord::NoDatabaseError
$stderr.puts "Database '#{configuration['database']}' does not exist"
rescue Exception => error
$stderr.puts error
- $stderr.puts "Couldn't drop #{configuration['database']}"
+ $stderr.puts "Couldn't drop database '#{configuration['database']}'"
raise
end
@@ -155,6 +161,7 @@ module ActiveRecord
Migrator.migrate(migrations_paths, version) do |migration|
scope.blank? || scope == migration.scope
end
+ ActiveRecord::Base.clear_cache!
ensure
Migration.verbose = verbose_was
end
@@ -278,8 +285,7 @@ module ActiveRecord
def each_current_configuration(environment)
environments = [environment]
- # add test environment only if no RAILS_ENV was specified.
- environments << 'test' if environment == 'development' && ENV['RAILS_ENV'].nil?
+ environments << 'test' if environment == 'development'
configurations = ActiveRecord::Base.configurations.values_at(*environments)
configurations.compact.each do |configuration|
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 4cf32c2abe..b1a0ad0115 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -131,7 +131,7 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
'sslca' => '--ssl-ca',
'sslcert' => '--ssl-cert',
'sslcapath' => '--ssl-capath',
- 'sslcipher' => '--ssh-cipher',
+ 'sslcipher' => '--ssl-cipher',
'sslkey' => '--ssl-key'
}.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 8b4874044c..b19ab57ee4 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -2,6 +2,7 @@ module ActiveRecord
module Tasks # :nodoc:
class PostgreSQLDatabaseTasks # :nodoc:
DEFAULT_ENCODING = ENV['CHARSET'] || 'utf8'
+ ON_ERROR_STOP_1 = 'ON_ERROR_STOP=1'.freeze
delegate :connection, :establish_connection, :clear_active_connections!,
to: ActiveRecord::Base
@@ -67,7 +68,7 @@ module ActiveRecord
def structure_load(filename)
set_psql_env
- args = [ '-q', '-f', filename, configuration['database'] ]
+ args = [ '-v', ON_ERROR_STOP_1, '-q', '-f', filename, configuration['database'] ]
run_cmd('psql', args, 'loading' )
end
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index e210e94f00..4911d93dd9 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -61,7 +61,7 @@ module ActiveRecord
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(:datetime, Type::DateTime, override: false)
register(:decimal, Type::Decimal, override: false)
register(:float, Type::Float, override: false)
register(:integer, Type::Integer, override: false)
diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb
index 097d1bd363..513c938088 100644
--- a/activerecord/lib/active_record/type/internal/abstract_json.rb
+++ b/activerecord/lib/active_record/type/internal/abstract_json.rb
@@ -17,11 +17,7 @@ module ActiveRecord
end
def serialize(value)
- if value.is_a?(::Array) || value.is_a?(::Hash)
- ::ActiveSupport::JSON.encode(value)
- else
- value
- end
+ ::ActiveSupport::JSON.encode(value)
end
def accessor
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 4ff0740cfb..a3a5241780 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -32,7 +32,7 @@ module ActiveRecord
def changed_in_place?(raw_old_value, value)
return false if value.nil?
- raw_new_value = serialize(value)
+ raw_new_value = encoded(value)
raw_old_value.nil? != raw_new_value.nil? ||
subtype.changed_in_place?(raw_old_value, raw_new_value)
end
@@ -52,6 +52,12 @@ module ActiveRecord
def default_value?(value)
value == coder.load(nil)
end
+
+ def encoded(value)
+ unless default_value?(value)
+ coder.dump(value)
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb
index 70988d84ff..7da49e43c7 100644
--- a/activerecord/lib/active_record/type/time.rb
+++ b/activerecord/lib/active_record/type/time.rb
@@ -2,6 +2,18 @@ module ActiveRecord
module Type
class Time < ActiveModel::Type::Time
include Internal::Timezone
+
+ class Value < DelegateClass(::Time) # :nodoc:
+ end
+
+ def serialize(value)
+ case value = super
+ when ::Time
+ Value.new(value)
+ else
+ value
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 6677e6dc5f..ecaf04e39e 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -45,7 +45,7 @@ module ActiveRecord
end
# Attempts to save the record just like {ActiveRecord::Base#save}[rdoc-ref:Base#save] but
- # will raise a ActiveRecord::RecordInvalid exception instead of returning +false+ if the record is not valid.
+ # will raise an ActiveRecord::RecordInvalid exception instead of returning +false+ if the record is not valid.
def save!(options={})
perform_validations(options) ? super : raise_validation_error
end
diff --git a/activerecord/lib/active_record/validations/absence.rb b/activerecord/lib/active_record/validations/absence.rb
index 2e19e6dc5c..641d041f3d 100644
--- a/activerecord/lib/active_record/validations/absence.rb
+++ b/activerecord/lib/active_record/validations/absence.rb
@@ -2,7 +2,6 @@ module ActiveRecord
module Validations
class AbsenceValidator < ActiveModel::Validations::AbsenceValidator # :nodoc:
def validate_each(record, attribute, association_or_value)
- return unless should_validate?(record)
if record.class._reflect_on_association(attribute)
association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
end
diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb
index 69e048eef1..0e0cebce4a 100644
--- a/activerecord/lib/active_record/validations/length.rb
+++ b/activerecord/lib/active_record/validations/length.rb
@@ -2,23 +2,11 @@ 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
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index 7e85ed43ac..ad82ea66c4 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -2,7 +2,6 @@ module ActiveRecord
module Validations
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
def validate_each(record, attribute, association_or_value)
- return unless should_validate?(record)
if record.class._reflect_on_association(attribute)
association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index a376e2a17f..ec9f498c40 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -11,15 +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)
- if record.persisted? && finder_class.primary_key.to_s != attribute.to_s
+ if record.persisted?
if finder_class.primary_key
- relation = relation.where.not(finder_class.primary_key => record.id)
+ relation = relation.where.not(finder_class.primary_key => record.id_was || record.id)
else
raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
end
@@ -57,21 +56,15 @@ module ActiveRecord
value = value.attributes[reflection.klass.primary_key] unless value.nil?
end
- attribute_name = attribute.to_s
-
# the attribute may be an aliased attribute
- if klass.attribute_aliases[attribute_name]
- attribute = klass.attribute_aliases[attribute_name]
- attribute_name = attribute.to_s
+ if klass.attribute_alias?(attribute)
+ attribute = klass.attribute_alias(attribute)
end
+ attribute_name = attribute.to_s
+
column = klass.columns_hash[attribute_name]
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
comparison = if !options[:case_sensitive] && !value.nil?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
@@ -82,11 +75,9 @@ module ActiveRecord
if value.nil?
klass.unscoped.where(comparison)
else
- bind = Relation::QueryAttribute.new(attribute.to_s, value, Type::Value.new)
+ bind = Relation::QueryAttribute.new(attribute_name, value, cast_type)
klass.unscoped.where(comparison, bind)
end
- rescue RangeError
- klass.none
end
def scope_relation(record, table, relation)
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 107f107dc4..481c70201b 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -19,7 +19,11 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
def change
create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t|
<%- attributes.each do |attribute| -%>
+ <%- if attribute.reference? -%>
+ t.references :<%= attribute.name %><%= attribute.inject_options %>
+ <%- else -%>
<%= '# ' unless attribute.has_index? -%>t.index <%= attribute.index_name %><%= attribute.inject_index_options %>
+ <%- end -%>
<%- end -%>
end
end
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index 7395839fca..0d72913258 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -21,11 +21,13 @@ module ActiveRecord
end
def create_model_file
+ generate_application_record
template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
end
def create_module_file
return if regular_class_path.empty?
+ generate_application_record
template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke
end
@@ -37,26 +39,29 @@ module ActiveRecord
attributes.select { |a| !a.reference? && a.has_index? }
end
+ # FIXME: Change this file to a symlink once RubyGems 2.5.0 is required.
+ def generate_application_record
+ if self.behavior == :invoke && !application_record_exist?
+ template 'application_record.rb', application_record_file_name
+ end
+ end
+
# Used by the migration template to determine the parent name of the model
def parent_class_name
- options[:parent] || determine_default_parent_class
+ options[:parent] || 'ApplicationRecord'
end
- def determine_default_parent_class
- application_record = nil
-
- in_root do
- application_record = if mountable_engine?
- File.exist?("app/models/#{namespaced_path}/application_record.rb")
- else
- File.exist?('app/models/application_record.rb')
- end
- end
+ def application_record_exist?
+ file_exist = nil
+ in_root { file_exist = File.exist?(application_record_file_name) }
+ file_exist
+ end
- if application_record
- "ApplicationRecord"
+ def application_record_file_name
+ @application_record_file_name ||= if mountable_engine?
+ "app/models/#{namespaced_path}/application_record.rb"
else
- "ActiveRecord::Base"
+ 'app/models/application_record.rb'
end
end
end
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb b/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb
new file mode 100644
index 0000000000..60050e0bf8
--- /dev/null
+++ b/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb
@@ -0,0 +1,5 @@
+<% module_namespacing do -%>
+class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+end
+<% end -%>
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 0ee147cdba..34e3bc9d66 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -2,6 +2,7 @@ require "cases/helper"
require "models/book"
require "models/post"
require "models/author"
+require "models/event"
module ActiveRecord
class AdapterTest < ActiveRecord::TestCase
@@ -11,7 +12,8 @@ module ActiveRecord
##
# PostgreSQL does not support null bytes in strings
- unless current_adapter?(:PostgreSQLAdapter)
+ unless current_adapter?(:PostgreSQLAdapter) ||
+ (current_adapter?(:SQLite3Adapter) && !ActiveRecord::Base.connection.prepared_statements)
def test_update_prepared_statement
b = Book.create(name: "my \x00 book")
b.reload
@@ -22,6 +24,12 @@ module ActiveRecord
end
end
+ def test_create_record_with_pk_as_zero
+ Book.create(id: 0)
+ assert_equal 0, Book.find(0).id
+ assert_nothing_raised { Book.destroy(0) }
+ end
+
def test_tables
tables = nil
ActiveSupport::Deprecation.silence { tables = @connection.tables }
@@ -79,6 +87,17 @@ module ActiveRecord
@connection.remove_index(:accounts, :name => idx_name) rescue nil
end
+ def test_remove_index_when_name_and_wrong_column_name_specified
+ index_name = "accounts_idx"
+
+ @connection.add_index :accounts, :firm_id, :name => index_name
+ assert_raises ArgumentError do
+ @connection.remove_index :accounts, :name => index_name, :column => :wrong_column_name
+ end
+ ensure
+ @connection.remove_index(:accounts, :name => index_name)
+ end
+
def test_current_database
if @connection.respond_to?(:current_database)
assert_equal ARTest.connection_config['arunit']['database'], @connection.current_database
@@ -193,6 +212,14 @@ module ActiveRecord
assert_not_nil error.cause
end
+
+ def test_value_limit_violations_are_translated_to_specific_exception
+ error = assert_raises(ActiveRecord::ValueTooLong) do
+ Event.create(title: 'abcdefgh')
+ end
+
+ assert_not_nil error.cause
+ end
end
def test_disable_referential_integrity
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 99f97c7914..95d1f6b8a3 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -63,14 +63,14 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def (ActiveRecord::Base.connection).data_source_exists?(*); false; end
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
- expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB"
+ expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`)) ENGINE=InnoDB"
actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, type: type
end
assert_equal expected, actual
end
- expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)) ) ENGINE=InnoDB"
+ expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10))) ENGINE=InnoDB"
actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, length: 10, using: :btree
end
@@ -155,7 +155,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false)
ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false)
- expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query"
+ expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query"
actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
t.index :zip
end
diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
index 8575df9e43..739bb275ce 100644
--- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
@@ -43,11 +43,16 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
boolean = BooleanType.create!(archived: true, published: true)
attributes = boolean.reload.attributes_before_type_cast
-
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
+ boolean = BooleanType.create!(archived: false, published: false)
+ attributes = boolean.reload.attributes_before_type_cast
+ assert_equal 0, attributes["archived"]
+ assert_equal "0", attributes["published"]
+
assert_equal 1, @connection.type_cast(true)
+ assert_equal 0, @connection.type_cast(false)
end
test "test type casting without emulated booleans" do
@@ -55,11 +60,16 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
boolean = BooleanType.create!(archived: true, published: true)
attributes = boolean.reload.attributes_before_type_cast
-
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
+ boolean = BooleanType.create!(archived: false, published: false)
+ attributes = boolean.reload.attributes_before_type_cast
+ assert_equal 0, attributes["archived"]
+ assert_equal "0", attributes["published"]
+
assert_equal 1, @connection.type_cast(true)
+ assert_equal 0, @connection.type_cast(false)
end
test "with booleans stored as 1 and 0" do
diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
index 963116f08a..9cb05119a2 100644
--- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -51,4 +51,13 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
assert_no_match(/binary/i, cs_uniqueness_query)
end
+
+ def test_case_sensitive_comparison_for_binary_column
+ CollationTest.validates_uniqueness_of(:binary_column, case_sensitive: true)
+ CollationTest.create!(binary_column: 'A')
+ invalid = CollationTest.new(binary_column: 'A')
+ queries = assert_sql { invalid.save }
+ bin_uniqueness_query = queries.detect { |q| q.match(/binary_column/) }
+ assert_no_match(/\bBINARY\b/, bin_uniqueness_query)
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
index 668c07dacb..c8028b6b36 100644
--- a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
@@ -49,6 +49,6 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase
test "schema dump includes collation" do
output = dump_table_schema("charset_collations")
assert_match %r{t.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output
- assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output
+ assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+collation: "ucs2_unicode_ci"$}, output
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 575138eb2a..fe610ae951 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -69,40 +69,48 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_mysql_default_in_strict_mode
- result = @connection.exec_query "SELECT @@SESSION.sql_mode"
- assert_equal [["STRICT_ALL_TABLES"]], result.rows
+ result = @connection.select_value("SELECT @@SESSION.sql_mode")
+ assert_match %r(STRICT_ALL_TABLES), result
end
def test_mysql_strict_mode_disabled
run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.merge({:strict => false}))
- result = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
- assert_equal [['']], result.rows
+ ActiveRecord::Base.establish_connection(orig_connection.merge(strict: false))
+ result = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.sql_mode")
+ assert_no_match %r(STRICT_ALL_TABLES), result
+ end
+ end
+
+ def test_mysql_strict_mode_specified_default
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.merge(strict: :default))
+ global_sql_mode = ActiveRecord::Base.connection.select_value("SELECT @@GLOBAL.sql_mode")
+ session_sql_mode = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.sql_mode")
+ assert_equal global_sql_mode, session_sql_mode
+ end
+ end
+
+ def test_mysql_sql_mode_variable_overrides_strict_mode
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' }))
+ result = ActiveRecord::Base.connection.select_value('SELECT @@SESSION.sql_mode')
+ assert_no_match %r(STRICT_ALL_TABLES), result
end
end
def test_passing_arbitary_flags_to_adapter
- run_without_connection do |orig_connection|
+ run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge({flags: Mysql2::Client::COMPRESS}))
assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags]
end
end
-
+
def test_passing_flags_by_array_to_adapter
- run_without_connection do |orig_connection|
+ run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge({flags: ['COMPRESS'] }))
assert_equal ["COMPRESS", "FOUND_ROWS"], ActiveRecord::Base.connection.raw_connection.query_options[:flags]
end
end
-
- def test_mysql_strict_mode_specified_default
- run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.merge({strict: :default}))
- global_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.sql_mode"
- session_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
- assert_equal global_sql_mode.rows, session_sql_mode.rows
- end
- end
def test_mysql_set_session_variable
run_without_connection do |orig_connection|
@@ -112,14 +120,6 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
end
- def test_mysql_sql_mode_variable_overrides_strict_mode
- run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' }))
- result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode'
- assert_not_equal [['STRICT_ALL_TABLES']], result.rows
- end
- end
-
def test_mysql_set_session_variable_to_default
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}}))
@@ -144,7 +144,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_get_and_release_advisory_lock
- lock_name = "test_lock_name"
+ lock_name = "test lock'n'name"
got_lock = @connection.get_advisory_lock(lock_name)
assert got_lock, "get_advisory_lock should have returned true but it didn't"
@@ -159,7 +159,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_release_non_existent_advisory_lock
- lock_name = "fake_lock_name"
+ lock_name = "fake lock'n'name"
released_non_existent_lock = @connection.release_advisory_lock(lock_name)
assert_equal released_non_existent_lock, false,
'expected release_advisory_lock to return false when there was no lock to release'
@@ -168,6 +168,6 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
protected
def test_lock_free(lock_name)
- @connection.select_value("SELECT IS_FREE_LOCK('#{lock_name}');") == 1
+ @connection.select_value("SELECT IS_FREE_LOCK(#{@connection.quote(lock_name)})") == 1
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
new file mode 100644
index 0000000000..e349c67c93
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
@@ -0,0 +1,45 @@
+require "cases/helper"
+
+class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ test 'microsecond precision for MySQL gte 5.6.4' do
+ stub_version '5.6.4'
+ assert_microsecond_precision
+ end
+
+ test 'no microsecond precision for MySQL lt 5.6.4' do
+ stub_version '5.6.3'
+ assert_no_microsecond_precision
+ end
+
+ test 'microsecond precision for MariaDB gte 5.3.0' do
+ stub_version '5.5.5-10.1.8-MariaDB-log'
+ assert_microsecond_precision
+ end
+
+ test 'no microsecond precision for MariaDB lt 5.3.0' do
+ stub_version '5.2.9-MariaDB'
+ assert_no_microsecond_precision
+ end
+
+ private
+ def assert_microsecond_precision
+ assert_match_quoted_microsecond_datetime(/\.000001\z/)
+ end
+
+ def assert_no_microsecond_precision
+ assert_match_quoted_microsecond_datetime(/\d\z/)
+ end
+
+ def assert_match_quoted_microsecond_datetime(match)
+ assert_match match, @connection.quoted_date(Time.now.change(usec: 1))
+ end
+
+ def stub_version(full_version_string)
+ @connection.stubs(:full_version).returns(full_version_string)
+ @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index bb89e893e0..35dbc76d1b 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -18,4 +18,9 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
column = EnumTest.columns_hash['enum_column']
assert_not column.unsigned?
end
+
+ def test_should_not_be_bigint
+ column = EnumTest.columns_hash['enum_column']
+ assert_not column.bigint?
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index 4fc7414b18..b783b5fcd9 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -2,26 +2,20 @@ require "cases/helper"
require 'models/developer'
require 'models/computer'
-module ActiveRecord
- module ConnectionAdapters
- class Mysql2Adapter
- class ExplainTest < ActiveRecord::Mysql2TestCase
- fixtures :developers
+class Mysql2ExplainTest < ActiveRecord::Mysql2TestCase
+ fixtures :developers
- def test_explain_for_one_query
- explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
- end
+ def test_explain_for_one_query
+ explain = Developer.where(id: 1).explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %r(developers |.* const), explain
+ end
- def test_explain_with_eager_loading
- 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` = 1), explain
- assert_match %r(audit_logs |.* ALL), explain
- end
- end
- end
+ def test_explain_with_eager_loading
+ 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` = 1), explain
+ assert_match %r(audit_logs |.* ALL), explain
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb
index c8c933af5e..9c3fef1b59 100644
--- a/activerecord/test/cases/adapters/mysql2/json_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/json_test.rb
@@ -161,12 +161,19 @@ class Mysql2JSONTest < ActiveRecord::Mysql2TestCase
assert_not json.changed?
end
- def test_assigning_invalid_json
- json = JsonDataType.new
+ def test_assigning_string_literal
+ json = JsonDataType.create(payload: "foo")
+ assert_equal "foo", json.payload
+ end
- json.payload = 'foo'
+ def test_assigning_number
+ json = JsonDataType.create(payload: 1.234)
+ assert_equal 1.234, json.payload
+ end
- assert_nil json.payload
+ def test_assigning_boolean
+ json = JsonDataType.create(payload: true)
+ assert_equal true, json.payload
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
index 4efd728754..61dd0828d0 100644
--- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
@@ -1,10 +1,33 @@
require "cases/helper"
+require "support/ddl_helper"
class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
+ include DdlHelper
+
def setup
@conn = ActiveRecord::Base.connection
end
+ def test_exec_query_nothing_raises_with_no_result_queries
+ assert_nothing_raised do
+ with_example_table do
+ @conn.exec_query('INSERT INTO ex (number) VALUES (1)')
+ @conn.exec_query('DELETE FROM ex WHERE number = 1')
+ end
+ end
+ end
+
+ def test_valid_column
+ with_example_table do
+ column = @conn.columns('ex').find { |col| col.name == 'id' }
+ assert @conn.valid_type?(column.type)
+ end
+ end
+
+ def test_invalid_column
+ assert_not @conn.valid_type?(:foobar)
+ end
+
def test_columns_for_distinct_zero_orders
assert_equal "posts.id",
@conn.columns_for_distinct("posts.id", [])
@@ -41,4 +64,10 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
assert_equal "posts.id, posts.created_at AS alias_0",
@conn.columns_for_distinct("posts.id", [order])
end
+
+ private
+
+ def with_example_table(definition = 'id int auto_increment primary key, number int, data varchar(255)', &block)
+ super(@conn, 'ex', definition, &block)
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/quoting_test.rb b/activerecord/test/cases/adapters/mysql2/quoting_test.rb
deleted file mode 100644
index 2de7e1b526..0000000000
--- a/activerecord/test/cases/adapters/mysql2/quoting_test.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require "cases/helper"
-
-class Mysql2QuotingTest < ActiveRecord::Mysql2TestCase
- setup do
- @connection = ActiveRecord::Base.connection
- end
-
- test 'quoted date precision for gte 5.6.4' do
- @connection.stubs(:full_version).returns('5.6.4')
- @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
- t = Time.now.change(usec: 1)
- assert_match(/\.000001\z/, @connection.quoted_date(t))
- end
-
- test 'quoted date precision for lt 5.6.4' do
- @connection.stubs(:full_version).returns('5.6.3')
- @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
- t = Time.now.change(usec: 1)
- assert_no_match(/\.000001\z/, @connection.quoted_date(t))
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index 9a9a4fed42..7c89fda582 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -33,11 +33,6 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
end
end
- def test_key_limit_max_matches_max
- assert_equal ActiveRecord::InternalMetadata::KEY_LIMIT ,ActiveRecord::ConnectionAdapters::Mysql2Adapter::MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN
- end
-
-
private
def with_encoding_utf8mb4
diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
new file mode 100644
index 0000000000..0e37c70e5c
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
@@ -0,0 +1,62 @@
+require "cases/helper"
+require 'support/connection_helper'
+
+module ActiveRecord
+ class Mysql2TransactionTest < ActiveRecord::Mysql2TestCase
+ self.use_transactional_tests = false
+
+ class Sample < ActiveRecord::Base
+ self.table_name = 'samples'
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.clear_cache!
+
+ @connection.transaction do
+ @connection.drop_table 'samples', if_exists: true
+ @connection.create_table('samples') do |t|
+ t.integer 'value'
+ end
+ end
+
+ Sample.reset_column_information
+ end
+
+ teardown do
+ @connection.drop_table 'samples', if_exists: true
+ end
+
+ test "raises error when a serialization failure occurs" do
+ assert_raises(ActiveRecord::TransactionSerializationError) do
+ thread = Thread.new do
+ Sample.transaction isolation: :serializable do
+ Sample.delete_all
+
+ 10.times do |i|
+ sleep 0.1
+
+ Sample.create value: i
+ end
+ end
+ end
+
+ sleep 0.1
+
+ Sample.transaction isolation: :serializable do
+ Sample.delete_all
+
+ 10.times do |i|
+ sleep 0.1
+
+ Sample.create value: i
+ end
+
+ sleep 1
+ end
+
+ thread.join
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
index c95a64cc16..3df11ce11b 100644
--- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
@@ -28,10 +28,10 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
end
test "minus value is out of range" do
- assert_raise(RangeError) do
+ assert_raise(ActiveModel::RangeError) do
UnsignedType.create(unsigned_integer: -10)
end
- assert_raise(RangeError) do
+ assert_raise(ActiveModel::RangeError) do
UnsignedType.create(unsigned_bigint: -10)
end
assert_raise(ActiveRecord::StatementInvalid) do
@@ -58,7 +58,7 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
test "schema dump includes unsigned option" do
schema = dump_table_schema "unsigned_types"
assert_match %r{t.integer\s+"unsigned_integer",\s+unsigned: true$}, schema
- assert_match %r{t.integer\s+"unsigned_bigint",\s+limit: 8,\s+unsigned: true$}, schema
+ assert_match %r{t.bigint\s+"unsigned_bigint",\s+unsigned: true$}, schema
assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema
assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index ed44bf7362..439e2ce6f7 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -28,7 +28,13 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) { |*| false }
expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
- assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'")
+ assert_equal expected, add_index(:people, :last_name, unique: true, where: "state = 'active'")
+
+ expected = %(CREATE UNIQUE INDEX "index_people_on_lower_last_name" ON "people" (lower(last_name)))
+ assert_equal expected, add_index(:people, 'lower(last_name)', unique: true)
+
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name_varchar_pattern_ops" ON "people" (last_name varchar_pattern_ops))
+ assert_equal expected, add_index(:people, 'last_name varchar_pattern_ops', unique: true)
expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" ("last_name"))
assert_equal expected, add_index(:people, :last_name, algorithm: :concurrently)
@@ -39,16 +45,17 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
assert_equal expected, add_index(:people, :last_name, using: type, algorithm: :concurrently)
+
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING #{type} ("last_name") WHERE state = 'active')
+ assert_equal expected, add_index(:people, :last_name, using: type, unique: true, where: "state = 'active'")
+
+ expected = %(CREATE UNIQUE INDEX "index_people_on_lower_last_name" ON "people" USING #{type} (lower(last_name)))
+ assert_equal expected, add_index(:people, 'lower(last_name)', using: type, unique: true)
end
assert_raise ArgumentError do
add_index(:people, :last_name, algorithm: :copy)
end
- expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name"))
- assert_equal expected, add_index(:people, :last_name, :unique => true, :using => :gist)
-
- expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name") WHERE state = 'active')
- assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'", :using => :gist)
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_exists?
end
@@ -69,6 +76,11 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_for_remove
end
+ def test_remove_index_when_name_is_specified
+ expected = %(DROP INDEX CONCURRENTLY "index_people_on_last_name")
+ assert_equal expected, remove_index(:people, name: "index_people_on_last_name", algorithm: :concurrently)
+ end
+
private
def method_missing(method_symbol, *arguments)
ActiveRecord::Base.connection.send(method_symbol, *arguments)
diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
index 6f72fa6e0f..cec6081aec 100644
--- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
@@ -57,9 +57,11 @@ class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase
assert_match %r{t\.bit_varying\s+"a_bit_varying",\s+limit: 4,\s+default: "0011"$}, output
end
- def test_assigning_invalid_hex_string_raises_exception
- assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit: "FF" }
- assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit_varying: "FF" }
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_assigning_invalid_hex_string_raises_exception
+ assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit: "FF" }
+ assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit_varying: "F" }
+ end
end
def test_roundtrip
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index b6bb1929e6..7adc070430 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -1,6 +1,9 @@
require "cases/helper"
+require 'support/schema_dumping_helper'
class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+
class ByteaDataType < ActiveRecord::Base
self.table_name = 'bytea_data_type'
end
@@ -122,4 +125,10 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
obj.reload
assert_equal "hello world", obj.serialized
end
+
+ def test_schema_dumping
+ output = dump_table_schema("bytea_data_type")
+ assert_match %r{t\.binary\s+"payload"$}, output
+ assert_match %r{t\.binary\s+"serialized"$}, output
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index d559de3e28..f8403bfe1a 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -125,14 +125,16 @@ module ActiveRecord
assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
- def test_statement_key_is_logged
- bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new)
- @connection.exec_query('SELECT $1::integer', 'SQL', [bind], prepare: true)
- name = @subscriber.payloads.last[:statement_name]
- assert name
- 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
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_statement_key_is_logged
+ bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new)
+ @connection.exec_query('SELECT $1::integer', 'SQL', [bind], prepare: true)
+ name = @subscriber.payloads.last[:statement_name]
+ assert name
+ 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
end
# Must have PostgreSQL >= 9.2, or with_manual_interventions set to
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 4d0fd640aa..29bf2c15ea 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -6,15 +6,15 @@ class PostgreSQLExplainTest < ActiveRecord::PostgreSQLTestCase
fixtures :developers
def test_explain_for_one_query
- explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
+ explain = Developer.where(id: 1).explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = \$?1), explain
assert_match %(QUERY PLAN), explain
end
def test_explain_with_eager_loading
- explain = Developer.where(:id => 1).includes(:audit_logs).explain
+ 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 %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = \$?1), explain
assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
index b2a805333c..b56c226763 100644
--- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
@@ -24,9 +24,9 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase
return skip("no extension support")
end
- @old_schema_migration_tabel_name = ActiveRecord::SchemaMigration.table_name
- @old_tabel_name_prefix = ActiveRecord::Base.table_name_prefix
- @old_tabel_name_suffix = ActiveRecord::Base.table_name_suffix
+ @old_schema_migration_table_name = ActiveRecord::SchemaMigration.table_name
+ @old_table_name_prefix = ActiveRecord::Base.table_name_prefix
+ @old_table_name_suffix = ActiveRecord::Base.table_name_suffix
ActiveRecord::Base.table_name_prefix = "p_"
ActiveRecord::Base.table_name_suffix = "_s"
@@ -36,11 +36,11 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase
end
def teardown
- ActiveRecord::Base.table_name_prefix = @old_tabel_name_prefix
- ActiveRecord::Base.table_name_suffix = @old_tabel_name_suffix
+ ActiveRecord::Base.table_name_prefix = @old_table_name_prefix
+ ActiveRecord::Base.table_name_suffix = @old_table_name_suffix
ActiveRecord::SchemaMigration.delete_all rescue nil
ActiveRecord::Migration.verbose = true
- ActiveRecord::SchemaMigration.table_name = @old_schema_migration_tabel_name
+ ActiveRecord::SchemaMigration.table_name = @old_schema_migration_table_name
super
end
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index 9e250c2b7c..66f0a70394 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -104,6 +104,13 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
assert_equal ActiveRecord::Point.new(1, 2), p.x
end
+ def test_empty_string_assignment
+ assert_nothing_raised { PostgresqlPoint.new(x: "") }
+
+ p = PostgresqlPoint.new(x: "")
+ assert_equal nil, p.x
+ end
+
def test_array_of_points_round_trip
expected_value = [
ActiveRecord::Point.new(1, 2),
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index b3b121b4fb..663de680b5 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -38,7 +38,7 @@ module PostgresqlJSONSharedTestCases
end
def test_default
- @connection.add_column 'json_data_type', 'permissions', column_type, default: '{"users": "read", "posts": ["read", "write"]}'
+ @connection.add_column 'json_data_type', 'permissions', column_type, default: {"users": "read", "posts": ["read", "write"]}
JsonDataType.reset_column_information
assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.column_defaults['permissions'])
@@ -178,12 +178,19 @@ module PostgresqlJSONSharedTestCases
assert_not json.changed?
end
- def test_assigning_invalid_json
- json = JsonDataType.new
+ def test_assigning_string_literal
+ json = JsonDataType.create(payload: "foo")
+ assert_equal "foo", json.payload
+ end
- json.payload = 'foo'
+ def test_assigning_number
+ json = JsonDataType.create(payload: 1.234)
+ assert_equal 1.234, json.payload
+ end
- assert_nil json.payload
+ def test_assigning_boolean
+ json = JsonDataType.create(payload: true)
+ assert_equal true, json.payload
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 f1995b243a..9832df7839 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -5,6 +5,7 @@ require 'support/connection_helper'
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapterTest < ActiveRecord::PostgreSQLTestCase
+ self.use_transactional_tests = false
include DdlHelper
include ConnectionHelper
@@ -53,62 +54,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')
end
end
- 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
- end
- end
-
- def test_insert_sql_with_quoted_schema_and_table_name
- with_example_table do
- id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)')
- expect = @connection.query('select max(id) from ex').first.first
- assert_equal expect, id
- end
- end
-
- def test_insert_sql_with_no_space_after_table_name
- with_example_table do
- id = @connection.insert_sql("insert into ex(number) values(5150)")
- expect = @connection.query('select max(id) from ex').first.first
- assert_equal expect, id
- end
- end
-
- def test_multiline_insert_sql
- with_example_table do
- id = @connection.insert_sql(<<-SQL)
- insert into ex(
- number)
- values(
- 5152
- )
- SQL
- expect = @connection.query('select max(id) from ex').first.first
- assert_equal expect, id
- end
- end
-
- def test_insert_sql_with_returning_disabled
- 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.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')
@@ -245,30 +196,6 @@ module ActiveRecord
@connection.drop_table 'ex2', if_exists: true
end
- def test_exec_insert_number
- with_example_table do
- insert(@connection, 'number' => 10)
-
- result = @connection.exec_query('SELECT number FROM ex WHERE number = 10')
-
- assert_equal 1, result.rows.length
- assert_equal 10, result.rows.last.last
- end
- end
-
- def test_exec_insert_string
- with_example_table do
- str = 'いただきます!'
- insert(@connection, 'number' => 10, 'data' => str)
-
- result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10')
-
- value = result.rows.last.last
-
- assert_equal str, value
- end
- end
-
def test_table_alias_length
assert_nothing_raised do
@connection.table_alias_length
@@ -292,33 +219,35 @@ module ActiveRecord
end
end
- def test_exec_with_binds
- with_example_table do
- 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, [bind_param(1)])
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_exec_with_binds
+ with_example_table do
+ string = @connection.quote('foo')
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ bind = Relation::QueryAttribute.new("id", 1, Type::Value.new)
+ result = @connection.exec_query('SELECT id, data FROM ex WHERE id = $1', nil, [bind])
- assert_equal [[1, 'foo']], result.rows
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
+
+ assert_equal [[1, 'foo']], result.rows
+ end
end
- end
- def test_exec_typecasts_bind_vals
- with_example_table do
- string = @connection.quote('foo')
- @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
+ def test_exec_typecasts_bind_vals
+ with_example_table do
+ string = @connection.quote('foo')
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- 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, [bind])
+ bind = Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new)
+ result = @connection.exec_query('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, result.rows.length
+ assert_equal 2, result.columns.length
- assert_equal [[1, 'foo']], result.rows
+ assert_equal [[1, 'foo']], result.rows
+ end
end
end
@@ -330,6 +259,22 @@ module ActiveRecord
end
end
+ def test_expression_index
+ with_example_table do
+ @connection.add_index 'ex', 'mod(id, 10), abs(number)', name: 'expression'
+ index = @connection.indexes('ex').find { |idx| idx.name == 'expression' }
+ assert_equal 'mod(id, 10), abs(number)', index.columns
+ end
+ end
+
+ def test_index_with_opclass
+ with_example_table do
+ @connection.add_index 'ex', 'data varchar_pattern_ops', name: 'with_opclass'
+ index = @connection.indexes('ex').find { |idx| idx.name == 'with_opclass' }
+ assert_equal 'data varchar_pattern_ops', index.columns
+ end
+ end
+
def test_columns_for_distinct_zero_orders
assert_equal "posts.id",
@connection.columns_for_distinct("posts.id", [])
@@ -444,19 +389,6 @@ module ActiveRecord
end
private
- def insert(ctx, data)
- binds = data.map { |name, value|
- bind_param(value, name)
- }
- columns = binds.map(&:name)
-
- bind_subs = columns.length.times.map { |x| "$#{x + 1}" }
-
- sql = "INSERT INTO ex (#{columns.join(", ")})
- VALUES (#{bind_subs.join(', ')})"
-
- ctx.exec_insert(sql, 'SQL', binds)
- end
def with_example_table(definition = 'id serial primary key, number integer, data character varying(255)', &block)
super(@connection, 'ex', definition, &block)
@@ -465,10 +397,6 @@ 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/prepared_statements_test.rb b/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb
new file mode 100644
index 0000000000..f1519db48b
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb
@@ -0,0 +1,22 @@
+require "cases/helper"
+require "models/developer"
+
+class PreparedStatementsTest < ActiveRecord::PostgreSQLTestCase
+ fixtures :developers
+
+ def setup
+ @default_prepared_statements = Developer.connection_config[:prepared_statements]
+ Developer.connection_config[:prepared_statements] = false
+ end
+
+ def teardown
+ Developer.connection_config[:prepared_statements] = @default_prepared_statements
+ end
+
+ def nothing_raised_with_falsy_prepared_statements
+ assert_nothing_raised do
+ Developer.where(id: 1)
+ end
+ end
+
+end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
index a0afd922b2..285a92f60e 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -55,20 +55,22 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase
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', [bind_param(1)]).first['name']
+ assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = 1")
set_session_auth
end
end
end
- def test_auth_with_bind
- assert_nothing_raised do
- set_session_auth
- 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', [bind_param(1)]).first['name']
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_auth_with_bind
+ assert_nothing_raised do
set_session_auth
+ USERS.each do |u|
+ @connection.clear_cache!
+ set_session_auth u
+ assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)])
+ set_session_auth
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 4aeca4d709..52ef07f654 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -166,22 +166,24 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
end
end
- def test_raise_wraped_exception_on_bad_prepare
+ def test_raise_wrapped_exception_on_bad_prepare
assert_raises(ActiveRecord::StatementInvalid) do
@connection.exec_query "select * from developers where id = ?", 'sql', [bind_param(1)]
end
end
- def test_schema_change_with_prepared_stmt
- altered = false
- @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
- @connection.exec_query "alter table developers add column zomg int", 'sql', []
- altered = true
- @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
- @connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_schema_change_with_prepared_stmt
+ altered = false
+ @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
+ @connection.exec_query "alter table developers add column zomg int", 'sql', []
+ altered = true
+ @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
+ @connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered
+ end
end
def test_data_source_exists?
@@ -323,7 +325,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
def test_dump_indexes_for_table_with_scheme_specified_in_name
indexes = @connection.indexes("#{SCHEMA_NAME}.#{TABLE_NAME}")
- assert_equal 4, indexes.size
+ assert_equal 5, indexes.size
end
def test_with_uppercase_index_name
@@ -447,18 +449,22 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
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(&:name)
- assert_equal 4,indexes.size
-
- do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name)
- do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name)
- do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name)
- do_dump_index_assertions_for_one_index(indexes[3], INDEX_E_NAME, fourth_index_column_name)
-
- indexes.select{|i| i.name != INDEX_E_NAME}.each do |index|
- assert_equal :btree, index.using
- end
- assert_equal :gin, indexes.select{|i| i.name == INDEX_E_NAME}[0].using
- assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN]
+ assert_equal 5, indexes.size
+
+ index_a, index_b, index_c, index_d, index_e = indexes
+
+ do_dump_index_assertions_for_one_index(index_a, INDEX_A_NAME, first_index_column_name)
+ do_dump_index_assertions_for_one_index(index_b, INDEX_B_NAME, second_index_column_name)
+ do_dump_index_assertions_for_one_index(index_d, INDEX_D_NAME, third_index_column_name)
+ do_dump_index_assertions_for_one_index(index_e, INDEX_E_NAME, fourth_index_column_name)
+
+ assert_equal :btree, index_a.using
+ assert_equal :btree, index_b.using
+ assert_equal :gin, index_c.using
+ assert_equal :btree, index_d.using
+ assert_equal :gin, index_e.using
+
+ assert_equal :desc, index_d.orders[INDEX_D_COLUMN]
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/serial_test.rb b/activerecord/test/cases/adapters/postgresql/serial_test.rb
index 7d30db247b..8abe064bf1 100644
--- a/activerecord/test/cases/adapters/postgresql/serial_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/serial_test.rb
@@ -10,6 +10,7 @@ class PostgresqlSerialTest < ActiveRecord::PostgreSQLTestCase
@connection = ActiveRecord::Base.connection
@connection.create_table "postgresql_serials", force: true do |t|
t.serial :seq
+ t.integer :serials_id, default: -> { "nextval('postgresql_serials_id_seq')" }
end
end
@@ -24,9 +25,21 @@ class PostgresqlSerialTest < ActiveRecord::PostgreSQLTestCase
assert column.serial?
end
+ def test_not_serial_column
+ column = PostgresqlSerial.columns_hash["serials_id"]
+ assert_equal :integer, column.type
+ assert_equal "integer", column.sql_type
+ assert_not column.serial?
+ end
+
def test_schema_dump_with_shorthand
output = dump_table_schema "postgresql_serials"
- assert_match %r{t\.serial\s+"seq"}, output
+ assert_match %r{t\.serial\s+"seq",\s+null: false$}, output
+ end
+
+ def test_schema_dump_with_not_serial
+ output = dump_table_schema "postgresql_serials"
+ assert_match %r{t\.integer\s+"serials_id",\s+default: -> \{ "nextval\('postgresql_serials_id_seq'::regclass\)" \}$}, output
end
end
@@ -39,6 +52,7 @@ class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase
@connection = ActiveRecord::Base.connection
@connection.create_table "postgresql_big_serials", force: true do |t|
t.bigserial :seq
+ t.bigint :serials_id, default: -> { "nextval('postgresql_big_serials_id_seq')" }
end
end
@@ -53,8 +67,20 @@ class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase
assert column.serial?
end
+ def test_not_bigserial_column
+ column = PostgresqlBigSerial.columns_hash["serials_id"]
+ assert_equal :integer, column.type
+ assert_equal "bigint", column.sql_type
+ assert_not column.serial?
+ end
+
def test_schema_dump_with_shorthand
output = dump_table_schema "postgresql_big_serials"
- assert_match %r{t\.bigserial\s+"seq"}, output
+ assert_match %r{t\.bigserial\s+"seq",\s+null: false$}, output
+ end
+
+ def test_schema_dump_with_not_bigserial
+ output = dump_table_schema "postgresql_big_serials"
+ assert_match %r{t\.bigint\s+"serials_id",\s+default: -> \{ "nextval\('postgresql_big_serials_id_seq'::regclass\)" \}$}, output
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
new file mode 100644
index 0000000000..e76705a802
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
@@ -0,0 +1,72 @@
+require "cases/helper"
+require 'support/connection_helper'
+
+module ActiveRecord
+ class PostgresqlTransactionTest < ActiveRecord::PostgreSQLTestCase
+ self.use_transactional_tests = false
+
+ class Sample < ActiveRecord::Base
+ self.table_name = 'samples'
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+
+ @connection.transaction do
+ @connection.drop_table 'samples', if_exists: true
+ @connection.create_table('samples') do |t|
+ t.integer 'value'
+ end
+ end
+
+ Sample.reset_column_information
+ end
+
+ teardown do
+ @connection.drop_table 'samples', if_exists: true
+ end
+
+ test "raises error when a serialization failure occurs" do
+ with_warning_suppression do
+ assert_raises(ActiveRecord::TransactionSerializationError) do
+ thread = Thread.new do
+ Sample.transaction isolation: :serializable do
+ Sample.delete_all
+
+ 10.times do |i|
+ sleep 0.1
+
+ Sample.create value: i
+ end
+ end
+ end
+
+ sleep 0.1
+
+ Sample.transaction isolation: :serializable do
+ Sample.delete_all
+
+ 10.times do |i|
+ sleep 0.1
+
+ Sample.create value: i
+ end
+
+ sleep 1
+ end
+
+ thread.join
+ end
+ end
+ end
+
+ protected
+
+ def with_warning_suppression
+ log_level = @connection.client_min_messages
+ @connection.client_min_messages = 'error'
+ yield
+ @connection.client_min_messages = log_level
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
index 77a99ca778..ea0f0b8fa5 100644
--- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
@@ -18,7 +18,7 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase
bigint_array = @connection.type_map.lookup(1016, -1, "bigint[]")
big_array = [123456789123456789]
- assert_raises(RangeError) { int_array.serialize(big_array) }
+ assert_raises(ActiveModel::RangeError) { int_array.serialize(big_array) }
assert_equal "{123456789123456789}", bigint_array.serialize(big_array)
end
@@ -27,7 +27,7 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase
bigint_range = @connection.type_map.lookup(3926, -1, "int8range")
big_range = 0..123456789123456789
- assert_raises(RangeError) { int_range.serialize(big_range) }
+ assert_raises(ActiveModel::RangeError) { int_range.serialize(big_range) }
assert_equal "[0,123456789123456789]", bigint_range.serialize(big_range)
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index 2aec322582..a1a6e5f16a 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -2,26 +2,20 @@ require "cases/helper"
require 'models/developer'
require 'models/computer'
-module ActiveRecord
- module ConnectionAdapters
- class SQLite3Adapter
- class ExplainTest < ActiveRecord::SQLite3TestCase
- fixtures :developers
+class SQLite3ExplainTest < ActiveRecord::SQLite3TestCase
+ fixtures :developers
- def test_explain_for_one_query
- explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain
- assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
- end
+ def test_explain_for_one_query
+ explain = Developer.where(id: 1).explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\?|1)), explain
+ assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
+ end
- def test_explain_with_eager_loading
- explain = Developer.where(:id => 1).includes(:audit_logs).explain
- assert_match %(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" = 1), explain
- assert_match(/(SCAN )?TABLE audit_logs/, explain)
- end
- end
- end
+ def test_explain_with_eager_loading
+ explain = Developer.where(id: 1).includes(:audit_logs).explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\?|1)), explain
+ assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
+ assert_match(/(SCAN )?TABLE audit_logs/, explain)
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index 87a892db37..fe2425b845 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -3,93 +3,92 @@ require 'bigdecimal'
require 'yaml'
require 'securerandom'
-module ActiveRecord
- module ConnectionAdapters
- class SQLite3Adapter
- class QuotingTest < ActiveRecord::SQLite3TestCase
- def setup
- @conn = Base.sqlite3_connection :database => ':memory:',
- :adapter => 'sqlite3',
- :timeout => 100
- end
-
- def test_type_cast_binary_encoding_without_logger
- @conn.extend(Module.new { def logger; end })
- binary = SecureRandom.hex
- expected = binary.dup.encode!(Encoding::UTF_8)
- assert_equal expected, @conn.type_cast(binary)
- end
-
- def test_type_cast_symbol
- 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)
- end
-
- def test_type_cast_time
- time = Time.now
- expected = @conn.quoted_date(time)
- assert_equal expected, @conn.type_cast(time)
- end
-
- def test_type_cast_numeric
- 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)
- end
-
- def test_type_cast_true
- assert_equal 't', @conn.type_cast(true)
- end
-
- def test_type_cast_false
- 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)
- end
-
- def test_type_cast_unknown_should_raise_error
- obj = Class.new.new
- assert_raise(TypeError) { @conn.type_cast(obj) }
- end
-
- def test_type_cast_object_which_responds_to_quoted_id
- quoted_id_obj = Class.new {
- def quoted_id
- "'zomg'"
- end
-
- def id
- 10
- end
- }.new
- 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) }
- end
-
- def test_quoting_binary_strings
- value = "hello".encode('ascii-8bit')
- type = Type::String.new
-
- assert_equal "'hello'", @conn.quote(type.serialize(value))
- end
+class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
+ def setup
+ @conn = ActiveRecord::Base.connection
+ end
+
+ def test_type_cast_binary_encoding_without_logger
+ @conn.extend(Module.new { def logger; end })
+ binary = SecureRandom.hex
+ expected = binary.dup.encode!(Encoding::UTF_8)
+ assert_equal expected, @conn.type_cast(binary)
+ end
+
+ def test_type_cast_symbol
+ 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)
+ end
+
+ def test_type_cast_time
+ time = Time.now
+ expected = @conn.quoted_date(time)
+ assert_equal expected, @conn.type_cast(time)
+ end
+
+ def test_type_cast_numeric
+ 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)
+ end
+
+ def test_type_cast_true
+ assert_equal 't', @conn.type_cast(true)
+ end
+
+ def test_type_cast_false
+ 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)
+ end
+
+ def test_type_cast_unknown_should_raise_error
+ obj = Class.new.new
+ assert_raise(TypeError) { @conn.type_cast(obj) }
+ end
+
+ def test_type_cast_object_which_responds_to_quoted_id
+ quoted_id_obj = Class.new {
+ def quoted_id
+ "'zomg'"
+ end
+
+ def id
+ 10
end
- end
+ }.new
+ 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) }
+ end
+
+ def test_quoting_binary_strings
+ value = "hello".encode('ascii-8bit')
+ type = ActiveRecord::Type::String.new
+
+ assert_equal "'hello'", @conn.quote(type.serialize(value))
+ end
+
+ def test_quoted_time_returns_date_qualified_time
+ value = ::Time.utc(2000, 1, 1, 12, 30, 0, 999999)
+ type = ActiveRecord::Type::Time.new
+
+ assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value))
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 038d9e2d0f..bbc9f978bf 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -213,24 +213,12 @@ module ActiveRecord
assert_equal "''", @conn.quote_string("'")
end
- def test_insert_sql
- with_example_table do
- 2.times do |i|
- rv = @conn.insert_sql "INSERT INTO ex (number) VALUES (#{i})"
- assert_equal(i + 1, rv)
- end
-
- records = @conn.execute "SELECT * FROM ex"
- assert_equal 2, records.length
- end
- end
-
- def test_insert_sql_logged
+ def test_insert_logged
with_example_table do
sql = "INSERT INTO ex (number) VALUES (10)"
name = "foo"
assert_logged [[sql, name, []]] do
- @conn.insert_sql sql, name
+ @conn.insert(sql, name)
end
end
end
@@ -239,7 +227,7 @@ module ActiveRecord
with_example_table do
sql = "INSERT INTO ex (number) VALUES (10)"
idval = 'vuvuzela'
- id = @conn.insert_sql sql, nil, nil, idval
+ id = @conn.insert(sql, nil, nil, idval)
assert_equal idval, id
end
end
@@ -400,12 +388,6 @@ 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/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
index 559b951109..24cc6875ab 100644
--- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
@@ -1,24 +1,20 @@
require 'cases/helper'
-module ActiveRecord::ConnectionAdapters
- class SQLite3Adapter
- class StatementPoolTest < ActiveRecord::SQLite3TestCase
- if Process.respond_to?(:fork)
- def test_cache_is_per_pid
+class SQLite3StatementPoolTest < ActiveRecord::SQLite3TestCase
+ if Process.respond_to?(:fork)
+ def test_cache_is_per_pid
- cache = StatementPool.new(10)
- cache['foo'] = 'bar'
- assert_equal 'bar', cache['foo']
+ cache = ActiveRecord::ConnectionAdapters::SQLite3Adapter::StatementPool.new(10)
+ cache['foo'] = 'bar'
+ assert_equal 'bar', cache['foo']
- pid = fork {
- lookup = cache['foo'];
- exit!(!lookup)
- }
+ pid = fork {
+ lookup = cache['foo'];
+ exit!(!lookup)
+ }
- Process.waitpid pid
- assert $?.success?, 'process should exit successfully'
- end
- end
+ Process.waitpid pid
+ assert $?.success?, 'process should exit successfully'
end
end
end
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index 5536702f58..8a728902a8 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -138,6 +138,11 @@ class AggregationsTest < ActiveRecord::TestCase
assert_equal 'Barnoit GUMBLEAU', customers(:barney).fullname.to_s
assert_kind_of Fullname, customers(:barney).fullname
end
+
+ def test_assigning_hash_to_custom_converter
+ customers(:barney).fullname = { first: "Barney", last: "Stinson" }
+ assert_equal "Barney STINSON", customers(:barney).name
+ end
end
class OverridingAggregationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 1f32c48b95..9c99689c1e 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -21,7 +21,7 @@ if ActiveRecord::Base.connection.supports_migrations?
ActiveRecord::Migration.verbose = @original_verbose
end
- def test_has_has_primary_key
+ def test_has_primary_key
old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type
ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
assert_equal "version", ActiveRecord::SchemaMigration.primary_key
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 4f99c57c3c..9dadd114a1 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -168,7 +168,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
e = assert_raise(ActiveRecord::AssociationTypeMismatch) {
Admin::RegionalUser.new(region: 'wrong value')
}
- assert_match(/^Region\([^)]+\) expected, got String\([^)]+\)$/, e.message)
+ assert_match(/^Region\([^)]+\) expected, got "wrong value" which is an instance of String\([^)]+\)$/, e.message)
ensure
Admin.send :remove_const, "Region" if Admin.const_defined?("Region")
Admin.send :remove_const, "RegionalUser" if Admin.const_defined?("RegionalUser")
@@ -700,6 +700,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 17, reply.replies.size
end
+ def test_replace_counter_cache
+ topic = Topic.create(title: "Zoom-zoom-zoom")
+ reply = Reply.create(title: "re: zoom", content: "speedy quick!")
+
+ reply.topic = topic
+ reply.save
+ topic.reload
+
+ assert_equal 1, topic.replies_count
+ end
+
def test_association_assignment_sticks
post = Post.first
@@ -726,7 +737,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert companies(:first_client).readonly_firm.readonly?
end
- def test_test_polymorphic_assignment_foreign_key_type_string
+ def test_polymorphic_assignment_foreign_key_type_string
comment = Comment.first
comment.author = Author.first
comment.resource = Member.first
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index a531e0e02c..a4298a25a6 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -177,14 +177,14 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
end
def test_dont_add_if_before_callback_raises_exception
- assert !@david.unchangable_posts.include?(@authorless)
+ assert !@david.unchangeable_posts.include?(@authorless)
begin
- @david.unchangable_posts << @authorless
+ @david.unchangeable_posts << @authorless
rescue Exception
end
assert @david.post_log.empty?
- assert !@david.unchangable_posts.include?(@authorless)
+ assert !@david.unchangeable_posts.include?(@authorless)
@david.reload
- assert !@david.unchangable_posts.include?(@authorless)
+ assert !@david.unchangeable_posts.include?(@authorless)
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 874d53c51f..80d9a6083b 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -90,6 +90,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_no_queries { authors.map(&:post) }
end
+ def test_calculate_with_string_in_from_and_eager_loading
+ assert_equal 10, Post.from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").count
+ 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
@@ -749,6 +753,38 @@ class EagerAssociationTest < ActiveRecord::TestCase
}
end
+ def test_eager_has_many_through_with_order
+ tag = OrderedTag.create(name: 'Foo')
+ post1 = Post.create!(title: 'Beaches', body: "I like beaches!")
+ post2 = Post.create!(title: 'Pools', body: "I like pools!")
+
+ Tagging.create!(taggable_type: 'Post', taggable_id: post1.id, tag: tag)
+ Tagging.create!(taggable_type: 'Post', taggable_id: post2.id, tag: tag)
+
+ tag_with_includes = OrderedTag.includes(:tagged_posts).find(tag.id)
+ assert_equal(tag_with_includes.taggings.map(&:taggable).map(&:title), tag_with_includes.tagged_posts.map(&:title))
+ end
+
+ def test_eager_has_many_through_multiple_with_order
+ tag1 = OrderedTag.create!(name: 'Bar')
+ tag2 = OrderedTag.create!(name: 'Foo')
+
+ post1 = Post.create!(title: 'Beaches', body: "I like beaches!")
+ post2 = Post.create!(title: 'Pools', body: "I like pools!")
+
+ Tagging.create!(taggable: post1, tag: tag1)
+ Tagging.create!(taggable: post2, tag: tag1)
+ Tagging.create!(taggable: post2, tag: tag2)
+ Tagging.create!(taggable: post1, tag: tag2)
+
+ tags_with_includes = OrderedTag.where(id: [tag1, tag2].map(&:id)).includes(:tagged_posts).order(:id).to_a
+ tag1_with_includes = tags_with_includes.first
+ tag2_with_includes = tags_with_includes.last
+
+ assert_equal([post2, post1].map(&:title), tag1_with_includes.tagged_posts.map(&:title))
+ assert_equal([post1, post2].map(&:title), tag2_with_includes.tagged_posts.map(&:title))
+ end
+
def test_eager_with_default_scope
developer = EagerDeveloperWithDefaultScope.where(:name => 'David').first
projects = Project.order(:id).to_a
@@ -1216,7 +1252,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_join_eager_with_empty_order_should_generate_valid_sql
- assert_nothing_raised(ActiveRecord::StatementInvalid) do
+ assert_nothing_raised do
Post.includes(:comments).order("").where(:comments => {:body => "Thank you for the welcome"}).first
end
end
@@ -1309,6 +1345,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_nothing_raised do
authors(:david).essays.includes(:writer).any?
+ authors(:david).essays.includes(:writer).exists?
end
end
@@ -1360,6 +1397,18 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal('10 was not recognized for preload', exception.message)
end
+ test "associations with extensions are not instance dependent" do
+ assert_nothing_raised do
+ Author.includes(:posts_with_extension).to_a
+ end
+ end
+
+ test "including associations with extensions and an instance dependent scope is not supported" do
+ e = assert_raises(ArgumentError) do
+ Author.includes(:posts_with_extension_and_instance).to_a
+ end
+ assert_match(/Preloading instance dependent scopes is not supported/, e.message)
+ end
test "preloading readonly association" do
# has-one
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 ccb062f991..1bbca84bb2 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
@@ -146,6 +146,19 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, country.treaties.count
end
+ def test_join_table_composite_primary_key_should_not_warn
+ country = Country.new(:name => 'India')
+ country.country_id = 'c1'
+ country.save!
+
+ treaty = Treaty.new(:name => 'peace')
+ treaty.treaty_id = 't1'
+ warning = capture(:stderr) do
+ country.treaties << treaty
+ end
+ assert_no_match(/WARNING: Rails does not support composite primary key\./, warning)
+ end
+
def test_has_and_belongs_to_many
david = Developer.find(1)
@@ -827,12 +840,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_no_queries { david.projects.columns }
end
- def test_attributes_are_being_set_when_initialized_from_habm_association_with_where_clause
+ def test_attributes_are_being_set_when_initialized_from_habtm_association_with_where_clause
new_developer = projects(:action_controller).developers.where(:name => "Marcelo").build
assert_equal new_developer.name, "Marcelo"
end
- def test_attributes_are_being_set_when_initialized_from_habm_association_with_multiple_where_clauses
+ def test_attributes_are_being_set_when_initialized_from_habtm_association_with_multiple_where_clauses
new_developer = projects(:action_controller).developers.where(:name => "Marcelo").where(:salary => 90_000).build
assert_equal new_developer.name, "Marcelo"
assert_equal new_developer.salary, 90_000
@@ -925,7 +938,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_with_symbol_class_name
- assert_nothing_raised NoMethodError do
+ assert_nothing_raised do
DeveloperWithSymbolClassName.new
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 ad157582a4..7ec0dfce7a 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -408,6 +408,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
assert_no_queries do
+ bulbs.third_to_last()
+ bulbs.third_to_last({})
+ end
+
+ assert_no_queries do
+ bulbs.second_to_last()
+ bulbs.second_to_last({})
+ end
+
+ assert_no_queries do
bulbs.last()
bulbs.last({})
end
@@ -1305,7 +1315,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, summit.client_of
end
- def test_deleting_by_fixnum_id
+ def test_deleting_by_integer_id
david = Developer.find(1)
assert_difference 'david.projects.count', -1 do
@@ -1342,7 +1352,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
end
- def test_destroying_by_fixnum_id
+ def test_destroying_by_integer_id
force_signal37_to_load_all_clients_of_firm
assert_difference "Client.count", -1 do
@@ -2271,7 +2281,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [], authors(:david).posts_with_signature.map(&:title)
end
- test 'associations autosaves when object is already persited' do
+ test 'associations autosaves when object is already persisted' do
bulb = Bulb.create!
tyre = Tyre.create!
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 226ecf5447..aa35844a03 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -11,7 +11,9 @@ require 'models/tagging'
require 'models/author'
require 'models/owner'
require 'models/pet'
+require 'models/pet_treasure'
require 'models/toy'
+require 'models/treasure'
require 'models/contract'
require 'models/company'
require 'models/developer'
@@ -63,7 +65,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
end
- def test_ordered_habtm
+ def test_ordered_has_many_through
person_prime = Class.new(ActiveRecord::Base) do
def self.name; 'Person'; end
@@ -884,7 +886,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set
company = companies(:rails_core)
ids = [Developer.first.id, -9999]
- assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids}
+ assert_raises(ActiveRecord::AssociationTypeMismatch) {company.developer_ids= ids}
end
def test_build_a_model_from_hm_through_association_with_where_clause
@@ -1082,6 +1084,18 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [], person.posts
end
+ def test_preloading_empty_through_with_polymorphic_source_association
+ owner = Owner.create!(name: "Rainbow Unicat")
+ pet = Pet.create!(owner: owner)
+ person = Person.create!(first_name: "Gaga")
+ treasure = Treasure.create!(looter: person)
+ non_looted_treasure = Treasure.create!()
+ PetTreasure.create!(pet: pet, treasure: treasure, rainbow_color: "Ultra violet indigo")
+ PetTreasure.create!(pet: pet, treasure: non_looted_treasure, rainbow_color: "Ultra violet indigo")
+
+ assert_equal [person], Owner.where(name: "Rainbow Unicat").includes(pets: :persons).first.persons.to_a
+ end
+
def test_explicitly_joining_join_table
assert_equal owners(:blackbeard).toys, owners(:blackbeard).toys.with_pet
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index c9d9e29f09..1574f373c2 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -659,4 +659,22 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_deprecated { firm.account(true) }
end
+
+ class SpecialBook < ActiveRecord::Base
+ self.table_name = 'books'
+ belongs_to :author, class_name: 'SpecialAuthor'
+ end
+
+ class SpecialAuthor < ActiveRecord::Base
+ self.table_name = 'authors'
+ has_one :book, class_name: 'SpecialBook', foreign_key: 'author_id'
+ end
+
+ def test_assocation_enum_works_properly
+ author = SpecialAuthor.create!(name: 'Test')
+ book = SpecialBook.create!(status: 'published')
+ author.book = book
+
+ refute_equal 0, SpecialAuthor.joins(:book).where(books: { status: 'published' } ).count
+ end
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 57d1c8feda..c9743e80d3 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -130,15 +130,15 @@ end
class InverseAssociationTests < ActiveRecord::TestCase
def test_should_allow_for_inverse_of_options_in_associations
- assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on has_many') do
+ assert_nothing_raised do
Class.new(ActiveRecord::Base).has_many(:wheels, :inverse_of => :car)
end
- assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on has_one') do
+ assert_nothing_raised do
Class.new(ActiveRecord::Base).has_one(:engine, :inverse_of => :car)
end
- assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on belongs_to') do
+ assert_nothing_raised do
Class.new(ActiveRecord::Base).belongs_to(:car, :inverse_of => :driver)
end
end
@@ -666,7 +666,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
def test_trying_to_access_inverses_that_dont_exist_shouldnt_raise_an_error
# Ideally this would, if only for symmetry's sake with other association types
- assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_polymorphic_man }
+ assert_nothing_raised { Face.first.horrible_polymorphic_man }
end
def test_trying_to_set_polymorphic_inverses_that_dont_exist_at_all_should_raise_an_error
@@ -676,7 +676,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
def test_trying_to_set_polymorphic_inverses_that_dont_exist_on_the_instance_being_set_should_raise_an_error
# passes because Man does have the correct inverse_of
- assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Man.first }
+ assert_nothing_raised { Face.first.polymorphic_man = Man.first }
# fails because Interest does have the correct inverse_of
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Interest.first }
end
@@ -688,7 +688,7 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase
fixtures :men, :interests, :zines
def test_that_we_can_load_associations_that_have_the_same_reciprocal_name_from_different_models
- assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
+ assert_nothing_raised do
i = Interest.first
i.zine
i.man
@@ -696,7 +696,7 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase
end
def test_that_we_can_create_associations_that_have_the_same_reciprocal_name_from_different_models
- assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
+ assert_nothing_raised do
i = Interest.first
i.build_zine(:title => 'Get Some in Winter! 2008')
i.build_man(:name => 'Gordon')
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index f6dddaf5b4..3047914b70 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -88,7 +88,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins
assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first
- assert_nothing_raised(NoMethodError) { tag.author_id }
+ assert_nothing_raised { tag.author_id }
end
def test_polymorphic_has_many_going_through_join_model_with_custom_foreign_key
@@ -363,6 +363,13 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_equal posts(:welcome, :thinking).sort_by(&:id), tags(:general).tagged_posts.sort_by(&:id)
end
+ def test_has_many_polymorphic_associations_merges_through_scope
+ Tag.has_many :null_taggings, -> { none }, class_name: :Tagging
+ Tag.has_many :null_tagged_posts, :through => :null_taggings, :source => 'taggable', :source_type => 'Post'
+ assert_equal [], tags(:general).null_tagged_posts
+ refute_equal [], tags(:general).tagged_posts
+ end
+
def test_eager_has_many_polymorphic_with_source_type
tag_with_include = Tag.all.merge!(:includes => :tagged_posts).find(tags(:general).id)
desired = posts(:welcome, :thinking)
@@ -591,7 +598,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete(Object.new) }
end
- def test_deleting_by_fixnum_id_from_has_many_through
+ def test_deleting_by_integer_id_from_has_many_through
post = posts(:thinking)
assert_difference 'post.tags.count', -1 do
@@ -740,6 +747,23 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_equal aircraft.engines, [engine]
end
+ def test_proper_error_message_for_eager_load_and_includes_association_errors
+ includes_error = assert_raises(ActiveRecord::ConfigurationError) {
+ Post.includes(:nonexistent_relation).where(nonexistent_relation: {name: 'Rochester'}).find(1)
+ }
+ assert_equal("Can't join 'Post' to association named 'nonexistent_relation'; perhaps you misspelled it?", includes_error.message)
+
+ eager_load_error = assert_raises(ActiveRecord::ConfigurationError) {
+ Post.eager_load(:nonexistent_relation).where(nonexistent_relation: {name: 'Rochester'}).find(1)
+ }
+ assert_equal("Can't join 'Post' to association named 'nonexistent_relation'; perhaps you misspelled it?", eager_load_error.message)
+
+ includes_and_eager_load_error = assert_raises(ActiveRecord::ConfigurationError) {
+ Post.eager_load(:nonexistent_relation).includes(:nonexistent_relation).where(nonexistent_relation: {name: 'Rochester'}).find(1)
+ }
+ assert_equal("Can't join 'Post' to association named 'nonexistent_relation'; perhaps you misspelled it?", includes_and_eager_load_error.message)
+ end
+
private
# create dynamic Post models to allow different dependency options
def find_post_with_dependency(post_id, association, association_name, dependency)
diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb
index 4af791b758..eee135cfb8 100644
--- a/activerecord/test/cases/associations/left_outer_join_association_test.rb
+++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb
@@ -50,7 +50,7 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
def test_join_conditions_added_to_join_clause
queries = capture_sql { Author.left_outer_joins(:essays).to_a }
- assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1)/i =~ sql }
+ assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1|\:a1)/i =~ sql }
assert queries.none? { |sql| /WHERE/i =~ sql }
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 94dfbc3346..1db52af59b 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -714,6 +714,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_time_zone_aware_attributes_dont_recurse_infinitely_on_invalid_values
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.new(bonus_time: [])
+ assert_equal nil, record.bonus_time
+ 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]
@@ -791,7 +798,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_nil computer.system
end
- def test_global_methods_are_overwritte_when_subclassing
+ def test_global_methods_are_overwritten_when_subclassing
klass = Class.new(ActiveRecord::Base) { self.abstract_class = true }
subklass = Class.new(klass) do
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
index a24a4fc6a4..b1b8639696 100644
--- a/activerecord/test/cases/attribute_test.rb
+++ b/activerecord/test/cases/attribute_test.rb
@@ -242,5 +242,12 @@ module ActiveRecord
attribute.with_value_from_user(1)
end
end
+
+ test "with_type preserves mutations" do
+ attribute = Attribute.from_database(:foo, "", Type::Value.new)
+ attribute.value << "1"
+
+ assert_equal 1, attribute.with_type(Type::Integer.new).value
+ end
end
end
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 2991ca8b76..7bcaa53aa2 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -38,7 +38,7 @@ module ActiveRecord
data.reload
assert_equal 2, data.overloaded_float
- assert_kind_of Fixnum, OverloadedType.last.overloaded_float
+ assert_kind_of Integer, OverloadedType.last.overloaded_float
assert_equal 2.0, UnoverloadedType.last.overloaded_float
assert_kind_of Float, UnoverloadedType.last.overloaded_float
end
@@ -63,6 +63,15 @@ module ActiveRecord
end
end
+ test "model with nonexistent attribute with default value can be saved" do
+ klass = Class.new(OverloadedType) do
+ attribute :non_existent_string_with_default, :string, default: 'nonexistent'
+ end
+
+ model = klass.new
+ assert model.save
+ end
+
test "changing defaults" do
data = OverloadedType.new
unoverloaded_data = UnoverloadedType.new
@@ -135,6 +144,17 @@ module ActiveRecord
assert_equal 2, klass.new.counter
end
+ test "procs are memoized before type casting" do
+ klass = Class.new(OverloadedType) do
+ @@counter = 0
+ attribute :counter, :integer, default: -> { @@counter += 1 }
+ end
+
+ model = klass.new
+ assert_equal 1, model.counter_before_type_cast
+ assert_equal 1, model.counter_before_type_cast
+ end
+
test "user provided defaults are persisted even if unchanged" do
model = OverloadedType.create!
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 3608063b01..9e3266b7d6 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -749,7 +749,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
# has_one
- def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal
+ def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction
assert !@pirate.ship.marked_for_destruction?
@pirate.ship.mark_for_destruction
@@ -809,7 +809,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
# belongs_to
- def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal
+ def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction
assert !@ship.pirate.marked_for_destruction?
@ship.pirate.mark_for_destruction
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index ecdf508e3e..00e4e50ea1 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -14,7 +14,6 @@ require 'models/auto_id'
require 'models/boolean'
require 'models/column_name'
require 'models/subscriber'
-require 'models/keyboard'
require 'models/comment'
require 'models/minimalistic'
require 'models/warehouse_thing'
@@ -25,7 +24,6 @@ require 'models/joke'
require 'models/bird'
require 'models/car'
require 'models/bulb'
-require 'rexml/document'
require 'concurrent/atomic/count_down_latch'
class FirstAbstractClass < ActiveRecord::Base
@@ -804,7 +802,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal dev.salary.amount, dup.salary.amount
assert !dup.persisted?
- # test if the attributes have been dupd
+ # test if the attributes have been duped
original_amount = dup.salary.amount
dev.salary.amount = 1
assert_equal original_amount, dup.salary.amount
@@ -940,7 +938,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_kind_of Integer, m1.world_population
assert_equal 6000000000, m1.world_population
- assert_kind_of Fixnum, m1.my_house_population
+ assert_kind_of Integer, m1.my_house_population
assert_equal 3, m1.my_house_population
assert_kind_of BigDecimal, m1.bank_balance
@@ -968,7 +966,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_kind_of Integer, m1.world_population
assert_equal 6000000000, m1.world_population
- assert_kind_of Fixnum, m1.my_house_population
+ assert_kind_of Integer, m1.my_house_population
assert_equal 3, m1.my_house_population
assert_kind_of BigDecimal, m1.bank_balance
@@ -1504,6 +1502,10 @@ class BasicsTest < ActiveRecord::TestCase
assert_not_equal Post.new.hash, Post.new.hash
end
+ test "records of different classes have different hashes" do
+ assert_not_equal Post.new(id: 1).hash, Developer.new(id: 1).hash
+ end
+
test "resetting column information doesn't remove attribute methods" do
topic = topics(:first)
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 3602ee7ba2..db71840658 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -108,7 +108,7 @@ class EachTest < ActiveRecord::TestCase
end
end
- def test_find_in_batches_should_finish_the_end_option
+ def test_find_in_batches_should_end_at_the_finish_option
assert_queries(6) do
Post.find_in_batches(batch_size: 1, finish: 5) do |batch|
assert_kind_of Array, batch
@@ -164,6 +164,42 @@ class EachTest < ActiveRecord::TestCase
assert_equal posts(:welcome).id, posts.first.id
end
+ def test_find_in_batches_should_error_on_ignore_the_order
+ assert_raise(ArgumentError) do
+ PostWithDefaultScope.find_in_batches(error_on_ignore: true){}
+ end
+ end
+
+ def test_find_in_batches_should_not_error_if_config_overridden
+ # Set the config option which will be overridden
+ prev = ActiveRecord::Base.error_on_ignored_order_or_limit
+ ActiveRecord::Base.error_on_ignored_order_or_limit = true
+ assert_nothing_raised do
+ PostWithDefaultScope.find_in_batches(error_on_ignore: false){}
+ end
+ ensure
+ # Set back to default
+ ActiveRecord::Base.error_on_ignored_order_or_limit = prev
+ end
+
+ def test_find_in_batches_should_error_on_config_specified_to_error
+ # Set the config option
+ prev = ActiveRecord::Base.error_on_ignored_order_or_limit
+ ActiveRecord::Base.error_on_ignored_order_or_limit = true
+ assert_raise(ArgumentError) do
+ PostWithDefaultScope.find_in_batches(){}
+ end
+ ensure
+ # Set back to default
+ ActiveRecord::Base.error_on_ignored_order_or_limit = prev
+ end
+
+ def test_find_in_batches_should_not_error_by_default
+ assert_nothing_raised do
+ PostWithDefaultScope.find_in_batches(){}
+ end
+ end
+
def test_find_in_batches_should_not_ignore_the_default_scope_if_it_is_other_then_order
special_posts_ids = SpecialPostWithDefaultScope.all.map(&:id).sort
posts = []
@@ -316,7 +352,7 @@ class EachTest < ActiveRecord::TestCase
end
end
- def test_in_batches_should_finish_the_end_option
+ def test_in_batches_should_end_at_the_finish_option
post = Post.order('id DESC').where('id <= ?', 5).first
assert_queries(7) do
relation = Post.in_batches(of: 1, finish: 5, load: true).reverse_each.first
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index cd9c76f1f0..fa924fe4cb 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -31,7 +31,8 @@ module ActiveRecord
ActiveSupport::Notifications.unsubscribe(@subscription)
end
- if ActiveRecord::Base.connection.supports_statement_cache?
+ if ActiveRecord::Base.connection.supports_statement_cache? &&
+ ActiveRecord::Base.connection.prepared_statements
def test_bind_from_join_in_subquery
subquery = Author.joins(:thinking_posts).where(name: 'David')
scope = Author.from(subquery, 'authors').where(id: 1)
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index c922a8d1c2..cfae700159 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -124,7 +124,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_generate_valid_sql_with_joins_and_group
- assert_nothing_raised ActiveRecord::StatementInvalid do
+ assert_nothing_raised do
AuditLog.joins(:developer).group(:id).count
end
end
@@ -482,6 +482,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).reverse_order.count
end
+ def test_count_with_block
+ assert_equal 4, Account.count { |account| account.credit_limit.modulo(10).zero? }
+ end
+
def test_should_sum_expression
# Oracle adapter returns floating point value 636.0 after SUM
if current_adapter?(:OracleAdapter)
@@ -742,7 +746,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association
- assert_nothing_raised ActiveRecord::StatementInvalid do
+ assert_nothing_raised do
developer = Developer.create!(name: 'developer')
developer.ratings.includes(comment: :post).where(posts: { id: 1 }).count
end
diff --git a/activerecord/test/cases/coders/json_test.rb b/activerecord/test/cases/coders/json_test.rb
new file mode 100644
index 0000000000..d22d93d129
--- /dev/null
+++ b/activerecord/test/cases/coders/json_test.rb
@@ -0,0 +1,15 @@
+require "cases/helper"
+
+module ActiveRecord
+ module Coders
+ class JSONTest < ActiveRecord::TestCase
+ def test_returns_nil_if_empty_string_given
+ assert_nil JSON.load("")
+ end
+
+ def test_returns_nil_if_nil_given
+ assert_nil JSON.load(nil)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb
new file mode 100644
index 0000000000..839fdbe578
--- /dev/null
+++ b/activerecord/test/cases/comment_test.rb
@@ -0,0 +1,139 @@
+require 'cases/helper'
+require 'support/schema_dumping_helper'
+
+if ActiveRecord::Base.connection.supports_comments?
+
+class CommentTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ class Commented < ActiveRecord::Base
+ self.table_name = 'commenteds'
+ end
+
+ class BlankComment < ActiveRecord::Base
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+
+ @connection.create_table('commenteds', comment: 'A table with comment', force: true) do |t|
+ t.string 'name', comment: 'Comment should help clarify the column purpose'
+ t.boolean 'obvious', comment: 'Question is: should you comment obviously named objects?'
+ t.string 'content'
+ t.index 'name', comment: %Q["Very important" index that powers all the performance.\nAnd it's fun!]
+ end
+
+ @connection.create_table('blank_comments', comment: ' ', force: true) do |t|
+ t.string :space_comment, comment: ' '
+ t.string :empty_comment, comment: ''
+ t.string :nil_comment, comment: nil
+ t.string :absent_comment
+ t.index :space_comment, comment: ' '
+ t.index :empty_comment, comment: ''
+ t.index :nil_comment, comment: nil
+ t.index :absent_comment
+ end
+
+ Commented.reset_column_information
+ BlankComment.reset_column_information
+ end
+
+ teardown do
+ @connection.drop_table 'commenteds', if_exists: true
+ @connection.drop_table 'blank_comments', if_exists: true
+ end
+
+ def test_column_created_in_block
+ column = Commented.columns_hash['name']
+ assert_equal :string, column.type
+ assert_equal 'Comment should help clarify the column purpose', column.comment
+ end
+
+ def test_blank_columns_created_in_block
+ %w[ space_comment empty_comment nil_comment absent_comment ].each do |field|
+ column = BlankComment.columns_hash[field]
+ assert_equal :string, column.type
+ assert_nil column.comment
+ end
+ end
+
+ def test_blank_indexes_created_in_block
+ @connection.indexes('blank_comments').each do |index|
+ assert_nil index.comment
+ end
+ end
+
+ def test_add_column_with_comment_later
+ @connection.add_column :commenteds, :rating, :integer, comment: 'I am running out of imagination'
+ Commented.reset_column_information
+ column = Commented.columns_hash['rating']
+
+ assert_equal :integer, column.type
+ assert_equal 'I am running out of imagination', column.comment
+ end
+
+ def test_add_index_with_comment_later
+ @connection.add_index :commenteds, :obvious, name: 'idx_obvious', comment: 'We need to see obvious comments'
+ index = @connection.indexes('commenteds').find { |idef| idef.name == 'idx_obvious' }
+ assert_equal 'We need to see obvious comments', index.comment
+ end
+
+ def test_add_comment_to_column
+ @connection.change_column :commenteds, :content, :string, comment: 'Whoa, content describes itself!'
+
+ Commented.reset_column_information
+ column = Commented.columns_hash['content']
+
+ assert_equal :string, column.type
+ assert_equal 'Whoa, content describes itself!', column.comment
+ end
+
+ def test_remove_comment_from_column
+ @connection.change_column :commenteds, :obvious, :string, comment: nil
+
+ Commented.reset_column_information
+ column = Commented.columns_hash['obvious']
+
+ assert_equal :string, column.type
+ assert_nil column.comment
+ end
+
+ def test_schema_dump_with_comments
+ # Do all the stuff from other tests
+ @connection.add_column :commenteds, :rating, :integer, comment: 'I am running out of imagination'
+ @connection.change_column :commenteds, :content, :string, comment: 'Whoa, content describes itself!'
+ @connection.change_column :commenteds, :obvious, :string, comment: nil
+ @connection.add_index :commenteds, :obvious, name: 'idx_obvious', comment: 'We need to see obvious comments'
+
+ # And check that these changes are reflected in dump
+ output = dump_table_schema 'commenteds'
+ assert_match %r[create_table "commenteds",.+\s+comment: "A table with comment"], output
+ assert_match %r[t\.string\s+"name",\s+comment: "Comment should help clarify the column purpose"], output
+ assert_match %r[t\.string\s+"obvious"\n], output
+ assert_match %r[t\.string\s+"content",\s+comment: "Whoa, content describes itself!"], output
+ assert_match %r[t\.integer\s+"rating",\s+comment: "I am running out of imagination"], output
+ assert_match %r[t\.index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output
+ assert_match %r[t\.index\s+.+\s+name: "idx_obvious",.+\s+comment: "We need to see obvious comments"], output
+ end
+
+ def test_schema_dump_omits_blank_comments
+ output = dump_table_schema 'blank_comments'
+
+ assert_match %r[create_table "blank_comments"], output
+ assert_no_match %r[create_table "blank_comments",.+comment:], output
+
+ assert_match %r[t\.string\s+"space_comment"\n], output
+ assert_no_match %r[t\.string\s+"space_comment", comment:\n], output
+
+ assert_match %r[t\.string\s+"empty_comment"\n], output
+ assert_no_match %r[t\.string\s+"empty_comment", comment:\n], output
+
+ assert_match %r[t\.string\s+"nil_comment"\n], output
+ assert_no_match %r[t\.string\s+"nil_comment", comment:\n], output
+
+ assert_match %r[t\.string\s+"absent_comment"\n], output
+ assert_no_match %r[t\.string\s+"absent_comment", comment:\n], output
+ end
+end
+
+end
diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
index 580568c8ac..c7ca428ab7 100644
--- a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
+++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
@@ -37,7 +37,7 @@ module ActiveRecord
end
def test_close
- pool = Pool.new(ConnectionSpecification.new({}, nil))
+ pool = Pool.new(ConnectionSpecification.new("primary", {}, nil))
pool.insert_connection_for_test! @adapter
@adapter.pool = pool
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 9b1865e8bb..a019cc6490 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -4,43 +4,40 @@ module ActiveRecord
module ConnectionAdapters
class ConnectionHandlerTest < ActiveRecord::TestCase
def setup
- @klass = Class.new(Base) { def self.name; 'klass'; end }
- @subklass = Class.new(@klass) { def self.name; 'subklass'; end }
-
@handler = ConnectionHandler.new
- @pool = @handler.establish_connection(@klass, Base.connection_pool.spec)
+ @spec_name = "primary"
+ @pool = @handler.establish_connection(ActiveRecord::Base.configurations['arunit'])
+ end
+
+ def test_establish_connection_uses_spec_name
+ config = {"readonly" => {"adapter" => 'sqlite3'}}
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config)
+ spec = resolver.spec(:readonly)
+ @handler.establish_connection(spec.to_hash)
+
+ assert_not_nil @handler.retrieve_connection_pool('readonly')
+ ensure
+ @handler.remove_connection('readonly')
end
def test_retrieve_connection
- assert @handler.retrieve_connection(@klass)
+ assert @handler.retrieve_connection(@spec_name)
end
def test_active_connections?
assert !@handler.active_connections?
- assert @handler.retrieve_connection(@klass)
+ assert @handler.retrieve_connection(@spec_name)
assert @handler.active_connections?
@handler.clear_active_connections!
assert !@handler.active_connections?
end
- def test_retrieve_connection_pool_with_ar_base
- assert_nil @handler.retrieve_connection_pool(ActiveRecord::Base)
- end
-
def test_retrieve_connection_pool
- assert_not_nil @handler.retrieve_connection_pool(@klass)
+ assert_not_nil @handler.retrieve_connection_pool(@spec_name)
end
- def test_retrieve_connection_pool_uses_superclass_when_no_subclass_connection
- assert_not_nil @handler.retrieve_connection_pool(@subklass)
- end
-
- def test_retrieve_connection_pool_uses_superclass_pool_after_subclass_establish_and_remove
- sub_pool = @handler.establish_connection(@subklass, Base.connection_pool.spec)
- assert_same sub_pool, @handler.retrieve_connection_pool(@subklass)
-
- @handler.remove_connection @subklass
- assert_same @pool, @handler.retrieve_connection_pool(@subklass)
+ def test_retrieve_connection_pool_with_invalid_id
+ assert_nil @handler.retrieve_connection_pool("foo")
end
def test_connection_pools
@@ -79,7 +76,7 @@ module ActiveRecord
pid = fork {
rd.close
- pool = @handler.retrieve_connection_pool(@klass)
+ pool = @handler.retrieve_connection_pool(@spec_name)
wr.write Marshal.dump pool.schema_cache.size
wr.close
exit!
@@ -91,6 +88,36 @@ module ActiveRecord
assert_equal @pool.schema_cache.size, Marshal.load(rd.read)
rd.close
end
+
+ def test_a_class_using_custom_pool_and_switching_back_to_primary
+ klass2 = Class.new(Base) { def self.name; 'klass2'; end }
+
+ assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+
+ pool = klass2.establish_connection(ActiveRecord::Base.connection_pool.spec.config)
+ assert_equal klass2.connection.object_id, pool.connection.object_id
+ refute_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+
+ klass2.remove_connection
+
+ assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+ end
+
+ def test_connection_specification_name_should_fallback_to_parent
+ klassA = Class.new(Base)
+ klassB = Class.new(klassA)
+
+ assert_equal klassB.connection_specification_name, klassA.connection_specification_name
+ klassA.connection_specification_name = "readonly"
+ assert_equal "readonly", klassB.connection_specification_name
+ end
+
+ def test_remove_connection_should_not_remove_parent
+ klass2 = Class.new(Base) { def self.name; 'klass2'; end }
+ klass2.remove_connection
+ refute_nil ActiveRecord::Base.connection.object_id
+ assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+ end
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/connection_specification_test.rb b/activerecord/test/cases/connection_adapters/connection_specification_test.rb
index ea2196cda2..d204fce59f 100644
--- a/activerecord/test/cases/connection_adapters/connection_specification_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_specification_test.rb
@@ -4,7 +4,7 @@ module ActiveRecord
module ConnectionAdapters
class ConnectionSpecificationTest < ActiveRecord::TestCase
def test_dup_deep_copy_config
- spec = ConnectionSpecification.new({ :a => :b }, "bar")
+ spec = ConnectionSpecification.new("primary", { :a => :b }, "bar")
assert_not_equal(spec.config.object_id, spec.dup.config.object_id)
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 9ee92a3cd2..f25b85e8a7 100644
--- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -27,7 +27,7 @@ module ActiveRecord
ENV['DATABASE_URL'] = "postgres://localhost/foo"
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
actual = resolve_spec(:default_env, config)
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost", "name"=>"default_env" }
assert_equal expected, actual
end
@@ -37,7 +37,7 @@ module ActiveRecord
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
actual = resolve_spec(:foo, config)
- expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" }
+ expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost","name"=>"foo" }
assert_equal expected, actual
end
@@ -47,7 +47,7 @@ module ActiveRecord
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
actual = resolve_spec(:foo, config)
- expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" }
+ expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost","name"=>"foo" }
assert_equal expected, actual
end
@@ -55,7 +55,7 @@ module ActiveRecord
ENV['DATABASE_URL'] = "postgres://localhost/foo"
config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
actual = resolve_spec(:production, config)
- expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost" }
+ expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost", "name"=>"production" }
assert_equal expected, actual
end
@@ -93,7 +93,7 @@ module ActiveRecord
ENV['DATABASE_URL'] = "ibm-db://localhost/foo"
config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
actual = resolve_spec(:default_env, config)
- expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost" }
+ expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost", "name"=>"default_env" }
assert_equal expected, actual
end
diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
index 7566863653..3acbafbff4 100644
--- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strigns for lookup
+unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strings for lookup
module ActiveRecord
module ConnectionAdapters
class TypeLookupTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index d43668e57c..1f9b6add7a 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -4,6 +4,8 @@ require "rack"
module ActiveRecord
module ConnectionAdapters
class ConnectionManagementTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
class App
attr_reader :calls
def initialize
@@ -19,7 +21,7 @@ module ActiveRecord
def setup
@env = {}
@app = App.new
- @management = ConnectionManagement.new(@app)
+ @management = middleware(@app)
# make sure we have an active connection
assert ActiveRecord::Base.connection
@@ -27,17 +29,12 @@ module ActiveRecord
end
def test_app_delegation
- manager = ConnectionManagement.new(@app)
+ manager = middleware(@app)
manager.call @env
assert_equal [@env], @app.calls
end
- def test_connections_are_active_after_call
- @management.call(@env)
- assert ActiveRecord::Base.connection_handler.active_connections?
- end
-
def test_body_responds_to_each
_, _, body = @management.call(@env)
bits = []
@@ -51,46 +48,41 @@ module ActiveRecord
assert !ActiveRecord::Base.connection_handler.active_connections?
end
- def test_active_connections_are_not_cleared_on_body_close_during_test
- @env['rack.test'] = true
- _, _, body = @management.call(@env)
- body.close
- assert ActiveRecord::Base.connection_handler.active_connections?
+ def test_active_connections_are_not_cleared_on_body_close_during_transaction
+ ActiveRecord::Base.transaction do
+ _, _, body = @management.call(@env)
+ body.close
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
end
def test_connections_closed_if_exception
app = Class.new(App) { def call(env); raise NotImplementedError; end }.new
- explosive = ConnectionManagement.new(app)
+ explosive = middleware(app)
assert_raises(NotImplementedError) { explosive.call(@env) }
assert !ActiveRecord::Base.connection_handler.active_connections?
end
- def test_connections_not_closed_if_exception_and_test
- @env['rack.test'] = true
- app = Class.new(App) { def call(env); raise; end }.new
- explosive = ConnectionManagement.new(app)
- assert_raises(RuntimeError) { explosive.call(@env) }
- assert ActiveRecord::Base.connection_handler.active_connections?
- end
-
- def test_connections_closed_if_exception_and_explicitly_not_test
- @env['rack.test'] = false
- app = Class.new(App) { def call(env); raise NotImplementedError; end }.new
- explosive = ConnectionManagement.new(app)
- assert_raises(NotImplementedError) { explosive.call(@env) }
- assert !ActiveRecord::Base.connection_handler.active_connections?
+ def test_connections_not_closed_if_exception_inside_transaction
+ ActiveRecord::Base.transaction do
+ app = Class.new(App) { def call(env); raise RuntimeError; end }.new
+ explosive = middleware(app)
+ assert_raises(RuntimeError) { explosive.call(@env) }
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
end
test "doesn't clear active connections when running in a test case" do
- @env['rack.test'] = true
- @management.call(@env)
- assert ActiveRecord::Base.connection_handler.active_connections?
+ executor.wrap do
+ @management.call(@env)
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
end
test "proxy is polite to its body and responds to it" do
body = Class.new(String) { def to_path; "/path"; end }.new
app = lambda { |_| [200, {}, body] }
- response_body = ConnectionManagement.new(app).call(@env)[2]
+ response_body = middleware(app).call(@env)[2]
assert response_body.respond_to?(:to_path)
assert_equal "/path", response_body.to_path
end
@@ -98,9 +90,23 @@ module ActiveRecord
test "doesn't mutate the original response" do
original_response = [200, {}, 'hi']
app = lambda { |_| original_response }
- ConnectionManagement.new(app).call(@env)[2]
+ middleware(app).call(@env)[2]
assert_equal 'hi', original_response.last
end
+
+ private
+ def executor
+ @executor ||= Class.new(ActiveSupport::Executor).tap do |exe|
+ ActiveRecord::QueryCache.install_executor_hooks(exe)
+ end
+ end
+
+ def middleware(app)
+ lambda do |env|
+ a, b, c = executor.wrap { app.call(env) }
+ [a, b, Rack::BodyProxy.new(c) { }]
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index efa3e0455e..a45ee281c7 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -335,11 +335,10 @@ module ActiveRecord
# is called with an anonymous class
def test_anonymous_class_exception
anonymous = Class.new(ActiveRecord::Base)
- handler = ActiveRecord::Base.connection_handler
- assert_raises(RuntimeError) {
- handler.establish_connection anonymous, nil
- }
+ assert_raises(RuntimeError) do
+ anonymous.establish_connection
+ end
end
def test_pool_sets_connection_schema_cache
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index 3c2f5d4219..b30a83d9ce 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -28,7 +28,8 @@ module ActiveRecord
assert_equal({
"adapter" => "abstract",
"host" => "foo",
- "encoding" => "utf8" }, spec)
+ "encoding" => "utf8",
+ "name" => "production"}, spec)
end
def test_url_sub_key
@@ -36,7 +37,8 @@ module ActiveRecord
assert_equal({
"adapter" => "abstract",
"host" => "foo",
- "encoding" => "utf8" }, spec)
+ "encoding" => "utf8",
+ "name" => "production"}, spec)
end
def test_url_sub_key_merges_correctly
@@ -46,7 +48,8 @@ module ActiveRecord
"adapter" => "abstract",
"host" => "foo",
"encoding" => "utf8",
- "pool" => "3" }, spec)
+ "pool" => "3",
+ "name" => "production"}, spec)
end
def test_url_host_no_db
@@ -57,6 +60,12 @@ module ActiveRecord
"encoding" => "utf8" }, spec)
end
+ def test_url_missing_scheme
+ spec = resolve 'foo'
+ assert_equal({
+ "database" => "foo" }, spec)
+ end
+
def test_url_host_db
spec = resolve 'abstract://foo/bar?encoding=utf8'
assert_equal({
@@ -107,9 +116,19 @@ module ActiveRecord
assert_equal({
"adapter" => "sqlite3",
"database" => "foo",
- "encoding" => "utf8" }, spec)
+ "encoding" => "utf8",
+ "name" => "production"}, spec)
end
+ def test_spec_name_on_key_lookup
+ spec = spec(:readonly, 'readonly' => {'adapter' => 'sqlite3'})
+ assert_equal "readonly", spec.name
+ end
+
+ def test_spec_name_with_inline_config
+ spec = spec({'adapter' => 'sqlite3'})
+ assert_equal "primary", spec.name, "should default to primary id"
+ end
end
end
end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 922cb59280..66b4c3f1ff 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -151,7 +151,7 @@ class CounterCacheTest < ActiveRecord::TestCase
test "reset the right counter if two have the same foreign key" do
michael = people(:michael)
- assert_nothing_raised(ActiveRecord::StatementInvalid) do
+ assert_nothing_raised do
Person.reset_counters(michael.id, :friends_too)
end
end
diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb
index ba085991e0..3169408ac0 100644
--- a/activerecord/test/cases/database_statements_test.rb
+++ b/activerecord/test/cases/database_statements_test.rb
@@ -13,6 +13,12 @@ class DatabaseStatementsTest < ActiveRecord::TestCase
assert_not_nil return_the_inserted_id(method: :create)
end
+ def test_insert_update_delete_sql_is_deprecated
+ assert_deprecated { @connection.insert_sql("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") }
+ assert_deprecated { @connection.update_sql("UPDATE accounts SET credit_limit = 6000 WHERE firm_id = 42") }
+ assert_deprecated { @connection.delete_sql("DELETE FROM accounts WHERE firm_id = 42") }
+ end
+
private
def return_the_inserted_id(method:)
diff --git a/activerecord/test/cases/date_time_precision_test.rb b/activerecord/test/cases/date_time_precision_test.rb
index e996d142a2..f8664d83bd 100644
--- a/activerecord/test/cases/date_time_precision_test.rb
+++ b/activerecord/test/cases/date_time_precision_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
require 'support/schema_dumping_helper'
-if ActiveRecord::Base.connection.supports_datetime_with_precision?
+if subsecond_precision_supported?
class DateTimePrecisionTest < ActiveRecord::TestCase
include SchemaDumpingHelper
self.use_transactional_tests = false
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index 69b0487dd8..067513e24c 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -201,8 +201,7 @@ if current_adapter?(:Mysql2Adapter)
assert_equal '0', klass.columns_hash['zero'].default
assert !klass.columns_hash['zero'].null
- # 0 in MySQL 4, nil in 5.
- assert [0, nil].include?(klass.columns_hash['omit'].default)
+ assert_equal nil, klass.columns_hash['omit'].default
assert !klass.columns_hash['omit'].null
assert_raise(ActiveRecord::StatementInvalid) { klass.create! }
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index cd1967c373..f9794518c7 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -5,23 +5,6 @@ require 'models/parrot'
require 'models/person' # For optimistic locking
require 'models/aircraft'
-class Pirate # Just reopening it, not defining it
- attr_accessor :detected_changes_in_after_update # Boolean for if changes are detected
- attr_accessor :changes_detected_in_after_update # Actual changes
-
- after_update :check_changes
-
-private
- # after_save/update and the model itself
- # can end up checking dirty status and acting on the results
- def check_changes
- if self.changed?
- self.detected_changes_in_after_update = true
- self.changes_detected_in_after_update = self.changes
- end
- end
-end
-
class NumericData < ActiveRecord::Base
self.table_name = 'numeric_data'
end
@@ -37,8 +20,8 @@ class DirtyTest < ActiveRecord::TestCase
def test_attribute_changes
# New record - no changes.
pirate = Pirate.new
- assert !pirate.catchphrase_changed?
- assert_nil pirate.catchphrase_change
+ assert_equal false, pirate.catchphrase_changed?
+ assert_equal false, pirate.non_validated_parrot_id_changed?
# Change catchphrase.
pirate.catchphrase = 'arrr'
@@ -734,6 +717,17 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal "arr", pirate.catchphrase
end
+ test "attributes assigned but not selected are dirty" do
+ person = Person.select(:id).first
+ refute person.changed?
+
+ person.first_name = "Sean"
+ assert person.changed?
+
+ person.first_name = nil
+ assert person.changed?
+ end
+
private
def with_partial_writes(klass, on = true)
old = klass.partial_writes?
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 75a74c052d..6eaaa30cd0 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -43,7 +43,7 @@ class FinderTest < ActiveRecord::TestCase
end
assert_equal "should happen", exception.message
- assert_nothing_raised(RuntimeError) do
+ assert_nothing_raised do
Topic.all.find(-> { raise "should not happen" }) { |e| e.title == topics(:first).title }
end
end
@@ -101,7 +101,7 @@ class FinderTest < ActiveRecord::TestCase
def test_find_with_ids_where_and_limit
# Please note that Topic 1 is the only not approved so
- # if it were among the first 3 it would raise a ActiveRecord::RecordNotFound
+ # if it were among the first 3 it would raise an ActiveRecord::RecordNotFound
records = Topic.where(approved: true).limit(3).find([3,2,5,1,4])
assert_equal 3, records.size
assert_equal 'The Third Topic of the day', records[0].title
@@ -173,11 +173,9 @@ class FinderTest < ActiveRecord::TestCase
end
end
- def test_exists_fails_when_parameter_has_invalid_type
- assert_raises(RangeError) do
- assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int
- end
+ def test_exists_returns_false_when_parameter_has_invalid_type
assert_equal false, Topic.exists?("foo")
+ assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int
end
def test_exists_does_not_select_columns_without_alias
@@ -486,6 +484,66 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_second_to_last
+ assert_equal topics(:fourth).title, Topic.second_to_last.title
+
+ # test with offset
+ assert_equal topics(:fourth), Topic.offset(1).second_to_last
+ assert_equal topics(:fourth), Topic.offset(2).second_to_last
+ assert_equal topics(:fourth), Topic.offset(3).second_to_last
+ assert_equal nil, Topic.offset(4).second_to_last
+ assert_equal nil, Topic.offset(5).second_to_last
+
+ #test with limit
+ # assert_equal nil, Topic.limit(1).second # TODO: currently failing
+ assert_equal nil, Topic.limit(1).second_to_last
+ end
+
+ def test_second_to_last_have_primary_key_order_by_default
+ expected = topics(:fourth)
+ expected.touch # PostgreSQL changes the default order if no order clause is used
+ assert_equal expected, Topic.second_to_last
+ end
+
+ def test_model_class_responds_to_second_to_last_bang
+ assert Topic.second_to_last!
+ Topic.delete_all
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
+ Topic.second_to_last!
+ end
+ end
+
+ def test_third_to_last
+ assert_equal topics(:third).title, Topic.third_to_last.title
+
+ # test with offset
+ assert_equal topics(:third), Topic.offset(1).third_to_last
+ assert_equal topics(:third), Topic.offset(2).third_to_last
+ assert_equal nil, Topic.offset(3).third_to_last
+ assert_equal nil, Topic.offset(4).third_to_last
+ assert_equal nil, Topic.offset(5).third_to_last
+
+ # test with limit
+ # assert_equal nil, Topic.limit(1).third # TODO: currently failing
+ assert_equal nil, Topic.limit(1).third_to_last
+ # assert_equal nil, Topic.limit(2).third # TODO: currently failing
+ assert_equal nil, Topic.limit(2).third_to_last
+ end
+
+ def test_third_to_last_have_primary_key_order_by_default
+ expected = topics(:third)
+ expected.touch # PostgreSQL changes the default order if no order clause is used
+ assert_equal expected, Topic.third_to_last
+ end
+
+ def test_model_class_responds_to_third_to_last_bang
+ assert Topic.third_to_last!
+ Topic.delete_all
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
+ Topic.third_to_last!
+ end
+ end
+
def test_last_bang_present
assert_nothing_raised do
assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last!
@@ -516,16 +574,44 @@ class FinderTest < ActiveRecord::TestCase
assert_equal Topic.order("title").to_a.last(2), Topic.order("title").last(2)
end
- def test_last_with_integer_and_order_should_not_use_sql_limit
- query = assert_sql { Topic.order("title").last(5).entries }
- assert_equal 1, query.length
- assert_no_match(/LIMIT/, query.first)
+ def test_last_with_integer_and_order_should_use_sql_limit
+ relation = Topic.order("title")
+ assert_queries(1) { relation.last(5) }
+ assert !relation.loaded?
+ end
+
+ def test_last_with_integer_and_reorder_should_use_sql_limit
+ relation = Topic.reorder("title")
+ assert_queries(1) { relation.last(5) }
+ assert !relation.loaded?
+ end
+
+ def test_last_on_loaded_relation_should_not_use_sql
+ relation = Topic.limit(10).load
+ assert_no_queries do
+ relation.last
+ relation.last(2)
+ end
+ end
+
+ def test_last_with_irreversible_order
+ assert_deprecated do
+ Topic.order("coalesce(author_name, title)").last
+ end
end
- def test_last_with_integer_and_reorder_should_not_use_sql_limit
- query = assert_sql { Topic.reorder("title").last(5).entries }
- assert_equal 1, query.length
- assert_no_match(/LIMIT/, query.first)
+ def test_last_on_relation_with_limit_and_offset
+ post = posts('sti_comments')
+
+ comments = post.comments.order(id: :asc)
+ assert_equal comments.limit(2).to_a.last, comments.limit(2).last
+ assert_equal comments.limit(2).to_a.last(2), comments.limit(2).last(2)
+ assert_equal comments.limit(2).to_a.last(3), comments.limit(2).last(3)
+
+ comments = comments.offset(1)
+ assert_equal comments.limit(2).to_a.last, comments.limit(2).last
+ assert_equal comments.limit(2).to_a.last(2), comments.limit(2).last(2)
+ assert_equal comments.limit(2).to_a.last(3), comments.limit(2).last(3)
end
def test_take_and_first_and_last_with_integer_should_return_an_array
@@ -564,11 +650,16 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(approved: true).find(1) }
end
- def test_find_on_hash_conditions_with_explicit_table_name
+ def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_string
assert Topic.where('topics.approved' => false).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => true).find(1) }
end
+ def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_symbol
+ assert Topic.where('topics.approved': false).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved': true).find(1) }
+ end
+
def test_find_on_hash_conditions_with_hashed_table_name
assert Topic.where(topics: { approved: false }).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) }
@@ -1058,7 +1149,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_finder_with_offset_string
- assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.offset("3").to_a }
+ assert_nothing_raised { Topic.offset("3").to_a }
end
test "find_by with hash conditions returns the first matching record" do
diff --git a/activerecord/test/cases/fixture_set/file_test.rb b/activerecord/test/cases/fixture_set/file_test.rb
index 242e7a9bec..e64b90507e 100644
--- a/activerecord/test/cases/fixture_set/file_test.rb
+++ b/activerecord/test/cases/fixture_set/file_test.rb
@@ -135,6 +135,12 @@ END
end
end
+ def test_erb_filename
+ filename = 'filename.yaml'
+ erb = File.new(filename).send(:prepare_erb, "<% Rails.env %>\n")
+ assert_equal erb.filename, filename
+ end
+
private
def tmp_yaml(name, contents)
t = Tempfile.new name
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index da934ab8fe..9455d4886c 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -857,7 +857,7 @@ class FoxyFixturesTest < ActiveRecord::TestCase
assert_equal("X marks the spot!", pirates(:mark).catchphrase)
end
- def test_supports_label_interpolation_for_fixnum_label
+ def test_supports_label_interpolation_for_integer_label
assert_equal("#1 pirate!", pirates(1).catchphrase)
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 95f8706d73..d2fdf03e9d 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -1,5 +1,3 @@
-require File.expand_path('../../../../load_paths', __FILE__)
-
require 'config'
require 'active_support/testing/autorun'
diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb
index 5ba9a1029a..9fc75b7377 100644
--- a/activerecord/test/cases/hot_compatibility_test.rb
+++ b/activerecord/test/cases/hot_compatibility_test.rb
@@ -1,7 +1,9 @@
require 'cases/helper'
+require 'support/connection_helper'
class HotCompatibilityTest < ActiveRecord::TestCase
self.use_transactional_tests = false
+ include ConnectionHelper
setup do
@klass = Class.new(ActiveRecord::Base) do
@@ -51,4 +53,90 @@ class HotCompatibilityTest < ActiveRecord::TestCase
record.reload
assert_equal 'bar', record.foo
end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ test "cleans up after prepared statement failure in a transaction" do
+ with_two_connections do |original_connection, ddl_connection|
+ record = @klass.create! bar: 'bar'
+
+ # prepare the reload statement in a transaction
+ @klass.transaction do
+ record.reload
+ end
+
+ assert get_prepared_statement_cache(@klass.connection).any?,
+ "expected prepared statement cache to have something in it"
+
+ # add a new column
+ ddl_connection.add_column :hot_compatibilities, :baz, :string
+
+ assert_raise(ActiveRecord::PreparedStatementCacheExpired) do
+ @klass.transaction do
+ record.reload
+ end
+ end
+
+ assert_empty get_prepared_statement_cache(@klass.connection),
+ "expected prepared statement cache to be empty but it wasn't"
+ end
+ end
+
+ test "cleans up after prepared statement failure in nested transactions" do
+ with_two_connections do |original_connection, ddl_connection|
+ record = @klass.create! bar: 'bar'
+
+ # prepare the reload statement in a transaction
+ @klass.transaction do
+ record.reload
+ end
+
+ assert get_prepared_statement_cache(@klass.connection).any?,
+ "expected prepared statement cache to have something in it"
+
+ # add a new column
+ ddl_connection.add_column :hot_compatibilities, :baz, :string
+
+ assert_raise(ActiveRecord::PreparedStatementCacheExpired) do
+ @klass.transaction do
+ @klass.transaction do
+ @klass.transaction do
+ record.reload
+ end
+ end
+ end
+ end
+
+ assert_empty get_prepared_statement_cache(@klass.connection),
+ "expected prepared statement cache to be empty but it wasn't"
+ end
+ end
+ end
+
+ private
+
+ def get_prepared_statement_cache(connection)
+ connection.instance_variable_get(:@statements)
+ .instance_variable_get(:@cache)[Process.pid]
+ end
+
+ # Rails will automatically clear the prepared statements on the connection
+ # that runs the migration, so we use two connections to simulate what would
+ # actually happen on a production system; we'd have one connection running the
+ # migration from the rake task ("ddl_connection" here), and we'd have another
+ # connection in a web worker.
+ def with_two_connections
+ run_without_connection do |original_connection|
+ ActiveRecord::Base.establish_connection(original_connection.merge(pool_size: 2))
+ begin
+ ddl_connection = ActiveRecord::Base.connection_pool.checkout
+ begin
+ yield original_connection, ddl_connection
+ ensure
+ ActiveRecord::Base.connection_pool.checkin ddl_connection
+ end
+ ensure
+ ActiveRecord::Base.clear_all_connections!
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 7da6842047..e234b9a6a9 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -441,12 +441,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
include InheritanceTestHelper
fixtures :companies
- def setup
- ActiveSupport::Dependencies.log_activity = true
- end
-
teardown do
- ActiveSupport::Dependencies.log_activity = false
self.class.const_remove :FirmOnTheFly rescue nil
Firm.const_remove :FirmOnTheFly rescue nil
end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index e030f6c588..aba854820b 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -151,6 +151,14 @@ module ActiveRecord
end
end
+ class RevertCustomForeignKeyTable < SilentMigration
+ def change
+ change_table(:horses) do |t|
+ t.references :owner, foreign_key: { to_table: :developers }
+ end
+ end
+ end
+
setup do
@verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false
end
@@ -353,6 +361,13 @@ module ActiveRecord
ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = ''
end
+ def test_migrations_can_handle_foreign_keys_to_specific_tables
+ migration = RevertCustomForeignKeyTable.new
+ InvertibleMigration.migrate(:up)
+ migration.migrate(:up)
+ migration.migrate(:down)
+ end
+
# MySQL 5.7 and Oracle do not allow to create duplicate indexes on the same columns
unless current_adapter?(:Mysql2Adapter, :OracleAdapter)
def test_migrate_revert_add_index_with_name
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 4fe76e563a..9fc0041892 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -169,6 +169,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 1, p1.lock_version
end
+ def test_lock_new_when_explicitly_passing_nil
+ p1 = Person.new(:first_name => 'anika', lock_version: nil)
+ p1.save!
+ assert_equal 0, p1.lock_version
+ end
+
def test_touch_existing_lock
p1 = Person.find(1)
assert_equal 0, p1.lock_version
@@ -441,7 +447,7 @@ unless in_memory_db?
def test_lock_sending_custom_lock_statement
Person.transaction do
person = Person.find(1)
- assert_sql(/LIMIT \$\d FOR SHARE NOWAIT/) do
+ assert_sql(/LIMIT \$?\d FOR SHARE NOWAIT/) do
person.lock!('FOR SHARE NOWAIT')
end
end
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 707a2d1da1..c97960a412 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -215,5 +215,11 @@ class LogSubscriberTest < ActiveRecord::TestCase
wait
assert_match(/<16 bytes of binary data>/, @logger.logged(:debug).join)
end
+
+ def test_binary_data_hash
+ Binary.create(data: { a: 1 })
+ wait
+ assert_match(/<7 bytes of binary data>/, @logger.logged(:debug).join)
+ end
end
end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index c7a1b81a75..29546525f3 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -154,7 +154,7 @@ module ActiveRecord
assert_equal String, bob.first_name.class
assert_equal String, bob.last_name.class
assert_equal String, bob.bio.class
- assert_equal Fixnum, bob.age.class
+ assert_kind_of Integer, bob.age
assert_equal Time, bob.birthday.class
if current_adapter?(:OracleAdapter)
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 6a9cdd9d29..60ca90464d 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -21,7 +21,7 @@ module ActiveRecord
teardown do
connection.drop_table :testings rescue nil
ActiveRecord::Migration.verbose = @verbose_was
- ActiveRecord::SchemaMigration.delete_all
+ ActiveRecord::SchemaMigration.delete_all rescue nil
end
def test_migration_doesnt_remove_named_index
@@ -71,6 +71,48 @@ module ActiveRecord
ensure
connection.drop_table :more_testings rescue nil
end
+
+ def test_timestamps_have_null_constraints_if_not_present_in_migration_of_create_table
+ migration = Class.new(ActiveRecord::Migration) {
+ def migrate(x)
+ create_table :more_testings do |t|
+ t.timestamps
+ end
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert connection.columns(:more_testings).find { |c| c.name == 'created_at' }.null
+ assert connection.columns(:more_testings).find { |c| c.name == 'updated_at' }.null
+ ensure
+ connection.drop_table :more_testings rescue nil
+ end
+
+ def test_timestamps_have_null_constraints_if_not_present_in_migration_for_adding_timestamps_to_existing_table
+ migration = Class.new(ActiveRecord::Migration) {
+ def migrate(x)
+ add_timestamps :testings
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert connection.columns(:testings).find { |c| c.name == 'created_at' }.null
+ assert connection.columns(:testings).find { |c| c.name == 'updated_at' }.null
+ end
+
+ def test_legacy_migrations_get_deprecation_warning_when_run
+ migration = Class.new(ActiveRecord::Migration) {
+ def up
+ add_column :testings, :baz, :string
+ end
+ }
+
+ assert_deprecated do
+ migration.migrate :up
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index 0a7b57455c..920c472c73 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -132,6 +132,13 @@ module ActiveRecord
end
end
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_create_join_table_with_uuid
+ connection.create_join_table :artists, :musics, column_options: { type: :uuid }
+ assert_equal [:uuid, :uuid], connection.columns(:artists_musics).map(&:type)
+ end
+ end
+
private
def with_table_cleanup
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index b01415afb2..9e19eb9f73 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -144,6 +144,52 @@ module ActiveRecord
@connection.drop_table "testing", if_exists: true
end
end
+
+ class CreateDogsMigration < ActiveRecord::Migration::Current
+ def change
+ create_table :dog_owners
+
+ create_table :dogs do |t|
+ t.references :dog_owner, foreign_key: true
+ end
+ end
+ end
+
+ def test_references_foreign_key_with_prefix
+ ActiveRecord::Base.table_name_prefix = 'p_'
+ migration = CreateDogsMigration.new
+ silence_stream($stdout) { migration.migrate(:up) }
+ assert_equal 1, @connection.foreign_keys("p_dogs").size
+ ensure
+ silence_stream($stdout) { migration.migrate(:down) }
+ ActiveRecord::Base.table_name_prefix = nil
+ end
+
+ def test_references_foreign_key_with_suffix
+ ActiveRecord::Base.table_name_suffix = '_s'
+ migration = CreateDogsMigration.new
+ silence_stream($stdout) { migration.migrate(:up) }
+ assert_equal 1, @connection.foreign_keys("dogs_s").size
+ ensure
+ silence_stream($stdout) { migration.migrate(:down) }
+ ActiveRecord::Base.table_name_suffix = nil
+ end
+
+ test "multiple foreign keys can be added to the same table" do
+ @connection.create_table :testings do |t|
+ t.integer :col_1
+ t.integer :col_2
+
+ t.foreign_key :testing_parents, column: :col_1
+ t.foreign_key :testing_parents, column: :col_2
+ end
+
+ fks = @connection.foreign_keys("testings")
+
+ fk_definitions = fks.map {|fk| [fk.from_table, fk.to_table, fk.column] }
+ assert_equal([["testings", "testing_parents", "col_1"],
+ ["testings", "testing_parents", "col_2"]], fk_definitions)
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index b9ce6bbc55..70c64f3e71 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -55,6 +55,11 @@ module ActiveRecord
assert index_exists?(table_name, :tag_id, name: 'index_taggings_on_tag_id')
end
+ def test_creates_named_unique_index
+ add_reference table_name, :tag, index: { name: 'index_taggings_on_tag_id', unique: true }
+ assert index_exists?(table_name, :tag_id, name: 'index_taggings_on_tag_id', unique: true )
+ end
+
def test_creates_reference_id_with_specified_type
add_reference table_name, :user, type: :string
assert column_exists?(table_name, :user_id, :string)
diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb
deleted file mode 100644
index 24cba84a09..0000000000
--- a/activerecord/test/cases/migration/table_and_index_test.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- class Migration
- class TableAndIndexTest < ActiveRecord::TestCase
- def test_add_schema_info_respects_prefix_and_suffix
- conn = ActiveRecord::Base.connection
-
- 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_exists: true)
-
- conn.initialize_schema_migrations_table
-
- assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name]
- ensure
- ActiveRecord::Base.table_name_prefix = ""
- ActiveRecord::Base.table_name_suffix = ""
- end
- end
- end
-end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index f51e366b1d..36b6662820 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -69,6 +69,10 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migration.verbose = @verbose_was
end
+ def test_migration_version_matches_component_version
+ assert_equal ActiveRecord::VERSION::STRING.to_f, ActiveRecord::Migration.current_version
+ end
+
def test_migrator_versions
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
@@ -176,7 +180,7 @@ class MigrationTest < ActiveRecord::TestCase
# is_a?(Bignum)
assert_kind_of Integer, b.world_population
assert_equal 6000000000, b.world_population
- assert_kind_of Fixnum, b.my_house_population
+ assert_kind_of Integer, b.my_house_population
assert_equal 3, b.my_house_population
assert_kind_of BigDecimal, b.bank_balance
assert_equal BigDecimal("1586.43"), b.bank_balance
@@ -192,8 +196,6 @@ class MigrationTest < ActiveRecord::TestCase
# of 0, they take on the compile-time limit for precision and scale,
# so the following should succeed unless you have used really wacky
# compilation options
- # - SQLite2 has the default behavior of preserving all data sent in,
- # so this happens there too
assert_kind_of BigDecimal, b.value_of_e
assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e
elsif current_adapter?(:SQLite3Adapter)
@@ -202,7 +204,7 @@ class MigrationTest < ActiveRecord::TestCase
assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001
else
# - SQL standard is an integer
- assert_kind_of Fixnum, b.value_of_e
+ assert_kind_of Integer, b.value_of_e
assert_equal 2, b.value_of_e
end
@@ -357,14 +359,14 @@ class MigrationTest < ActiveRecord::TestCase
def test_internal_metadata_table_name
original_internal_metadata_table_name = ActiveRecord::Base.internal_metadata_table_name
- assert_equal "active_record_internal_metadatas", ActiveRecord::InternalMetadata.table_name
- ActiveRecord::Base.table_name_prefix = "prefix_"
- ActiveRecord::Base.table_name_suffix = "_suffix"
+ assert_equal "ar_internal_metadata", ActiveRecord::InternalMetadata.table_name
+ ActiveRecord::Base.table_name_prefix = "p_"
+ ActiveRecord::Base.table_name_suffix = "_s"
Reminder.reset_table_name
- assert_equal "prefix_active_record_internal_metadatas_suffix", ActiveRecord::InternalMetadata.table_name
+ assert_equal "p_ar_internal_metadata_s", ActiveRecord::InternalMetadata.table_name
ActiveRecord::Base.internal_metadata_table_name = "changed"
Reminder.reset_table_name
- assert_equal "prefix_changed_suffix", ActiveRecord::InternalMetadata.table_name
+ assert_equal "p_changed_s", ActiveRecord::InternalMetadata.table_name
ActiveRecord::Base.table_name_prefix = ""
ActiveRecord::Base.table_name_suffix = ""
Reminder.reset_table_name
@@ -426,6 +428,38 @@ class MigrationTest < ActiveRecord::TestCase
ENV["RACK_ENV"] = original_rack_env
end
+ def test_internal_metadata_stores_environment_when_other_data_exists
+ ActiveRecord::InternalMetadata.delete_all
+ ActiveRecord::InternalMetadata[:foo] = 'bar'
+
+ current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ migrations_path = MIGRATIONS_ROOT + "/valid"
+ old_path = ActiveRecord::Migrator.migrations_paths
+ ActiveRecord::Migrator.migrations_paths = migrations_path
+
+ current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
+ assert_equal 'bar', ActiveRecord::InternalMetadata[:foo]
+ ensure
+ ActiveRecord::Migrator.migrations_paths = old_path
+ end
+
+ def test_rename_internal_metadata_table
+ original_internal_metadata_table_name = ActiveRecord::Base.internal_metadata_table_name
+
+ ActiveRecord::Base.internal_metadata_table_name = "active_record_internal_metadatas"
+ Reminder.reset_table_name
+
+ ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name
+ Reminder.reset_table_name
+
+ assert_equal "ar_internal_metadata", ActiveRecord::InternalMetadata.table_name
+ ensure
+ ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name
+ Reminder.reset_table_name
+ end
+
def test_proper_table_name_on_migration
reminder_class = new_isolated_reminder_class
migration = ActiveRecord::Migration.new
@@ -503,13 +537,12 @@ class MigrationTest < ActiveRecord::TestCase
data_column = columns.detect { |c| c.name == "data" }
assert_nil data_column.default
-
+ ensure
Person.connection.drop_table :binary_testings, if_exists: true
end
unless mysql_enforcing_gtid_consistency?
def test_create_table_with_query
- Person.connection.drop_table :table_from_query_testings rescue nil
Person.connection.create_table(:person, force: true)
Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM person"
@@ -517,12 +550,11 @@ class MigrationTest < ActiveRecord::TestCase
columns = Person.connection.columns(:table_from_query_testings)
assert_equal 1, columns.length
assert_equal "id", columns.first.name
-
+ ensure
Person.connection.drop_table :table_from_query_testings rescue nil
end
def test_create_table_with_query_from_relation
- Person.connection.drop_table :table_from_query_testings rescue nil
Person.connection.create_table(:person, force: true)
Person.connection.create_table :table_from_query_testings, as: Person.select(:id)
@@ -530,7 +562,7 @@ class MigrationTest < ActiveRecord::TestCase
columns = Person.connection.columns(:table_from_query_testings)
assert_equal 1, columns.length
assert_equal "id", columns.first.name
-
+ ensure
Person.connection.drop_table :table_from_query_testings rescue nil
end
end
@@ -573,8 +605,7 @@ class MigrationTest < ActiveRecord::TestCase
end
if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
- def test_out_of_range_limit_should_raise
- Person.connection.drop_table :test_limits rescue nil
+ def test_out_of_range_integer_limit_should_raise
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
@@ -582,16 +613,22 @@ class MigrationTest < ActiveRecord::TestCase
end
assert_match(/No integer type has byte size 10/, e.message)
+ ensure
+ Person.connection.drop_table :test_integer_limits, if_exists: true
+ end
+ end
- 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|
- t.column :bigtext, :text, :limit => 0xfffffffff
- end
+ if current_adapter?(:Mysql2Adapter)
+ def test_out_of_range_text_limit_should_raise
+ e = assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do
+ Person.connection.create_table :test_text_limits, force: true do |t|
+ t.text :bigtext, limit: 0xfffffffff
end
end
- Person.connection.drop_table :test_limits rescue nil
+ assert_match(/No text type has byte length #{0xfffffffff}/, e.message)
+ ensure
+ Person.connection.drop_table :test_text_limits, if_exists: true
end
end
@@ -713,7 +750,7 @@ class ReservedWordsMigrationTest < ActiveRecord::TestCase
connection.add_index :values, :value
connection.remove_index :values, :column => :value
end
-
+ ensure
connection.drop_table :values rescue nil
end
end
@@ -725,11 +762,11 @@ class ExplicitlyNamedIndexMigrationTest < ActiveRecord::TestCase
t.integer :value
end
- assert_nothing_raised ArgumentError do
+ assert_nothing_raised do
connection.add_index :values, :value, name: 'a_different_name'
connection.remove_index :values, column: :value, name: 'a_different_name'
end
-
+ ensure
connection.drop_table :values rescue nil
end
end
@@ -1091,4 +1128,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
ActiveRecord::Base.logger = old
end
+ def test_unknown_migration_version_should_raise_an_argument_error
+ assert_raise(ArgumentError) { ActiveRecord::Migration[1.0] }
+ end
end
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index 7f31325f47..486bcc22df 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -63,7 +63,7 @@ class ModulesTest < ActiveRecord::TestCase
def test_assign_ids
firm = MyApplication::Business::Firm.first
- assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do
+ assert_nothing_raised do
firm.client_ids = [MyApplication::Business::Client.first.id]
end
end
@@ -72,7 +72,7 @@ class ModulesTest < ActiveRecord::TestCase
def test_eager_loading_in_modules
clients = []
- assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do
+ assert_nothing_raised do
clients << MyApplication::Business::Client.references(:accounts).merge!(:includes => {:firm => :account}, :where => 'accounts.id IS NOT NULL').find(3)
clients << MyApplication::Business::Client.includes(:firm => :account).find(3)
end
diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb
index ae18573126..d05cb22740 100644
--- a/activerecord/test/cases/multiparameter_attributes_test.rb
+++ b/activerecord/test/cases/multiparameter_attributes_test.rb
@@ -11,15 +11,13 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
topic.attributes = attributes
# note that extra #to_date call allows test to pass for Oracle, which
# treats dates/times the same
- assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
+ assert_equal Date.new(2004, 6, 24), topic.last_read.to_date
end
def test_multiparameter_attributes_on_date_with_empty_year
attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "24" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -27,8 +25,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "24" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -36,8 +32,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -45,8 +39,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -54,8 +46,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -63,8 +53,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "24" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index 39cdcf5403..a4fbf579a1 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -24,6 +24,13 @@ class MultipleDbTest < ActiveRecord::TestCase
assert_equal(ActiveRecord::Base.connection, Entrant.connection)
end
+ def test_swapping_the_connection
+ old_spec_name, Course.connection_specification_name = Course.connection_specification_name, "primary"
+ assert_equal(Entrant.connection, Course.connection)
+ ensure
+ Course.connection_specification_name = old_spec_name
+ end
+
def test_find
c1 = Course.find(1)
assert_equal "Ruby Development", c1.name
@@ -89,8 +96,8 @@ class MultipleDbTest < ActiveRecord::TestCase
end
def test_connection
- assert_equal Entrant.arel_engine.connection, Bird.arel_engine.connection
- assert_not_equal Entrant.arel_engine.connection, Course.arel_engine.connection
+ assert_equal Entrant.arel_engine.connection.object_id, Bird.arel_engine.connection.object_id
+ assert_not_equal Entrant.arel_engine.connection.object_id, Course.arel_engine.connection.object_id
end
unless in_memory_db?
@@ -104,7 +111,7 @@ class MultipleDbTest < ActiveRecord::TestCase
def test_associations_should_work_when_model_has_no_connection
begin
ActiveRecord::Base.remove_connection
- assert_nothing_raised ActiveRecord::ConnectionNotEstablished do
+ assert_nothing_raised do
College.first.courses.first
end
ensure
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 6fbc6196cc..11fb164d50 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -61,6 +61,13 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
assert_equal "No association found for name `honesty'. Has it been defined yet?", exception.message
end
+ def test_should_raise_an_UnknownAttributeError_for_non_existing_nested_attributes
+ exception = assert_raise ActiveModel::UnknownAttributeError do
+ Pirate.new(:ship_attributes => { :sail => true })
+ end
+ assert_equal "unknown attribute 'sail' for Ship.", exception.message
+ end
+
def test_should_disable_allow_destroy_by_default
Pirate.accepts_nested_attributes_for :ship
@@ -69,7 +76,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
pirate.update(ship_attributes: { '_destroy' => true, :id => ship.id })
- assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.ship.reload }
+ assert_nothing_raised { pirate.ship.reload }
end
def test_a_model_should_respond_to_underscore_destroy_and_return_if_it_is_marked_for_destruction
@@ -173,7 +180,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
pirate.ship_attributes = { :id => "" }
- assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.save! }
+ assert_nothing_raised { pirate.save! }
end
def test_first_and_array_index_zero_methods_return_the_same_value_when_nested_attributes_are_set_to_update_existing_record
@@ -504,7 +511,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
[nil, '0', 0, 'false', false].each do |not_truth|
@ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: not_truth })
- assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload }
+ assert_nothing_raised { @ship.pirate.reload }
end
end
@@ -512,7 +519,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
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 }
+ assert_nothing_raised { @ship.pirate.reload }
ensure
Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc(&:empty?)
end
@@ -529,7 +536,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
pirate = @ship.pirate
@ship.attributes = { :pirate_attributes => { :id => pirate.id, '_destroy' => true } }
- assert_nothing_raised(ActiveRecord::RecordNotFound) { Pirate.find(pirate.id) }
+ assert_nothing_raised { Pirate.find(pirate.id) }
@ship.save
assert_raise(ActiveRecord::RecordNotFound) { Pirate.find(pirate.id) }
end
@@ -582,6 +589,13 @@ module NestedAttributesOnACollectionAssociationTests
assert_respond_to @pirate, association_setter
end
+ def test_should_raise_an_UnknownAttributeError_for_non_existing_nested_attributes_for_has_many
+ exception = assert_raise ActiveModel::UnknownAttributeError do
+ @pirate.parrots_attributes = [{ peg_leg: true }]
+ end
+ assert_equal "unknown attribute 'peg_leg' for Parrot.", exception.message
+ end
+
def test_should_save_only_one_association_on_create
pirate = Pirate.create!({
:catchphrase => 'Arr',
@@ -699,7 +713,7 @@ module NestedAttributesOnACollectionAssociationTests
end
def test_should_not_assign_destroy_key_to_a_record
- assert_nothing_raised ActiveRecord::UnknownAttributeError do
+ assert_nothing_raised do
@pirate.send(association_setter, { 'foo' => { '_destroy' => '0' }})
end
end
@@ -734,8 +748,8 @@ module NestedAttributesOnACollectionAssociationTests
end
def test_should_raise_an_argument_error_if_something_else_than_a_hash_is_passed
- assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, {}) }
- assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, Hash.new) }
+ assert_nothing_raised { @pirate.send(association_setter, {}) }
+ assert_nothing_raised { @pirate.send(association_setter, Hash.new) }
exception = assert_raise ArgumentError do
@pirate.send(association_setter, "foo")
@@ -810,7 +824,7 @@ module NestedAttributesOnACollectionAssociationTests
def test_can_use_symbols_as_object_identifier
@pirate.attributes = { :parrots_attributes => { :foo => { :name => 'Lovely Day' }, :bar => { :name => 'Blown Away' } } }
- assert_nothing_raised(NoMethodError) { @pirate.save! }
+ assert_nothing_raised { @pirate.save! }
end
def test_numeric_column_changes_from_zero_to_no_empty_string
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index af15e63d9c..56092aaa0c 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -183,7 +183,7 @@ class PersistenceTest < ActiveRecord::TestCase
end
end
- def test_dupd_becomes_persists_changes_from_the_original
+ def test_duped_becomes_persists_changes_from_the_original
original = topics(:first)
copy = original.dup.becomes(Reply)
copy.save!
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 7e18313c00..52eac4a124 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -130,27 +130,25 @@ class PrimaryKeysTest < ActiveRecord::TestCase
end
def test_supports_primary_key
- assert_nothing_raised NoMethodError do
+ assert_nothing_raised do
ActiveRecord::Base.connection.supports_primary_key?
end
end
- def test_primary_key_returns_value_if_it_exists
- klass = Class.new(ActiveRecord::Base) do
- self.table_name = 'developers'
- end
+ if ActiveRecord::Base.connection.supports_primary_key?
+ def test_primary_key_returns_value_if_it_exists
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'developers'
+ end
- if ActiveRecord::Base.connection.supports_primary_key?
assert_equal 'id', klass.primary_key
end
- end
- def test_primary_key_returns_nil_if_it_does_not_exist
- klass = Class.new(ActiveRecord::Base) do
- self.table_name = 'developers_projects'
- end
+ def test_primary_key_returns_nil_if_it_does_not_exist
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'developers_projects'
+ end
- if ActiveRecord::Base.connection.supports_primary_key?
assert_nil klass.primary_key
end
end
@@ -230,9 +228,10 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
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
+ column = Barcode.column_for_attribute(Barcode.primary_key)
+ assert_not column.null
+ assert_equal :string, column.type
+ assert_equal 42, column.limit
end
test "schema dump primary key includes type and options" do
@@ -262,6 +261,13 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
assert_equal ["region", "code"], @connection.primary_keys("barcodes")
end
+ def test_primary_key_issues_warning
+ warning = capture(:stderr) do
+ assert_nil @connection.primary_key("barcodes")
+ end
+ assert_match(/WARNING: Rails does not support composite primary key\./, warning)
+ end
+
def test_collectly_dump_composite_primary_key
schema = dump_table_schema "barcodes"
assert_match %r{create_table "barcodes", primary_key: \["region", "code"\]}, schema
@@ -345,9 +351,9 @@ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
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
+ assert_match %r{create_table "widgets", id: :bigserial, force: :cascade}, schema
else
- assert_match %r{create_table "widgets", id: :bigint}, schema
+ assert_match %r{create_table "widgets", id: :bigint, force: :cascade}, schema
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index d84653e4c9..d5c01315c1 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -16,7 +16,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_exceptional_middleware_clears_and_disables_cache_on_error
assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
Task.find 1
Task.find 1
assert_equal 1, ActiveRecord::Base.connection.query_cache.length
@@ -31,7 +31,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_exceptional_middleware_leaves_enabled_cache_alone
ActiveRecord::Base.connection.enable_query_cache!
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
raise "lol borked"
}
assert_raises(RuntimeError) { mw.call({}) }
@@ -42,7 +42,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_exceptional_middleware_assigns_original_connection_id_on_error
connection_id = ActiveRecord::Base.connection_id
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
ActiveRecord::Base.connection_id = self.object_id
raise "lol borked"
}
@@ -53,7 +53,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_middleware_delegates
called = false
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
called = true
[200, {}, nil]
}
@@ -62,7 +62,7 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_middleware_caches
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
Task.find 1
Task.find 1
assert_equal 1, ActiveRecord::Base.connection.query_cache.length
@@ -74,50 +74,13 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_enabled_during_call
assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on'
[200, {}, nil]
}
mw.call({})
end
- def test_cache_on_during_body_write
- streaming = Class.new do
- def each
- yield ActiveRecord::Base.connection.query_cache_enabled
- end
- end
-
- mw = ActiveRecord::QueryCache.new lambda { |env|
- [200, {}, streaming.new]
- }
- body = mw.call({}).last
- body.each { |x| assert x, 'cache should be on' }
- body.close
- assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
- end
-
- def test_cache_off_after_close
- mw = ActiveRecord::QueryCache.new lambda { |env| [200, {}, nil] }
- body = mw.call({}).last
-
- assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled'
- body.close
- assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
- end
-
- def test_cache_clear_after_close
- mw = ActiveRecord::QueryCache.new lambda { |env|
- Post.first
- [200, {}, nil]
- }
- body = mw.call({}).last
-
- assert !ActiveRecord::Base.connection.query_cache.empty?, 'cache not empty'
- body.close
- assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty'
- end
-
def test_cache_passing_a_relation
post = Post.first
Post.cache do
@@ -170,7 +133,6 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_is_flat
Task.cache do
- Topic.columns # don't count this query
assert_queries(1) { Topic.find(1); Topic.find(1); }
end
@@ -181,13 +143,12 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_does_not_wrap_string_results_in_arrays
Task.cache do
- # Oracle adapter returns count() as Fixnum or Float
+ # Oracle adapter returns count() as Integer 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, :PostgreSQLAdapter)
# Future versions of the sqlite3 adapter will return numeric
- assert_instance_of Fixnum,
- Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
+ assert_instance_of Fixnum, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
else
assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
end
@@ -213,6 +174,22 @@ class QueryCacheTest < ActiveRecord::TestCase
ActiveRecord::Base.configurations = conf
end
+ def test_cache_is_not_available_when_using_a_not_connected_connection
+ spec_name = Task.connection_specification_name
+ conf = ActiveRecord::Base.configurations['arunit'].merge('name' => 'test2')
+ ActiveRecord::Base.connection_handler.establish_connection(conf)
+ Task.connection_specification_name = "test2"
+ refute Task.connected?
+
+ Task.cache do
+ Task.connection # warmup postgresql connection setup queries
+ assert_queries(2) { Task.find(1); Task.find(1) }
+ end
+ ensure
+ ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name)
+ Task.connection_specification_name = spec_name
+ end
+
def test_query_cache_doesnt_leak_cached_results_of_rolled_back_queries
ActiveRecord::Base.connection.enable_query_cache!
post = Post.first
@@ -244,6 +221,13 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_equal 0, Post.where(title: 'rollback').to_a.count
end
end
+
+ private
+ def middleware(&app)
+ executor = Class.new(ActiveSupport::Executor)
+ ActiveRecord::QueryCache.install_executor_hooks executor
+ lambda { |env| executor.wrap { app.call(env) } }
+ end
end
class QueryCacheExpiryTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index 6d91f96bf6..c01c82f4f5 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -102,9 +102,9 @@ module ActiveRecord
assert_equal float.to_s, @quoter.quote(float, nil)
end
- def test_quote_fixnum
- fixnum = 1
- assert_equal fixnum.to_s, @quoter.quote(fixnum, nil)
+ def test_quote_integer
+ integer = 1
+ assert_equal integer.to_s, @quoter.quote(integer, nil)
end
def test_quote_bignum
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index d0f60a84b5..ffb2da7a26 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -26,6 +26,10 @@ module ActiveRecord
def sanitize_sql_for_order(sql)
sql
end
+
+ def arel_attribute(name, table)
+ table[name]
+ end
end
def relation
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
index 28a0862f91..ce8c5ca489 100644
--- a/activerecord/test/cases/relation/or_test.rb
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -82,5 +82,11 @@ module ActiveRecord
assert_equal p.loaded?, true
assert_equal expected, p.or(Post.where('id = 2')).to_a
end
+
+ def test_or_with_non_relation_object_raises_error
+ assert_raises ArgumentError do
+ Post.where(id: [1, 2, 3]).or(title: 'Rails')
+ end
+ 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
index 62f0a7cc49..0e0e23b24b 100644
--- a/activerecord/test/cases/relation/record_fetch_warning_test.rb
+++ b/activerecord/test/cases/relation/record_fetch_warning_test.rb
@@ -1,28 +1,40 @@
require 'cases/helper'
require 'models/post'
+require 'active_record/relation/record_fetch_warning'
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
+ def setup
+ @original_logger = ActiveRecord::Base.logger
+ @original_warn_on_records_fetched_greater_than = ActiveRecord::Base.warn_on_records_fetched_greater_than
+ @log = StringIO.new
+ end
+
+ def teardown
+ ActiveRecord::Base.logger = @original_logger
+ ActiveRecord::Base.warn_on_records_fetched_greater_than = @original_warn_on_records_fetched_greater_than
+ end
- log = StringIO.new
- ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
+ def test_warn_on_records_fetched_greater_than_allowed_limit
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(@log)
ActiveRecord::Base.logger.level = Logger::WARN
+ ActiveRecord::Base.warn_on_records_fetched_greater_than = 1
- require 'active_record/relation/record_fetch_warning'
+ Post.all.to_a
- ActiveRecord::Base.warn_on_records_fetched_greater_than = 1
+ assert_match(/Query fetched/, @log.string)
+ end
+
+ def test_does_not_warn_on_records_fetched_less_than_allowed_limit
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(@log)
+ ActiveRecord::Base.logger.level = Logger::WARN
+ ActiveRecord::Base.warn_on_records_fetched_greater_than = 100
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
+ assert_no_match(/Query fetched/, @log.string)
end
end
end
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index bc6378b90e..56a2b5b8c6 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -2,6 +2,7 @@ require "cases/helper"
require "models/author"
require "models/binary"
require "models/cake_designer"
+require "models/car"
require "models/chef"
require "models/comment"
require "models/edge"
@@ -14,7 +15,7 @@ require "models/vertex"
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
- fixtures :posts, :edges, :authors, :binaries, :essays
+ fixtures :posts, :edges, :authors, :binaries, :essays, :cars, :treasures, :price_estimates
def test_where_copies_bind_params
author = authors(:david)
@@ -114,6 +115,17 @@ module ActiveRecord
assert_equal expected.to_sql, actual.to_sql
end
+ def test_polymorphic_array_where_multiple_types
+ treasure_1 = treasures(:diamond)
+ treasure_2 = treasures(:sapphire)
+ car = cars(:honda)
+
+ expected = [price_estimates(:diamond), price_estimates(:sapphire_1), price_estimates(:sapphire_2), price_estimates(:honda)].sort
+ actual = PriceEstimate.where(estimate_of: [treasure_1, treasure_2, car]).to_a.sort
+
+ assert_equal expected, actual
+ end
+
def test_polymorphic_nested_relation_where
expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: Treasure.where(id: [1,2]))
actual = PriceEstimate.where(estimate_of: Treasure.where(id: [1,2]))
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 0638edacbd..5604124bb3 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -19,6 +19,7 @@ require 'models/aircraft'
require "models/possession"
require "models/reader"
require "models/categorization"
+require "models/edge"
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
@@ -223,6 +224,39 @@ class RelationTest < ActiveRecord::TestCase
assert_equal topics(:fifth).title, topics.first.title
end
+ def test_reverse_order_with_function
+ topics = Topic.order("length(title)").reverse_order
+ assert_equal topics(:second).title, topics.first.title
+ end
+
+ def test_reverse_order_with_function_other_predicates
+ topics = Topic.order("author_name, length(title), id").reverse_order
+ assert_equal topics(:second).title, topics.first.title
+ topics = Topic.order("length(author_name), id, length(title)").reverse_order
+ assert_equal topics(:fifth).title, topics.first.title
+ end
+
+ def test_reverse_order_with_multiargument_function
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order("concat(author_name, title)").reverse_order
+ end
+ end
+
+ def test_reverse_order_with_nulls_first_or_last
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order("title NULLS FIRST").reverse_order
+ end
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order("title nulls last").reverse_order
+ end
+ end
+
+ def test_default_reverse_order_on_table_without_primary_key
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Edge.all.reverse_order
+ end
+ end
+
def test_order_with_hash_and_symbol_generates_the_same_sql
assert_equal Topic.order(:id).to_sql, Topic.order(:id => :asc).to_sql
end
@@ -324,6 +358,12 @@ class RelationTest < ActiveRecord::TestCase
def test_finding_with_sanitized_order
query = Tag.order(["field(id, ?)", [1,3,2]]).to_sql
assert_match(/field\(id, 1,3,2\)/, query)
+
+ query = Tag.order(["field(id, ?)", []]).to_sql
+ assert_match(/field\(id, NULL\)/, query)
+
+ query = Tag.order(["field(id, ?)", nil]).to_sql
+ assert_match(/field\(id, NULL\)/, query)
end
def test_finding_with_order_limit_and_offset
@@ -1046,6 +1086,11 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 9, posts.where(:comments_count => 0).count
end
+ def test_count_with_block
+ posts = Post.all
+ assert_equal 10, posts.count { |p| p.comments_count.even? }
+ end
+
def test_count_on_association_relation
author = Author.last
another_author = Author.first
@@ -1239,6 +1284,16 @@ class RelationTest < ActiveRecord::TestCase
assert posts.loaded?
end
+ def test_to_a_should_dup_target
+ posts = Post.all
+
+ original_size = posts.size
+ removed = posts.to_a.pop
+
+ assert_equal original_size, posts.size
+ assert_includes posts.to_a, removed
+ end
+
def test_build
posts = Post.all
@@ -1939,4 +1994,22 @@ class RelationTest < ActiveRecord::TestCase
def test_relation_join_method
assert_equal 'Thank you for the welcome,Thank you again for the welcome', Post.first.comments.join(",")
end
+
+ def test_connection_adapters_can_reorder_binds
+ posts = Post.limit(1).offset(2)
+
+ stubbed_connection = Post.connection.dup
+ def stubbed_connection.combine_bind_parameters(**kwargs)
+ offset = kwargs[:offset]
+ kwargs[:offset] = kwargs[:limit]
+ kwargs[:limit] = offset
+ super(**kwargs)
+ end
+
+ posts.define_singleton_method(:connection) do
+ stubbed_connection
+ end
+
+ assert_equal 2, posts.to_a.length
+ end
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 7b93d20e05..12f4196724 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -29,8 +29,22 @@ class SchemaDumperTest < ActiveRecord::TestCase
ActiveRecord::SchemaMigration.delete_all
end
- def test_magic_comment
- assert_match "# encoding: #{Encoding.default_external.name}", standard_dump
+ if current_adapter?(:SQLite3Adapter)
+ %w{3.7.8 3.7.11 3.7.12}.each do |version_string|
+ test "dumps schema version for sqlite version #{version_string}" do
+ version = ActiveRecord::ConnectionAdapters::SQLite3Adapter::Version.new(version_string)
+ ActiveRecord::Base.connection.stubs(:sqlite_version).returns(version)
+
+ versions = %w{ 20100101010101 20100201010101 20100301010101 }
+ versions.reverse_each do |v|
+ ActiveRecord::SchemaMigration.create!(:version => v)
+ end
+
+ schema_info = ActiveRecord::Base.connection.dump_schema_information
+ assert_match(/20100201010101.*20100301010101/m, schema_info)
+ ActiveRecord::SchemaMigration.delete_all
+ end
+ end
end
def test_schema_dump
@@ -38,7 +52,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
- assert_no_match %r{create_table "active_record_internal_metadatas"}, output
+ assert_no_match %r{create_table "ar_internal_metadata"}, output
end
def test_schema_dump_uses_force_cascade_on_create_table
@@ -74,7 +88,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
next if column_set.empty?
lengths = column_set.map do |column|
- if match = column.match(/\bt\.\w+\s+"/)
+ if match = column.match(/\bt\.\w+\s+(?="\w+?")/)
match[0].length
end
end.compact
@@ -147,10 +161,10 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{c_int_7.*limit: 7}, output
assert_match %r{c_int_8.*limit: 8}, output
else
- assert_match %r{c_int_5.*limit: 8}, output
- assert_match %r{c_int_6.*limit: 8}, output
- assert_match %r{c_int_7.*limit: 8}, output
- assert_match %r{c_int_8.*limit: 8}, output
+ assert_match %r{t\.bigint\s+"c_int_5"$}, output
+ assert_match %r{t\.bigint\s+"c_int_6"$}, output
+ assert_match %r{t\.bigint\s+"c_int_7"$}, output
+ assert_match %r{t\.bigint\s+"c_int_8"$}, output
end
end
@@ -159,7 +173,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
- assert_no_match %r{create_table "active_record_internal_metadatas"}, output
+ assert_no_match %r{create_table "ar_internal_metadata"}, output
end
def test_schema_dump_with_regexp_ignored_table
@@ -167,7 +181,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
- assert_no_match %r{create_table "active_record_internal_metadatas"}, output
+ assert_no_match %r{create_table "ar_internal_metadata"}, output
end
def test_schema_dumps_index_columns_in_right_order
@@ -204,12 +218,17 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.boolean\s+"has_fun",.+default: false}, output
end
- if current_adapter?(: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
- end
+ def test_schema_dump_does_not_include_limit_for_text_field
+ output = standard_dump
+ assert_match %r{t\.text\s+"params"$}, output
+ end
+
+ def test_schema_dump_does_not_include_limit_for_binary_field
+ output = standard_dump
+ assert_match %r{t\.binary\s+"data"$}, output
+ end
+ if current_adapter?(:Mysql2Adapter)
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
@@ -219,11 +238,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
output = standard_dump
assert_match %r{t\.blob\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+"normal_blob"$}, 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+"normal_text"$}, 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
@@ -248,12 +267,12 @@ class SchemaDumperTest < ActiveRecord::TestCase
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\.bigint\s+"bigint_default",\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
+ assert_match %r{t\.bigint\s+"big_int_data_points\",\s+array: true}, output
end
def test_schema_dump_allows_array_of_decimal_defaults
@@ -261,6 +280,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.decimal\s+"decimal_array_default",\s+default: \["1.23", "3.45"\],\s+array: true}, output
end
+ def test_schema_dump_expression_indices
+ index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
+ assert_equal 't.index "lower((name)::text)", name: "company_expression_index", using: :btree', index_definition
+ end
+
if ActiveRecord::Base.connection.supports_extensions?
def test_schema_dump_includes_extensions
connection = ActiveRecord::Base.connection
@@ -323,9 +347,9 @@ class SchemaDumperTest < ActiveRecord::TestCase
create_table("dogs") do |t|
t.column :name, :string
t.column :owner_id, :integer
+ t.index [:name]
+ t.foreign_key :dog_owners, column: "owner_id" if supports_foreign_keys?
end
- add_index "dogs", [:name]
- add_foreign_key :dogs, :dog_owners, column: "owner_id" if supports_foreign_keys?
end
def down
drop_table("dogs")
@@ -345,7 +369,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "foo_.+_bar"}, output
assert_no_match %r{add_index "foo_.+_bar"}, output
assert_no_match %r{create_table "schema_migrations"}, output
- assert_no_match %r{create_table "active_record_internal_metadatas"}, output
+ assert_no_match %r{create_table "ar_internal_metadata"}, output
if ActiveRecord::Base.connection.supports_foreign_keys?
assert_no_match %r{add_foreign_key "foo_.+_bar"}, output
diff --git a/activerecord/test/cases/schema_loading_test.rb b/activerecord/test/cases/schema_loading_test.rb
new file mode 100644
index 0000000000..3d92a5e104
--- /dev/null
+++ b/activerecord/test/cases/schema_loading_test.rb
@@ -0,0 +1,52 @@
+require "cases/helper"
+
+module SchemaLoadCounter
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ attr_accessor :load_schema_calls
+
+ def load_schema!
+ self.load_schema_calls ||= 0
+ self.load_schema_calls +=1
+ super
+ end
+ end
+end
+
+class SchemaLoadingTest < ActiveRecord::TestCase
+ def test_basic_model_is_loaded_once
+ klass = define_model
+ klass.new
+ assert_equal 1, klass.load_schema_calls
+ end
+
+ def test_model_with_custom_lock_is_loaded_once
+ klass = define_model do |c|
+ c.table_name = :lock_without_defaults_cust
+ c.locking_column = :custom_lock_version
+ end
+ klass.new
+ assert_equal 1, klass.load_schema_calls
+ end
+
+ def test_model_with_changed_custom_lock_is_loaded_twice
+ klass = define_model do |c|
+ c.table_name = :lock_without_defaults_cust
+ end
+ klass.new
+ klass.locking_column = :custom_lock_version
+ klass.new
+ assert_equal 2, klass.load_schema_calls
+ end
+
+ private
+
+ def define_model
+ Class.new(ActiveRecord::Base) do
+ include SchemaLoadCounter
+ self.table_name = :lock_without_defaults
+ yield self if block_given?
+ end
+ end
+end
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index ad5ca70f36..dcd09b6973 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -4,6 +4,7 @@ require 'models/comment'
require 'models/developer'
require 'models/computer'
require 'models/vehicle'
+require 'models/cat'
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts, :comments
@@ -374,6 +375,18 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 10, DeveloperCalledJamis.unscoped { DeveloperCalledJamis.poor }.length
end
+ def test_default_scope_with_joins
+ assert_equal Comment.where(post_id: SpecialPostWithDefaultScope.pluck(:id)).count,
+ Comment.joins(:special_post_with_default_scope).count
+ assert_equal Comment.where(post_id: Post.pluck(:id)).count,
+ Comment.joins(:post).count
+ end
+
+ def test_unscoped_with_joins_should_not_have_default_scope
+ assert_equal SpecialPostWithDefaultScope.unscoped { Comment.joins(:special_post_with_default_scope).to_a },
+ Comment.joins(:post).to_a
+ end
+
def test_default_scope_select_ignored_by_aggregations
assert_equal DeveloperWithSelect.all.to_a.count, DeveloperWithSelect.count
end
@@ -473,4 +486,15 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 1, SubConditionalStiPost.all.to_a.size
assert_equal 2, SubConditionalStiPost.unscope(where: :title).to_a.size
end
+
+ def test_with_abstract_class_scope_should_be_executed_in_correct_context
+ vegetarian_pattern, gender_pattern = if current_adapter?(:Mysql2Adapter)
+ [/`lions`.`is_vegetarian`/, /`lions`.`gender`/]
+ else
+ [/"lions"."is_vegetarian"/, /"lions"."gender"/]
+ end
+
+ assert_match vegetarian_pattern, Lion.all.to_sql
+ assert_match gender_pattern, Lion.female.to_sql
+ end
end
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 7a8eaeccb7..0e277ed235 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -301,7 +301,7 @@ class NamedScopingTest < ActiveRecord::TestCase
:relation, # private class method on AR::Base
:new, # redefined class method on AR::Base
:all, # a default scope
- :public, # some imporant methods on Module and Class
+ :public, # some important methods on Module and Class
:protected,
:private,
:name,
@@ -440,6 +440,25 @@ class NamedScopingTest < ActiveRecord::TestCase
end
end
+ def test_scopes_with_reserved_names
+ class << Topic
+ def public_method; end
+ public :public_method
+
+ def protected_method; end
+ protected :protected_method
+
+ def private_method; end
+ private :private_method
+ end
+
+ [:public_method, :protected_method, :private_method].each do |reserved_method|
+ assert Topic.respond_to?(reserved_method, true)
+ ActiveRecord::Base.logger.expects(:warn)
+ silence_warnings { Topic.scope(reserved_method, -> { }) }
+ end
+ end
+
def test_scopes_on_relations
# Topic.replied
approved_topics = Topic.all.approved.order('id DESC')
@@ -525,4 +544,18 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal 1, SpecialComment.where(body: 'go crazy').created.count
end
+ def test_model_class_should_respond_to_none
+ assert !Topic.none?
+ Topic.delete_all
+ assert Topic.none?
+ end
+
+ def test_model_class_should_respond_to_one
+ assert !Topic.one?
+ Topic.delete_all
+ assert !Topic.one?
+ Topic.create!
+ assert Topic.one?
+ end
+
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 6056156698..846be857d0 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -295,4 +295,37 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic.update_attribute :content, nil
assert_equal [topic], Topic.where(content: nil)
end
+
+ def test_mutation_detection_does_not_double_serialize
+ coder = Object.new
+ def coder.dump(value)
+ return if value.nil?
+ value + " encoded"
+ end
+ def coder.load(value)
+ return if value.nil?
+ value.gsub(" encoded", "")
+ end
+ type = Class.new(ActiveModel::Type::Value) do
+ include ActiveModel::Type::Helpers::Mutable
+
+ def serialize(value)
+ return if value.nil?
+ value + " serialized"
+ end
+
+ def deserialize(value)
+ return if value.nil?
+ value.gsub(" serialized", "")
+ end
+ end.new
+ model = Class.new(Topic) do
+ attribute :foo, type
+ serialize :foo, coder
+ end
+
+ topic = model.create!(foo: "bar")
+ topic.foo
+ refute topic.changed?
+ end
end
diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb
index a704b861cb..104226010a 100644
--- a/activerecord/test/cases/statement_cache_test.rb
+++ b/activerecord/test/cases/statement_cache_test.rb
@@ -94,5 +94,17 @@ module ActiveRecord
additional_books = cache.execute([], Book, Book.connection)
assert first_books != additional_books
end
+
+ def test_unprepared_statements_dont_share_a_cache_with_prepared_statements
+ Book.create(name: "my book")
+ Book.create(name: "my other book")
+
+ book = Book.find_by(name: "my book")
+ other_book = Book.connection.unprepared_statement do
+ Book.find_by(name: "my other book")
+ end
+
+ refute_equal book, other_book
+ end
end
end
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index e9cdf94c99..bce86875e1 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -104,7 +104,7 @@ class StoreTest < ActiveRecord::TestCase
assert_equal true, user.settings.instance_of?(ActiveSupport::HashWithIndifferentAccess)
end
- test "convert store attributes from any format other than Hash or HashWithIndifferent access losing the data" do
+ test "convert store attributes from any format other than Hash or HashWithIndifferentAccess losing the data" do
@john.json_data = "somedata"
@john.height = 'low'
assert_equal true, @john.json_data.instance_of?(ActiveSupport::HashWithIndifferentAccess)
@@ -177,6 +177,7 @@ class StoreTest < ActiveRecord::TestCase
assert_equal [:color], first_model.stored_attributes[:data]
assert_equal [:color, :width, :height], second_model.stored_attributes[:data]
assert_equal [:color, :area, :volume], third_model.stored_attributes[:data]
+ assert_equal [:color], first_model.stored_attributes[:data]
end
test "YAML coder initializes the store when a Nil value is given" do
diff --git a/activerecord/test/cases/suppressor_test.rb b/activerecord/test/cases/suppressor_test.rb
index 72c5c16555..2f00241de2 100644
--- a/activerecord/test/cases/suppressor_test.rb
+++ b/activerecord/test/cases/suppressor_test.rb
@@ -46,7 +46,30 @@ class SuppressorTest < ActiveRecord::TestCase
Notification.suppress { UserWithNotification.create! }
assert_difference -> { Notification.count } do
- Notification.create!
+ Notification.create!(message: "New Comment")
+ end
+ end
+
+ def test_suppresses_validations_on_create
+ assert_no_difference -> { Notification.count } do
+ Notification.suppress do
+ User.create
+ User.create!
+ User.new.save
+ User.new.save!
+ end
+ end
+ end
+
+ def test_suppresses_when_nested_multiple_times
+ assert_no_difference -> { Notification.count } do
+ Notification.suppress do
+ Notification.suppress { }
+ Notification.create
+ Notification.create!
+ Notification.new.save
+ Notification.new.save!
+ end
end
end
end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 49df6628eb..510bb088c8 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -8,6 +8,13 @@ module ActiveRecord
ActiveRecord::Tasks::MySQLDatabaseTasks.stubs(:new).returns @mysql_tasks
ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stubs(:new).returns @postgresql_tasks
ActiveRecord::Tasks::SQLiteDatabaseTasks.stubs(:new).returns @sqlite_tasks
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
end
@@ -161,21 +168,25 @@ module ActiveRecord
with('database' => 'dev-db')
ActiveRecord::Tasks::DatabaseTasks.expects(:create).
with('database' => 'test-db')
- ENV.expects(:[]).with('RAILS_ENV').returns(nil)
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new('development')
)
end
- def test_creates_only_development_database_when_rails_env_is_development
+ def test_creates_test_and_development_databases_when_rails_env_is_development
+ old_env = ENV['RAILS_ENV']
+ ENV['RAILS_ENV'] = 'development'
ActiveRecord::Tasks::DatabaseTasks.expects(:create).
with('database' => 'dev-db')
- ENV.expects(:[]).with('RAILS_ENV').returns('development')
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).
+ with('database' => 'test-db')
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new('development')
)
+ ensure
+ ENV['RAILS_ENV'] = old_env
end
def test_establishes_connection_for_the_given_environment
@@ -282,38 +293,55 @@ module ActiveRecord
with('database' => 'dev-db')
ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
with('database' => 'test-db')
- ENV.expects(:[]).with('RAILS_ENV').returns(nil)
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new('development')
)
end
- def test_drops_only_development_database_when_rails_env_is_development
+ def test_drops_testand_development_databases_when_rails_env_is_development
+ old_env = ENV['RAILS_ENV']
+ ENV['RAILS_ENV'] = 'development'
ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
with('database' => 'dev-db')
- ENV.expects(:[]).with('RAILS_ENV').returns('development')
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
+ with('database' => 'test-db')
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new('development')
)
+ ensure
+ ENV['RAILS_ENV'] = old_env
end
end
class DatabaseTasksMigrateTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ def setup
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths = 'custom/path'
+ end
+
+ def teardown
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil
+ end
+
def test_migrate_receives_correct_env_vars
verbose, version = ENV['VERBOSE'], ENV['VERSION']
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths = 'custom/path'
ENV['VERBOSE'] = 'false'
ENV['VERSION'] = '4'
ActiveRecord::Migrator.expects(:migrate).with('custom/path', 4)
ActiveRecord::Tasks::DatabaseTasks.migrate
ensure
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil
ENV['VERBOSE'], ENV['VERSION'] = verbose, version
end
+
+ def test_migrate_clears_schema_cache_afterward
+ ActiveRecord::Base.expects(:clear_cache!)
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+ end
end
class DatabaseTasksPurgeTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 7c1da51990..70e406038f 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -1,4 +1,5 @@
require 'cases/helper'
+require 'active_record/tasks/database_tasks'
if current_adapter?(:Mysql2Adapter)
module ActiveRecord
@@ -12,6 +13,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_establishes_connection_without_database
@@ -48,14 +56,20 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
- def test_create_when_database_exists_outputs_info_to_stderr
- $stderr.expects(:puts).with("my-app-db already exists").once
+ def test_when_database_created_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ assert_equal $stdout.string, "Created database 'my-app-db'\n"
+ end
+
+ def test_create_when_database_exists_outputs_info_to_stderr
ActiveRecord::Base.connection.stubs(:create_database).raises(
- ActiveRecord::StatementInvalid.new("Can't create database 'dev'; database exists:")
+ ActiveRecord::Tasks::DatabaseAlreadyExists
)
ActiveRecord::Tasks::DatabaseTasks.create @configuration
+
+ assert_equal $stderr.string, "Database 'my-app-db' already exists\n"
end
end
@@ -77,6 +91,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:establish_connection).
raises(@error).
then.returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_root_password_is_requested
@@ -160,6 +181,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_establishes_connection_to_mysql_database
@@ -173,6 +201,12 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.drop @configuration
end
+
+ def test_when_database_dropped_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+
+ assert_equal $stdout.string, "Dropped database 'my-app-db'\n"
+ end
end
class MySQLPurgeTest < ActiveRecord::TestCase
@@ -307,6 +341,5 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
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 ba53f340ae..99d73e91a4 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -1,4 +1,5 @@
require 'cases/helper'
+require 'active_record/tasks/database_tasks'
if current_adapter?(:PostgreSQLAdapter)
module ActiveRecord
@@ -12,6 +13,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_establishes_connection_to_postgresql_database
@@ -63,14 +71,20 @@ module ActiveRecord
assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration }
end
- def test_create_when_database_exists_outputs_info_to_stderr
- $stderr.expects(:puts).with("my-app-db already exists").once
+ def test_when_database_created_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ assert_equal $stdout.string, "Created database 'my-app-db'\n"
+ end
+
+ def test_create_when_database_exists_outputs_info_to_stderr
ActiveRecord::Base.connection.stubs(:create_database).raises(
- ActiveRecord::StatementInvalid.new('database "my-app-db" already exists')
+ ActiveRecord::Tasks::DatabaseAlreadyExists
)
ActiveRecord::Tasks::DatabaseTasks.create @configuration
+
+ assert_equal $stderr.string, "Database 'my-app-db' already exists\n"
end
end
@@ -84,6 +98,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_establishes_connection_to_postgresql_database
@@ -101,6 +122,12 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.drop @configuration
end
+
+ def test_when_database_dropped_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+
+ assert_equal $stdout.string, "Dropped database 'my-app-db'\n"
+ end
end
class PostgreSQLPurgeTest < ActiveRecord::TestCase
@@ -261,18 +288,17 @@ module ActiveRecord
def test_structure_load
filename = "awesome-file.sql"
- Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true)
+ Kernel.expects(:system).with('psql', '-v', 'ON_ERROR_STOP=1', '-q', '-f', filename, @configuration['database']).returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
def test_structure_load_accepts_path_with_spaces
filename = "awesome file.sql"
- Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true)
+ Kernel.expects(:system).with('psql', '-v', 'ON_ERROR_STOP=1', '-q', '-f', filename, @configuration['database']).returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
end
-
end
end
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index 0aea0c3b38..4be03c7f61 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -1,4 +1,5 @@
require 'cases/helper'
+require 'active_record/tasks/database_tasks'
require 'pathname'
if current_adapter?(:SQLite3Adapter)
@@ -15,6 +16,13 @@ module ActiveRecord
File.stubs(:exist?).returns(false)
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_db_checks_database_exists
@@ -23,12 +31,18 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
end
+ def test_when_db_created_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+
+ assert_equal $stdout.string, "Created database '#{@database}'\n"
+ end
+
def test_db_create_when_file_exists
File.stubs(:exist?).returns(true)
- $stderr.expects(:puts).with("#{@database} already exists")
-
ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+
+ assert_equal $stderr.string, "Database '#{@database}' already exists\n"
end
def test_db_create_with_file_does_nothing
@@ -69,6 +83,13 @@ module ActiveRecord
Pathname.stubs(:new).returns(@path)
File.stubs(:join).returns('/former/relative/path')
FileUtils.stubs(:rm).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_creates_path_from_database
@@ -103,6 +124,12 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
end
+
+ def test_when_db_dropped_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+
+ assert_equal $stdout.string, "Dropped database '#{@database}'\n"
+ end
end
class SqliteDBCharsetTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 87299c0dab..c8adc21bbc 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -12,10 +12,6 @@ module ActiveRecord
SQLCounter.clear_log
end
- def assert_date_from_db(expected, actual, message = nil)
- assert_equal expected.to_s, actual.to_s, message
- end
-
def capture_sql
SQLCounter.clear_log
yield
diff --git a/activerecord/test/cases/time_precision_test.rb b/activerecord/test/cases/time_precision_test.rb
index 3b6e4dcc2b..628a8eb771 100644
--- a/activerecord/test/cases/time_precision_test.rb
+++ b/activerecord/test/cases/time_precision_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
require 'support/schema_dumping_helper'
-if ActiveRecord::Base.connection.supports_datetime_with_precision?
+if subsecond_precision_supported?
class TimePrecisionTest < ActiveRecord::TestCase
include SchemaDumpingHelper
self.use_transactional_tests = false
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 970f6bcf4a..937b84bccc 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -98,8 +98,11 @@ class TimestampTest < ActiveRecord::TestCase
task = Task.first
previous_value = task.ending
task.touch(:ending)
+
+ now = Time.now.change(usec: 0)
+
assert_not_equal previous_value, task.ending
- assert_in_delta Time.now, task.ending, 1
+ assert_in_delta now, task.ending, 1
end
def test_touching_an_attribute_updates_timestamp_with_given_time
@@ -120,10 +123,12 @@ class TimestampTest < ActiveRecord::TestCase
previous_ending = task.ending
task.touch(:starting, :ending)
+ now = Time.now.change(usec: 0)
+
assert_not_equal previous_starting, task.starting
assert_not_equal previous_ending, task.ending
- assert_in_delta Time.now, task.starting, 1
- assert_in_delta Time.now, task.ending, 1
+ assert_in_delta now, task.starting, 1
+ assert_in_delta now, task.ending, 1
end
def test_touching_a_record_without_timestamps_is_unexceptional
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 637f89196e..8a7f19293d 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -34,6 +34,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id"
+ before_commit { |record| record.do_before_commit(nil) }
after_commit { |record| record.do_after_commit(nil) }
after_create_commit { |record| record.do_after_commit(:create) }
after_update_commit { |record| record.do_after_commit(:update) }
@@ -47,6 +48,12 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
@history ||= []
end
+ def before_commit_block(on = nil, &block)
+ @before_commit ||= {}
+ @before_commit[on] ||= []
+ @before_commit[on] << block
+ end
+
def after_commit_block(on = nil, &block)
@after_commit ||= {}
@after_commit[on] ||= []
@@ -59,6 +66,11 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
@after_rollback[on] << block
end
+ def do_before_commit(on)
+ blocks = @before_commit[on] if defined?(@before_commit)
+ blocks.each{|b| b.call(self)} if blocks
+ end
+
def do_after_commit(on)
blocks = @after_commit[on] if defined?(@after_commit)
blocks.each{|b| b.call(self)} if blocks
@@ -74,6 +86,20 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
@first = TopicWithCallbacks.find(1)
end
+ # FIXME: Test behavior, not implementation.
+ def test_before_commit_exception_should_pop_transaction_stack
+ @first.before_commit_block { raise 'better pop this txn from the stack!' }
+
+ original_txn = @first.class.connection.current_transaction
+
+ begin
+ @first.save!
+ fail
+ rescue
+ assert_equal original_txn, @first.class.connection.current_transaction
+ end
+ end
+
def test_call_after_commit_after_transaction_commits
@first.after_commit_block{|r| r.history << :after_commit}
@first.after_rollback_block{|r| r.history << :after_rollback}
diff --git a/activerecord/test/cases/validations/absence_validation_test.rb b/activerecord/test/cases/validations/absence_validation_test.rb
index dd43ee358c..c0b3750bcc 100644
--- a/activerecord/test/cases/validations/absence_validation_test.rb
+++ b/activerecord/test/cases/validations/absence_validation_test.rb
@@ -57,19 +57,17 @@ class AbsenceValidationTest < ActiveRecord::TestCase
assert_nothing_raised { boy_klass.new(face: face_with_to_a).valid? }
end
- def test_does_not_validate_if_parent_record_is_validate_false
+ def test_validates_absence_of_virtual_attribute_on_model
repair_validations(Interest) do
- Interest.validates_absence_of(:topic)
- interest = Interest.new(topic: Topic.new(title: "Math"))
- interest.save!(validate: false)
- assert interest.persisted?
+ Interest.send(:attr_accessor, :token)
+ Interest.validates_absence_of(:token)
- man = Man.new(interest_ids: [interest.id])
- man.save!
-
- assert_equal man.interests.size, 1
+ interest = Interest.create!(topic: 'Thought Leadering')
assert interest.valid?
- assert man.valid?
+
+ interest.token = 'tl'
+
+ assert interest.invalid?
end
end
end
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index 981239c4d6..5b5307489a 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -41,14 +41,10 @@ class I18nValidationTest < ActiveRecord::TestCase
[ "given custom message", {:message => "custom"}, {:message => "custom"}],
[ "given if condition", {:if => lambda { true }}, {}],
[ "given unless condition", {:unless => lambda { false }}, {}],
- [ "given option that is not reserved", {:format => "jpg"}, {:format => "jpg" }]
- # TODO Add :on case, but below doesn't work, because then the validation isn't run for some reason
- # even when using .save instead .valid?
- # [ "given on condition", {on: :save}, {}]
+ [ "given option that is not reserved", {:format => "jpg"}, {:format => "jpg" }],
+ [ "given on condition", {on: [:create, :update] }, {}]
]
- # validates_uniqueness_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_uniqueness_of on generated message #{name}" do
Topic.validates_uniqueness_of :title, validation_options
@@ -59,8 +55,6 @@ class I18nValidationTest < ActiveRecord::TestCase
end
end
- # validates_associated w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_associated on generated message #{name}" do
Topic.validates_associated :replies, validation_options
@@ -70,8 +64,6 @@ class I18nValidationTest < ActiveRecord::TestCase
end
end
- # validates_associated w/o mocha
-
def test_validates_associated_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
index c5d8f8895c..78263fd955 100644
--- a/activerecord/test/cases/validations/length_validation_test.rb
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -61,17 +61,19 @@ class LengthValidationTest < ActiveRecord::TestCase
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?
+ def test_validates_length_of_virtual_attribute_on_model
+ repair_validations(Pet) do
+ Pet.send(:attr_accessor, :nickname)
+ Pet.validates_length_of(:name, minimum: 1)
+ Pet.validates_length_of(:nickname, minimum: 1)
- pet = Pet.new(owner_id: owner.id)
- pet.save!
+ pet = Pet.create!(name: 'Fancy Pants', nickname: 'Fancy')
- assert_equal owner.pets.size, 1
- assert owner.valid?
- assert pet.valid?
+ assert pet.valid?
+
+ pet.nickname = ''
+
+ assert pet.invalid?
+ end
end
end
diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb
index 6f8ad06ab6..868d111b8c 100644
--- a/activerecord/test/cases/validations/presence_validation_test.rb
+++ b/activerecord/test/cases/validations/presence_validation_test.rb
@@ -65,19 +65,39 @@ class PresenceValidationTest < ActiveRecord::TestCase
assert_nothing_raised { s.valid? }
end
- def test_does_not_validate_presence_of_if_parent_record_is_validate_false
+ def test_validates_presence_of_virtual_attribute_on_model
repair_validations(Interest) do
+ Interest.send(:attr_accessor, :abbreviation)
Interest.validates_presence_of(:topic)
+ Interest.validates_presence_of(:abbreviation)
+
+ interest = Interest.create!(topic: 'Thought Leadering', abbreviation: 'tl')
+ assert interest.valid?
+
+ interest.abbreviation = ''
+
+ assert interest.invalid?
+ end
+ end
+
+ def test_validations_run_on_persisted_record
+ repair_validations(Interest) do
interest = Interest.new
- interest.save!(validate: false)
- assert interest.persisted?
+ interest.save!
+ assert_predicate interest, :valid?
- man = Man.new(interest_ids: [interest.id])
- man.save!
+ Interest.validates_presence_of(:topic)
- assert_equal man.interests.size, 1
- assert interest.valid?
- assert man.valid?
+ assert_not_predicate interest, :valid?
+ end
+ end
+
+ def test_validates_presence_with_on_context
+ repair_validations(Interest) do
+ Interest.validates_presence_of(:topic, on: :required_name)
+ interest = Interest.new
+ interest.save!
+ assert_not interest.valid?(:required_name)
end
end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 7502a55391..4b0a590adb 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -5,6 +5,7 @@ require 'models/warehouse_thing'
require 'models/guid'
require 'models/event'
require 'models/dashboard'
+require 'models/uuid_item'
class Wizard < ActiveRecord::Base
self.abstract_class = true
@@ -48,6 +49,18 @@ class BigIntReverseTest < ActiveRecord::Base
validates :engines_count, uniqueness: true
end
+class CoolTopic < Topic
+ validates_uniqueness_of :id
+end
+
+class TopicWithAfterCreate < Topic
+ after_create :set_author
+
+ def set_author
+ update_attributes!(:author_name => "#{title} #{id}")
+ end
+end
+
class UniquenessValidationTest < ActiveRecord::TestCase
INT_MAX_VALUE = 2147483647
@@ -336,19 +349,41 @@ class UniquenessValidationTest < ActiveRecord::TestCase
end
def test_validate_uniqueness_with_limit
- # Event.title is limited to 5 characters
- e1 = Event.create(:title => "abcde")
- assert e1.valid?, "Could not create an event with a unique, 5 character title"
- e2 = Event.create(:title => "abcdefgh")
- assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique"
+ if current_adapter?(:SQLite3Adapter)
+ # Event.title has limit 5, but SQLite doesn't truncate.
+ e1 = Event.create(title: "abcdefgh")
+ assert e1.valid?, "Could not create an event with a unique 8 characters title"
+
+ e2 = Event.create(title: "abcdefgh")
+ assert_not e2.valid?, "Created an event whose title is not unique"
+ elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
+ assert_raise(ActiveRecord::ValueTooLong) do
+ Event.create(title: "abcdefgh")
+ end
+ else
+ assert_raise(ActiveRecord::StatementInvalid) do
+ Event.create(title: "abcdefgh")
+ end
+ end
end
def test_validate_uniqueness_with_limit_and_utf8
- # Event.title is limited to 5 characters
- e1 = Event.create(:title => "一二三四五")
- assert e1.valid?, "Could not create an event with a unique, 5 character title"
- e2 = Event.create(:title => "一二三四五六七八")
- assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique"
+ if current_adapter?(:SQLite3Adapter)
+ # Event.title has limit 5, but does SQLite doesn't truncate.
+ e1 = Event.create(title: "一二三四五六七八")
+ assert e1.valid?, "Could not create an event with a unique 8 characters title"
+
+ e2 = Event.create(title: "一二三四五六七八")
+ assert_not e2.valid?, "Created an event whose title is not unique"
+ elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
+ assert_raise(ActiveRecord::ValueTooLong) do
+ Event.create(title: "一二三四五六七八")
+ end
+ else
+ assert_raise(ActiveRecord::StatementInvalid) do
+ Event.create(title: "一二三四五六七八")
+ end
+ end
end
def test_validate_straight_inheritance_uniqueness
@@ -412,23 +447,6 @@ class UniquenessValidationTest < ActiveRecord::TestCase
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
-
def test_validate_uniqueness_of_custom_primary_key
klass = Class.new(ActiveRecord::Base) do
self.table_name = "keyboards"
@@ -469,4 +487,46 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert_match(/\AUnknown primary key for table dashboards in model/, e.message)
assert_match(/Can not validate uniqueness for persisted record without primary key.\z/, e.message)
end
+
+ def test_validate_uniqueness_ignores_itself_when_primary_key_changed
+ Topic.validates_uniqueness_of(:title)
+
+ t = Topic.new("title" => "This is a unique title")
+ assert t.save, "Should save t as unique"
+
+ t.id += 1
+ assert t.valid?, "Should be valid"
+ assert t.save, "Should still save t as unique"
+ end
+
+ def test_validate_uniqueness_with_after_create_performing_save
+ TopicWithAfterCreate.validates_uniqueness_of(:title)
+ topic = TopicWithAfterCreate.create!(:title => "Title1")
+ assert topic.author_name.start_with?("Title1")
+
+ topic2 = TopicWithAfterCreate.new(:title => "Title1")
+ refute topic2.valid?
+ assert_equal(["has already been taken"], topic2.errors[:title])
+ end
+
+ def test_validate_uniqueness_uuid
+ skip unless current_adapter?(:PostgreSQLAdapter)
+ item = UuidItem.create!(uuid: SecureRandom.uuid, title: 'item1')
+ item.update(title: 'item1-title2')
+ assert_empty item.errors
+
+ item2 = UuidValidatingItem.create!(uuid: SecureRandom.uuid, title: 'item2')
+ item2.update(title: 'item2-title2')
+ assert_empty item2.errors
+ end
+
+ def test_validate_uniqueness_regular_id
+ item = CoolTopic.create!(title: 'MyItem')
+ assert_empty item.errors
+
+ item2 = CoolTopic.new(id: item.id, title: 'MyItem2')
+ refute item2.valid?
+
+ assert_equal(["has already been taken"], item2.errors[:id])
+ end
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index d04f4f7ce7..85e33d2218 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -130,7 +130,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_validates_acceptance_of_with_non_existent_table
Object.const_set :IncorporealModel, Class.new(ActiveRecord::Base)
- assert_nothing_raised ActiveRecord::StatementInvalid do
+ assert_nothing_raised do
IncorporealModel.validates_acceptance_of(:incorporeal_column)
end
end
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 56909a8630..d1c9a00786 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -109,6 +109,16 @@ class YamlSerializationTest < ActiveRecord::TestCase
assert_equal("Have a nice day", topic.content)
end
+ def test_yaml_encoding_keeps_mutations
+ author = Author.first
+ author.name = "Sean"
+ dumped = YAML.load(YAML.dump(author))
+
+ assert_equal "Sean", dumped.name
+ assert_equal author.name_was, dumped.name_was
+ assert_equal author.changes, dumped.changes
+ end
+
private
def yaml_fixture(file_name)
diff --git a/activerecord/test/fixtures/price_estimates.yml b/activerecord/test/fixtures/price_estimates.yml
index 1149ab17a2..406d65a142 100644
--- a/activerecord/test/fixtures/price_estimates.yml
+++ b/activerecord/test/fixtures/price_estimates.yml
@@ -1,7 +1,16 @@
-saphire_1:
+sapphire_1:
price: 10
estimate_of: sapphire (Treasure)
sapphire_2:
price: 20
estimate_of: sapphire (Treasure)
+
+diamond:
+ price: 30
+ estimate_of: diamond (Treasure)
+
+honda:
+ price: 40
+ estimate_of_type: Car
+ estimate_of_id: 1
diff --git a/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb
index 607113b091..76734bcd7d 100644
--- a/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb
+++ b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveHobbies < ActiveRecord::Migration::Current
def self.up
add_column "people", "hobbies", :text
end
diff --git a/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb
index d4cbddab50..7f883dbb45 100644
--- a/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb
+++ b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveDescriptions < ActiveRecord::Migration::Current
def self.up
add_column "people", "description", :text
end
diff --git a/activerecord/test/migrations/to_copy2/2_create_comments.rb b/activerecord/test/migrations/to_copy2/2_create_comments.rb
index 2e9f5ec6bc..d361847d4b 100644
--- a/activerecord/test/migrations/to_copy2/2_create_comments.rb
+++ b/activerecord/test/migrations/to_copy2/2_create_comments.rb
@@ -1,4 +1,4 @@
-class CreateArticles < ActiveRecord::Migration::Current
+class CreateComments < ActiveRecord::Migration::Current
def self.up
end
diff --git a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
index 8f81805fe1..1a863367dd 100644
--- a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
+++ b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveHobbies < ActiveRecord::Migration::Current
def self.up
add_column "people", "hobbies", :string
end
diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
index 607113b091..76734bcd7d 100644
--- a/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
+++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveHobbies < ActiveRecord::Migration::Current
def self.up
add_column "people", "hobbies", :text
end
diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
index d4cbddab50..7f883dbb45 100644
--- a/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
+++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveDescriptions < ActiveRecord::Migration::Current
def self.up
add_column "people", "description", :text
end
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 0d90cbb110..38b983eda0 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -75,7 +75,7 @@ class Author < ActiveRecord::Base
has_many :posts_with_multiple_callbacks, :class_name => "Post",
:before_add => [:log_before_adding, Proc.new {|o, r| o.post_log << "before_adding_proc#{r.id || '<new>'}"}],
:after_add => [:log_after_adding, Proc.new {|o, r| o.post_log << "after_adding_proc#{r.id || '<new>'}"}]
- has_many :unchangable_posts, :class_name => "Post", :before_add => :raise_exception, :after_add => :log_after_adding
+ has_many :unchangeable_posts, :class_name => "Post", :before_add => :raise_exception, :after_add => :log_after_adding
has_many :categorizations
has_many :categories, :through => :categorizations
@@ -144,6 +144,14 @@ class Author < ActiveRecord::Base
has_many :posts_with_signature, ->(record) { where("posts.title LIKE ?", "%by #{record.name.downcase}%") }, class_name: "Post"
+ has_many :posts_with_extension, -> { order(:title) }, class_name: "Post" do
+ def extension_method; end
+ end
+
+ has_many :posts_with_extension_and_instance, ->(record) { order(:title) }, class_name: "Post" do
+ def extension_method; end
+ end
+
attr_accessor :post_log
after_initialize :set_post_log
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index 778c22b1f6..0f37e9a289 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -12,6 +12,8 @@ class Car < ActiveRecord::Base
has_many :engines, :dependent => :destroy, inverse_of: :my_car
has_many :wheels, :as => :wheelable, :dependent => :destroy
+ has_many :price_estimates, :as => :estimate_of
+
scope :incl_tyres, -> { includes(:tyres) }
scope :incl_engines, -> { includes(:engines) }
diff --git a/activerecord/test/models/cat.rb b/activerecord/test/models/cat.rb
new file mode 100644
index 0000000000..dfdde18641
--- /dev/null
+++ b/activerecord/test/models/cat.rb
@@ -0,0 +1,10 @@
+class Cat < ActiveRecord::Base
+ self.abstract_class = true
+
+ enum gender: [:female, :male]
+
+ default_scope -> { where(is_vegetarian: false) }
+end
+
+class Lion < Cat
+end
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index b38b17e90e..dcc5c5a310 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -14,6 +14,7 @@ class Comment < ActiveRecord::Base
has_many :ratings
belongs_to :first_post, :foreign_key => :post_id
+ belongs_to :special_post_with_default_scope, foreign_key: :post_id
has_many :children, :class_name => 'Comment', :foreign_key => :parent_id
belongs_to :parent, :class_name => 'Comment', :counter_cache => :children_count
diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb
index afe4b3d707..3338aaf7e1 100644
--- a/activerecord/test/models/customer.rb
+++ b/activerecord/test/models/customer.rb
@@ -64,7 +64,12 @@ class Fullname
def self.parse(str)
return nil unless str
- new(*str.to_s.split)
+
+ if str.is_a?(Hash)
+ new(str[:first], str[:last])
+ else
+ new(*str.to_s.split)
+ end
end
def initialize(first, last = nil)
diff --git a/activerecord/test/models/notification.rb b/activerecord/test/models/notification.rb
index b4b4b8f1b6..82edc64b68 100644
--- a/activerecord/test/models/notification.rb
+++ b/activerecord/test/models/notification.rb
@@ -1,2 +1,3 @@
class Notification < ActiveRecord::Base
+ validates_presence_of :message
end
diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb
index cedb774b10..ce8242cf2f 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -1,7 +1,8 @@
class Owner < ActiveRecord::Base
self.primary_key = :owner_id
has_many :pets, -> { order 'pets.name desc' }
- has_many :toys, :through => :pets
+ has_many :toys, through: :pets
+ has_many :persons, through: :pets
belongs_to :last_pet, class_name: 'Pet'
scope :including_last_pet, -> {
diff --git a/activerecord/test/models/pet.rb b/activerecord/test/models/pet.rb
index f7970d7aab..53489fa1b3 100644
--- a/activerecord/test/models/pet.rb
+++ b/activerecord/test/models/pet.rb
@@ -4,6 +4,9 @@ class Pet < ActiveRecord::Base
self.primary_key = :pet_id
belongs_to :owner, :touch => true
has_many :toys
+ has_many :pet_treasures
+ has_many :treasures, through: :pet_treasures
+ has_many :persons, through: :treasures, source: :looter, source_type: 'Person'
class << self
attr_accessor :after_destroy_output
diff --git a/activerecord/test/models/pet_treasure.rb b/activerecord/test/models/pet_treasure.rb
new file mode 100644
index 0000000000..1fe7807ffe
--- /dev/null
+++ b/activerecord/test/models/pet_treasure.rb
@@ -0,0 +1,6 @@
+class PetTreasure < ActiveRecord::Base
+ self.table_name = "pets_treasures"
+
+ belongs_to :pet
+ belongs_to :treasure
+end
diff --git a/activerecord/test/models/tag.rb b/activerecord/test/models/tag.rb
index 80d4725f7e..b48b9a2155 100644
--- a/activerecord/test/models/tag.rb
+++ b/activerecord/test/models/tag.rb
@@ -5,3 +5,9 @@ class Tag < ActiveRecord::Base
has_many :tagged_posts, :through => :taggings, :source => 'taggable', :source_type => 'Post'
end
+
+class OrderedTag < Tag
+ self.table_name = "tags"
+
+ has_many :taggings, -> { order('taggings.id DESC') }, foreign_key: 'tag_id'
+end
diff --git a/activerecord/test/models/uuid_item.rb b/activerecord/test/models/uuid_item.rb
new file mode 100644
index 0000000000..2353e40213
--- /dev/null
+++ b/activerecord/test/models/uuid_item.rb
@@ -0,0 +1,6 @@
+class UuidItem < ActiveRecord::Base
+end
+
+class UuidValidatingItem < UuidItem
+ validates_uniqueness_of :uuid
+end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index 701e6f45b3..5a49b38457 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -21,19 +21,19 @@ ActiveRecord::Schema.define do
t.index :var_binary
end
- create_table :key_tests, force: true, :options => 'ENGINE=MyISAM' do |t|
+ create_table :key_tests, force: true, options: 'ENGINE=MyISAM' do |t|
t.string :awesome
t.string :pizza
t.string :snacks
+ t.index :awesome, type: :fulltext, name: 'index_key_tests_on_awesome'
+ t.index :pizza, using: :btree, name: 'index_key_tests_on_pizza'
+ t.index :snacks, name: 'index_key_tests_on_snack'
end
- add_index :key_tests, :awesome, :type => :fulltext, :name => 'index_key_tests_on_awesome'
- 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'
+ t.binary :binary_column, limit: 1
end
ActiveRecord::Base.connection.execute <<-SQL
@@ -62,7 +62,7 @@ SQL
ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE enum_tests (
- enum_column ENUM('text','blob','tiny','medium','long','unsigned')
+ enum_column ENUM('text','blob','tiny','medium','long','unsigned','bigint')
)
SQL
end
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 3a5d73a0ed..24713f722a 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -106,4 +106,9 @@ _SQL
t.integer :big_int_data_points, limit: 8, array: true
t.decimal :decimal_array_default, array: true, default: [1.23, 3.45]
end
+
+ create_table :uuid_items, force: true, id: false do |t|
+ t.uuid :uuid, primary_key: true
+ t.string :title
+ end
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index fcc4eee79c..628a59c2e3 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -1,10 +1,4 @@
ActiveRecord::Schema.define do
- def except(adapter_names_to_exclude)
- unless [adapter_names_to_exclude].flatten.include?(adapter_name)
- yield
- end
- end
-
# ------------------------------------------------------------------- #
# #
# Please keep these create table statements in alphabetical order #
@@ -202,12 +196,12 @@ ActiveRecord::Schema.define do
t.integer :rating, default: 1
t.integer :account_id
t.string :description, default: ""
+ t.index [:firm_id, :type, :rating], name: "company_index"
+ t.index [:firm_id, :type], name: "company_partial_index", where: "rating > 10"
+ t.index :name, name: 'company_name_index', using: :btree
+ t.index 'lower(name)', name: "company_expression_index" if supports_expression_index?
end
- add_index :companies, [:firm_id, :type, :rating], name: "company_index"
- add_index :companies, [:firm_id, :type], name: "company_partial_index", where: "rating > 10"
- add_index :companies, :name, name: 'company_name_index', using: :btree
-
create_table :content, force: true do |t|
t.string :title
end
@@ -304,8 +298,8 @@ ActiveRecord::Schema.define do
create_table :edges, force: true, id: false do |t|
t.column :source_id, :integer, null: false
t.column :sink_id, :integer, null: false
+ t.index [:source_id, :sink_id], unique: true, name: 'unique_edge_index'
end
- add_index :edges, [:source_id, :sink_id], unique: true, name: 'unique_edge_index'
create_table :engines, force: true do |t|
t.integer :car_id
@@ -422,6 +416,11 @@ ActiveRecord::Schema.define do
t.integer :amount
end
+ create_table :lions, force: true do |t|
+ t.integer :gender
+ t.boolean :is_vegetarian, default: false
+ end
+
create_table :lock_without_defaults, force: true do |t|
t.column :lock_version, :integer
end
@@ -616,6 +615,12 @@ ActiveRecord::Schema.define do
end
end
+ create_table :pets_treasures, force: true do |t|
+ t.column :treasure_id, :integer
+ t.column :pet_id, :integer
+ t.column :rainbow_color, :string
+ end
+
create_table :pirates, force: true do |t|
t.column :catchphrase, :string
t.column :parrot_id, :integer
@@ -782,8 +787,8 @@ ActiveRecord::Schema.define do
t.string :nick, null: false
t.string :name
t.column :books_count, :integer, null: false, default: 0
+ t.index :nick, unique: true
end
- add_index :subscribers, :nick, unique: true
create_table :subscriptions, force: true do |t|
t.string :subscriber_id
@@ -930,7 +935,7 @@ ActiveRecord::Schema.define do
t.string :treaty_id
t.string :name
end
- create_table :countries_treaties, force: true, id: false do |t|
+ create_table :countries_treaties, force: true, primary_key: [:country_id, :treaty_id] do |t|
t.string :country_id, null: false
t.string :treaty_id, null: false
end
@@ -987,7 +992,7 @@ ActiveRecord::Schema.define do
create_table :records, force: true do |t|
end
- except 'SQLite' do
+ if supports_foreign_keys?
# fk_test_has_fk should be before fk_test_has_pk
create_table :fk_test_has_fk, force: true do |t|
t.integer :fk_id, null: false
diff --git a/activerecord/test/schema/sqlite_specific_schema.rb b/activerecord/test/schema/sqlite_specific_schema.rb
index b5552c2755..cc7c36fe2b 100644
--- a/activerecord/test/schema/sqlite_specific_schema.rb
+++ b/activerecord/test/schema/sqlite_specific_schema.rb
@@ -1,8 +1,4 @@
ActiveRecord::Schema.define do
- create_table :table_with_autoincrement, :force => true do |t|
- t.column :name, :string
- end
-
execute "DROP TABLE fk_test_has_fk" rescue nil
execute "DROP TABLE fk_test_has_pk" rescue nil
execute <<_SQL
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 21c79949ca..d9f23c55ba 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,529 +1,64 @@
-* Match `HashWithIndifferentAccess#default`'s behaviour with `Hash#default`.
+* Remove deprecated arguments in `assert_nothing_raised`.
- *David Cornu*
+ *Rafel Mendonça França*
-* Adds `:exception_object` key to `ActiveSupport::Notifications::Instrumenter`
- payload when an exception is raised.
+* `Date.to_s` doesn't produce too many spaces. For example, `to_s(:short)`
+ will now produce `01 Feb` instead of ` 1 Feb`.
- Adds new key/value pair to payload when an exception is raised:
- e.g. `:exception_object => #<RuntimeError: FAIL>`.
+ Fixes #25251.
- *Ryan T. Hosford*
+ *Sean Griffin*
-* Support extended grapheme clusters and UAX 29.
+* Introduce Module#delegate_missing_to.
- *Adam Roben*
+ When building a decorator, a common pattern emerges:
-* Add petabyte and exabyte numeric conversion.
-
- *Akshay Vishnoi*
-
-## Rails 5.0.0.beta1 (December 18, 2015) ##
-
-* Add thread_m/cattr_accessor/reader/writer suite of methods for declaring class and module variables that live per-thread.
- This makes it easy to declare per-thread globals that are encapsulated. Note: This is a sharp edge. A wild proliferation
- of globals is A Bad Thing. But like other sharp tools, when it's right, it's right.
-
- Here's an example of a simple event tracking system where the object being tracked needs not pass a creator that it
- doesn't need itself along:
-
- module Current
- thread_mattr_accessor :account
- thread_mattr_accessor :user
-
- def self.reset() self.account = self.user = nil end
- end
-
- class ApplicationController < ActionController::Base
- before_action :set_current
- after_action { Current.reset }
-
- private
- def set_current
- Current.account = Account.find(params[:account_id])
- Current.user = Current.account.users.find(params[:user_id])
- end
- end
-
- class MessagesController < ApplicationController
- def create
- @message = Message.create!(message_params)
- end
- end
-
- class Message < ApplicationRecord
- has_many :events
- after_create :track_created
-
- private
- def track_created
- events.create! origin: self, action: :create
- end
- end
-
- class Event < ApplicationRecord
- belongs_to :creator, class_name: 'User'
- before_validation { self.creator ||= Current.user }
- end
-
- *DHH*
-
-
-* Deprecated `Module#qualified_const_` in favour of the builtin Module#const_
- methods.
-
- *Genadi Samokovarov*
-
-* Deprecate passing string to define callback.
-
- *Yuichiro Kaneko*
-
-* `ActiveSupport::Cache::Store#namespaced_key`,
- `ActiveSupport::Cache::MemCachedStore#escape_key`, and
- `ActiveSupport::Cache::FileStore#key_file_path`
- are deprecated and replaced with `normalize_key` that now calls `super`.
-
- `ActiveSupport::Cache::LocaleCache#set_cache_value` is deprecated and replaced with `write_cache_value`.
-
- *Michael Grosser*
-
-* Implements an evented file watcher to asynchronously detect changes in the
- application source code, routes, locales, etc.
-
- This watcher is disabled by default, applications my enable it in the configuration:
-
- # config/environments/development.rb
- config.file_watcher = ActiveSupport::EventedFileUpdateChecker
-
- This feature depends on the [listen](https://github.com/guard/listen) gem:
-
- group :development do
- gem 'listen', '~> 3.0.5'
- end
-
- *Puneet Agarwal* and *Xavier Noria*
-
-* Added `Time.days_in_year` to return the number of days in the given year, or the
- current year if no argument is provided.
-
- *Jon Pascoe*
-
-* Updated `parameterize` to preserve the case of a string, optionally.
-
- Example:
-
- parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
- parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
-
- *Swaathi Kakarla*
-
-* `HashWithIndifferentAccess.new` respects the default value or proc on objects
- that respond to `#to_hash`. `.new_from_hash_copying_default` simply invokes `.new`.
- All calls to `.new_from_hash_copying_default` are replaced with `.new`.
-
- *Gordon Chan*
-
-* Change Integer#year to return a Fixnum instead of a Float to improve
- consistency.
-
- Integer#years returned a Float while the rest of the accompanying methods
- (days, weeks, months, etc.) return a Fixnum.
-
- Before:
-
- 1.year # => 31557600.0
-
- After:
-
- 1.year # => 31557600
-
- *Konstantinos Rousis*
-
-* Handle invalid UTF-8 strings when HTML escaping.
-
- Use `ActiveSupport::Multibyte::Unicode.tidy_bytes` to handle invalid UTF-8
- strings in `ERB::Util.unwrapped_html_escape` and `ERB::Util.html_escape_once`.
- Prevents user-entered input passed from a querystring into a form field from
- causing invalid byte sequence errors.
-
- *Grey Baker*
-
-* Update `ActiveSupport::Multibyte::Chars#slice!` to return `nil` if the
- arguments are out of bounds, to mirror the behavior of `String#slice!`
-
- *Gourav Tiwari*
-
-* Fix `number_to_human` so that 999999999 rounds to "1 Billion" instead of
- "1000 Million".
-
- *Max Jacobson*
-
-* Fix `ActiveSupport::Deprecation#deprecate_methods` to report using the
- current deprecator instance, where applicable.
-
- *Brandon Dunne*
-
-* `Cache#fetch` instrumentation marks whether it was a `:hit`.
-
- *Robin Clowers*
-
-* `assert_difference` and `assert_no_difference` now returns the result of the
- yielded block.
-
- Example:
-
- post = assert_difference -> { Post.count }, 1 do
- Post.create
- end
-
- *Lucas Mazza*
-
-* Short-circuit `blank?` on date and time values since they are never blank.
-
- Fixes #21657.
-
- *Andrew White*
-
-* Replaced deprecated `ThreadSafe::Cache` with its successor `Concurrent::Map` now that
- the thread_safe gem has been merged into concurrent-ruby.
-
- *Jerry D'Antonio*
-
-* Updated Unicode version to 8.0.0
-
- *Anshul Sharma*
-
-* `number_to_currency` and `number_with_delimiter` now accept custom `delimiter_pattern` option
- to handle placement of delimiter, to support currency formats like INR
-
- Example:
-
- number_to_currency(1230000, delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/, unit: '₹', format: "%u %n")
- # => '₹ 12,30,000.00'
-
- *Vipul A M*
-
-* Deprecate `:prefix` option of `number_to_human_size` with no replacement.
-
- *Jean Boussier*
-
-* Fix `TimeWithZone#eql?` to properly handle `TimeWithZone` created from `DateTime`:
- twz = DateTime.now.in_time_zone
- twz.eql?(twz.dup) => true
-
- Fixes #14178.
-
- *Roque Pinel*
-
-* ActiveSupport::HashWithIndifferentAccess `select` and `reject` will now return
- enumerator if called without block.
-
- Fixes #20095.
-
- *Bernard Potocki*
-
-* Removed `ActiveSupport::Concurrency::Latch`, superseded by `Concurrent::CountDownLatch`
- from the concurrent-ruby gem.
-
- *Jerry D'Antonio*
-
-* Fix not calling `#default` on `HashWithIndifferentAccess#to_hash` when only
- `default_proc` is set, which could raise.
-
- *Simon Eskildsen*
-
-* Fix setting `default_proc` on `HashWithIndifferentAccess#dup`.
-
- *Simon Eskildsen*
-
-* Fix a range of values for parameters of the Time#change.
-
- *Nikolay Kondratyev*
-
-* Add `Enumerable#pluck` to get the same values from arrays as from ActiveRecord
- associations.
-
- Fixes #20339.
-
- *Kevin Deisz*
+ class Partition
+ def initialize(first_event)
+ @events = [ first_event ]
+ end
-* Add a bang version to `ActiveSupport::OrderedOptions` get methods which will raise
- an `KeyError` if the value is `.blank?`.
+ def people
+ if @events.first.detail.people.any?
+ @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ else
+ @events.collect(&:creator).uniq
+ end
+ end
- Before:
+ private
+ def respond_to_missing?(name, include_private = false)
+ @events.respond_to?(name, include_private)
+ end
- if (slack_url = Rails.application.secrets.slack_url).present?
- # Do something worthwhile
- else
- # Raise as important secret password is not specified
+ def method_missing(method, *args, &block)
+ @events.send(method, *args, &block)
+ end
end
- After:
-
- slack_url = Rails.application.secrets.slack_url!
+ With `Module#delegate_missing_to`, the above is condensed to:
- *Aditya Sanghi*, *Gaurish Sharma*
+ class Partition
+ delegate_missing_to :@events
-* Remove deprecated `Class#superclass_delegating_accessor`.
- Use `Class#class_attribute` instead.
-
- *Akshay Vishnoi*
-
-* Patch `Delegator` to work with `#try`.
-
- Fixes #5790.
-
- *Nate Smith*
-
-* Add `Integer#positive?` and `Integer#negative?` query methods
- in the vein of `Fixnum#zero?`.
-
- This makes it nicer to do things like `bunch_of_numbers.select(&:positive?)`.
-
- *DHH*
-
-* Encoding `ActiveSupport::TimeWithZone` to YAML now preserves the timezone information.
-
- Fixes #9183.
-
- *Andrew White*
-
-* Added `ActiveSupport::TimeZone#strptime` to allow parsing times as if
- from a given timezone.
-
- *Paul A Jungwirth*
-
-* `ActiveSupport::Callbacks#skip_callback` now raises an `ArgumentError` if
- an unrecognized callback is removed.
-
- *Iain Beeston*
-
-* Added `ActiveSupport::ArrayInquirer` and `Array#inquiry`.
-
- Wrapping an array in an `ArrayInquirer` gives a friendlier way to check its
- 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
-
- `Array#inquiry` is a shortcut for wrapping the receiving array in an
- `ArrayInquirer`.
-
- *George Claghorn*
-
-* Deprecate `alias_method_chain` in favour of `Module#prepend` introduced in
- Ruby 2.0.
-
- *Kir Shatrov*
-
-* Added `#without` on `Enumerable` and `Array` to return a copy of an
- enumerable without the specified elements.
-
- *Todd Bealmear*
-
-* Fixed a problem where `String#truncate_words` would get stuck with a complex
- string.
-
- *Henrik Nygren*
-
-* Fixed a roundtrip problem with `AS::SafeBuffer` where primitive-like strings
- will be dumped as primitives:
-
- Before:
-
- 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:
-
- 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*
-
-* Enable `number_to_percentage` to keep the number's precision by allowing
- `:precision` to be `nil`.
-
- *Jack Xu*
-
-* `config_accessor` became a private method, as with Ruby's `attr_accessor`.
-
- *Akira Matsuda*
-
-* `AS::Testing::TimeHelpers#travel_to` now changes `DateTime.now` as well as
- `Time.now` and `Date.today`.
-
- *Yuki Nishijima*
-
-* Add `file_fixture` to `ActiveSupport::TestCase`.
- It provides a simple mechanism to access sample files in your test cases.
-
- By default file fixtures are stored in `test/fixtures/files`. This can be
- configured per test-case using the `file_fixture_path` class attribute.
-
- *Yves Senn*
-
-* Return value of yielded block in `File.atomic_write`.
-
- *Ian Ker-Seymer*
-
-* Duplicate frozen array when assigning it to a `HashWithIndifferentAccess` so
- that it doesn't raise a `RuntimeError` when calling `map!` on it in `convert_value`.
-
- Fixes #18550.
-
- *Aditya Kapoor*
-
-* Add missing time zone definitions for Russian Federation and sync them
- with `zone.tab` file from tzdata version 2014j (latest).
-
- *Andrey Novikov*
-
-* Add `SecureRandom.base58` for generation of random base58 strings.
-
- *Matthew Draper*, *Guillermo Iguaran*
-
-* Add `#prev_day` and `#next_day` counterparts to `#yesterday` and
- `#tomorrow` for `Date`, `Time`, and `DateTime`.
-
- *George Claghorn*
-
-* Add `same_time` option to `#next_week` and `#prev_week` for `Date`, `Time`,
- and `DateTime`.
-
- *George Claghorn*
-
-* Add `#on_weekend?`, `#next_weekday`, `#prev_weekday` methods to `Date`,
- `Time`, and `DateTime`.
-
- `#on_weekend?` returns `true` if the receiving date/time falls on a Saturday
- or Sunday.
-
- `#next_weekday` returns a new date/time representing the next day that does
- not fall on a Saturday or Sunday.
-
- `#prev_weekday` returns a new date/time representing the previous day that
- does not fall on a Saturday or Sunday.
-
- *George Claghorn*
-
-* Change the default test order from `:sorted` to `:random`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActiveSupport::JSON::Encoding::CircularReferenceError`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated methods `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string=`
- and `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActiveSupport::SafeBuffer#prepend`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated methods at `Kernel`.
-
- `silence_stderr`, `silence_stream`, `capture` and `quietly`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `active_support/core_ext/big_decimal/yaml_conversions`
- file.
-
- *Rafael Mendonça França*
-
-* Remove deprecated methods `ActiveSupport::Cache::Store.instrument` and
- `ActiveSupport::Cache::Store.instrument=`.
-
- *Rafael Mendonça França*
-
-* Change the way in which callback chains can be halted.
-
- The preferred method to halt a callback chain from now on is to explicitly
- `throw(:abort)`.
- In the past, callbacks could only be halted by explicitly providing a
- terminator and by having a callback match the conditions of the terminator.
-
-* Add `ActiveSupport.halt_callback_chains_on_return_false`
-
- Setting `ActiveSupport.halt_callback_chains_on_return_false`
- to `true` will let an app support the deprecated way of halting Active Record,
- and Active Model callback chains by returning `false`.
-
- Setting the value to `false` will tell the app to ignore any `false` value
- returned by those callbacks, and only halt the chain upon `throw(:abort)`.
-
- 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.
-
- *claudiob*, *Roque Pinel*
-
-* Changes arguments and default value of CallbackChain's `:terminator` option
-
- Chains of callbacks defined without an explicit `:terminator` option will
- now be halted as soon as a `before_` callback throws `:abort`.
-
- 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.
-
- *claudiob*
-
-* Deprecate `MissingSourceFile` in favor of `LoadError`.
-
- `MissingSourceFile` was just an alias to `LoadError` and was not being
- raised inside the framework.
-
- *Rafael Mendonça França*
-
-* Add support for error dispatcher classes in `ActiveSupport::Rescuable`.
- Now it acts closer to Ruby's rescue.
-
- Example:
-
- class BaseController < ApplicationController
- module ErrorDispatcher
- def self.===(other)
- Exception === other && other.respond_to?(:status)
- end
+ def initialize(first_event)
+ @events = [ first_event ]
end
- rescue_from ErrorDispatcher do |error|
- render status: error.status, json: { error: error.to_s }
+ def people
+ if @events.first.detail.people.any?
+ @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ else
+ @events.collect(&:creator).uniq
+ end
end
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.
+ *Genadi Samokovarov*, *DHH*
- 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.
+* Rescuable: If a handler doesn't match the exception, check for handlers
+ matching the exception's cause.
- *Logan Leger*
+ *Jeremy Daer*
-Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activesupport/CHANGELOG.md) for previous changes.
+Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index 81c242d4b1..7b56e36abf 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -1,6 +1,9 @@
require 'rake/testtask'
task :default => :test
+
+task :package
+
Rake::TestTask.new do |t|
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 32e28c0212..c6ca969844 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.homepage = 'http://rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
@@ -21,9 +21,7 @@ Gem::Specification.new do |s|
s.rdoc_options.concat ['--encoding', 'UTF-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 'concurrent-ruby', '~> 1.0'
- s.add_dependency 'method_source'
+ s.add_dependency 'concurrent-ruby', '~> 1.0', '>= 1.0.2'
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 94fe893149..11569add37 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -26,6 +26,7 @@ require "active_support/dependencies/autoload"
require "active_support/version"
require "active_support/logger"
require "active_support/lazy_load_hooks"
+require "active_support/core_ext/date_and_time/compatibility"
module ActiveSupport
extend ActiveSupport::Autoload
@@ -33,10 +34,13 @@ module ActiveSupport
autoload :Concern
autoload :Dependencies
autoload :DescendantsTracker
+ autoload :ExecutionWrapper
+ autoload :Executor
autoload :FileUpdateChecker
autoload :EventedFileUpdateChecker
autoload :LogSubscriber
autoload :Notifications
+ autoload :Reloader
eager_autoload do
autoload :BacktraceCleaner
@@ -82,6 +86,14 @@ module ActiveSupport
def self.halt_callback_chains_on_return_false=(value)
Callbacks.halt_and_display_warning_on_return_false = value
end
+
+ def self.to_time_preserves_timezone
+ DateAndTime::Compatibility.preserve_timezone
+ end
+
+ def self.to_time_preserves_timezone=(value)
+ DateAndTime::Compatibility.preserve_timezone = value
+ end
end
autoload :I18n, "active_support/i18n"
diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb
index f59ddf5403..ea328f603e 100644
--- a/activesupport/lib/active_support/array_inquirer.rb
+++ b/activesupport/lib/active_support/array_inquirer.rb
@@ -9,8 +9,10 @@ module ActiveSupport
# variants.desktop? # => false
class ArrayInquirer < Array
# Passes each element of +candidates+ collection to ArrayInquirer collection.
- # The method returns true if at least one element is the same. If +candidates+
- # collection is not given, method returns true.
+ # The method returns true if any element from the ArrayInquirer collection
+ # is equal to the stringified or symbolized form of any element in the +candidates+ collection.
+ #
+ # If +candidates+ collection is not given, method returns true.
#
# variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
#
diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb
index 805b7a714f..3988b147ac 100644
--- a/activesupport/lib/active_support/benchmarkable.rb
+++ b/activesupport/lib/active_support/benchmarkable.rb
@@ -38,7 +38,7 @@ module ActiveSupport
options[:level] ||= :info
result = nil
- ms = Benchmark.ms { result = options[:silence] ? silence { yield } : yield }
+ ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
result
else
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 610105f41c..179ca13b49 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -158,20 +158,20 @@ module ActiveSupport
attr_reader :silence, :options
alias :silence? :silence
- # Create a new cache. The options will be passed to any write method calls
+ # Creates a new cache. The options will be passed to any write method calls
# except for <tt>:namespace</tt> which can be used to set the global
# namespace for the cache.
def initialize(options = nil)
@options = options ? options.dup : {}
end
- # Silence the logger.
+ # Silences the logger.
def silence!
@silence = true
self
end
- # Silence the logger within a block.
+ # Silences the logger within a block.
def mute
previous_silence, @silence = defined?(@silence) && @silence, true
yield
@@ -198,10 +198,17 @@ module ActiveSupport
# cache.fetch('city') # => "Duckburgh"
#
# You may also specify additional options via the +options+ argument.
- # Setting <tt>force: true</tt> will force a cache miss:
+ # Setting <tt>force: true</tt> forces a cache "miss," meaning we treat
+ # the cache value as missing even if it's present. Passing a block is
+ # required when `force` is true so this always results in a cache write.
#
# cache.write('today', 'Monday')
- # cache.fetch('today', force: true) # => nil
+ # cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
+ # cache.fetch('today', force: true) # => ArgumentError
+ #
+ # The `:force` option is useful when you're calling some other method to
+ # ask whether you should force a cache write. Otherwise, it's clearer to
+ # just call `Cache#write`.
#
# Setting <tt>:compress</tt> will store a large cache entry set by the call
# in a compressed format.
@@ -243,14 +250,14 @@ module ActiveSupport
# sleep 60
#
# Thread.new do
- # val_1 = cache.fetch('foo', race_condition_ttl: 10) do
+ # val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
# sleep 1
# 'new value 1'
# end
# end
#
# Thread.new do
- # val_2 = cache.fetch('foo', race_condition_ttl: 10) do
+ # val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
# 'new value 2'
# end
# end
@@ -292,6 +299,8 @@ module ActiveSupport
else
save_block_result_to_cache(name, options) { |_name| yield _name }
end
+ elsif options && options[:force]
+ raise ArgumentError, 'Missing block: Calling `Cache#fetch` with `force: true` requires a block.'
else
read(name, options)
end
@@ -323,7 +332,7 @@ module ActiveSupport
end
end
- # Read multiple values at once from the cache. Options can be passed
+ # Reads multiple values at once from the cache. Options can be passed
# in the last argument.
#
# Some cache implementation may optimize this method.
@@ -333,21 +342,19 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- instrument_multi(:read, names, options) do |payload|
- results = {}
- names.each do |name|
- key = normalize_key(name, options)
- entry = read_entry(key, options)
- if entry
- if entry.expired?
- delete_entry(key, options)
- else
- results[name] = entry.value
- end
+ results = {}
+ names.each do |name|
+ key = normalize_key(name, options)
+ entry = read_entry(key, options)
+ if entry
+ if entry.expired?
+ delete_entry(key, options)
+ else
+ results[name] = entry.value
end
end
- results
end
+ results
end
# Fetches data from the cache, using the given keys. If there is data in
@@ -415,7 +422,7 @@ module ActiveSupport
end
end
- # Delete all entries with keys matching the pattern.
+ # Deletes all entries with keys matching the pattern.
#
# Options are passed to the underlying cache implementation.
#
@@ -424,7 +431,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
end
- # Increment an integer value in the cache.
+ # Increments an integer value in the cache.
#
# Options are passed to the underlying cache implementation.
#
@@ -433,7 +440,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support increment")
end
- # Decrement an integer value in the cache.
+ # Decrements an integer value in the cache.
#
# Options are passed to the underlying cache implementation.
#
@@ -442,7 +449,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support decrement")
end
- # Cleanup the cache by removing expired entries.
+ # Cleanups the cache by removing expired entries.
#
# Options are passed to the underlying cache implementation.
#
@@ -451,7 +458,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support cleanup")
end
- # Clear the entire cache. Be careful with this method since it could
+ # Clears the entire cache. Be careful with this method since it could
# affect other processes if shared cache is being used.
#
# The options hash is passed to the underlying cache implementation.
@@ -462,7 +469,7 @@ module ActiveSupport
end
protected
- # Add the namespace defined in the options to a pattern designed to
+ # Adds the namespace defined in the options to a pattern designed to
# match keys. Implementations that support delete_matched should call
# this method to translate a pattern that matches names into one that
# matches namespaced keys.
@@ -481,26 +488,26 @@ module ActiveSupport
end
end
- # Read an entry from the cache implementation. Subclasses must implement
+ # Reads an entry from the cache implementation. Subclasses must implement
# this method.
def read_entry(key, options) # :nodoc:
raise NotImplementedError.new
end
- # Write an entry to the cache implementation. Subclasses must implement
+ # Writes an entry to the cache implementation. Subclasses must implement
# this method.
def write_entry(key, entry, options) # :nodoc:
raise NotImplementedError.new
end
- # Delete an entry from the cache implementation. Subclasses must
+ # Deletes an entry from the cache implementation. Subclasses must
# implement this method.
def delete_entry(key, options) # :nodoc:
raise NotImplementedError.new
end
private
- # Merge the default options with ones specific to a method call.
+ # Merges the default options with ones specific to a method call.
def merged_options(call_options) # :nodoc:
if call_options
options.merge(call_options)
@@ -509,7 +516,7 @@ module ActiveSupport
end
end
- # Expand key to be a consistent string value. Invoke +cache_key+ if
+ # Expands key to be a consistent string value. Invokes +cache_key+ if
# object responds to +cache_key+. Otherwise, +to_param+ method will be
# called. If the key is a Hash, then keys will be sorted alphabetically.
def expanded_key(key) # :nodoc:
@@ -529,7 +536,7 @@ module ActiveSupport
key.to_param
end
- # Prefix a key with the namespace. Namespace and key will be delimited
+ # Prefixes a key with the namespace. Namespace and key will be delimited
# with a colon.
def normalize_key(key, options)
key = expanded_key(key)
@@ -555,17 +562,6 @@ module ActiveSupport
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
end
- 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(yield)
@@ -588,12 +584,12 @@ module ActiveSupport
end
def get_entry_value(entry, name, options)
- instrument(:fetch_hit, name, options) { |payload| }
+ instrument(:fetch_hit, name, options) { }
entry.value
end
def save_block_result_to_cache(name, options)
- result = instrument(:generate, name, options) do |payload|
+ result = instrument(:generate, name, options) do
yield(name)
end
@@ -611,7 +607,7 @@ module ActiveSupport
class Entry # :nodoc:
DEFAULT_COMPRESS_LIMIT = 16.kilobytes
- # Create a new cache entry for the specified value. Options supported are
+ # Creates a new cache entry for the specified value. Options supported are
# +:compress+, +:compress_threshold+, and +:expires_in+.
def initialize(value, options = {})
if should_compress?(value, options)
@@ -630,7 +626,7 @@ module ActiveSupport
compressed? ? uncompress(@value) : @value
end
- # Check if the entry is expired. The +expires_in+ parameter can override
+ # Checks if the entry is expired. The +expires_in+ parameter can override
# the value set when the entry was created.
def expired?
@expires_in && @created_at + @expires_in <= Time.now.to_f
@@ -665,7 +661,7 @@ module ActiveSupport
end
end
- # Duplicate the value in a class. This is used by cache implementations that don't natively
+ # Duplicates 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!
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 174913365a..2ca4b51efa 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -96,16 +96,14 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- instrument_multi(:read, names, options) do
- keys_to_names = Hash[names.map{|name| [normalize_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
+ keys_to_names = Hash[names.map{|name| [normalize_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
# 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 df38dbcf11..1c678dc2af 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -126,7 +126,7 @@ module ActiveSupport
def set_cache_value(value, name, amount, options) # :nodoc:
ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
`set_cache_value` is deprecated and will be removed from Rails 5.1.
- Please use `write_cache_value`
+ Please use `write_cache_value` instead.
MESSAGE
write_cache_value name, value, options
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index e6baddf5db..557a4695a6 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -103,7 +103,7 @@ module ActiveSupport
end
# A hook invoked every time a before callback is halted.
- # This can be overridden in AS::Callback implementors in order
+ # This can be overridden in ActiveSupport::Callbacks implementors in order
# to provide better debugging/logging.
def halted_callback_hook(filter)
end
@@ -571,15 +571,15 @@ module ActiveSupport
# Install a callback for the given event.
#
- # set_callback :save, :before, :before_meth
- # set_callback :save, :after, :after_meth, if: :condition
+ # set_callback :save, :before, :before_method
+ # set_callback :save, :after, :after_method, if: :condition
# set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
#
# The second argument indicates whether the callback is to be run +:before+,
# +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
# means the first example above can also be written as:
#
- # set_callback :save, :before_meth
+ # set_callback :save, :before_method
#
# The callback can be specified as a symbol naming an instance method; as a
# proc, lambda, or block; as a string to be instance evaluated(deprecated); or as an
@@ -736,8 +736,13 @@ module ActiveSupport
#
# would call <tt>Audit#save</tt>.
#
- # NOTE: +method_name+ passed to `define_model_callbacks` must not end with
+ # ===== Notes
+ #
+ # +names+ passed to `define_callbacks` must not end with
# `!`, `?` or `=`.
+ #
+ # Calling `define_callbacks` multiple times with the same +names+ will
+ # overwrite previous callbacks registered with `set_callback`.
def define_callbacks(*names)
options = names.extract_options!
@@ -782,7 +787,7 @@ module ActiveSupport
def display_deprecation_warning_for_false_terminator
ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in the next release of Rails.
+ Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in Rails 5.1.
To explicitly halt the callback chain, please use `throw :abort` instead.
MSG
end
diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb
index ca48164c54..89e63aefd4 100644
--- a/activesupport/lib/active_support/concurrency/share_lock.rb
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -6,12 +6,6 @@ module ActiveSupport
# A share/exclusive lock, otherwise known as a read/write lock.
#
# https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
- #--
- # Note that a pending Exclusive lock attempt does not block incoming
- # Share requests (i.e., we are "read-preferring"). That seems
- # consistent with the behavior of "loose" upgrades, but may be the
- # wrong choice otherwise: it nominally reduces the possibility of
- # deadlock by risking starvation instead.
class ShareLock
include MonitorMixin
@@ -48,17 +42,11 @@ module ActiveSupport
def start_exclusive(purpose: nil, compatible: [], no_wait: false)
synchronize do
unless @exclusive_thread == Thread.current
- if busy?(purpose)
+ if busy_for_exclusive?(purpose)
return false if no_wait
- loose_shares = @sharing.delete(Thread.current)
- @waiting[Thread.current] = compatible if loose_shares
-
- begin
- @cv.wait_while { busy?(purpose) }
- ensure
- @waiting.delete Thread.current
- @sharing[Thread.current] = loose_shares if loose_shares
+ yield_shares(purpose: purpose, compatible: compatible, block_share: true) do
+ @cv.wait_while { busy_for_exclusive?(purpose) }
end
end
@exclusive_thread = Thread.current
@@ -71,13 +59,19 @@ module ActiveSupport
# Relinquish the exclusive lock. Must only be called by the thread
# that called start_exclusive (and currently holds the lock).
- def stop_exclusive
+ def stop_exclusive(compatible: [])
synchronize do
raise "invalid unlock" if @exclusive_thread != Thread.current
@exclusive_depth -= 1
if @exclusive_depth == 0
@exclusive_thread = nil
+
+ if eligible_waiters?(compatible)
+ yield_shares(compatible: compatible, block_share: true) do
+ @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) }
+ end
+ end
@cv.broadcast
end
end
@@ -85,8 +79,16 @@ module ActiveSupport
def start_sharing
synchronize do
- if @exclusive_thread && @exclusive_thread != Thread.current
+ if @sharing[Thread.current] > 0 || @exclusive_thread == Thread.current
+ # We already hold a lock; nothing to wait for
+ elsif @waiting[Thread.current]
+ # We're nested inside a +yield_shares+ call: we'll resume as
+ # soon as there isn't an exclusive lock in our way
@cv.wait_while { @exclusive_thread }
+ else
+ # This is an initial / outermost share call: any outstanding
+ # requests for an exclusive lock get to go first
+ @cv.wait_while { busy_for_sharing?(false) }
end
@sharing[Thread.current] += 1
end
@@ -109,12 +111,12 @@ module ActiveSupport
# the block.
#
# See +start_exclusive+ for other options.
- def exclusive(purpose: nil, compatible: [], no_wait: false)
+ def exclusive(purpose: nil, compatible: [], after_compatible: [], no_wait: false)
if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait)
begin
yield
ensure
- stop_exclusive
+ stop_exclusive(compatible: after_compatible)
end
end
end
@@ -129,14 +131,56 @@ module ActiveSupport
end
end
+ # Temporarily give up all held Share locks while executing the
+ # supplied block, allowing any +compatible+ exclusive lock request
+ # to proceed.
+ def yield_shares(purpose: nil, compatible: [], block_share: false)
+ loose_shares = previous_wait = nil
+ synchronize do
+ if loose_shares = @sharing.delete(Thread.current)
+ if previous_wait = @waiting[Thread.current]
+ purpose = nil unless purpose == previous_wait[0]
+ compatible &= previous_wait[1]
+ end
+ compatible |= [false] unless block_share
+ @waiting[Thread.current] = [purpose, compatible]
+ end
+
+ @cv.broadcast
+ end
+
+ begin
+ yield
+ ensure
+ synchronize do
+ @cv.wait_while { @exclusive_thread && @exclusive_thread != Thread.current }
+
+ if previous_wait
+ @waiting[Thread.current] = previous_wait
+ else
+ @waiting.delete Thread.current
+ end
+ @sharing[Thread.current] = loose_shares if loose_shares
+ end
+ end
+ end
+
private
# Must be called within synchronize
- def busy?(purpose)
- (@exclusive_thread && @exclusive_thread != Thread.current) ||
- @waiting.any? { |k, v| k != Thread.current && !v.include?(purpose) } ||
+ def busy_for_exclusive?(purpose)
+ busy_for_sharing?(purpose) ||
@sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0)
end
+
+ def busy_for_sharing?(purpose)
+ (@exclusive_thread && @exclusive_thread != Thread.current) ||
+ @waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) }
+ end
+
+ def eligible_waiters?(compatible)
+ @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } }
+ end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 3177d8498e..37d833887a 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -73,4 +73,18 @@ class Array
def forty_two
self[41]
end
+
+ # Equal to <tt>self[-3]</tt>.
+ #
+ # %w( a b c d e ).third_to_last # => "c"
+ def third_to_last
+ self[-3]
+ end
+
+ # Equal to <tt>self[-2]</tt>.
+ #
+ # %w( a b c d e ).second_to_last # => "d"
+ def second_to_last
+ self[-2]
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index 87ae052eb0..ea9d85f6e3 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -89,28 +89,19 @@ class Array
# [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
# (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
def split(value = nil)
+ arr = self.dup
+ result = []
if block_given?
- inject([[]]) do |results, element|
- if yield(element)
- results << []
- else
- results.last << element
- end
-
- results
+ while (idx = arr.index { |i| yield i })
+ result << arr.shift(idx)
+ arr.shift
end
else
- results, arr = [[]], self.dup
- until arr.empty?
- if (idx = arr.index(value))
- results.last.concat(arr.shift(idx))
- arr.shift
- results << []
- else
- results.last.concat(arr.shift(arr.size))
- end
+ while (idx = arr.index(value))
+ result << arr.shift(idx)
+ arr.shift
end
- results
end
+ result << arr
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 22fc7ecf92..074e2eabf8 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -3,10 +3,8 @@ require 'bigdecimal/util'
module ActiveSupport
module BigDecimalWithDefaultFormat #:nodoc:
- DEFAULT_STRING_FORMAT = 'F'
-
- def to_s(format = nil)
- super(format || DEFAULT_STRING_FORMAT)
+ def to_s(format = 'F')
+ super(format)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index b0f9a8be34..1d8c33b43e 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -26,8 +26,6 @@ class Class
# Returns an array with the direct children of +self+.
#
- # Integer.subclasses # => [Fixnum, Bignum]
- #
# class Foo; end
# class Bar < Foo; end
# class Baz < Bar; end
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index ed8bca77ac..6e3b4a89ce 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -5,15 +5,15 @@ require 'active_support/core_ext/module/remove_method'
class Date
DATE_FORMATS = {
- :short => '%e %b',
- :long => '%B %e, %Y',
+ :short => '%d %b',
+ :long => '%B %d, %Y',
:db => '%Y-%m-%d',
:number => '%Y%m%d',
:long_ordinal => lambda { |date|
day_format = ActiveSupport::Inflector.ordinalize(date.day)
date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007"
},
- :rfc822 => '%e %b %Y',
+ :rfc822 => '%d %b %Y',
:iso8601 => lambda { |date| date.iso8601 }
}
@@ -80,6 +80,7 @@ class Date
#
# date.to_time(:utc) # => 2007-11-10 00:00:00 UTC
def to_time(form = :local)
+ raise ArgumentError, "Expected :local or :utc, got #{form.inspect}." unless [:local, :utc].include?(form)
::Time.send(form, year, month, day)
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 e079af594d..6206546672 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/try'
+
module DateAndTime
module Calculations
DAYS_INTO_WEEK = {
@@ -51,6 +53,11 @@ module DateAndTime
WEEKEND_DAYS.include?(wday)
end
+ # Returns true if the date/time does not fall on a Saturday or Sunday.
+ def on_weekday?
+ !WEEKEND_DAYS.include?(wday)
+ end
+
# Returns a new date/time the specified number of days ago.
def days_ago(days)
advance(:days => -days)
@@ -287,6 +294,11 @@ module DateAndTime
end
alias :at_end_of_year :end_of_year
+ # Returns a Range representing the whole day of the current date/time.
+ def all_day
+ beginning_of_day..end_of_day
+ end
+
# Returns a Range representing the whole week of the current date/time.
# Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set.
def all_week(start_day = Date.beginning_of_week)
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
new file mode 100644
index 0000000000..19e596a144
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
@@ -0,0 +1,18 @@
+require 'active_support/core_ext/module/attribute_accessors'
+
+module DateAndTime
+ module Compatibility
+ # If true, +to_time+ preserves the timezone offset of receiver.
+ #
+ # NOTE: With Ruby 2.4+ the default for +to_time+ changed from
+ # converting to the local system time, to preserving the offset
+ # of the receiver. For backwards compatibility we're overriding
+ # this behavior, but new apps will have an initializer that sets
+ # this to true, because the new behavior is preferred.
+ mattr_accessor(:preserve_timezone, instance_writer: false) { false }
+
+ def to_time
+ preserve_timezone ? getlocal(utc_offset) : getlocal
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb
index d29a8db5cf..e2432c8f8a 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb
@@ -5,7 +5,7 @@ module DateAndTime
#
# Time.zone = 'Hawaii' # => 'Hawaii'
# Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
- # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00
+ # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00
#
# This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone
# instead of the operating system's time zone.
@@ -14,7 +14,7 @@ module DateAndTime
# and the conversion will be based on that zone instead of <tt>Time.zone</tt>.
#
# Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
- # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00
+ # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
time_zone = ::Time.find_zone! zone
time = acts_like?(:time) ? self : nil
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index bcb228b09a..86177488c0 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -1,5 +1,5 @@
require 'active_support/core_ext/date_time/acts_like'
require 'active_support/core_ext/date_time/blank'
require 'active_support/core_ext/date_time/calculations'
+require 'active_support/core_ext/date_time/compatibility'
require 'active_support/core_ext/date_time/conversions'
-require 'active_support/core_ext/date_time/zones'
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 95617fb8c2..9e89a33491 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -28,6 +28,13 @@ class DateTime
end_of_day.to_i - to_i
end
+ # Returns the fraction of a second as a +Rational+
+ #
+ # DateTime.new(2012, 8, 29, 0, 0, 0.5).subsec # => (1/2)
+ def subsec
+ sec_fraction
+ end
+
# Returns a new DateTime where one or more of the elements have been changed
# according to the +options+ parameter. The time options (<tt>:hour</tt>,
# <tt>:min</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is
@@ -143,14 +150,32 @@ class DateTime
end
alias :at_end_of_minute :end_of_minute
- # Adjusts DateTime to UTC by adding its offset value; offset is set to 0.
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the system timezone.
+ def localtime(utc_offset = nil)
+ utc = new_offset(0)
+
+ Time.utc(
+ utc.year, utc.month, utc.day,
+ utc.hour, utc.min, utc.sec + utc.sec_fraction
+ ).getlocal(utc_offset)
+ end
+ alias_method :getlocal, :localtime
+
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone.
#
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
- # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000
+ # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 UTC
def utc
- new_offset(0)
+ utc = new_offset(0)
+
+ Time.utc(
+ utc.year, utc.month, utc.day,
+ utc.hour, utc.min, utc.sec + utc.sec_fraction
+ )
end
+ alias_method :getgm, :utc
alias_method :getutc, :utc
+ alias_method :gmtime, :utc
# Returns +true+ if <tt>offset == 0</tt>.
def utc?
@@ -165,13 +190,10 @@ class DateTime
# Layers additional behavior on DateTime#<=> so that Time and
# ActiveSupport::TimeWithZone instances can be compared with a DateTime.
def <=>(other)
- if other.kind_of?(Infinity)
- super
- elsif other.respond_to? :to_datetime
+ if other.respond_to? :to_datetime
super other.to_datetime rescue nil
else
- nil
+ super
end
end
-
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
new file mode 100644
index 0000000000..03e4a2adfa
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
@@ -0,0 +1,5 @@
+require 'active_support/core_ext/date_and_time/compatibility'
+
+class DateTime
+ prepend DateAndTime::Compatibility
+end
diff --git a/activesupport/lib/active_support/core_ext/date_time/zones.rb b/activesupport/lib/active_support/core_ext/date_time/zones.rb
deleted file mode 100644
index c39f358395..0000000000
--- a/activesupport/lib/active_support/core_ext/date_time/zones.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-require 'date'
-require 'active_support/core_ext/date_and_time/zones'
-
-class DateTime
- include DateAndTime::Zones
-end
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 8a74ad4d66..8ebe758078 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -17,11 +17,12 @@ module Enumerable
# The default sum of an empty list is zero. You can override this default:
#
# [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
- def sum(identity = 0, &block)
+ def sum(identity = nil, &block)
if block_given?
map(&block).sum(identity)
else
- inject(:+) || identity
+ sum = identity ? inject(identity, :+) : inject(:+)
+ sum || identity || 0
end
end
@@ -33,7 +34,9 @@ module Enumerable
# => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
def index_by
if block_given?
- Hash[map { |elem| [yield(elem), elem] }]
+ result = {}
+ each { |elem| result[yield(elem)] = elem }
+ result
else
to_enum(:index_by) { size if respond_to?(:size) }
end
@@ -91,15 +94,36 @@ end
class Range #:nodoc:
# Optimize range sum to use arithmetic progression if a block is not given and
# we have a range of numeric values.
- def sum(identity = 0)
+ def sum(identity = nil)
if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer))
super
else
actual_last = exclude_end? ? (last - 1) : last
if actual_last >= first
- (actual_last - first + 1) * (actual_last + first) / 2
+ sum = identity || 0
+ sum + (actual_last - first + 1) * (actual_last + first) / 2
else
- identity
+ identity || 0
+ end
+ end
+ end
+end
+
+# Array#sum was added in Ruby 2.4 but it only works with Numeric elements.
+#
+# We tried shimming it to attempt the fast native method, rescue TypeError,
+# and fall back to the compatible implementation, but that's much slower than
+# just calling the compat method in the first place.
+if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false)
+ class Array
+ alias :orig_sum :sum
+
+ def sum(init = nil, &block) #:nodoc:
+ if init.is_a?(Numeric) || first.is_a?(Numeric)
+ init ||= 0
+ orig_sum(init, &block)
+ else
+ super
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb
index 5dc9a05ec7..f072530e04 100644
--- a/activesupport/lib/active_support/core_ext/hash/compact.rb
+++ b/activesupport/lib/active_support/core_ext/hash/compact.rb
@@ -1,19 +1,22 @@
class Hash
# Returns a hash with non +nil+ values.
#
- # hash = { a: true, b: false, c: nil}
- # hash.compact # => { a: true, b: false}
- # hash # => { a: true, b: false, c: nil}
- # { c: nil }.compact # => {}
+ # hash = { a: true, b: false, c: nil }
+ # hash.compact # => { a: true, b: false }
+ # hash # => { a: true, b: false, c: nil }
+ # { c: nil }.compact # => {}
+ # { c: true }.compact # => { c: true }
def compact
self.select { |_, value| !value.nil? }
end
# Replaces current hash with non +nil+ values.
+ # Returns nil if no changes were made, otherwise returns the hash.
#
- # hash = { a: true, b: false, c: nil}
- # hash.compact! # => { a: true, b: false}
- # hash # => { a: true, b: false}
+ # hash = { a: true, b: false, c: nil }
+ # hash.compact! # => { a: true, b: false }
+ # hash # => { a: true, b: false }
+ # { c: true }.compact! # => nil
def compact!
self.reject! { |_, value| value.nil? }
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 6741e732f0..2fc514cfce 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -31,7 +31,7 @@ class Hash
# with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
# callable can add nodes by using <tt>options[:builder]</tt>.
#
- # 'foo'.to_xml(lambda { |options, key| options[:builder].b(key) })
+ # {foo: lambda { |options, key| options[:builder].b(key) }}.to_xml
# # => "<b>foo</b>"
#
# * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
@@ -55,8 +55,7 @@ class Hash
#
# XML_TYPE_NAMES = {
# "Symbol" => "symbol",
- # "Fixnum" => "integer",
- # "Bignum" => "integer",
+ # "Integer" => "integer",
# "BigDecimal" => "decimal",
# "Float" => "float",
# "TrueClass" => "boolean",
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index 8b2366c4b3..1bfa18aeee 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -11,7 +11,7 @@ class Hash
# hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"}
def transform_keys
return enum_for(:transform_keys) { size } unless block_given?
- result = self.class.new
+ result = {}
each_key do |key|
result[yield(key)] = self[key]
end
diff --git a/activesupport/lib/active_support/core_ext/kernel/concern.rb b/activesupport/lib/active_support/core_ext/kernel/concern.rb
index bf72caa058..18bcc01fa4 100644
--- a/activesupport/lib/active_support/core_ext/kernel/concern.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/concern.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/module/concerning'
module Kernel
+ module_function
+
# A shortcut to define a toplevel concern, not within a module.
#
# See Module::Concerning for more.
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index 8afc258df8..d0197af95f 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -1,4 +1,6 @@
module Kernel
+ module_function
+
# Sets $VERBOSE to nil for the duration of the block and back to its original
# value afterwards.
#
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
index e333b26133..edfc8296fe 100644
--- a/activesupport/lib/active_support/core_ext/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -3,9 +3,12 @@ module ActiveSupport
def load(source)
super(source)
rescue ArgumentError, NameError => exc
- if exc.message.match(%r|undefined class/module (.+)|)
+ if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|)
# try loading the class/module
- $1.constantize
+ loaded = $1.constantize
+
+ raise unless $1 == loaded.name
+
# if it is an IO we need to go back to read the object
source.rewind if source.respond_to?(:rewind)
retry
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 76825862d7..567ac825e9 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -27,7 +27,7 @@ class Module
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
#
# module HairColors
- # mattr_writer :hair_colors, instance_reader: false
+ # mattr_reader :hair_colors, instance_reader: false
# end
#
# class Person
@@ -40,7 +40,7 @@ class Module
# Also, you can pass a block to set up the attribute with a default value.
#
# module HairColors
- # cattr_reader :hair_colors do
+ # mattr_reader :hair_colors do
# [:brown, :black, :blonde, :red]
# end
# end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
index 8a7e6776da..045668c207 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
@@ -25,7 +25,7 @@ class Module
# end
# # => NameError: invalid attribute name: 1_Badname
#
- # If you want to opt out the creation on the instance reader method, pass
+ # If you want to opt out of the creation of the instance reader method, pass
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
#
# class Current
@@ -47,7 +47,7 @@ class Module
unless options[:instance_reader] == false || options[:instance_accessor] == false
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}
- Thread.current[:"attr_#{self.class.name}_#{sym}"]
+ Thread.current[:"attr_#{name}_#{sym}"]
end
EOS
end
@@ -65,7 +65,7 @@ class Module
# Current.user = "DHH"
# Thread.current[:attr_Current_user] # => "DHH"
#
- # If you want to opt out the instance writer method, pass
+ # If you want to opt out of the creation of the instance writer method, pass
# <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
#
# class Current
@@ -86,7 +86,7 @@ class Module
unless options[:instance_writer] == false || options[:instance_accessor] == false
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}=(obj)
- Thread.current[:"attr_#{self.class.name}_#{sym}"] = obj
+ Thread.current[:"attr_#{name}_#{sym}"] = obj
end
EOS
end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 0d46248582..3f6e8bd26c 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -148,15 +148,11 @@ class Module
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
#
# The target method must be public, otherwise it will raise +NoMethodError+.
- #
- def delegate(*methods)
- options = methods.pop
- unless options.is_a?(Hash) && to = options[:to]
+ def delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
+ unless to
raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
end
- prefix, allow_nil = options.values_at(:prefix, :allow_nil)
-
if prefix == true && to =~ /^[^a-z_]/
raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
end
@@ -168,8 +164,8 @@ class Module
''
end
- file, line = caller(1, 1).first.split(':'.freeze, 2)
- line = line.to_i
+ location = caller_locations(1, 1).first
+ file, line = location.path, location.lineno
to = to.to_s
to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
@@ -215,4 +211,68 @@ class Module
module_eval(method_def, file, line)
end
end
+
+ # When building decorators, a common pattern may emerge:
+ #
+ # class Partition
+ # def initialize(first_event)
+ # @events = [ first_event ]
+ # end
+ #
+ # def people
+ # if @events.first.detail.people.any?
+ # @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ # else
+ # @events.collect(&:creator).uniq
+ # end
+ # end
+ #
+ # private
+ # def respond_to_missing?(name, include_private = false)
+ # @events.respond_to?(name, include_private)
+ # end
+ #
+ # def method_missing(method, *args, &block)
+ # @events.send(method, *args, &block)
+ # end
+ # end
+ #
+ # With `Module#delegate_missing_to`, the above is condensed to:
+ #
+ # class Partition
+ # delegate_missing_to :@events
+ #
+ # def initialize(first_event)
+ # @events = [ first_event ]
+ # end
+ #
+ # def people
+ # if @events.first.detail.people.any?
+ # @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ # else
+ # @events.collect(&:creator).uniq
+ # end
+ # end
+ # end
+ #
+ # The target can be anything callable withing the object. E.g. instance
+ # variables, methods, constants ant the likes.
+ def delegate_missing_to(target)
+ target = target.to_s
+ target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
+
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def respond_to_missing?(name, include_private = false)
+ #{target}.respond_to?(name, include_private)
+ end
+
+ def method_missing(method, *args, &block)
+ if #{target}.respond_to?(method)
+ #{target}.public_send(method, *args, &block)
+ else
+ super
+ end
+ end
+ RUBY
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb
index 56d670fbe8..f3f2e7f5fc 100644
--- a/activesupport/lib/active_support/core_ext/module/deprecation.rb
+++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb
@@ -13,8 +13,8 @@ class Module
#
# class MyLib::Deprecator
# def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
- # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
- # Kernel.warn message
+ # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
+ # Kernel.warn message
# end
# end
def deprecate(*method_names)
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index f1d26ef28f..fa692e1b0e 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -57,6 +57,10 @@ class Module
end
def local_constants #:nodoc:
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Module#local_constants is deprecated and will be removed in Rails 5.1.
+ Use Module#constants(false) instead.
+ MSG
constants(false)
end
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 9d832897ed..6586a351f8 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -15,72 +15,72 @@ module ActiveSupport::NumericWithFormat
# ==== Examples
#
# Phone Numbers:
- # 5551234.to_s(:phone) # => 555-1234
- # 1235551234.to_s(:phone) # => 123-555-1234
- # 1235551234.to_s(:phone, area_code: true) # => (123) 555-1234
- # 1235551234.to_s(:phone, delimiter: ' ') # => 123 555 1234
- # 1235551234.to_s(:phone, area_code: true, extension: 555) # => (123) 555-1234 x 555
- # 1235551234.to_s(:phone, country_code: 1) # => +1-123-555-1234
+ # 5551234.to_s(:phone) # => "555-1234"
+ # 1235551234.to_s(:phone) # => "123-555-1234"
+ # 1235551234.to_s(:phone, area_code: true) # => "(123) 555-1234"
+ # 1235551234.to_s(:phone, delimiter: ' ') # => "123 555 1234"
+ # 1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
+ # 1235551234.to_s(:phone, country_code: 1) # => "+1-123-555-1234"
# 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.')
- # # => +1.123.555.1234 x 1343
+ # # => "+1.123.555.1234 x 1343"
#
# Currency:
- # 1234567890.50.to_s(:currency) # => $1,234,567,890.50
- # 1234567890.506.to_s(:currency) # => $1,234,567,890.51
- # 1234567890.506.to_s(:currency, precision: 3) # => $1,234,567,890.506
- # 1234567890.506.to_s(:currency, locale: :fr) # => 1 234 567 890,51 €
+ # 1234567890.50.to_s(:currency) # => "$1,234,567,890.50"
+ # 1234567890.506.to_s(:currency) # => "$1,234,567,890.51"
+ # 1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506"
+ # 1234567890.506.to_s(:currency, locale: :fr) # => "1 234 567 890,51 €"
# -1234567890.50.to_s(:currency, negative_format: '(%u%n)')
- # # => ($1,234,567,890.50)
+ # # => "($1,234,567,890.50)"
# 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '')
- # # => &pound;1234567890,50
+ # # => "&pound;1234567890,50"
# 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
- # # => 1234567890,50 &pound;
+ # # => "1234567890,50 &pound;"
#
# Percentage:
- # 100.to_s(:percentage) # => 100.000%
- # 100.to_s(:percentage, precision: 0) # => 100%
- # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => 1.000,000%
- # 302.24398923423.to_s(:percentage, precision: 5) # => 302.24399%
- # 1000.to_s(:percentage, locale: :fr) # => 1 000,000%
- # 100.to_s(:percentage, format: '%n %') # => 100.000 %
+ # 100.to_s(:percentage) # => "100.000%"
+ # 100.to_s(:percentage, precision: 0) # => "100%"
+ # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%"
+ # 302.24398923423.to_s(:percentage, precision: 5) # => "302.24399%"
+ # 1000.to_s(:percentage, locale: :fr) # => "1 000,000%"
+ # 100.to_s(:percentage, format: '%n %') # => "100.000 %"
#
# Delimited:
- # 12345678.to_s(:delimited) # => 12,345,678
- # 12345678.05.to_s(:delimited) # => 12,345,678.05
- # 12345678.to_s(:delimited, delimiter: '.') # => 12.345.678
- # 12345678.to_s(:delimited, delimiter: ',') # => 12,345,678
- # 12345678.05.to_s(:delimited, separator: ' ') # => 12,345,678 05
- # 12345678.05.to_s(:delimited, locale: :fr) # => 12 345 678,05
+ # 12345678.to_s(:delimited) # => "12,345,678"
+ # 12345678.05.to_s(:delimited) # => "12,345,678.05"
+ # 12345678.to_s(:delimited, delimiter: '.') # => "12.345.678"
+ # 12345678.to_s(:delimited, delimiter: ',') # => "12,345,678"
+ # 12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05"
+ # 12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05"
# 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',')
- # # => 98 765 432,98
+ # # => "98 765 432,98"
#
# Rounded:
- # 111.2345.to_s(:rounded) # => 111.235
- # 111.2345.to_s(:rounded, precision: 2) # => 111.23
- # 13.to_s(:rounded, precision: 5) # => 13.00000
- # 389.32314.to_s(:rounded, precision: 0) # => 389
- # 111.2345.to_s(:rounded, significant: true) # => 111
- # 111.2345.to_s(:rounded, precision: 1, significant: true) # => 100
- # 13.to_s(:rounded, precision: 5, significant: true) # => 13.000
- # 111.234.to_s(:rounded, locale: :fr) # => 111,234
+ # 111.2345.to_s(:rounded) # => "111.235"
+ # 111.2345.to_s(:rounded, precision: 2) # => "111.23"
+ # 13.to_s(:rounded, precision: 5) # => "13.00000"
+ # 389.32314.to_s(:rounded, precision: 0) # => "389"
+ # 111.2345.to_s(:rounded, significant: true) # => "111"
+ # 111.2345.to_s(:rounded, precision: 1, significant: true) # => "100"
+ # 13.to_s(:rounded, precision: 5, significant: true) # => "13.000"
+ # 111.234.to_s(:rounded, locale: :fr) # => "111,234"
# 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true)
- # # => 13
- # 389.32314.to_s(:rounded, precision: 4, significant: true) # => 389.3
+ # # => "13"
+ # 389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3"
# 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.')
- # # => 1.111,23
+ # # => "1.111,23"
#
# Human-friendly size in Bytes:
- # 123.to_s(:human_size) # => 123 Bytes
- # 1234.to_s(:human_size) # => 1.21 KB
- # 12345.to_s(:human_size) # => 12.1 KB
- # 1234567.to_s(:human_size) # => 1.18 MB
- # 1234567890.to_s(:human_size) # => 1.15 GB
- # 1234567890123.to_s(:human_size) # => 1.12 TB
- # 1234567890123456.to_s(:human_size) # => 1.1 PB
- # 1234567890123456789.to_s(:human_size) # => 1.07 EB
- # 1234567.to_s(:human_size, precision: 2) # => 1.2 MB
- # 483989.to_s(:human_size, precision: 2) # => 470 KB
- # 1234567.to_s(:human_size, precision: 2, separator: ',') # => 1,2 MB
+ # 123.to_s(:human_size) # => "123 Bytes"
+ # 1234.to_s(:human_size) # => "1.21 KB"
+ # 12345.to_s(:human_size) # => "12.1 KB"
+ # 1234567.to_s(:human_size) # => "1.18 MB"
+ # 1234567890.to_s(:human_size) # => "1.15 GB"
+ # 1234567890123.to_s(:human_size) # => "1.12 TB"
+ # 1234567890123456.to_s(:human_size) # => "1.1 PB"
+ # 1234567890123456789.to_s(:human_size) # => "1.07 EB"
+ # 1234567.to_s(:human_size, precision: 2) # => "1.2 MB"
+ # 483989.to_s(:human_size, precision: 2) # => "470 KB"
+ # 1234567.to_s(:human_size, precision: 2, separator: ',') # => "1,2 MB"
# 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB"
# 524288000.to_s(:human_size, precision: 5) # => "500 MB"
#
@@ -134,6 +134,12 @@ module ActiveSupport::NumericWithFormat
deprecate to_formatted_s: :to_s
end
-[Fixnum, Bignum, Float, BigDecimal].each do |klass|
- klass.prepend(ActiveSupport::NumericWithFormat)
+# Ruby 2.4+ unifies Fixnum & Bignum into Integer.
+if Integer == Fixnum
+ Integer.prepend ActiveSupport::NumericWithFormat
+else
+ Fixnum.prepend ActiveSupport::NumericWithFormat
+ Bignum.prepend ActiveSupport::NumericWithFormat
end
+Float.prepend ActiveSupport::NumericWithFormat
+BigDecimal.prepend ActiveSupport::NumericWithFormat
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 6c4a975495..c6ece22f8d 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -25,17 +25,17 @@ class Numeric
# Returns a Duration instance matching the number of minutes provided.
#
- # 2.minutes # => 120 seconds
+ # 2.minutes # => 2 minutes
def minutes
- ActiveSupport::Duration.new(self * 60, [[:seconds, self * 60]])
+ ActiveSupport::Duration.new(self * 60, [[:minutes, self]])
end
alias :minute :minutes
# Returns a Duration instance matching the number of hours provided.
#
- # 2.hours # => 7_200 seconds
+ # 2.hours # => 2 hours
def hours
- ActiveSupport::Duration.new(self * 3600, [[:seconds, self * 3600]])
+ ActiveSupport::Duration.new(self * 3600, [[:hours, self]])
end
alias :hour :hours
@@ -49,17 +49,17 @@ class Numeric
# Returns a Duration instance matching the number of weeks provided.
#
- # 2.weeks # => 14 days
+ # 2.weeks # => 2 weeks
def weeks
- ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]])
+ ActiveSupport::Duration.new(self * 7.days, [[:weeks, self]])
end
alias :week :weeks
# Returns a Duration instance matching the number of fortnights provided.
#
- # 2.fortnights # => 28 days
+ # 2.fortnights # => 4 weeks
def fortnights
- ActiveSupport::Duration.new(self * 2.weeks, [[:days, self * 14]])
+ ActiveSupport::Duration.new(self * 2.weeks, [[:weeks, self * 2]])
end
alias :fortnight :fortnights
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index 039c50a4a2..cb74bad73e 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -112,7 +112,10 @@ class String
#
# @return [true, false]
def blank?
- BLANK_RE === self
+ # The regexp that matches blank strings is expensive. For the case of empty
+ # strings we can speed up this method (~3.5x) with an empty? call. The
+ # penalty for the rest of strings is marginal.
+ empty? || BLANK_RE === self
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 befa5aee21..9bc5ee65ba 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -70,7 +70,7 @@ class Numeric
# Numbers are not duplicable:
#
# 3.duplicable? # => false
- # 3.dup # => TypeError: can't dup Fixnum
+ # 3.dup # => TypeError: can't dup Integer
def duplicable?
false
end
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
index 0db787010c..d49b2fbe54 100644
--- a/activesupport/lib/active_support/core_ext/object/json.rb
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -197,3 +197,9 @@ class Process::Status #:nodoc:
{ :exitstatus => exitstatus, :pid => pid }
end
end
+
+class Exception
+ def as_json(options = nil)
+ to_s
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 8c16d95b62..3b6d9da216 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -99,7 +99,7 @@ class Object
#
# "a".try!(:upcase) # => "A"
# nil.try!(:upcase) # => nil
- # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum
+ # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer
end
class Delegator
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index ebd0dd3fc7..213a91aa7a 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -1,5 +1,5 @@
class String
- # If you pass a single Fixnum, returns a substring of one character at that
+ # If you pass a single integer, returns a substring of one character at that
# position. The first character of the string is at position 0, the next at
# position 1, and so on. If a range is supplied, a substring containing
# characters at offsets given by the range is returned. In both cases, if an
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index fd79a40e31..946976c5e9 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -18,7 +18,8 @@ class String
# "12/13/2012".to_time # => ArgumentError: argument out of range
def to_time(form = :local)
parts = Date._parse(self, false)
- return if parts.empty?
+ used_keys = %i(year mon mday hour min sec sec_fraction offset)
+ return if (parts.keys & used_keys).empty?
now = Time.now
time = Time.new(
@@ -31,7 +32,7 @@ class String
parts.fetch(:offset, form == :utc ? 0 : nil)
)
- form == :utc ? time.utc : time.getlocal
+ form == :utc ? time.utc : time.to_time
end
# Converts a string to a Date value.
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index cc71b8155d..7277f51076 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -222,6 +222,15 @@ class String
ActiveSupport::Inflector.humanize(self, options)
end
+ # Converts just the first character to uppercase.
+ #
+ # 'what a Lovely Day'.upcase_first # => "What a Lovely Day"
+ # 'w'.upcase_first # => "W"
+ # ''.upcase_first # => ""
+ def upcase_first
+ ActiveSupport::Inflector.upcase_first(self)
+ end
+
# Creates a foreign key name from a class name.
# +separate_class_name_and_id_with_underscore+ sets whether
# the method should put '_' between the name and 'id'.
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 6251f34daf..005ad93b08 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -171,7 +171,7 @@ module ActiveSupport #:nodoc:
original_concat(value)
end
- def initialize(*)
+ def initialize(str = '')
@html_safe = true
super
end
@@ -250,7 +250,7 @@ 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
+ # additional escaping performed. It is your responsibility 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.
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index 72c3234630..0bce632222 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/time/acts_like'
require 'active_support/core_ext/time/calculations'
+require 'active_support/core_ext/time/compatibility'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/time/zones'
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 768c9a1b2c..e81b48ab26 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -73,6 +73,13 @@ class Time
end_of_day.to_i - to_i
end
+ # Returns the fraction of a second as a +Rational+
+ #
+ # Time.new(2012, 8, 29, 0, 0, 0.5).sec_fraction # => (1/2)
+ def sec_fraction
+ subsec
+ end
+
# Returns a new Time where one or more of the elements have been changed according
# to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
# <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly, so if only
@@ -220,11 +227,6 @@ class Time
end
alias :at_end_of_minute :end_of_minute
- # Returns a Range representing the whole day of the current time.
- def all_day
- beginning_of_day..end_of_day
- end
-
def plus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
other.since(self)
diff --git a/activesupport/lib/active_support/core_ext/time/compatibility.rb b/activesupport/lib/active_support/core_ext/time/compatibility.rb
new file mode 100644
index 0000000000..945319461b
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/time/compatibility.rb
@@ -0,0 +1,5 @@
+require 'active_support/core_ext/date_and_time/compatibility'
+
+class Time
+ prepend DateAndTime::Compatibility
+end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index af18ff746f..57f6286de3 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -88,15 +88,6 @@ module ActiveSupport #:nodoc:
mattr_accessor :explicitly_unloadable_constants
self.explicitly_unloadable_constants = []
- # The logger is used for generating information on the action run-time
- # (including benchmarking) if available. Can be set to nil for no logging.
- # Compatible with both Ruby's own Logger and Log4r loggers.
- mattr_accessor :logger
-
- # Set to +true+ to enable logging of const_missing and file loads.
- mattr_accessor :log_activity
- self.log_activity = false
-
# The WatchStack keeps a stack of the modules being watched as files are
# loaded. If a file in the process of being loaded (parent.rb) triggers the
# load of another file (child.rb) the stack will ensure that child.rb
@@ -143,11 +134,11 @@ module ActiveSupport #:nodoc:
next unless mod.is_a?(Module)
# Get a list of the constants that were added
- new_constants = mod.local_constants - original_constants
+ new_constants = mod.constants(false) - original_constants
- # self[namespace] returns an Array of the constants that are being evaluated
+ # @stack[namespace] returns an Array of the constants that are being evaluated
# for that namespace. For instance, if parent.rb requires child.rb, the first
- # element of self[Object] will be an Array of the constants that were present
+ # element of @stack[Object] will be an Array of the constants that were present
# before parent.rb was required. The second element will be an Array of the
# constants that were present before child.rb was required.
@stack[namespace].each do |namespace_constants|
@@ -171,7 +162,7 @@ module ActiveSupport #:nodoc:
@watching << namespaces.map do |namespace|
module_name = Dependencies.to_constant_name(namespace)
original_constants = Dependencies.qualified_const_defined?(module_name) ?
- Inflector.constantize(module_name).local_constants : []
+ Inflector.constantize(module_name).constants(false) : []
@stack[module_name] << original_constants
module_name
@@ -262,7 +253,7 @@ module ActiveSupport #:nodoc:
end
def load_dependency(file)
- if Dependencies.load? && ActiveSupport::Dependencies.constant_watch_stack.watching?
+ if Dependencies.load? && Dependencies.constant_watch_stack.watching?
Dependencies.new_constants_in(Object) { yield }
else
yield
@@ -352,7 +343,6 @@ module ActiveSupport #:nodoc:
end
def clear
- log_call
Dependencies.unload_interlock do
loaded.clear
loading.clear
@@ -361,7 +351,6 @@ module ActiveSupport #:nodoc:
end
def require_or_load(file_name, const_path = nil)
- log_call file_name, const_path
file_name = $` if file_name =~ /\.rb\z/
expanded = File.expand_path(file_name)
return if loaded.include?(expanded)
@@ -377,8 +366,6 @@ module ActiveSupport #:nodoc:
begin
if load?
- log "loading #{file_name}"
-
# Enable warnings if this file has not been loaded before and
# warnings_on_first_load is set.
load_args = ["#{file_name}.rb"]
@@ -390,7 +377,6 @@ module ActiveSupport #:nodoc:
enable_warnings { result = load_file(*load_args) }
end
else
- log "requiring #{file_name}"
result = require file_name
end
rescue Exception
@@ -483,7 +469,6 @@ module ActiveSupport #:nodoc:
# set of names that the file at +path+ may define. See
# +loadable_constants_for_path+ for more details.
def load_file(path, const_paths = loadable_constants_for_path(path))
- log_call path, const_paths
const_paths = [const_paths].compact unless const_paths.is_a? Array
parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || ::Object }
@@ -494,7 +479,6 @@ module ActiveSupport #:nodoc:
autoloaded_constants.concat newly_defined_paths unless load_once_path?(path)
autoloaded_constants.uniq!
- log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty?
result
end
@@ -508,8 +492,6 @@ module ActiveSupport #:nodoc:
# it is not possible to load the constant into from_mod, try its parent
# module using +const_missing+.
def load_missing_constant(from_mod, const_name)
- log_call from_mod, const_name
-
unless qualified_const_defined?(from_mod.name) && Inflector.constantize(from_mod.name).equal?(from_mod)
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
end
@@ -673,25 +655,20 @@ module ActiveSupport #:nodoc:
# exception, any new constants are regarded as being only partially defined
# and will be removed immediately.
def new_constants_in(*descs)
- log_call(*descs)
-
constant_watch_stack.watch_namespaces(descs)
- aborting = true
+ success = false
begin
yield # Now yield to the code that is to define new constants.
- aborting = false
+ success = true
ensure
new_constants = constant_watch_stack.new_constants
- log "New constants: #{new_constants * ', '}"
- return new_constants unless aborting
+ return new_constants if success
- log "Error during loading, removing partially loaded constants "
- new_constants.each { |c| remove_constant(c) }.clear
+ # Remove partially loaded constants.
+ new_constants.each { |c| remove_constant(c) }
end
-
- []
end
# Convert the provided const desc to a qualified constant name (as a string).
@@ -738,8 +715,6 @@ module ActiveSupport #:nodoc:
parent = constantize(parent_name)
end
- log "removing constant #{const}"
-
# In an autoloaded user.rb like this
#
# autoload :Foo, 'foo'
@@ -760,7 +735,7 @@ module ActiveSupport #:nodoc:
begin
constantized = parent.const_get(to_remove, false)
rescue NameError
- log "the constant #{const} is not reachable anymore, skipping"
+ # The constant is no longer reachable, just skip it.
return
else
constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
@@ -770,27 +745,9 @@ module ActiveSupport #:nodoc:
begin
parent.instance_eval { remove_const to_remove }
rescue NameError
- log "the constant #{const} is not reachable anymore, skipping"
+ # The constant is no longer reachable, just skip it.
end
end
-
- protected
- def log_call(*args)
- if log_activity?
- arg_str = args.collect(&:inspect) * ', '
- /in `([a-z_\?\!]+)'/ =~ caller(1).first
- selector = $1 || '<unknown>'
- log "called #{selector}(#{arg_str})"
- end
- end
-
- def log(msg)
- logger.debug "Dependencies: #{msg}" if log_activity?
- end
-
- def log_activity?
- logger && log_activity
- end
end
end
diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb
index fbeb904684..f1865ca2f8 100644
--- a/activesupport/lib/active_support/dependencies/interlock.rb
+++ b/activesupport/lib/active_support/dependencies/interlock.rb
@@ -8,25 +8,23 @@ module ActiveSupport #:nodoc:
end
def loading
- @lock.exclusive(purpose: :load, compatible: [:load]) do
+ @lock.exclusive(purpose: :load, compatible: [:load], after_compatible: [:load]) do
yield
end
end
def unloading
- @lock.exclusive(purpose: :unload, compatible: [:load, :unload]) do
+ @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload]) do
yield
end
end
- # Attempt to obtain an "unloading" (exclusive) lock. If possible,
- # execute the supplied block while holding the lock. If there is
- # concurrent activity, return immediately (without executing the
- # block) instead of waiting.
- def attempt_unloading
- @lock.exclusive(purpose: :unload, compatible: [:load, :unload], no_wait: true) do
- yield
- end
+ def start_unloading
+ @lock.start_exclusive(purpose: :unload, compatible: [:load, :unload])
+ end
+
+ def done_unloading
+ @lock.stop_exclusive(compatible: [:load, :unload])
end
def start_running
@@ -42,6 +40,12 @@ module ActiveSupport #:nodoc:
yield
end
end
+
+ def permit_concurrent_loads
+ @lock.yield_shares(compatible: [:load]) do
+ yield
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 24545d766c..b581710067 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -32,7 +32,7 @@ module ActiveSupport
# and the second is a library name
#
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
- def initialize(deprecation_horizon = '5.1', gem_name = 'Rails')
+ def initialize(deprecation_horizon = '5.2', gem_name = 'Rails')
self.gem_name = gem_name
self.deprecation_horizon = deprecation_horizon
# By default, warnings are not silenced and debugging is off.
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 0de891f1a2..35a9e5f8b8 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -2,7 +2,7 @@ require "active_support/notifications"
module ActiveSupport
# Raised when <tt>ActiveSupport::Deprecation::Behavior#behavior</tt> is set with <tt>:raise</tt>.
- # You would set <tt>:raise</tt>, as a behaviour to raise errors and proactively report exceptions from deprecations.
+ # You would set <tt>:raise</tt>, as a behavior to raise errors and proactively report exceptions from deprecations.
class DeprecationException < StandardError
end
@@ -11,7 +11,7 @@ module ActiveSupport
DEFAULT_BEHAVIORS = {
raise: ->(message, callstack) {
e = DeprecationException.new(message)
- e.set_backtrace(callstack)
+ e.set_backtrace(callstack.map(&:to_s))
raise e
},
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index 6f0ad445fc..0cb2d4d22e 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -80,7 +80,7 @@ module ActiveSupport
# example.old_request.to_s
# # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of
# @request.to_s
- # (Bactrace information…)
+ # (Backtrace information…)
# "special_request"
#
# example.request.to_s
@@ -118,7 +118,7 @@ module ActiveSupport
#
# PLANETS.map { |planet| planet.capitalize }
# # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
- # (Bactrace information…)
+ # (Backtrace information…)
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
class DeprecatedConstantProxy < DeprecationProxy
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance)
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index f89fc0fe14..de5b233679 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -1,3 +1,5 @@
+require 'rbconfig'
+
module ActiveSupport
class Deprecation
module Reporting
@@ -63,7 +65,6 @@ module ActiveSupport
def deprecation_message(callstack, message = nil)
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
- message += '.' unless message =~ /\.$/
"DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}"
end
@@ -81,17 +82,17 @@ module ActiveSupport
def extract_callstack(callstack)
return _extract_callstack(callstack) if callstack.first.is_a? String
- rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/"
offending_line = callstack.find { |frame|
- frame.absolute_path && !frame.absolute_path.start_with?(rails_gem_root)
+ frame.absolute_path && !ignored_callstack(frame.absolute_path)
} || callstack.first
+
[offending_line.path, offending_line.lineno, offending_line.label]
end
def _extract_callstack(callstack)
warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE
- rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/"
- offending_line = callstack.find { |line| !line.start_with?(rails_gem_root) } || callstack.first
+ offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first
+
if offending_line
if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/)
md.captures
@@ -100,6 +101,12 @@ module ActiveSupport
end
end
end
+
+ RAILS_GEM_ROOT = File.expand_path("../../../../..", __FILE__) + "/"
+
+ def ignored_callstack(path)
+ path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG['rubylibdir'])
+ end
end
end
end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index c63b61e97a..47d09f4f5a 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -9,6 +9,9 @@ module ActiveSupport
class Duration
attr_accessor :value, :parts
+ autoload :ISO8601Parser, 'active_support/duration/iso8601_parser'
+ autoload :ISO8601Serializer, 'active_support/duration/iso8601_serializer'
+
def initialize(value, parts) #:nodoc:
@value, @parts = value, parts
end
@@ -117,7 +120,7 @@ module ActiveSupport
def inspect #:nodoc:
parts.
reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }.
- sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}.
+ sort_by {|unit, _ | [:years, :months, :weeks, :days, :hours, :minutes, :seconds].index(unit)}.
map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
to_sentence(locale: ::I18n.default_locale)
end
@@ -130,6 +133,23 @@ module ActiveSupport
@value.respond_to?(method, include_private)
end
+ # Creates a new Duration from string formatted according to ISO 8601 Duration.
+ #
+ # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
+ # This method allows negative parts to be present in pattern.
+ # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
+ def self.parse(iso8601duration)
+ parts = ISO8601Parser.new(iso8601duration).parse!
+ time = ::Time.current
+ new(time.advance(parts) - time, parts)
+ end
+
+ # Build ISO 8601 Duration string for this duration.
+ # The +precision+ parameter can be used to limit seconds' precision of duration.
+ def iso8601(precision: nil)
+ ISO8601Serializer.new(self, precision: precision).serialize
+ end
+
delegate :<=>, to: :value
protected
@@ -139,6 +159,10 @@ module ActiveSupport
if t.acts_like?(:time) || t.acts_like?(:date)
if type == :seconds
t.since(sign * number)
+ elsif type == :minutes
+ t.since(sign * number * 60)
+ elsif type == :hours
+ t.since(sign * number * 3600)
else
t.advance(type => sign * number)
end
diff --git a/activesupport/lib/active_support/duration/iso8601_parser.rb b/activesupport/lib/active_support/duration/iso8601_parser.rb
new file mode 100644
index 0000000000..07af58ad99
--- /dev/null
+++ b/activesupport/lib/active_support/duration/iso8601_parser.rb
@@ -0,0 +1,122 @@
+require 'strscan'
+
+module ActiveSupport
+ class Duration
+ # Parses a string formatted according to ISO 8601 Duration into the hash.
+ #
+ # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
+ #
+ # This parser allows negative parts to be present in pattern.
+ class ISO8601Parser # :nodoc:
+ class ParsingError < ::ArgumentError; end
+
+ PERIOD_OR_COMMA = /\.|,/
+ PERIOD = '.'.freeze
+ COMMA = ','.freeze
+
+ SIGN_MARKER = /\A\-|\+|/
+ DATE_MARKER = /P/
+ TIME_MARKER = /T/
+ DATE_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(Y|M|D|W)/
+ TIME_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(H|M|S)/
+
+ DATE_TO_PART = { 'Y' => :years, 'M' => :months, 'W' => :weeks, 'D' => :days }
+ TIME_TO_PART = { 'H' => :hours, 'M' => :minutes, 'S' => :seconds }
+
+ DATE_COMPONENTS = [:years, :months, :days]
+ TIME_COMPONENTS = [:hours, :minutes, :seconds]
+
+ attr_reader :parts, :scanner
+ attr_accessor :mode, :sign
+
+ def initialize(string)
+ @scanner = StringScanner.new(string)
+ @parts = {}
+ @mode = :start
+ @sign = 1
+ end
+
+ def parse!
+ while !finished?
+ case mode
+ when :start
+ if scan(SIGN_MARKER)
+ self.sign = (scanner.matched == '-') ? -1 : 1
+ self.mode = :sign
+ else
+ raise_parsing_error
+ end
+
+ when :sign
+ if scan(DATE_MARKER)
+ self.mode = :date
+ else
+ raise_parsing_error
+ end
+
+ when :date
+ if scan(TIME_MARKER)
+ self.mode = :time
+ elsif scan(DATE_COMPONENT)
+ parts[DATE_TO_PART[scanner[2]]] = number * sign
+ else
+ raise_parsing_error
+ end
+
+ when :time
+ if scan(TIME_COMPONENT)
+ parts[TIME_TO_PART[scanner[2]]] = number * sign
+ else
+ raise_parsing_error
+ end
+
+ end
+ end
+
+ validate!
+ parts
+ end
+
+ private
+
+ def finished?
+ scanner.eos?
+ end
+
+ # Parses number which can be a float with either comma or period.
+ def number
+ scanner[1] =~ PERIOD_OR_COMMA ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i
+ end
+
+ def scan(pattern)
+ scanner.scan(pattern)
+ end
+
+ def raise_parsing_error(reason = nil)
+ raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip
+ end
+
+ # Checks for various semantic errors as stated in ISO 8601 standard.
+ def validate!
+ raise_parsing_error('is empty duration') if parts.empty?
+
+ # Mixing any of Y, M, D with W is invalid.
+ if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
+ raise_parsing_error('mixing weeks with other date parts not allowed')
+ end
+
+ # Specifying an empty T part is invalid.
+ if mode == :time && (parts.keys & TIME_COMPONENTS).empty?
+ raise_parsing_error('time part marker is present but time part is empty')
+ end
+
+ fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 }
+ unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last)
+ raise_parsing_error '(only last part can be fractional)'
+ end
+
+ return true
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb
new file mode 100644
index 0000000000..05c6a083a9
--- /dev/null
+++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb
@@ -0,0 +1,51 @@
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/transform_values'
+
+module ActiveSupport
+ class Duration
+ # Serializes duration to string according to ISO 8601 Duration format.
+ class ISO8601Serializer
+ def initialize(duration, precision: nil)
+ @duration = duration
+ @precision = precision
+ end
+
+ # Builds and returns output string.
+ def serialize
+ output = 'P'
+ parts, sign = normalize
+ output << "#{parts[:years]}Y" if parts.key?(:years)
+ output << "#{parts[:months]}M" if parts.key?(:months)
+ output << "#{parts[:weeks]}W" if parts.key?(:weeks)
+ output << "#{parts[:days]}D" if parts.key?(:days)
+ time = ''
+ time << "#{parts[:hours]}H" if parts.key?(:hours)
+ time << "#{parts[:minutes]}M" if parts.key?(:minutes)
+ if parts.key?(:seconds)
+ time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
+ end
+ output << "T#{time}" if time.present?
+ "#{sign}#{output}"
+ end
+
+ private
+
+ # Return pair of duration's parts and whole duration sign.
+ # Parts are summarized (as they can become repetitive due to addition, etc).
+ # Zero parts are removed as not significant.
+ # If all parts are negative it will negate all of them and return minus as a sign.
+ def normalize
+ parts = @duration.parts.each_with_object(Hash.new(0)) do |(k,v),p|
+ p[k] += v unless v.zero?
+ end
+ # If all parts are negative - let's make a negative duration
+ sign = ''
+ if parts.values.all? { |v| v < 0 }
+ sign = '-'
+ parts.transform_values!(&:-@)
+ end
+ [parts, sign]
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb
index 315be85fb3..a2dcf31132 100644
--- a/activesupport/lib/active_support/evented_file_update_checker.rb
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -3,6 +3,33 @@ require 'pathname'
require 'concurrent/atomic/atomic_boolean'
module ActiveSupport
+ # Allows you to "listen" to changes in a file system.
+ # The evented file updater does not hit disk when checking for updates
+ # instead it uses platform specific file system events to trigger a change
+ # in state.
+ #
+ # The file checker takes an array of files to watch or a hash specifying directories
+ # and file extensions to watch. It also takes a block that is called when
+ # EventedFileUpdateChecker#execute is run or when EventedFileUpdateChecker#execute_if_updated
+ # is run and there have been changes to the file system.
+ #
+ # Note: Forking will cause the first call to `updated?` to return `true`.
+ #
+ # Example:
+ #
+ # checker = EventedFileUpdateChecker.new(["/tmp/foo"], -> { puts "changed" })
+ # checker.updated?
+ # # => false
+ # checker.execute_if_updated
+ # # => nil
+ #
+ # FileUtils.touch("/tmp/foo")
+ #
+ # checker.updated?
+ # # => true
+ # checker.execute_if_updated
+ # # => "changed"
+ #
class EventedFileUpdateChecker #:nodoc: all
def initialize(files, dirs = {}, &block)
@ph = PathHelper.new
@@ -13,20 +40,35 @@ module ActiveSupport
@dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) }
end
- @block = block
- @updated = Concurrent::AtomicBoolean.new(false)
- @lcsp = @ph.longest_common_subpath(@dirs.keys)
+ @block = block
+ @updated = Concurrent::AtomicBoolean.new(false)
+ @lcsp = @ph.longest_common_subpath(@dirs.keys)
+ @pid = Process.pid
+ @boot_mutex = Mutex.new
- if (dtw = directories_to_watch).any?
+ if (@dtw = directories_to_watch).any?
# Loading listen triggers warnings. These are originated by a legit
# usage of attr_* macros for private attributes, but adds a lot of noise
# to our test suite. Thus, we lazy load it and disable warnings locally.
- silence_warnings { require 'listen' }
- Listen.to(*dtw, &method(:changed)).start
+ silence_warnings do
+ begin
+ require 'listen'
+ rescue LoadError => e
+ raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
+ end
+ end
end
+ boot!
end
def updated?
+ @boot_mutex.synchronize do
+ if @pid != Process.pid
+ boot!
+ @pid = Process.pid
+ @updated.make_true
+ end
+ end
@updated.true?
end
@@ -37,12 +79,16 @@ module ActiveSupport
def execute_if_updated
if updated?
+ yield if block_given?
execute
true
end
end
private
+ def boot!
+ Listen.to(*@dtw, &method(:changed)).start
+ end
def changed(modified, added, removed)
unless updated?
@@ -79,16 +125,6 @@ module ActiveSupport
end
class PathHelper
- using Module.new {
- refine Pathname do
- def ascendant_of?(other)
- self != other && other.ascend do |ascendant|
- break true if self == ascendant
- end
- end
- end
- }
-
def xpath(path)
Pathname.new(path).expand_path
end
@@ -105,7 +141,7 @@ module ActiveSupport
lcsp = Pathname.new(paths[0])
paths[1..-1].each do |path|
- until lcsp.ascendant_of?(path)
+ until ascendant_of?(lcsp, path)
if lcsp.root?
# If we get here a root directory is not an ascendant of path.
# This may happen if there are paths in different drives on
@@ -138,13 +174,21 @@ module ActiveSupport
dir = dirs_sorted_by_nparts.shift
dirs_sorted_by_nparts.reject! do |possible_descendant|
- dir.ascendant_of?(possible_descendant) && descendants << possible_descendant
+ ascendant_of?(dir, possible_descendant) && descendants << possible_descendant
end
end
# Array#- preserves order.
dirs - descendants
end
+
+ private
+
+ def ascendant_of?(base, other)
+ base != other && other.ascend do |ascendant|
+ break true if base == ascendant
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb
new file mode 100644
index 0000000000..00c5745a25
--- /dev/null
+++ b/activesupport/lib/active_support/execution_wrapper.rb
@@ -0,0 +1,117 @@
+require 'active_support/callbacks'
+
+module ActiveSupport
+ class ExecutionWrapper
+ include ActiveSupport::Callbacks
+
+ Null = Object.new # :nodoc:
+ def Null.complete! # :nodoc:
+ end
+
+ define_callbacks :run
+ define_callbacks :complete
+
+ def self.to_run(*args, &block)
+ set_callback(:run, *args, &block)
+ end
+
+ def self.to_complete(*args, &block)
+ set_callback(:complete, *args, &block)
+ end
+
+ # Register an object to be invoked during both the +run+ and
+ # +complete+ steps.
+ #
+ # +hook.complete+ will be passed the value returned from +hook.run+,
+ # and will only be invoked if +run+ has previously been called.
+ # (Mostly, this means it won't be invoked if an exception occurs in
+ # a preceding +to_run+ block; all ordinary +to_complete+ blocks are
+ # invoked in that situation.)
+ def self.register_hook(hook, outer: false)
+ if outer
+ run_args = [prepend: true]
+ complete_args = [:after]
+ else
+ run_args = complete_args = []
+ end
+
+ to_run(*run_args) do
+ hook_state[hook] = hook.run
+ end
+ to_complete(*complete_args) do
+ if hook_state.key?(hook)
+ hook.complete hook_state[hook]
+ end
+ end
+ end
+
+ # Run this execution.
+ #
+ # Returns an instance, whose +complete!+ method *must* be invoked
+ # after the work has been performed.
+ #
+ # Where possible, prefer +wrap+.
+ def self.run!
+ if active?
+ Null
+ else
+ new.tap do |instance|
+ success = nil
+ begin
+ instance.run!
+ success = true
+ ensure
+ instance.complete! unless success
+ end
+ end
+ end
+ end
+
+ # Perform the work in the supplied block as an execution.
+ def self.wrap
+ return yield if active?
+
+ instance = run!
+ begin
+ yield
+ ensure
+ instance.complete!
+ end
+ end
+
+ class << self # :nodoc:
+ attr_accessor :active
+ end
+
+ def self.inherited(other) # :nodoc:
+ super
+ other.active = Concurrent::Hash.new
+ end
+
+ self.active = Concurrent::Hash.new
+
+ def self.active? # :nodoc:
+ @active[Thread.current]
+ end
+
+ def run! # :nodoc:
+ self.class.active[Thread.current] = true
+ run_callbacks(:run)
+ end
+
+ # Complete this in-flight execution. This method *must* be called
+ # exactly once on the result of any call to +run!+.
+ #
+ # Where possible, prefer +wrap+.
+ def complete!
+ run_callbacks(:complete)
+ ensure
+ self.class.active.delete Thread.current
+ end
+
+ private
+ def hook_state
+ @_hook_state ||= {}
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/executor.rb b/activesupport/lib/active_support/executor.rb
new file mode 100644
index 0000000000..602fb11a44
--- /dev/null
+++ b/activesupport/lib/active_support/executor.rb
@@ -0,0 +1,6 @@
+require 'active_support/execution_wrapper'
+
+module ActiveSupport
+ class Executor < ExecutionWrapper
+ end
+end
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 1fa9335080..b5667b6ac8 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/time/calculations'
+
module ActiveSupport
# FileUpdateChecker specifies the API used by Rails to watch files
# and control reloading. The API depends on four methods:
@@ -23,7 +25,7 @@ module ActiveSupport
# I18n.reload!
# end
#
- # ActionDispatch::Reloader.to_prepare do
+ # ActiveSupport::Reloader.to_prepare do
# i18n_reloader.execute_if_updated
# end
class FileUpdateChecker
@@ -81,6 +83,7 @@ module ActiveSupport
# Execute the block given if updated.
def execute_if_updated
if updated?
+ yield if block_given?
execute
true
else
@@ -111,7 +114,24 @@ module ActiveSupport
# reloading is not triggered.
def max_mtime(paths)
time_now = Time.now
- paths.map {|path| File.mtime(path)}.reject {|mtime| time_now < mtime}.max
+ max_mtime = nil
+
+ # Time comparisons are performed with #compare_without_coercion because
+ # AS redefines these operators in a way that is much slower and does not
+ # bring any benefit in this particular code.
+ #
+ # Read t1.compare_without_coercion(t2) < 0 as t1 < t2.
+ paths.each do |path|
+ mtime = File.mtime(path)
+
+ next if time_now.compare_without_coercion(mtime) < 0
+
+ if max_mtime.nil? || max_mtime.compare_without_coercion(mtime) < 0
+ max_mtime = mtime
+ end
+ end
+
+ max_mtime
end
def compile_glob(hash)
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index 3104d0e00f..74f2d8dd4b 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -6,9 +6,9 @@ module ActiveSupport
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta1.1"
+ 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 b878f31e75..03770a197c 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -69,9 +69,13 @@ module ActiveSupport
end
def default(*args)
- key = args.first
- args[0] = key.to_s if key.is_a?(Symbol)
- super(*args)
+ arg_key = args.first
+
+ if include?(key = convert_key(arg_key))
+ self[key]
+ else
+ super
+ end
end
def self.new_from_hash_copying_default(hash)
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 82aacf3b24..6cc7c90c12 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -64,8 +64,8 @@ module I18n
end
app.reloaders << reloader
- ActionDispatch::Reloader.to_prepare do
- reloader.execute_if_updated
+ app.reloader.to_run do
+ reloader.execute_if_updated { require_unload_lock! }
# TODO: remove the following line as soon as the return value of
# callbacks is ignored, that is, returning `false` does not
# display a deprecation warning or halts the callback chain.
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index f741c0bfb8..f94e12e14f 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -140,6 +140,15 @@ module ActiveSupport
result
end
+ # Converts just the first character to uppercase.
+ #
+ # upcase_first('what a Lovely Day') # => "What a Lovely Day"
+ # upcase_first('w') # => "W"
+ # upcase_first('') # => ""
+ def upcase_first(string)
+ string.length > 0 ? string[0].upcase.concat(string[1..-1]) : ''
+ end
+
# Capitalizes all the words and replaces some characters in the string to
# create a nicer looking title. +titleize+ is meant for creating pretty
# output. It is not used in the Rails internals.
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 7626b28108..92b890dbb0 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -1,8 +1,10 @@
require 'active_support/logger_silence'
+require 'active_support/logger_thread_safe_level'
require 'logger'
module ActiveSupport
class Logger < ::Logger
+ include ActiveSupport::LoggerThreadSafeLevel
include LoggerSilence
# Returns true if the logger destination matches one of the sources
@@ -48,6 +50,29 @@ module ActiveSupport
logger.level = level
super(level)
end
+
+ define_method(:local_level=) do |level|
+ logger.local_level = level if logger.respond_to?(:local_level=)
+ super(level) if respond_to?(:local_level=)
+ end
+
+ define_method(:silence) do |level = Logger::ERROR, &block|
+ if logger.respond_to?(:silence)
+ logger.silence(level) do
+ if respond_to?(:silence)
+ super(level, &block)
+ else
+ block.call(self)
+ end
+ end
+ else
+ if respond_to?(:silence)
+ super(level, &block)
+ else
+ block.call(self)
+ end
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/logger_silence.rb b/activesupport/lib/active_support/logger_silence.rb
index 125d81d973..3eb8098c77 100644
--- a/activesupport/lib/active_support/logger_silence.rb
+++ b/activesupport/lib/active_support/logger_silence.rb
@@ -7,39 +7,22 @@ module LoggerSilence
included do
cattr_accessor :silencer
- attr_reader :local_levels
self.silencer = true
end
- def after_initialize
- @local_levels = Concurrent::Map.new(:initial_capacity => 2)
- end
-
- def local_log_id
- Thread.current.__id__
- end
-
- def level
- local_levels[local_log_id] || super
- end
-
# Silences the logger for the duration of the block.
def silence(temporary_level = Logger::ERROR)
if silencer
begin
- old_local_level = local_levels[local_log_id]
- local_levels[local_log_id] = temporary_level
+ old_local_level = local_level
+ self.local_level = temporary_level
yield self
ensure
- if old_local_level
- local_levels[local_log_id] = old_local_level
- else
- local_levels.delete(local_log_id)
- end
+ self.local_level = old_local_level
end
else
yield self
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb
new file mode 100644
index 0000000000..5fedb5e689
--- /dev/null
+++ b/activesupport/lib/active_support/logger_thread_safe_level.rb
@@ -0,0 +1,31 @@
+require 'active_support/concern'
+
+module ActiveSupport
+ module LoggerThreadSafeLevel # :nodoc:
+ extend ActiveSupport::Concern
+
+ def after_initialize
+ @local_levels = Concurrent::Map.new(initial_capacity: 2)
+ end
+
+ def local_log_id
+ Thread.current.__id__
+ end
+
+ def local_level
+ @local_levels[local_log_id]
+ end
+
+ def local_level=(level)
+ if level
+ @local_levels[local_log_id] = level
+ else
+ @local_levels.delete(local_log_id)
+ end
+ end
+
+ def level
+ local_level || super
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 2dde01c844..721efea789 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -97,7 +97,7 @@ module ActiveSupport
end
def new_cipher
- OpenSSL::Cipher::Cipher.new(@cipher)
+ OpenSSL::Cipher.new(@cipher)
end
def verifier
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 854029bf83..4c3deffe6e 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -24,6 +24,12 @@ module ActiveSupport
# hash upon initialization:
#
# @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML)
+ #
+ # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default.
+ # If you want to use a different hash algorithm, you can change it by providing
+ # `:digest` key as an option while initializing the verifier:
+ #
+ # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256')
class MessageVerifier
class InvalidSignature < StandardError; end
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 64d9e71f37..7a49bbb960 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -15,7 +15,7 @@ module ActiveSupport
extend self
- # Formats a +number+ into a US phone number (e.g., (555)
+ # Formats a +number+ into a phone number (US by default e.g., (555)
# 123-9876). You can customize the format in the +options+ hash.
#
# ==== Options
@@ -27,19 +27,26 @@ module ActiveSupport
# end of the generated number.
# * <tt>:country_code</tt> - Sets the country code for the phone
# number.
+ # * <tt>:pattern</tt> - Specifies how the number is divided into three
+ # groups with the custom regexp to override the default format.
# ==== Examples
#
- # number_to_phone(5551234) # => 555-1234
- # number_to_phone('5551234') # => 555-1234
- # number_to_phone(1235551234) # => 123-555-1234
- # number_to_phone(1235551234, area_code: true) # => (123) 555-1234
- # number_to_phone(1235551234, delimiter: ' ') # => 123 555 1234
- # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555
- # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234
- # number_to_phone('123a456') # => 123a456
+ # number_to_phone(5551234) # => "555-1234"
+ # number_to_phone('5551234') # => "555-1234"
+ # number_to_phone(1235551234) # => "123-555-1234"
+ # number_to_phone(1235551234, area_code: true) # => "(123) 555-1234"
+ # number_to_phone(1235551234, delimiter: ' ') # => "123 555 1234"
+ # number_to_phone(1235551234, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
+ # number_to_phone(1235551234, country_code: 1) # => "+1-123-555-1234"
+ # number_to_phone('123a456') # => "123a456"
#
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.')
- # # => +1.123.555.1234 x 1343
+ # # => "+1.123.555.1234 x 1343"
+ #
+ # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
+ # # => "(755) 6123-4567"
+ # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/))
+ # # => "133-1234-5678"
def number_to_phone(number, options = {})
NumberToPhoneConverter.convert(number, options)
end
@@ -78,18 +85,18 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_currency(1234567890.50) # => $1,234,567,890.50
- # number_to_currency(1234567890.506) # => $1,234,567,890.51
- # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506
- # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 €
- # number_to_currency('123a456') # => $123a456
+ # number_to_currency(1234567890.50) # => "$1,234,567,890.50"
+ # number_to_currency(1234567890.506) # => "$1,234,567,890.51"
+ # number_to_currency(1234567890.506, precision: 3) # => "$1,234,567,890.506"
+ # number_to_currency(1234567890.506, locale: :fr) # => "1 234 567 890,51 €"
+ # number_to_currency('123a456') # => "$123a456"
#
# number_to_currency(-1234567890.50, negative_format: '(%u%n)')
- # # => ($1,234,567,890.50)
+ # # => "($1,234,567,890.50)"
# number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '')
- # # => &pound;1234567890,50
+ # # => "&pound;1234567890,50"
# number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
- # # => 1234567890,50 &pound;
+ # # => "1234567890,50 &pound;"
def number_to_currency(number, options = {})
NumberToCurrencyConverter.convert(number, options)
end
@@ -118,15 +125,15 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_percentage(100) # => 100.000%
- # number_to_percentage('98') # => 98.000%
- # number_to_percentage(100, precision: 0) # => 100%
- # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000%
- # number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
- # number_to_percentage(1000, locale: :fr) # => 1000,000%
- # number_to_percentage(1000, precision: nil) # => 1000%
- # number_to_percentage('98a') # => 98a%
- # number_to_percentage(100, format: '%n %') # => 100.000 %
+ # number_to_percentage(100) # => "100.000%"
+ # number_to_percentage('98') # => "98.000%"
+ # number_to_percentage(100, precision: 0) # => "100%"
+ # number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%"
+ # number_to_percentage(302.24398923423, precision: 5) # => "302.24399%"
+ # number_to_percentage(1000, locale: :fr) # => "1000,000%"
+ # number_to_percentage(1000, precision: nil) # => "1000%"
+ # number_to_percentage('98a') # => "98a%"
+ # number_to_percentage(100, format: '%n %') # => "100.000 %"
def number_to_percentage(number, options = {})
NumberToPercentageConverter.convert(number, options)
end
@@ -149,19 +156,19 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_delimited(12345678) # => 12,345,678
- # number_to_delimited('123456') # => 123,456
- # number_to_delimited(12345678.05) # => 12,345,678.05
- # number_to_delimited(12345678, delimiter: '.') # => 12.345.678
- # number_to_delimited(12345678, delimiter: ',') # => 12,345,678
- # number_to_delimited(12345678.05, separator: ' ') # => 12,345,678 05
- # number_to_delimited(12345678.05, locale: :fr) # => 12 345 678,05
- # number_to_delimited('112a') # => 112a
+ # number_to_delimited(12345678) # => "12,345,678"
+ # number_to_delimited('123456') # => "123,456"
+ # number_to_delimited(12345678.05) # => "12,345,678.05"
+ # number_to_delimited(12345678, delimiter: '.') # => "12.345.678"
+ # number_to_delimited(12345678, delimiter: ',') # => "12,345,678"
+ # number_to_delimited(12345678.05, separator: ' ') # => "12,345,678 05"
+ # number_to_delimited(12345678.05, locale: :fr) # => "12 345 678,05"
+ # number_to_delimited('112a') # => "112a"
# number_to_delimited(98765432.98, delimiter: ' ', separator: ',')
- # # => 98 765 432,98
+ # # => "98 765 432,98"
# number_to_delimited("123456.78",
# delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/)
- # # => 1,23,456.78
+ # # => "1,23,456.78"
def number_to_delimited(number, options = {})
NumberToDelimitedConverter.convert(number, options)
end
@@ -190,22 +197,22 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_rounded(111.2345) # => 111.235
- # number_to_rounded(111.2345, precision: 2) # => 111.23
- # number_to_rounded(13, precision: 5) # => 13.00000
- # number_to_rounded(389.32314, precision: 0) # => 389
- # 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(111.2345) # => "111.235"
+ # number_to_rounded(111.2345, precision: 2) # => "111.23"
+ # number_to_rounded(13, precision: 5) # => "13.00000"
+ # number_to_rounded(389.32314, precision: 0) # => "389"
+ # 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)
- # # => 13
+ # # => "13"
#
- # number_to_rounded(389.32314, precision: 4, significant: true) # => 389.3
+ # number_to_rounded(389.32314, precision: 4, significant: true) # => "389.3"
# number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.')
- # # => 1.111,23
+ # # => "1.111,23"
def number_to_rounded(number, options = {})
NumberToRoundedConverter.convert(number, options)
end
@@ -237,17 +244,17 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_human_size(123) # => 123 Bytes
- # number_to_human_size(1234) # => 1.21 KB
- # number_to_human_size(12345) # => 12.1 KB
- # number_to_human_size(1234567) # => 1.18 MB
- # number_to_human_size(1234567890) # => 1.15 GB
- # number_to_human_size(1234567890123) # => 1.12 TB
- # number_to_human_size(1234567890123456) # => 1.1 PB
- # number_to_human_size(1234567890123456789) # => 1.07 EB
- # number_to_human_size(1234567, precision: 2) # => 1.2 MB
- # number_to_human_size(483989, precision: 2) # => 470 KB
- # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
+ # number_to_human_size(123) # => "123 Bytes"
+ # number_to_human_size(1234) # => "1.21 KB"
+ # number_to_human_size(12345) # => "12.1 KB"
+ # number_to_human_size(1234567) # => "1.18 MB"
+ # number_to_human_size(1234567890) # => "1.15 GB"
+ # number_to_human_size(1234567890123) # => "1.12 TB"
+ # number_to_human_size(1234567890123456) # => "1.1 PB"
+ # number_to_human_size(1234567890123456789) # => "1.07 EB"
+ # number_to_human_size(1234567, precision: 2) # => "1.2 MB"
+ # number_to_human_size(483989, precision: 2) # => "470 KB"
+ # number_to_human_size(1234567, precision: 2, separator: ',') # => "1,2 MB"
# number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
# number_to_human_size(524288000, precision: 5) # => "500 MB"
def number_to_human_size(number, options = {})
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 7986eb50f0..57f40f33bf 100644
--- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/numeric/inquiry'
+
module ActiveSupport
module NumberHelper
class NumberToCurrencyConverter < NumberConverter # :nodoc:
@@ -7,7 +9,7 @@ module ActiveSupport
number = self.number.to_s.strip
format = options[:format]
- if is_negative?(number)
+ if number.to_f.negative?
format = options[:negative_format]
number = absolute_value(number)
end
@@ -18,10 +20,6 @@ module ActiveSupport
private
- def is_negative?(number)
- number.to_f.phase != 0
- end
-
def absolute_value(number)
number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, '')
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
index 45ae8f1a93..43c5540b6f 100644
--- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
@@ -12,7 +12,7 @@ module ActiveSupport
private
def parts
- left, right = number.to_s.split('.')
+ left, right = number.to_s.split('.'.freeze)
left.gsub!(delimiter_pattern) do |digit_to_delimit|
"#{digit_to_delimit}#{options[:delimiter]}"
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
index af2ee56d91..dee74fa7a6 100644
--- a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
@@ -18,12 +18,16 @@ module ActiveSupport
end
def convert_with_area_code(number)
- number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
+ default_pattern = /(\d{1,3})(\d{3})(\d{4}$)/
+ number.gsub!(regexp_pattern(default_pattern),
+ "(\\1) \\2#{delimiter}\\3")
number
end
def convert_without_area_code(number)
- number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
+ default_pattern = /(\d{0,3})(\d{3})(\d{4})$/
+ number.gsub!(regexp_pattern(default_pattern),
+ "\\1#{delimiter}\\2#{delimiter}\\3")
number.slice!(0, 1) if start_with_delimiter?(number)
number
end
@@ -43,6 +47,11 @@ module ActiveSupport
def phone_ext(ext)
ext.blank? ? "" : " x #{ext}"
end
+
+ def regexp_pattern(default_pattern)
+ opts.fetch :pattern, default_pattern
+ end
+
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 981c562551..9fb7dfb779 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
@@ -29,9 +29,11 @@ module ActiveSupport
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]
+ s = rounded_number.to_s('F')
+ s << '0'.freeze * precision
+ a, b = s.split('.'.freeze, 2)
+ a << '.'.freeze
+ a << b[0, precision]
else
"%00.#{precision}f" % rounded_number
end
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index 53a55bd986..501ba7fc76 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/blank'
+
module ActiveSupport
# Usually key value pairs are handled something like this:
#
diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb
new file mode 100644
index 0000000000..5623bdd349
--- /dev/null
+++ b/activesupport/lib/active_support/reloader.rb
@@ -0,0 +1,129 @@
+require 'active_support/execution_wrapper'
+
+module ActiveSupport
+ #--
+ # This class defines several callbacks:
+ #
+ # to_prepare -- Run once at application startup, and also from
+ # +to_run+.
+ #
+ # to_run -- Run before a work run that is reloading. If
+ # +reload_classes_only_on_change+ is true (the default), the class
+ # unload will have already occurred.
+ #
+ # to_complete -- Run after a work run that has reloaded. If
+ # +reload_classes_only_on_change+ is false, the class unload will
+ # have occurred after the work run, but before this callback.
+ #
+ # before_class_unload -- Run immediately before the classes are
+ # unloaded.
+ #
+ # after_class_unload -- Run immediately after the classes are
+ # unloaded.
+ #
+ class Reloader < ExecutionWrapper
+ define_callbacks :prepare
+
+ define_callbacks :class_unload
+
+ def self.to_prepare(*args, &block)
+ set_callback(:prepare, *args, &block)
+ end
+
+ def self.before_class_unload(*args, &block)
+ set_callback(:class_unload, *args, &block)
+ end
+
+ def self.after_class_unload(*args, &block)
+ set_callback(:class_unload, :after, *args, &block)
+ end
+
+ to_run(:after) { self.class.prepare! }
+
+ # Initiate a manual reload
+ def self.reload!
+ executor.wrap do
+ new.tap do |instance|
+ begin
+ instance.run!
+ ensure
+ instance.complete!
+ end
+ end
+ end
+ prepare!
+ end
+
+ def self.run! # :nodoc:
+ if check!
+ super
+ else
+ Null
+ end
+ end
+
+ # Run the supplied block as a work unit, reloading code as needed
+ def self.wrap
+ executor.wrap do
+ super
+ end
+ end
+
+ class_attribute :executor
+ class_attribute :check
+
+ self.executor = Executor
+ self.check = lambda { false }
+
+ def self.check! # :nodoc:
+ @should_reload ||= check.call
+ end
+
+ def self.reloaded! # :nodoc:
+ @should_reload = false
+ end
+
+ def self.prepare! # :nodoc:
+ new.run_callbacks(:prepare)
+ end
+
+ def initialize
+ super
+ @locked = false
+ end
+
+ # Acquire the ActiveSupport::Dependencies::Interlock unload lock,
+ # ensuring it will be released automatically
+ def require_unload_lock!
+ unless @locked
+ ActiveSupport::Dependencies.interlock.start_unloading
+ @locked = true
+ end
+ end
+
+ # Release the unload lock if it has been previously obtained
+ def release_unload_lock!
+ if @locked
+ @locked = false
+ ActiveSupport::Dependencies.interlock.done_unloading
+ end
+ end
+
+ def run! # :nodoc:
+ super
+ release_unload_lock!
+ end
+
+ def class_unload!(&block) # :nodoc:
+ require_unload_lock!
+ run_callbacks(:class_unload, &block)
+ end
+
+ def complete! # :nodoc:
+ super
+ self.class.reloaded!
+ ensure
+ release_unload_lock!
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index fcf5553061..2c05deee41 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -1,7 +1,6 @@
require 'active_support/concern'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/string/inflections'
-require 'active_support/core_ext/array/extract_options'
module ActiveSupport
# Rescuable module adds support for easier exception handling.
@@ -48,14 +47,12 @@ module ActiveSupport
# end
#
# Exceptions raised inside exception handlers are not propagated up.
- def rescue_from(*klasses, &block)
- options = klasses.extract_options!
-
- unless options.has_key?(:with)
+ def rescue_from(*klasses, with: nil, &block)
+ unless with
if block_given?
- options[:with] = block
+ with = block
else
- raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument."
+ raise ArgumentError, 'Need a handler. Pass the with: keyword argument or provide a block.'
end
end
@@ -65,55 +62,104 @@ module ActiveSupport
elsif klass.is_a?(String)
klass
else
- raise ArgumentError, "#{klass} is neither an Exception nor a String"
+ raise ArgumentError, "#{klass.inspect} must be an Exception class or a String referencing an Exception class"
end
# Put the new handler at the end because the list is read in reverse.
- self.rescue_handlers += [[key, options[:with]]]
+ self.rescue_handlers += [[key, with]]
end
end
- end
- # Tries to rescue the exception by looking up and calling a registered handler.
- def rescue_with_handler(exception)
- if handler = handler_for_rescue(exception)
- handler.arity != 0 ? handler.call(exception) : handler.call
- true # don't rely on the return value of the handler
+ # Matches an exception to a handler based on the exception class.
+ #
+ # If no handler matches the exception, check for a handler matching the
+ # (optional) exception.cause. If no handler matches the exception or its
+ # cause, this returns nil so you can deal with unhandled exceptions.
+ # Be sure to re-raise unhandled exceptions if this is what you expect.
+ #
+ # begin
+ # …
+ # rescue => exception
+ # rescue_with_handler(exception) || raise
+ # end
+ #
+ # Returns the exception if it was handled and nil if it was not.
+ def rescue_with_handler(exception, object: self)
+ if handler = handler_for_rescue(exception, object: object)
+ handler.call exception
+ exception
+ end
end
- end
- def handler_for_rescue(exception)
- # We go from right to left because pairs are pushed onto rescue_handlers
- # as rescue_from declarations are found.
- _, rescuer = self.class.rescue_handlers.reverse.detect do |klass_name, handler|
- # The purpose of allowing strings in rescue_from is to support the
- # declaration of handler associations for exception classes whose
- # definition is yet unknown.
- #
- # Since this loop needs the constants it would be inconsistent to
- # assume they should exist at this point. An early raised exception
- # could trigger some other handler and the array could include
- # precisely a string whose corresponding constant has not yet been
- # seen. This is why we are tolerant to unknown constants.
- #
- # Note that this tolerance only matters if the exception was given as
- # 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)
- klass === exception if klass
+ def handler_for_rescue(exception, object: self) #:nodoc:
+ case rescuer = find_rescue_handler(exception)
+ when Symbol
+ method = object.method(rescuer)
+ if method.arity == 0
+ -> e { method.call }
+ else
+ method
+ end
+ when Proc
+ if rescuer.arity == 0
+ -> e { object.instance_exec(&rescuer) }
+ else
+ -> e { object.instance_exec(e, &rescuer) }
+ end
+ end
end
- case rescuer
- when Symbol
- method(rescuer)
- when Proc
- if rescuer.arity == 0
- Proc.new { instance_exec(&rescuer) }
- else
- Proc.new { |_exception| instance_exec(_exception, &rescuer) }
+ private
+ def find_rescue_handler(exception)
+ if exception
+ # Handlers are in order of declaration but the most recently declared
+ # is the highest priority match, so we search for matching handlers
+ # in reverse.
+ _, handler = rescue_handlers.reverse_each.detect do |class_or_name, _|
+ if klass = constantize_rescue_handler_class(class_or_name)
+ klass === exception
+ end
+ end
+
+ handler || find_rescue_handler(exception.cause)
+ end
end
- end
+
+ def constantize_rescue_handler_class(class_or_name)
+ case class_or_name
+ when String, Symbol
+ begin
+ # Try a lexical lookup first since we support
+ #
+ # class Super
+ # rescue_from 'Error', with: …
+ # end
+ #
+ # class Sub
+ # class Error < StandardError; end
+ # end
+ #
+ # so an Error raised in Sub will hit the 'Error' handler.
+ const_get class_or_name
+ rescue NameError
+ class_or_name.safe_constantize
+ end
+ else
+ class_or_name
+ end
+ end
+ end
+
+ # Delegates to the class method, but uses the instance as the subject for
+ # rescue_from handlers (method calls, instance_exec blocks).
+ def rescue_with_handler(exception)
+ self.class.rescue_with_handler exception, object: self
+ end
+
+ # Internal handler lookup. Delegates to class method. Some libraries call
+ # this directly, so keeping it around for compatibility.
+ def handler_for_rescue(exception) #:nodoc:
+ self.class.handler_for_rescue exception, object: self
end
end
end
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index d9a668c0ea..221e1171e7 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -66,12 +66,15 @@ module ActiveSupport
alias :assert_not_respond_to :refute_respond_to
alias :assert_not_same :refute_same
- # Reveals the intention that the block should not raise any exception.
+
+ # Assertion that the block should not raise an exception.
+ #
+ # Passes if evaluated code in the yielded block raises no exception.
#
# assert_nothing_raised do
- # ...
+ # perform_service(param: 'no_exception')
# end
- def assert_nothing_raised(*args)
+ def assert_nothing_raised
yield
end
end
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 29305e0082..ad83638572 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/object/blank'
-
module ActiveSupport
module Testing
module Assertions
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index edf8b30a0a..7dd03ce65d 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -13,17 +13,6 @@ module ActiveSupport
!ENV["NO_FORK"] && Process.respond_to?(:fork)
end
- @@class_setup_mutex = Mutex.new
-
- def _run_class_setup # class setup method should only happen in parent
- @@class_setup_mutex.synchronize do
- unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST']
- self.class.setup if self.class.respond_to?(:setup)
- @@ran_class_setup = true
- end
- end
- end
-
def run
serialized = run_in_isolation do
super
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 79cc748cf5..b1cec43124 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,6 +1,7 @@
require 'active_support/duration'
require 'active_support/values/time_zone'
require 'active_support/core_ext/object/acts_like'
+require 'active_support/core_ext/date_and_time/compatibility'
module ActiveSupport
# A Time-like class that can represent a time in any time zone. Necessary
@@ -44,20 +45,21 @@ module ActiveSupport
PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N".freeze }
PRECISIONS[0] = '%FT%T'.freeze
- include Comparable
+ include Comparable, DateAndTime::Compatibility
attr_reader :time_zone
def initialize(utc_time, time_zone, local_time = nil, period = nil)
- @utc, @time_zone, @time = utc_time, time_zone, local_time
+ @utc = utc_time ? transfer_time_values_to_utc_constructor(utc_time) : nil
+ @time_zone, @time = time_zone, local_time
@period = @utc ? period : get_period_and_ensure_valid_local_time(period)
end
- # Returns a Time or DateTime instance that represents the time in +time_zone+.
+ # Returns a <tt>Time</tt> instance that represents the time in +time_zone+.
def time
@time ||= period.to_local(@utc)
end
- # Returns a Time or DateTime instance that represents the time in UTC.
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone.
def utc
@utc ||= period.to_utc(@time)
end
@@ -77,10 +79,9 @@ module ActiveSupport
utc.in_time_zone(new_zone)
end
- # Returns a <tt>Time.local()</tt> instance of the simultaneous time in your
- # system's <tt>ENV['TZ']</tt> zone.
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the system timezone.
def localtime(utc_offset = nil)
- utc.respond_to?(:getlocal) ? utc.getlocal(utc_offset) : utc.to_time.getlocal(utc_offset)
+ utc.getlocal(utc_offset)
end
alias_method :getlocal, :localtime
@@ -401,11 +402,6 @@ module ActiveSupport
utc.to_r
end
- # Returns an instance of Time in the system timezone.
- def to_time
- utc.to_time
- end
-
# Returns an instance of DateTime with the timezone's UTC offset
#
# Time.zone.now.to_datetime # => Tue, 18 Aug 2015 02:32:20 +0000
@@ -454,7 +450,6 @@ module ActiveSupport
# Ensure proxy class responds to all methods that underlying time instance
# responds to.
def respond_to_missing?(sym, include_priv)
- # consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime
return false if sym.to_sym == :acts_like_date?
time.respond_to?(sym, include_priv)
end
@@ -482,7 +477,7 @@ module ActiveSupport
end
def transfer_time_values_to_utc_constructor(time)
- ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec, Rational(time.nsec, 1000))
+ ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + time.subsec)
end
def duration_of_variable_length?(obj)
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 7ca3592520..19420cee5e 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,7 +1,6 @@
require 'tzinfo'
require 'concurrent/map'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/object/try'
module ActiveSupport
# The TimeZone class serves as a wrapper around TZInfo::Timezone instances.
@@ -185,6 +184,7 @@ module ActiveSupport
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(':', '')
@lazy_zones_map = Concurrent::Map.new
+ @country_zones = Concurrent::Map.new
class << self
# Assumes self represents an offset from UTC in seconds (as returned from
@@ -243,7 +243,18 @@ module ActiveSupport
# A convenience method for returning a collection of TimeZone objects
# for time zones in the USA.
def us_zones
- @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
+ country_zones(:us)
+ end
+
+ # A convenience method for returning a collection of TimeZone objects
+ # for time zones in the country specified by its ISO 3166-1 Alpha2 code.
+ def country_zones(country_code)
+ code = country_code.to_s.upcase
+ @country_zones[code] ||=
+ TZInfo::Country.get(code).zone_identifiers.map do |tz_id|
+ name = MAPPING.key(tz_id)
+ name && self[name]
+ end.compact.sort!
end
private
@@ -267,7 +278,6 @@ module ActiveSupport
@name = name
@utc_offset = utc_offset
@tzinfo = tzinfo || TimeZone.find_tzinfo(name)
- @current_period = nil
end
# Returns the offset of this time zone from UTC in seconds.
@@ -275,8 +285,7 @@ module ActiveSupport
if @utc_offset
@utc_offset
else
- @current_period ||= tzinfo.current_period if tzinfo
- @current_period.utc_offset if @current_period
+ tzinfo.current_period.utc_offset if tzinfo && tzinfo.current_period
end
end
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index df7b081993..99fc26549e 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -32,20 +32,25 @@ module ActiveSupport
"binary" => "base64"
} unless defined?(DEFAULT_ENCODINGS)
- TYPE_NAMES = {
- "Symbol" => "symbol",
- "Fixnum" => "integer",
- "Bignum" => "integer",
- "BigDecimal" => "decimal",
- "Float" => "float",
- "TrueClass" => "boolean",
- "FalseClass" => "boolean",
- "Date" => "date",
- "DateTime" => "dateTime",
- "Time" => "dateTime",
- "Array" => "array",
- "Hash" => "hash"
- } unless defined?(TYPE_NAMES)
+ unless defined?(TYPE_NAMES)
+ TYPE_NAMES = {
+ "Symbol" => "symbol",
+ "Integer" => "integer",
+ "BigDecimal" => "decimal",
+ "Float" => "float",
+ "TrueClass" => "boolean",
+ "FalseClass" => "boolean",
+ "Date" => "date",
+ "DateTime" => "dateTime",
+ "Time" => "dateTime",
+ "Array" => "array",
+ "Hash" => "hash"
+ }
+
+ # No need to map these on Ruby 2.4+
+ TYPE_NAMES["Fixnum"] = "integer" unless Fixnum == Integer
+ TYPE_NAMES["Bignum"] = "integer" unless Bignum == Integer
+ end
FORMATTING = {
"symbol" => Proc.new { |symbol| symbol.to_s },
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index 924ed72345..95af5af2c0 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -20,11 +20,9 @@ module ActiveSupport
data = StringIO.new(data || '')
end
- char = data.getc
- if char.nil?
+ if data.eof?
{}
else
- data.ungetc(char)
silence_warnings { require 'rexml/document' } unless defined?(REXML::Document)
doc = REXML::Document.new(data)
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index c0e23e89f7..bab7440397 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -1,12 +1,5 @@
ORIG_ARGV = ARGV.dup
-begin
- old, $VERBOSE = $VERBOSE, nil
- require File.expand_path('../../../load_paths', __FILE__)
-ensure
- $VERBOSE = old
-end
-
require 'active_support/core_ext/kernel/reporting'
silence_warnings do
@@ -25,6 +18,9 @@ Thread.abort_on_exception = true
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
+# Default to old to_time behavior but allow running tests with new behavior
+ActiveSupport.to_time_preserves_timezone = ENV['PRESERVE_TIMEZONES'] == '1'
+
# Disable available locale checks to avoid warnings running the test suite.
I18n.enforce_available_locales = false
diff --git a/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb
new file mode 100644
index 0000000000..3ca4213c71
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb
@@ -0,0 +1,4 @@
+RaisesArbitraryException = 1
+_ = A::B # Autoloading recursion, also expected to be watched and discarded.
+
+raise Exception, 'arbitray exception message'
diff --git a/activesupport/test/autoloading_fixtures/throws.rb b/activesupport/test/autoloading_fixtures/throws.rb
new file mode 100644
index 0000000000..e1d96cc512
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/throws.rb
@@ -0,0 +1,4 @@
+Throws = 1
+_ = A::B # Autoloading recursion, expected to be discarded.
+
+throw :t
diff --git a/activesupport/test/benchmarkable_test.rb b/activesupport/test/benchmarkable_test.rb
index 04d4f5e503..5af041f458 100644
--- a/activesupport/test/benchmarkable_test.rb
+++ b/activesupport/test/benchmarkable_test.rb
@@ -41,6 +41,20 @@ class BenchmarkableTest < ActiveSupport::TestCase
assert_last_logged 'test_run'
end
+ def test_with_silence
+ assert_difference 'buffer.count', +2 do
+ benchmark('test_run') do
+ logger.info "SOMETHING"
+ end
+ end
+
+ assert_difference 'buffer.count', +1 do
+ benchmark('test_run', silence: true) do
+ logger.info "NOTHING"
+ end
+ end
+ end
+
def test_within_level
logger.level = ActiveSupport::Logger::DEBUG
benchmark('included_debug_run', :level => :debug) { }
diff --git a/activesupport/test/broadcast_logger_test.rb b/activesupport/test/broadcast_logger_test.rb
index 6d4e3b74f7..f95b3e5ad3 100644
--- a/activesupport/test/broadcast_logger_test.rb
+++ b/activesupport/test/broadcast_logger_test.rb
@@ -3,75 +3,147 @@ require 'abstract_unit'
module ActiveSupport
class BroadcastLoggerTest < TestCase
attr_reader :logger, :log1, :log2
- def setup
+
+ setup do
@log1 = FakeLogger.new
@log2 = FakeLogger.new
@log1.extend Logger.broadcast @log2
@logger = @log1
end
- def test_debug
- logger.debug "foo"
- assert_equal 'foo', log1.adds.first[2]
- assert_equal 'foo', log2.adds.first[2]
+ Logger::Severity.constants.each do |level_name|
+ method = level_name.downcase
+ level = Logger::Severity.const_get(level_name)
+
+ test "##{method} adds the message to all loggers" do
+ logger.send(method, "msg")
+
+ assert_equal [level, "msg", nil], log1.adds.first
+ assert_equal [level, "msg", nil], log2.adds.first
+ end
end
- def test_close
+ test "#close broadcasts to all loggers" do
logger.close
+
assert log1.closed, 'should be closed'
assert log2.closed, 'should be closed'
end
- def test_chevrons
+ test "#<< shovels the value into all loggers" do
logger << "foo"
+
assert_equal %w{ foo }, log1.chevrons
assert_equal %w{ foo }, log2.chevrons
end
- def test_level
- assert_nil logger.level
- logger.level = 10
- assert_equal 10, log1.level
- assert_equal 10, log2.level
+ test "#level= assigns the level to all loggers" do
+ assert_equal ::Logger::DEBUG, logger.level
+ logger.level = ::Logger::FATAL
+
+ assert_equal ::Logger::FATAL, log1.level
+ assert_equal ::Logger::FATAL, log2.level
end
- def test_progname
+ test "#progname= assigns to all the loggers" do
assert_nil logger.progname
- logger.progname = 10
- assert_equal 10, log1.progname
- assert_equal 10, log2.progname
+ logger.progname = ::Logger::FATAL
+
+ assert_equal ::Logger::FATAL, log1.progname
+ assert_equal ::Logger::FATAL, log2.progname
end
- def test_formatter
+ test "#formatter= assigns to all the loggers" do
assert_nil logger.formatter
- logger.formatter = 10
- assert_equal 10, log1.formatter
- assert_equal 10, log2.formatter
+ logger.formatter = ::Logger::FATAL
+
+ assert_equal ::Logger::FATAL, log1.formatter
+ assert_equal ::Logger::FATAL, log2.formatter
+ end
+
+ test "#local_level= assigns the local_level to all loggers" do
+ assert_equal ::Logger::DEBUG, logger.local_level
+ logger.local_level = ::Logger::FATAL
+
+ assert_equal ::Logger::FATAL, log1.local_level
+ assert_equal ::Logger::FATAL, log2.local_level
+ end
+
+ test "#silence silences all loggers below the default level of ERROR" do
+ logger.silence do
+ logger.debug "test"
+ end
+
+ assert_equal [], log1.adds
+ assert_equal [], log2.adds
+ end
+
+ test "#silence does not silence at or above ERROR" do
+ logger.silence do
+ logger.error "from error"
+ logger.unknown "from unknown"
+ end
+
+ assert_equal [[::Logger::ERROR, "from error", nil], [::Logger::UNKNOWN, "from unknown", nil]], log1.adds
+ assert_equal [[::Logger::ERROR, "from error", nil], [::Logger::UNKNOWN, "from unknown", nil]], log2.adds
+ end
+
+ test "#silence allows you to override the silence level" do
+ logger.silence(::Logger::FATAL) do
+ logger.error "unseen"
+ logger.fatal "seen"
+ end
+
+ assert_equal [[::Logger::FATAL, "seen", nil]], log1.adds
+ assert_equal [[::Logger::FATAL, "seen", nil]], log2.adds
end
class FakeLogger
+ include LoggerSilence
+
attr_reader :adds, :closed, :chevrons
- attr_accessor :level, :progname, :formatter
+ attr_accessor :level, :progname, :formatter, :local_level
def initialize
- @adds = []
- @closed = false
- @chevrons = []
- @level = nil
- @progname = nil
- @formatter = nil
+ @adds = []
+ @closed = false
+ @chevrons = []
+ @level = ::Logger::DEBUG
+ @local_level = ::Logger::DEBUG
+ @progname = nil
+ @formatter = nil
+ end
+
+ def debug(message, &block)
+ add(::Logger::DEBUG, message, &block)
+ end
+
+ def info(message, &block)
+ add(::Logger::INFO, message, &block)
+ end
+
+ def warn(message, &block)
+ add(::Logger::WARN, message, &block)
+ end
+
+ def error(message, &block)
+ add(::Logger::ERROR, message, &block)
+ end
+
+ def fatal(message, &block)
+ add(::Logger::FATAL, message, &block)
end
- def debug msg, &block
- add(:omg, nil, msg, &block)
+ def unknown(message, &block)
+ add(::Logger::UNKNOWN, message, &block)
end
def << x
@chevrons << x
end
- def add(*args)
- @adds << args
+ def add(message_level, message=nil, progname=nil, &block)
+ @adds << [message_level, message, progname] if message_level >= local_level
end
def close
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 4a299429f3..ec7d028d7e 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -266,6 +266,20 @@ module CacheStoreBehavior
end
end
+ def test_fetch_with_forced_cache_miss_with_block
+ @cache.write('foo', 'bar')
+ assert_equal 'foo_bar', @cache.fetch('foo', force: true) { 'foo_bar' }
+ end
+
+ def test_fetch_with_forced_cache_miss_without_block
+ @cache.write('foo', 'bar')
+ assert_raises(ArgumentError) do
+ @cache.fetch('foo', force: true)
+ end
+
+ assert_equal 'bar', @cache.read('foo')
+ end
+
def test_should_read_and_write_hash
assert @cache.write('foo', {:a => "b"})
assert_equal({:a => "b"}, @cache.read('foo'))
@@ -836,7 +850,7 @@ class FileStoreTest < ActiveSupport::TestCase
# If nothing has been stored in the cache, there is a chance the cache directory does not yet exist
# Ensure delete_matched gracefully handles this case
def test_delete_matched_when_cache_directory_does_not_exist
- assert_nothing_raised(Exception) do
+ assert_nothing_raised do
ActiveSupport::Cache::FileStore.new('/test/cache/directory').delete_matched(/does_not_exist/)
end
end
@@ -844,7 +858,7 @@ class FileStoreTest < ActiveSupport::TestCase
def test_delete_does_not_delete_empty_parent_dir
sub_cache_dir = File.join(cache_dir, 'subdir/')
sub_cache_store = ActiveSupport::Cache::FileStore.new(sub_cache_dir)
- assert_nothing_raised(Exception) do
+ assert_nothing_raised do
assert sub_cache_store.write('foo', 'bar')
assert sub_cache_store.delete('foo')
end
@@ -1151,15 +1165,6 @@ 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
diff --git a/activesupport/test/core_ext/array/access_test.rb b/activesupport/test/core_ext/array/access_test.rb
index 3f1e0c4cb4..1d834667f0 100644
--- a/activesupport/test/core_ext/array/access_test.rb
+++ b/activesupport/test/core_ext/array/access_test.rb
@@ -26,6 +26,8 @@ class AccessTest < ActiveSupport::TestCase
assert_equal array[3], array.fourth
assert_equal array[4], array.fifth
assert_equal array[41], array.forty_two
+ assert_equal array[-3], array.third_to_last
+ assert_equal array[-2], array.second_to_last
end
def test_without
diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb
index 507e13f968..de36e2026d 100644
--- a/activesupport/test/core_ext/array/conversions_test.rb
+++ b/activesupport/test/core_ext/array/conversions_test.rb
@@ -101,10 +101,10 @@ class ToXmlTest < ActiveSupport::TestCase
end
def test_to_xml_with_non_hash_elements
- xml = [1, 2, 3].to_xml(skip_instruct: true, indent: 0)
+ xml = %w[1 2 3].to_xml(skip_instruct: true, indent: 0)
- assert_equal '<fixnums type="array"><fixnum', xml.first(29)
- assert xml.include?(%(<fixnum type="integer">2</fixnum>)), xml
+ assert_equal '<strings type="array"><string', xml.first(29)
+ assert xml.include?(%(<string>2</string>)), xml
end
def test_to_xml_with_non_hash_different_type_elements
diff --git a/activesupport/test/core_ext/array/grouping_test.rb b/activesupport/test/core_ext/array/grouping_test.rb
index 2eb0f05141..0682241f0b 100644
--- a/activesupport/test/core_ext/array/grouping_test.rb
+++ b/activesupport/test/core_ext/array/grouping_test.rb
@@ -3,11 +3,12 @@ require 'active_support/core_ext/array'
class GroupingTest < ActiveSupport::TestCase
def setup
- Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn)
+ # In Ruby < 2.4, test we avoid Integer#/ (redefined by mathn)
+ Fixnum.send :private, :/ unless Fixnum == Integer
end
def teardown
- Fixnum.send :public, :/
+ Fixnum.send :public, :/ unless Fixnum == Integer
end
def test_in_groups_of_with_perfect_fit
@@ -123,4 +124,12 @@ class SplitTest < ActiveSupport::TestCase
assert_equal [[], [2, 3, 4], []], a.split { |i| i == 1 || i == 5 }
assert_equal [1, 2, 3, 4, 5], a
end
+
+ def test_split_with_repeated_values
+ a = [1, 2, 3, 5, 5, 3, 4, 6, 2, 1, 3]
+ assert_equal [[1, 2], [5, 5], [4, 6, 2, 1], []], a.split(3)
+ assert_equal [[1, 2, 3], [], [3, 4, 6, 2, 1, 3]], a.split(5)
+ assert_equal [[1, 2], [], [], [], [4, 6, 2, 1], []], a.split { |i| i == 3 || i == 5 }
+ assert_equal [1, 2, 3, 5, 5, 3, 4, 6, 2, 1, 3], a
+ end
end
diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb
index 423a3f2e9d..6e82e3892b 100644
--- a/activesupport/test/core_ext/bigdecimal_test.rb
+++ b/activesupport/test/core_ext/bigdecimal_test.rb
@@ -5,5 +5,7 @@ class BigDecimalTest < ActiveSupport::TestCase
def test_to_s
bd = BigDecimal.new '0.01'
assert_equal '0.01', bd.to_s
+ assert_equal '+0.01', bd.to_s('+F')
+ assert_equal '+0.0 1', bd.to_s('+1F')
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 784547bdf8..54df87def8 100644
--- a/activesupport/test/core_ext/date_and_time_behavior.rb
+++ b/activesupport/test/core_ext/date_and_time_behavior.rb
@@ -301,6 +301,16 @@ module DateAndTimeBehavior
assert_not date_time_init(2015,1,5,15,15,10).on_weekend?
end
+ def test_on_weekday_on_sunday
+ assert_not date_time_init(2015,1,4,0,0,0).on_weekday?
+ assert_not date_time_init(2015,1,4,15,15,10).on_weekday?
+ end
+
+ def test_on_weekday_on_monday
+ assert date_time_init(2015,1,5,0,0,0).on_weekday?
+ assert date_time_init(2015,1,5,15,15,10).on_weekday?
+ 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/date_and_time_compatibility_test.rb b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
new file mode 100644
index 0000000000..11cb1469da
--- /dev/null
+++ b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
@@ -0,0 +1,127 @@
+require 'abstract_unit'
+require 'active_support/time'
+require 'time_zone_test_helpers'
+
+class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
+ include TimeZoneTestHelpers
+
+ def setup
+ @utc_time = Time.utc(2016, 4, 23, 14, 11, 12)
+ @date_time = DateTime.new(2016, 4, 23, 14, 11, 12, 0)
+ @utc_offset = 3600
+ @system_offset = -14400
+ @zone = ActiveSupport::TimeZone['London']
+ end
+
+ def test_time_to_time_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz 'US/Eastern' do
+ time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_time_to_time_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz 'US/Eastern' do
+ time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_datetime_to_time_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz 'US/Eastern' do
+ time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1,24)).to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_datetime_to_time_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz 'US/Eastern' do
+ time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1,24)).to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_twz_to_time_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz 'US/Eastern' do
+ time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+
+ time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time
+
+ assert_instance_of Time, time
+ assert_equal @date_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_twz_to_time_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz 'US/Eastern' do
+ time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+
+ time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time
+
+ assert_instance_of Time, time
+ assert_equal @date_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_string_to_time_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz 'US/Eastern' do
+ time = "2016-04-23T15:11:12+01:00".to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_string_to_time_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz 'US/Eastern' do
+ time = "2016-04-23T15:11:12+01:00".to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index 0fc3f765f5..a7219eee31 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -30,6 +30,17 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
assert_equal "2005-02-21", date.to_s(:iso8601)
end
+ def test_to_s_with_single_digit_day
+ date = Date.new(2005, 2, 1)
+ assert_equal "2005-02-01", date.to_s
+ assert_equal "01 Feb", date.to_s(:short)
+ assert_equal "February 01, 2005", date.to_s(:long)
+ assert_equal "February 1st, 2005", date.to_s(:long_ordinal)
+ assert_equal "2005-02-01", date.to_s(:db)
+ assert_equal "01 Feb 2005", date.to_s(:rfc822)
+ assert_equal "2005-02-01", date.to_s(:iso8601)
+ end
+
def test_readable_inspect
assert_equal "Mon, 21 Feb 2005", Date.new(2005, 2, 21).readable_inspect
assert_equal Date.new(2005, 2, 21).readable_inspect, Date.new(2005, 2, 21).inspect
@@ -49,6 +60,10 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
end
end
+
+ assert_raise(ArgumentError) do
+ Date.new(2005, 2, 21).to_time(:tokyo)
+ end
end
def test_compare_to_time
@@ -280,6 +295,23 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_all_day
+ beginning_of_day = Time.local(2011,6,7,0,0,0)
+ end_of_day = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000))
+ assert_equal beginning_of_day..end_of_day, Date.new(2011,6,7).all_day
+ end
+
+ def test_all_day_when_zone_is_set
+ zone = ActiveSupport::TimeZone["Hawaii"]
+ with_env_tz "UTC" do
+ with_tz_default zone do
+ beginning_of_day = zone.local(2011,6,7,0,0,0)
+ end_of_day = zone.local(2011,6,7,23,59,59,Rational(999999999, 1000))
+ assert_equal beginning_of_day..end_of_day, Date.new(2011,6,7).all_day
+ end
+ end
+ end
+
def test_all_week
assert_equal Date.new(2011,6,6)..Date.new(2011,6,12), Date.new(2011,6,7).all_week
assert_equal Date.new(2011,6,5)..Date.new(2011,6,11), Date.new(2011,6,7).all_week(:sunday)
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 6fe38c45ec..306316efcd 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -40,6 +40,24 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
Time::DATE_FORMATS.delete(:custom)
end
+ def test_localtime
+ with_env_tz 'US/Eastern' do
+ assert_instance_of Time, DateTime.new(2016, 3, 11, 15, 11, 12, 0).localtime
+ assert_equal Time.local(2016, 3, 11, 10, 11, 12), DateTime.new(2016, 3, 11, 15, 11, 12, 0).localtime
+ assert_equal Time.local(2016, 3, 21, 11, 11, 12), DateTime.new(2016, 3, 21, 15, 11, 12, 0).localtime
+ assert_equal Time.local(2016, 4, 1, 11, 11, 12), DateTime.new(2016, 4, 1, 16, 11, 12, Rational(1,24)).localtime
+ end
+ end
+
+ def test_getlocal
+ with_env_tz 'US/Eastern' do
+ assert_instance_of Time, DateTime.new(2016, 3, 11, 15, 11, 12, 0).getlocal
+ assert_equal Time.local(2016, 3, 11, 10, 11, 12), DateTime.new(2016, 3, 11, 15, 11, 12, 0).getlocal
+ assert_equal Time.local(2016, 3, 21, 11, 11, 12), DateTime.new(2016, 3, 21, 15, 11, 12, 0).getlocal
+ assert_equal Time.local(2016, 4, 1, 11, 11, 12), DateTime.new(2016, 4, 1, 16, 11, 12, Rational(1,24)).getlocal
+ end
+ end
+
def test_to_date
assert_equal Date.new(2005, 2, 21), DateTime.new(2005, 2, 21, 14, 30, 0).to_date
end
@@ -50,9 +68,15 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
def test_to_time
with_env_tz 'US/Eastern' do
- assert_equal Time, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.class
- assert_equal Time.local(2005, 2, 21, 5, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
- assert_equal Time.local(2005, 2, 21, 5, 11, 12).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset
+ assert_instance_of Time, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
+
+ if ActiveSupport.to_time_preserves_timezone
+ assert_equal Time.local(2005, 2, 21, 5, 11, 12).getlocal(0), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
+ assert_equal Time.local(2005, 2, 21, 5, 11, 12).getlocal(0).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset
+ else
+ assert_equal Time.local(2005, 2, 21, 5, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
+ assert_equal Time.local(2005, 2, 21, 5, 11, 12).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset
+ end
end
end
@@ -186,6 +210,10 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2006,11,15), DateTime.civil(2006,11,23,0,0,0).last_week(:wednesday)
end
+ def test_date_time_should_have_correct_last_week_for_leap_year
+ assert_equal DateTime.civil(2016, 2, 29), DateTime.civil(2016, 3, 7).last_week
+ end
+
def test_last_month_on_31st
assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).last_month
end
@@ -306,6 +334,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_utc
+ assert_instance_of Time, DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc
assert_equal DateTime.civil(2005, 2, 21, 16, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc
assert_equal DateTime.civil(2005, 2, 21, 15, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).utc
assert_equal DateTime.civil(2005, 2, 21, 10, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc
@@ -350,6 +379,24 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal nil, DateTime.civil(2000) <=> "Invalid as Time"
end
+ def test_compare_with_integer
+ assert_equal 1, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440587
+ assert_equal 0, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440588
+ assert_equal(-1, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440589)
+ end
+
+ def test_compare_with_float
+ assert_equal 1, DateTime.civil(1970) <=> 2440586.5
+ assert_equal 0, DateTime.civil(1970) <=> 2440587.5
+ assert_equal(-1, DateTime.civil(1970) <=> 2440588.5)
+ end
+
+ def test_compare_with_rational
+ assert_equal 1, DateTime.civil(1970) <=> Rational(4881173, 2)
+ assert_equal 0, DateTime.civil(1970) <=> Rational(4881175, 2)
+ assert_equal(-1, DateTime.civil(1970) <=> Rational(4881177, 2))
+ end
+
def test_to_f
assert_equal 946684800.0, DateTime.civil(2000).to_f
assert_equal 946684800.0, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_f
@@ -370,4 +417,9 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal 0, DateTime.civil(2000).nsec
assert_equal 500000000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).nsec
end
+
+ def test_subsec
+ assert_equal 0, DateTime.civil(2000).subsec
+ assert_equal Rational(1,2), DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).subsec
+ end
end
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 9e97acaffb..502e2811fa 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -12,7 +12,7 @@ class DurationTest < ActiveSupport::TestCase
assert d.is_a?(ActiveSupport::Duration)
assert_kind_of ActiveSupport::Duration, d
assert_kind_of Numeric, d
- assert_kind_of Fixnum, d
+ assert_kind_of Integer, d
assert !d.is_a?(Hash)
k = Class.new
@@ -66,8 +66,9 @@ class DurationTest < ActiveSupport::TestCase
assert_equal '10 years, 2 months, and 1 day', (10.years + 2.months + 1.day).inspect
assert_equal '10 years, 2 months, and 1 day', (10.years + 1.month + 1.day + 1.month).inspect
assert_equal '10 years, 2 months, and 1 day', (1.day + 10.years + 2.months).inspect
- assert_equal '7 days', 1.week.inspect
- assert_equal '14 days', 1.fortnight.inspect
+ assert_equal '7 days', 7.days.inspect
+ assert_equal '1 week', 1.week.inspect
+ assert_equal '2 weeks', 1.fortnight.inspect
end
def test_inspect_locale
@@ -87,6 +88,15 @@ class DurationTest < ActiveSupport::TestCase
assert_equal 1 + 1.second, 1.second + 1, "Duration + Numeric should == Numeric + Duration"
end
+ def test_time_plus_duration_returns_same_time_datatype
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Moscow'] , Time.utc(2016,4,28,00,45))
+ now = Time.now.utc
+ %w( second minute hour day week month year ).each do |unit|
+ assert_equal((now + 1.send(unit)).class, Time, "Time + 1.#{unit} must be Time")
+ assert_equal((twz + 1.send(unit)).class, ActiveSupport::TimeWithZone, "TimeWithZone + 1.#{unit} must be TimeWithZone")
+ end
+ end
+
def test_argument_error
e = assert_raise ArgumentError do
1.second.ago('')
@@ -222,4 +232,89 @@ class DurationTest < ActiveSupport::TestCase
assert_equal(1, (1.minute <=> 1.second))
assert_equal(1, (61 <=> 1.minute))
end
+
+ # ISO8601 string examples are taken from ISO8601 gem at https://github.com/arnau/ISO8601/blob/b93d466840/spec/iso8601/duration_spec.rb
+ # published under the conditions of MIT license at https://github.com/arnau/ISO8601/blob/b93d466840/LICENSE
+ #
+ # Copyright (c) 2012-2014 Arnau Siches
+ #
+ # MIT License
+ #
+ # Permission is hereby granted, free of charge, to any person obtaining
+ # a copy of this software and associated documentation files (the
+ # "Software"), to deal in the Software without restriction, including
+ # without limitation the rights to use, copy, modify, merge, publish,
+ # distribute, sublicense, and/or sell copies of the Software, and to
+ # permit persons to whom the Software is furnished to do so, subject to
+ # the following conditions:
+ #
+ # The above copyright notice and this permission notice shall be
+ # included in all copies or substantial portions of the Software.
+ #
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ def test_iso8601_parsing_wrong_patterns_with_raise
+ invalid_patterns = ['', 'P', 'PT', 'P1YT', 'T', 'PW', 'P1Y1W', '~P1Y', '.P1Y', 'P1.5Y0.5M', 'P1.5Y1M', 'P1.5MT10.5S']
+ invalid_patterns.each do |pattern|
+ assert_raise ActiveSupport::Duration::ISO8601Parser::ParsingError, pattern.inspect do
+ ActiveSupport::Duration.parse(pattern)
+ end
+ end
+ end
+
+ def test_iso8601_output
+ expectations = [
+ ['P1Y', 1.year ],
+ ['P1W', 1.week ],
+ ['P1Y1M', 1.year + 1.month ],
+ ['P1Y1M1D', 1.year + 1.month + 1.day ],
+ ['-P1Y1D', -1.year - 1.day ],
+ ['P1Y-1DT-1S', 1.year - 1.day - 1.second ], # Parts with different signs are exists in PostgreSQL interval datatype.
+ ['PT1S', 1.second ],
+ ['PT1.4S', (1.4).seconds ],
+ ['P1Y1M1DT1H', 1.year + 1.month + 1.day + 1.hour],
+ ]
+ expectations.each do |expected_output, duration|
+ assert_equal expected_output, duration.iso8601, expected_output.inspect
+ end
+ end
+
+ def test_iso8601_output_precision
+ expectations = [
+ [nil, 'P1Y1MT5.55S', 1.year + 1.month + (5.55).seconds ],
+ [0, 'P1Y1MT6S', 1.year + 1.month + (5.55).seconds ],
+ [1, 'P1Y1MT5.5S', 1.year + 1.month + (5.55).seconds ],
+ [2, 'P1Y1MT5.55S', 1.year + 1.month + (5.55).seconds ],
+ [3, 'P1Y1MT5.550S', 1.year + 1.month + (5.55).seconds ],
+ [nil, 'PT1S', 1.second ],
+ [2, 'PT1.00S', 1.second ],
+ [nil, 'PT1.4S', (1.4).seconds ],
+ [0, 'PT1S', (1.4).seconds ],
+ [1, 'PT1.4S', (1.4).seconds ],
+ [5, 'PT1.40000S', (1.4).seconds ],
+ ]
+ expectations.each do |precision, expected_output, duration|
+ assert_equal expected_output, duration.iso8601(precision: precision), expected_output.inspect
+ end
+ end
+
+ def test_iso8601_output_and_reparsing
+ patterns = %w[
+ P1Y P0.5Y P0,5Y P1Y1M P1Y0.5M P1Y0,5M P1Y1M1D P1Y1M0.5D P1Y1M0,5D P1Y1M1DT1H P1Y1M1DT0.5H P1Y1M1DT0,5H P1W +P1Y -P1Y
+ P1Y1M1DT1H1M P1Y1M1DT1H0.5M P1Y1M1DT1H0,5M P1Y1M1DT1H1M1S P1Y1M1DT1H1M1.0S P1Y1M1DT1H1M1,0S P-1Y-2M3DT-4H-5M-6S
+ ]
+ # That could be weird, but if we parse P1Y1M0.5D and output it to ISO 8601, we'll get P1Y1MT12.0H.
+ # So we check that initially parsed and reparsed duration added to time will result in the same time.
+ time = Time.current
+ patterns.each do |pattern|
+ duration = ActiveSupport::Duration.parse(pattern)
+ assert_equal time+duration, time+ActiveSupport::Duration.parse(duration.iso8601), pattern.inspect
+ end
+ end
end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index f09b7d8850..99c3236c35 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -10,22 +10,27 @@ class SummablePayment < Payment
end
class EnumerableTests < ActiveSupport::TestCase
-
class GenericEnumerable
include Enumerable
+
def initialize(values = [1, 2, 3])
@values = values
end
def each
- @values.each{|v| yield v}
+ @values.each { |v| yield v }
end
end
+ def assert_typed_equal(e, v, cls, msg=nil)
+ assert_kind_of(cls, v, msg)
+ assert_equal(e, v, msg)
+ end
+
def test_sums
enum = GenericEnumerable.new([5, 15, 10])
assert_equal 30, enum.sum
- assert_equal 60, enum.sum { |i| i * 2}
+ assert_equal 60, enum.sum { |i| i * 2 }
enum = GenericEnumerable.new(%w(a b c))
assert_equal 'abc', enum.sum
@@ -38,6 +43,40 @@ class EnumerableTests < ActiveSupport::TestCase
payments = GenericEnumerable.new([ SummablePayment.new(5), SummablePayment.new(15) ])
assert_equal SummablePayment.new(20), payments.sum
assert_equal SummablePayment.new(20), payments.sum { |p| p }
+
+ sum = GenericEnumerable.new([3, 5.quo(1)]).sum
+ assert_typed_equal(8, sum, Rational)
+
+ sum = GenericEnumerable.new([3, 5.quo(1)]).sum(0.0)
+ assert_typed_equal(8.0, sum, Float)
+
+ sum = GenericEnumerable.new([3, 5.quo(1), 7.0]).sum
+ assert_typed_equal(15.0, sum, Float)
+
+ sum = GenericEnumerable.new([3, 5.quo(1), Complex(7)]).sum
+ assert_typed_equal(Complex(15), sum, Complex)
+ assert_typed_equal(15, sum.real, Rational)
+ assert_typed_equal(0, sum.imag, Integer)
+
+ sum = GenericEnumerable.new([3.5, 5]).sum
+ assert_typed_equal(8.5, sum, Float)
+
+ sum = GenericEnumerable.new([2, 8.5]).sum
+ assert_typed_equal(10.5, sum, Float)
+
+ sum = GenericEnumerable.new([1.quo(2), 1]).sum
+ assert_typed_equal(3.quo(2), sum, Rational)
+
+ sum = GenericEnumerable.new([1.quo(2), 1.quo(3)]).sum
+ assert_typed_equal(5.quo(6), sum, Rational)
+
+ sum = GenericEnumerable.new([2.0, 3.0*Complex::I]).sum
+ assert_typed_equal(Complex(2.0, 3.0), sum, Complex)
+ assert_typed_equal(2.0, sum.real, Float)
+ assert_typed_equal(3.0, sum.imag, Float)
+
+ sum = GenericEnumerable.new([1, 2]).sum(10) {|v| v * 2 }
+ assert_typed_equal(16, sum, Integer)
end
def test_nil_sums
@@ -55,6 +94,7 @@ class EnumerableTests < ActiveSupport::TestCase
assert_equal 0, GenericEnumerable.new([]).sum
assert_equal 0, GenericEnumerable.new([]).sum { |i| i + 10 }
assert_equal Payment.new(0), GenericEnumerable.new([]).sum(Payment.new(0))
+ assert_typed_equal 0.0, GenericEnumerable.new([]).sum(0.0), Float
end
def test_range_sums
@@ -68,6 +108,62 @@ class EnumerableTests < ActiveSupport::TestCase
assert_equal 5, (10..0).sum(5)
assert_equal 10, (10..10).sum
assert_equal 42, (10...10).sum(42)
+ assert_typed_equal 20.0, (1..4).sum(0.0) { |i| i * 2 }, Float
+ assert_typed_equal 10.0, (1..4).sum(0.0), Float
+ assert_typed_equal 20.0, (1..4).sum(10.0), Float
+ assert_typed_equal 5.0, (10..0).sum(5.0), Float
+ end
+
+ def test_array_sums
+ enum = [5, 15, 10]
+ assert_equal 30, enum.sum
+ assert_equal 60, enum.sum { |i| i * 2 }
+
+ enum = %w(a b c)
+ assert_equal 'abc', enum.sum
+ assert_equal 'aabbcc', enum.sum { |i| i * 2 }
+
+ payments = [ Payment.new(5), Payment.new(15), Payment.new(10) ]
+ assert_equal 30, payments.sum(&:price)
+ assert_equal 60, payments.sum { |p| p.price * 2 }
+
+ payments = [ SummablePayment.new(5), SummablePayment.new(15) ]
+ assert_equal SummablePayment.new(20), payments.sum
+ assert_equal SummablePayment.new(20), payments.sum { |p| p }
+
+ sum = [3, 5.quo(1)].sum
+ assert_typed_equal(8, sum, Rational)
+
+ sum = [3, 5.quo(1)].sum(0.0)
+ assert_typed_equal(8.0, sum, Float)
+
+ sum = [3, 5.quo(1), 7.0].sum
+ assert_typed_equal(15.0, sum, Float)
+
+ sum = [3, 5.quo(1), Complex(7)].sum
+ assert_typed_equal(Complex(15), sum, Complex)
+ assert_typed_equal(15, sum.real, Rational)
+ assert_typed_equal(0, sum.imag, Integer)
+
+ sum = [3.5, 5].sum
+ assert_typed_equal(8.5, sum, Float)
+
+ sum = [2, 8.5].sum
+ assert_typed_equal(10.5, sum, Float)
+
+ sum = [1.quo(2), 1].sum
+ assert_typed_equal(3.quo(2), sum, Rational)
+
+ sum = [1.quo(2), 1.quo(3)].sum
+ assert_typed_equal(5.quo(6), sum, Rational)
+
+ sum = [2.0, 3.0*Complex::I].sum
+ assert_typed_equal(Complex(2.0, 3.0), sum, Complex)
+ assert_typed_equal(2.0, sum.real, Float)
+ assert_typed_equal(3.0, sum.imag, Float)
+
+ sum = [1, 2].sum(10) {|v| v * 2 }
+ assert_typed_equal(16, sum, Integer)
end
def test_index_by
diff --git a/activesupport/test/core_ext/hash/transform_keys_test.rb b/activesupport/test/core_ext/hash/transform_keys_test.rb
index 99af274614..962d3a30b6 100644
--- a/activesupport/test/core_ext/hash/transform_keys_test.rb
+++ b/activesupport/test/core_ext/hash/transform_keys_test.rb
@@ -43,4 +43,20 @@ class TransformKeysTest < ActiveSupport::TestCase
original.transform_keys!.with_index { |k, i| [k, i].join.to_sym }
assert_equal({ a0: 'a', b1: 'b' }, original)
end
+
+ test "transform_keys returns a Hash instance when self is inherited from Hash" do
+ class HashDescendant < ::Hash
+ def initialize(elements = nil)
+ super(elements)
+ (elements || {}).each_pair{ |key, value| self[key] = value }
+ end
+ end
+
+ original = HashDescendant.new({ a: 'a', b: 'b' })
+ mapped = original.transform_keys { |k| "#{k}!".to_sym }
+
+ assert_equal({ a: 'a', b: 'b' }, original)
+ assert_equal({ a!: 'a', b!: 'b' }, mapped)
+ assert_equal(::Hash, mapped.class)
+ end
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 1b66f784e4..e8099baa35 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -36,12 +36,12 @@ class HashExtTest < ActiveSupport::TestCase
def setup
@strings = { 'a' => 1, 'b' => 2 }
@nested_strings = { 'a' => { 'b' => { 'c' => 3 } } }
- @symbols = { :a => 1, :b => 2 }
+ @symbols = { :a => 1, :b => 2 }
@nested_symbols = { :a => { :b => { :c => 3 } } }
- @mixed = { :a => 1, 'b' => 2 }
- @nested_mixed = { 'a' => { :b => { 'c' => 3 } } }
- @fixnums = { 0 => 1, 1 => 2 }
- @nested_fixnums = { 0 => { 1 => { 2 => 3} } }
+ @mixed = { :a => 1, 'b' => 2 }
+ @nested_mixed = { 'a' => { :b => { 'c' => 3 } } }
+ @integers = { 0 => 1, 1 => 2 }
+ @nested_integers = { 0 => { 1 => { 2 => 3} } }
@illegal_symbols = { [] => 3 }
@nested_illegal_symbols = { [] => { [] => 3} }
@upcase_strings = { 'A' => 1, 'B' => 2 }
@@ -196,14 +196,14 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_dup.deep_symbolize_keys!
end
- def test_symbolize_keys_preserves_fixnum_keys
- assert_equal @fixnums, @fixnums.symbolize_keys
- assert_equal @fixnums, @fixnums.dup.symbolize_keys!
+ def test_symbolize_keys_preserves_integer_keys
+ assert_equal @integers, @integers.symbolize_keys
+ assert_equal @integers, @integers.dup.symbolize_keys!
end
- def test_deep_symbolize_keys_preserves_fixnum_keys
- assert_equal @nested_fixnums, @nested_fixnums.deep_symbolize_keys
- assert_equal @nested_fixnums, @nested_fixnums.deep_dup.deep_symbolize_keys!
+ def test_deep_symbolize_keys_preserves_integer_keys
+ assert_equal @nested_integers, @nested_integers.deep_symbolize_keys
+ assert_equal @nested_integers, @nested_integers.deep_dup.deep_symbolize_keys!
end
def test_stringify_keys
@@ -299,14 +299,14 @@ class HashExtTest < ActiveSupport::TestCase
assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
end
- def test_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
- assert_equal @fixnums, @fixnums.with_indifferent_access.symbolize_keys
- assert_raise(NoMethodError) { @fixnums.with_indifferent_access.dup.symbolize_keys! }
+ def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @integers, @integers.with_indifferent_access.symbolize_keys
+ assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! }
end
- def test_deep_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
- assert_equal @nested_fixnums, @nested_fixnums.with_indifferent_access.deep_symbolize_keys
- assert_raise(NoMethodError) { @nested_fixnums.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys
+ assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! }
end
def test_stringify_keys_for_hash_with_indifferent_access
@@ -702,6 +702,12 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal h.class, h.dup.class
end
+ def test_nested_dig_indifferent_access
+ skip if RUBY_VERSION < "2.3.0"
+ data = {"this" => {"views" => 1234}}.with_indifferent_access
+ assert_equal 1234, data.dig(:this, :views)
+ end
+
def test_assert_valid_keys
assert_nothing_raised do
{ :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
@@ -993,6 +999,10 @@ class HashExtTest < ActiveSupport::TestCase
h = hash_with_only_nil_values.dup
assert_equal({}, h.compact)
assert_equal(hash_with_only_nil_values, h)
+
+ h = @symbols.dup
+ assert_equal(@symbols, h.compact)
+ assert_equal(@symbols, h)
end
def test_compact!
@@ -1006,6 +1016,10 @@ class HashExtTest < ActiveSupport::TestCase
h = hash_with_only_nil_values.dup
assert_equal({}, h.compact!)
assert_equal({}, h)
+
+ h = @symbols.dup
+ assert_equal(nil, h.compact!)
+ assert_equal(@symbols, h)
end
def test_new_with_to_hash_conversion
@@ -1028,7 +1042,7 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal 3, new_hash[2]
new_hash.default = 2
- assert_equal 2, new_hash[:non_existant]
+ assert_equal 2, new_hash[:non_existent]
end
def test_to_hash_with_raising_default_proc
diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb
index 825df439a5..380f64c6fd 100644
--- a/activesupport/test/core_ext/marshal_test.rb
+++ b/activesupport/test/core_ext/marshal_test.rb
@@ -29,7 +29,12 @@ class MarshalTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of EM, Marshal.load(dumped)
+ object = nil
+ assert_nothing_raised do
+ object = Marshal.load(dumped)
+ end
+
+ assert_kind_of EM, object
end
end
@@ -43,7 +48,12 @@ class MarshalTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of ClassFolder::ClassFolderSubclass, Marshal.load(dumped)
+ object = nil
+ assert_nothing_raised do
+ object = Marshal.load(dumped)
+ end
+
+ assert_kind_of ClassFolder::ClassFolderSubclass, object
end
end
@@ -64,6 +74,17 @@ class MarshalTest < ActiveSupport::TestCase
end
end
+ test "when one constant resolves to another" do
+ class Parent; C = Class.new; end
+ class Child < Parent; C = Class.new; end
+
+ dump = Marshal.dump(Child::C.new)
+
+ Child.send(:remove_const, :C)
+
+ assert_raise(ArgumentError) { Marshal.load(dump) }
+ end
+
test "that a real missing class is causing an exception" do
dumped = nil
with_autoloading_fixtures do
@@ -96,7 +117,7 @@ class MarshalTest < ActiveSupport::TestCase
Marshal.load(dumped)
end
- assert_nothing_raised("EM failed to load while we expect only SomeClass to fail loading") do
+ assert_nothing_raised do
EM.new
end
@@ -117,7 +138,12 @@ class MarshalTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of EM, Marshal.load(f)
+ object = nil
+ assert_nothing_raised do
+ object = Marshal.load(f)
+ end
+
+ assert_kind_of EM, object
end
end
end
diff --git a/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
index 65fadc5c20..a9fd878b80 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
@@ -106,4 +106,10 @@ class ModuleAttributeAccessorPerThreadTest < ActiveSupport::TestCase
end
assert_equal "invalid attribute name: 2valid_part", exception.message
end
+
+ def test_should_return_same_value_by_class_or_instance_accessor
+ @class.foo = 'fries'
+
+ assert_equal @class.foo, @object.foo
+ end
end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 0ed66f8c37..566f29b470 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -40,6 +40,12 @@ class Someone < Struct.new(:name, :place)
FAILED_DELEGATE_LINE_2 = __LINE__ + 1
delegate :bar, :to => :place, :allow_nil => true
+
+ private
+
+ def private_name
+ "Private"
+ end
end
Invoice = Struct.new(:client) do
@@ -83,6 +89,20 @@ Product = Struct.new(:name) do
end
end
+DecoratedTester = Struct.new(:client) do
+ delegate_missing_to :client
+end
+
+class DecoratedReserved
+ delegate_missing_to :case
+
+ attr_reader :case
+
+ def initialize(kase)
+ @case = kase
+ end
+end
+
class Block
def hello?
true
@@ -173,6 +193,21 @@ class ModuleTest < ActiveSupport::TestCase
end
end
+ def test_delegation_target_when_prefix_is_true
+ assert_nothing_raised do
+ Name.send :delegate, :go, to: :you, prefix: true
+ end
+ assert_nothing_raised do
+ Name.send :delegate, :go, to: :_you, prefix: true
+ end
+ assert_raise(ArgumentError) do
+ Name.send :delegate, :go, to: :You, prefix: true
+ end
+ assert_raise(ArgumentError) do
+ Name.send :delegate, :go, to: :@you, prefix: true
+ end
+ end
+
def test_delegation_prefix
invoice = Invoice.new(@david)
assert_equal invoice.client_name, "David"
@@ -316,6 +351,30 @@ class ModuleTest < ActiveSupport::TestCase
assert has_block.hello?
end
+ def test_delegate_to_missing_with_method
+ assert_equal "David", DecoratedTester.new(@david).name
+ end
+
+ def test_delegate_to_missing_with_reserved_methods
+ assert_equal "David", DecoratedReserved.new(@david).name
+ end
+
+ def test_delegate_to_missing_does_not_delegate_to_private_methods
+ e = assert_raises(NoMethodError) do
+ DecoratedReserved.new(@david).private_name
+ end
+
+ assert_match(/undefined method `private_name' for/, e.message)
+ end
+
+ def test_delegate_to_missing_does_not_delegate_to_fake_methods
+ e = assert_raises(NoMethodError) do
+ DecoratedReserved.new(@david).my_fake_method
+ end
+
+ assert_match(/undefined method `my_fake_method' for/, e.message)
+ end
+
def test_parent
assert_equal Yz::Zy, Yz::Zy::Cd.parent
assert_equal Yz, Yz::Zy.parent
@@ -328,7 +387,13 @@ class ModuleTest < ActiveSupport::TestCase
end
def test_local_constants
- assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s)
+ ActiveSupport::Deprecation.silence do
+ assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s)
+ end
+ end
+
+ def test_local_constants_is_deprecated
+ assert_deprecated { Ab.local_constants.sort.map(&:to_s) }
end
end
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 5654aeb4f8..69c30a8a9e 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -387,16 +387,9 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
end
def test_to_s__injected_on_proper_types
- assert_equal Fixnum, 1230.class
assert_equal '1.23 Thousand', 1230.to_s(:human)
-
- assert_equal Float, Float(1230).class
assert_equal '1.23 Thousand', Float(1230).to_s(:human)
-
- assert_equal Bignum, (100**10).class
assert_equal '100000 Quadrillion', (100**10).to_s(:human)
-
- assert_equal BigDecimal, BigDecimal("1000010").class
assert_equal '1 Million', BigDecimal("1000010").to_s(:human)
end
diff --git a/activesupport/test/core_ext/object/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb
index 791b5e7172..aa839201ea 100644
--- a/activesupport/test/core_ext/object/deep_dup_test.rb
+++ b/activesupport/test/core_ext/object/deep_dup_test.rb
@@ -51,7 +51,7 @@ class DeepDupTest < ActiveSupport::TestCase
end
def test_deep_dup_with_hash_class_key
- hash = { Fixnum => 1 }
+ hash = { Integer => 1 }
dup = hash.deep_dup
assert_equal 1, dup.keys.length
end
diff --git a/activesupport/test/core_ext/object/json_gem_encoding_test.rb b/activesupport/test/core_ext/object/json_gem_encoding_test.rb
index 02ab17fb64..2cbb1d590f 100644
--- a/activesupport/test/core_ext/object/json_gem_encoding_test.rb
+++ b/activesupport/test/core_ext/object/json_gem_encoding_test.rb
@@ -10,7 +10,7 @@ require 'json/encoding_test_cases'
# The AS::JSON encoder requires the BigDecimal core_ext, which, unfortunately,
# changes the BigDecimal#to_s output, and consequently the JSON gem output. So
-# we need to require this unfront to ensure we don't get a false failure, but
+# we need to require this upfront to ensure we don't get a false failure, but
# ideally we should just fix the BigDecimal core_ext to not change to_s without
# arguments.
require 'active_support/core_ext/big_decimal'
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 2e69816364..d68a77680b 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -10,6 +10,7 @@ require 'active_support/core_ext/string/strip'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/indent'
require 'time_zone_test_helpers'
+require 'yaml'
class StringInflectionsTest < ActiveSupport::TestCase
include InflectorTestCases
@@ -76,6 +77,18 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_upcase_first
+ assert_equal "What a Lovely Day", "what a Lovely Day".upcase_first
+ end
+
+ def test_upcase_first_with_one_char
+ assert_equal "W", "w".upcase_first
+ end
+
+ def test_upcase_first_with_empty_string
+ assert_equal "", "".upcase_first
+ end
+
def test_camelize
CamelToUnderscore.each do |camel, underscore|
assert_equal(camel, underscore.camelize)
@@ -332,7 +345,7 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
class StringAccessTest < ActiveSupport::TestCase
- test "#at with Fixnum, returns a substring of one character at that position" do
+ test "#at with Integer, returns a substring of one character at that position" do
assert_equal "h", "hello".at(0)
end
@@ -345,19 +358,19 @@ class StringAccessTest < ActiveSupport::TestCase
assert_equal nil, "hello".at(/nonexisting/)
end
- test "#from with positive Fixnum, returns substring from the given position to the end" do
+ test "#from with positive Integer, returns substring from the given position to the end" do
assert_equal "llo", "hello".from(2)
end
- test "#from with negative Fixnum, position is counted from the end" do
+ test "#from with negative Integer, position is counted from the end" do
assert_equal "lo", "hello".from(-2)
end
- test "#to with positive Fixnum, substring from the beginning to the given position" do
+ test "#to with positive Integer, substring from the beginning to the given position" do
assert_equal "hel", "hello".to(2)
end
- test "#to with negative Fixnum, position is counted from the end" do
+ test "#to with negative Integer, position is counted from the end" do
assert_equal "hell", "hello".to(-2)
end
@@ -371,14 +384,14 @@ class StringAccessTest < ActiveSupport::TestCase
assert_equal 'x', 'x'.first
end
- test "#first with Fixnum, returns a substring from the beginning to position" do
+ test "#first with Integer, returns a substring from the beginning to position" do
assert_equal "he", "hello".first(2)
assert_equal "", "hello".first(0)
assert_equal "hello", "hello".first(10)
assert_equal 'x', 'x'.first(4)
end
- test "#first with Fixnum >= string length still returns a new string" do
+ test "#first with Integer >= string length still returns a new string" do
string = "hello"
different_string = string.first(5)
assert_not_same different_string, string
@@ -389,14 +402,14 @@ class StringAccessTest < ActiveSupport::TestCase
assert_equal 'x', 'x'.last
end
- test "#last with Fixnum, returns a substring from the end to position" do
+ test "#last with Integer, returns a substring from the end to position" do
assert_equal "llo", "hello".last(3)
assert_equal "hello", "hello".last(10)
assert_equal "", "hello".last(0)
assert_equal 'x', 'x'.last(4)
end
- test "#last with Fixnum >= string length still returns a new string" do
+ test "#last with Integer >= string length still returns a new string" do
string = "hello"
different_string = string.last(5)
assert_not_same different_string, string
@@ -444,16 +457,24 @@ class StringConversionsTest < ActiveSupport::TestCase
assert_equal Time.local(2011, 2, 27, 17, 50), "2011-02-27 13:50 -0100".to_time
assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time(:utc)
assert_equal Time.local(2005, 2, 27, 22, 50), "2005-02-27 14:50 -0500".to_time
+ assert_nil "010".to_time
assert_nil "".to_time
end
end
def test_string_to_time_utc_offset
with_env_tz "US/Eastern" do
- assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset
- assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset)
- assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset
- assert_equal(-18000, "2005-02-27 22:50 -0100".to_time.utc_offset)
+ if ActiveSupport.to_time_preserves_timezone
+ assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset
+ assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset)
+ assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset
+ assert_equal(-3600, "2005-02-27 22:50 -0100".to_time.utc_offset)
+ else
+ assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset
+ assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset)
+ assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset
+ assert_equal(-18000, "2005-02-27 22:50 -0100".to_time.utc_offset)
+ end
end
end
@@ -661,7 +682,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert_equal @string, @string.html_safe
end
- test "A fixnum is safe by default" do
+ test "An integer is safe by default" do
assert 5.html_safe?
end
@@ -792,7 +813,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert_equal ["<p>", "<b>", "<h1>"], @other_string
end
- test "Concatting a fixnum to safe always yields safe" do
+ test "Concatting an integer to safe always yields safe" do
string = @string.html_safe
string = string.concat(13)
assert_equal "hello".concat(13), string
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index e45df63fd5..1205797fac 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -107,6 +107,20 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_sec_fraction
+ time = Time.utc(2016, 4, 23, 0, 0, Rational(1,10000000000))
+ assert_equal Rational(1,10000000000), time.sec_fraction
+
+ time = Time.utc(2016, 4, 23, 0, 0, 0.0000000001)
+ assert_equal 0.0000000001.to_r, time.sec_fraction
+
+ time = Time.utc(2016, 4, 23, 0, 0, 0, Rational(1,10000))
+ assert_equal Rational(1,10000000000), time.sec_fraction
+
+ time = Time.utc(2016, 4, 23, 0, 0, 0, 0.0001)
+ assert_equal 0.0001.to_r / 1000000, time.sec_fraction
+ end
+
def test_beginning_of_day
assert_equal Time.local(2005,2,4,0,0,0), Time.local(2005,2,4,10,10,10).beginning_of_day
with_env_tz 'US/Eastern' do
@@ -392,7 +406,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,44).change(:usec => 8)
assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,2).change(:nsec => 8000)
assert_raise(ArgumentError) { Time.local(2005,1,2,11,22,33, 8).change(:usec => 1, :nsec => 1) }
- assert_nothing_raised(ArgumentError) { Time.new(2015, 5, 9, 10, 00, 00, '+03:00').change(nsec: 999999999) }
+ assert_nothing_raised { Time.new(2015, 5, 9, 10, 00, 00, '+03:00').change(nsec: 999999999) }
end
def test_utc_change
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 7acada011d..d90714acdb 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -11,10 +11,13 @@ class TimeWithZoneTest < ActiveSupport::TestCase
@utc = Time.utc(2000, 1, 1, 0)
@time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
@twz = ActiveSupport::TimeWithZone.new(@utc, @time_zone)
+ @dt_twz = ActiveSupport::TimeWithZone.new(@utc.to_datetime, @time_zone)
end
def test_utc
assert_equal @utc, @twz.utc
+ assert_instance_of Time, @twz.utc
+ assert_instance_of Time, @dt_twz.utc
end
def test_time
@@ -47,6 +50,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_localtime
assert_equal @twz.localtime, @twz.utc.getlocal
+ assert_instance_of Time, @twz.localtime
+ assert_instance_of Time, @dt_twz.localtime
end
def test_utc?
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 757e600646..04e7b24d30 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -76,6 +76,7 @@ class DependenciesTest < ActiveSupport::TestCase
def test_dependency_which_raises_exception_isnt_added_to_loaded_set
with_loading do
filename = 'dependencies/raises_exception'
+ expanded = File.expand_path(filename)
$raises_exception_load_count = 0
5.times do |count|
@@ -86,8 +87,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_not ActiveSupport::Dependencies.loaded.include?(filename)
- assert_not ActiveSupport::Dependencies.history.include?(filename)
+ assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
+ assert_not ActiveSupport::Dependencies.history.include?(expanded)
end
end
end
@@ -268,6 +269,28 @@ class DependenciesTest < ActiveSupport::TestCase
remove_constants(:ModuleFolder)
end
+ def test_raising_discards_autoloaded_constants
+ with_autoloading_fixtures do
+ assert_raises(Exception, 'arbitray exception message') { RaisesArbitraryException }
+ assert_not defined?(A)
+ assert_not defined?(RaisesArbitraryException)
+ end
+ ensure
+ remove_constants(:A, :RaisesArbitraryException)
+ end
+
+ def test_throwing_discards_autoloaded_constants
+ with_autoloading_fixtures do
+ catch :t do
+ Throws
+ end
+ assert_not defined?(A)
+ assert_not defined?(Throws)
+ end
+ ensure
+ remove_constants(:A, :Throws)
+ end
+
def test_doesnt_break_normal_require
path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
original_path = $:.dup
@@ -1047,12 +1070,4 @@ class DependenciesTest < ActiveSupport::TestCase
ensure
ActiveSupport::Dependencies.hook!
end
-
- def test_unhook
- ActiveSupport::Dependencies.unhook!
- assert !Module.new.respond_to?(:const_missing_without_dependencies)
- assert !Module.new.respond_to?(:load_without_new_constant_marking)
- ensure
- ActiveSupport::Dependencies.hook!
- end
end
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 58a0a3964d..dbde3d2e15 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -105,13 +105,13 @@ class DeprecationTest < ActiveSupport::TestCase
ActiveSupport::Deprecation.behavior = :raise
message = 'Revise this deprecated stuff now!'
- callstack = %w(foo bar baz)
+ callstack = caller_locations
e = assert_raise ActiveSupport::DeprecationException do
ActiveSupport::Deprecation.behavior.first.call(message, callstack)
end
assert_equal message, e.message
- assert_equal callstack, e.backtrace
+ assert_equal callstack.map(&:to_s), e.backtrace.map(&:to_s)
end
def test_default_stderr_behavior
@@ -143,7 +143,7 @@ class DeprecationTest < ActiveSupport::TestCase
stderr_output = capture(:stderr) {
assert_nil behavior.call('Some error!', ['call stack!'])
}
- assert stderr_output.blank?
+ assert stderr_output.empty?
end
def test_deprecated_instance_variable_proxy
@@ -341,7 +341,7 @@ class DeprecationTest < ActiveSupport::TestCase
end
def test_default_deprecation_horizon_should_always_bigger_than_current_rails_version
- assert ActiveSupport::Deprecation.new.deprecation_horizon > ActiveSupport::VERSION::STRING
+ assert_operator ActiveSupport::Deprecation.new.deprecation_horizon, :>, ActiveSupport::VERSION::STRING
end
def test_default_gem_name
diff --git a/activesupport/test/evented_file_update_checker_test.rb b/activesupport/test/evented_file_update_checker_test.rb
index bc3f77bd54..2cb2d8167f 100644
--- a/activesupport/test/evented_file_update_checker_test.rb
+++ b/activesupport/test/evented_file_update_checker_test.rb
@@ -11,7 +11,7 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
end
def new_checker(files = [], dirs = {}, &block)
- ActiveSupport::EventedFileUpdateChecker.new(files, dirs, &block).tap do
+ ActiveSupport::EventedFileUpdateChecker.new(files, dirs, &block).tap do |c|
wait
end
end
@@ -34,6 +34,48 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
super
wait
end
+
+ test 'notifies forked processes' do
+ FileUtils.touch(tmpfiles)
+
+ checker = new_checker(tmpfiles) { }
+ assert !checker.updated?
+
+ # Pipes used for flow controll across fork.
+ boot_reader, boot_writer = IO.pipe
+ touch_reader, touch_writer = IO.pipe
+
+ pid = fork do
+ assert checker.updated?
+
+ # Clear previous check value.
+ checker.execute
+ assert !checker.updated?
+
+ # Fork is booted, ready for file to be touched
+ # notify parent process.
+ boot_writer.write("booted")
+
+ # Wait for parent process to signal that file
+ # has been touched.
+ IO.select([touch_reader])
+
+ assert checker.updated?
+ end
+
+ assert pid
+
+ # Wait for fork to be booted before touching files.
+ IO.select([boot_reader])
+ touch(tmpfiles)
+
+ # Notify fork that files have been touched.
+ touch_writer.write("touched")
+
+ assert checker.updated?
+
+ Process.wait(pid)
+ end
end
class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase
diff --git a/activesupport/test/executor_test.rb b/activesupport/test/executor_test.rb
new file mode 100644
index 0000000000..d9b389461a
--- /dev/null
+++ b/activesupport/test/executor_test.rb
@@ -0,0 +1,183 @@
+require 'abstract_unit'
+
+class ExecutorTest < ActiveSupport::TestCase
+ class DummyError < RuntimeError
+ end
+
+ def test_wrap_invokes_callbacks
+ called = []
+ executor.to_run { called << :run }
+ executor.to_complete { called << :complete }
+
+ executor.wrap do
+ called << :body
+ end
+
+ assert_equal [:run, :body, :complete], called
+ end
+
+ def test_callbacks_share_state
+ result = false
+ executor.to_run { @foo = true }
+ executor.to_complete { result = @foo }
+
+ executor.wrap { }
+
+ assert result
+ end
+
+ def test_separated_calls_invoke_callbacks
+ called = []
+ executor.to_run { called << :run }
+ executor.to_complete { called << :complete }
+
+ state = executor.run!
+ called << :body
+ state.complete!
+
+ assert_equal [:run, :body, :complete], called
+ end
+
+ def test_exceptions_unwind
+ called = []
+ executor.to_run { called << :run_1 }
+ executor.to_run { raise DummyError }
+ executor.to_run { called << :run_2 }
+ executor.to_complete { called << :complete }
+
+ assert_raises(DummyError) do
+ executor.wrap { called << :body }
+ end
+
+ assert_equal [:run_1, :complete], called
+ end
+
+ def test_avoids_double_wrapping
+ called = []
+ executor.to_run { called << :run }
+ executor.to_complete { called << :complete }
+
+ executor.wrap do
+ called << :early
+ executor.wrap do
+ called << :body
+ end
+ called << :late
+ end
+
+ assert_equal [:run, :early, :body, :late, :complete], called
+ end
+
+ def test_hooks_carry_state
+ supplied_state = :none
+
+ hook = Class.new do
+ define_method(:run) do
+ :some_state
+ end
+
+ define_method(:complete) do |state|
+ supplied_state = state
+ end
+ end.new
+
+ executor.register_hook(hook)
+
+ executor.wrap { }
+
+ assert_equal :some_state, supplied_state
+ end
+
+ def test_nil_state_is_sufficient
+ supplied_state = :none
+
+ hook = Class.new do
+ define_method(:run) do
+ nil
+ end
+
+ define_method(:complete) do |state|
+ supplied_state = state
+ end
+ end.new
+
+ executor.register_hook(hook)
+
+ executor.wrap { }
+
+ assert_equal nil, supplied_state
+ end
+
+ def test_exception_skips_uninvoked_hook
+ supplied_state = :none
+
+ hook = Class.new do
+ define_method(:run) do
+ :some_state
+ end
+
+ define_method(:complete) do |state|
+ supplied_state = state
+ end
+ end.new
+
+ executor.to_run do
+ raise DummyError
+ end
+ executor.register_hook(hook)
+
+ assert_raises(DummyError) do
+ executor.wrap { }
+ end
+
+ assert_equal :none, supplied_state
+ end
+
+ def test_exception_unwinds_invoked_hook
+ supplied_state = :none
+
+ hook = Class.new do
+ define_method(:run) do
+ :some_state
+ end
+
+ define_method(:complete) do |state|
+ supplied_state = state
+ end
+ end.new
+
+ executor.register_hook(hook)
+ executor.to_run do
+ raise DummyError
+ end
+
+ assert_raises(DummyError) do
+ executor.wrap { }
+ end
+
+ assert_equal :some_state, supplied_state
+ end
+
+ def test_separate_classes_can_wrap
+ other_executor = Class.new(ActiveSupport::Executor)
+
+ called = []
+ executor.to_run { called << :run }
+ executor.to_complete { called << :complete }
+ other_executor.to_run { called << :other_run }
+ other_executor.to_complete { called << :other_complete }
+
+ executor.wrap do
+ other_executor.wrap do
+ called << :body
+ end
+ end
+
+ assert_equal [:run, :other_run, :body, :other_complete, :complete], called
+ end
+
+ private
+ def executor
+ @executor ||= Class.new(ActiveSupport::Executor)
+ end
+end
diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb
index 9c07e38fe5..40ae0c7617 100644
--- a/activesupport/test/file_update_checker_shared_tests.rb
+++ b/activesupport/test/file_update_checker_shared_tests.rb
@@ -5,19 +5,21 @@ module FileUpdateCheckerSharedTests
include FileUtils
def tmpdir
- @tmpdir ||= Dir.mktmpdir(nil, __dir__)
+ @tmpdir
end
def tmpfile(name)
- "#{tmpdir}/#{name}"
+ File.join(tmpdir, name)
end
def tmpfiles
@tmpfiles ||= %w(foo.rb bar.rb baz.rb).map { |f| tmpfile(f) }
end
- def teardown
- FileUtils.rm_rf(@tmpdir) if defined? @tmpdir
+ def run(*args)
+ capture_exceptions do
+ Dir.mktmpdir(nil, __dir__) { |dir| @tmpdir = dir; super }
+ end
end
test 'should not execute the block if no paths are given' do
@@ -134,6 +136,22 @@ module FileUpdateCheckerSharedTests
assert_equal 1, i
end
+ test 'should return max_time for files with mtime = Time.at(0)' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ time = Time.at(0) # wrong mtime from the future
+ File.utime(time, time, tmpfiles[0])
+
+ checker = new_checker(tmpfiles) { i += 1 }
+
+ touch(tmpfiles[1..-1])
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
test 'should cache updated result until execute' do
i = 0
@@ -209,7 +227,7 @@ module FileUpdateCheckerSharedTests
assert !checker.execute_if_updated
assert_equal 0, i
- touch("#{subdir}/nested.rb")
+ touch(File.join(subdir, "nested.rb"))
assert checker.execute_if_updated
assert_equal 1, i
@@ -229,12 +247,12 @@ module FileUpdateCheckerSharedTests
assert_equal 0, i
# subdir does not look for Ruby files, but its parent tmpdir does.
- touch("#{subdir}/nested.rb")
+ touch(File.join(subdir, "nested.rb"))
assert checker.execute_if_updated
assert_equal 1, i
- touch("#{subdir}/nested.txt")
+ touch(File.join(subdir, "nested.txt"))
assert checker.execute_if_updated
assert_equal 2, i
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 14fe97a986..c7dc1dadb6 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -270,7 +270,8 @@ module InflectorTestCases
"maybe you'll be there" => "Maybe You'll Be There",
"¿por qué?" => '¿Por Qué?',
"Fred’s" => "Fred’s",
- "Fred`s" => "Fred`s"
+ "Fred`s" => "Fred`s",
+ ActiveSupport::SafeBuffer.new("confirmation num") => "Confirmation Num"
}
OrdinalNumbers = {
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 9f4b62fd8b..5fc2e16336 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -422,6 +422,11 @@ EXPECTED
assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
end
+ def test_exception_to_json
+ exception = Exception.new("foo")
+ assert_equal '"foo"', ActiveSupport::JSON.encode(exception)
+ end
+
protected
def object_keys(json_object)
diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb
index 317e09b7f2..dfc5f3fdf4 100644
--- a/activesupport/test/logger_test.rb
+++ b/activesupport/test/logger_test.rb
@@ -141,6 +141,52 @@ class LoggerTest < ActiveSupport::TestCase
assert @output.string.include?("THIS IS HERE")
end
+ def test_logger_silencing_works_for_broadcast
+ another_output = StringIO.new
+ another_logger = ActiveSupport::Logger.new(another_output)
+
+ @logger.extend ActiveSupport::Logger.broadcast(another_logger)
+
+ @logger.debug "CORRECT DEBUG"
+ @logger.silence do |logger|
+ assert_kind_of ActiveSupport::Logger, logger
+ @logger.debug "FAILURE"
+ @logger.error "CORRECT ERROR"
+ end
+
+ assert @output.string.include?("CORRECT DEBUG")
+ assert @output.string.include?("CORRECT ERROR")
+ assert_not @output.string.include?("FAILURE")
+
+ assert another_output.string.include?("CORRECT DEBUG")
+ assert another_output.string.include?("CORRECT ERROR")
+ assert_not another_output.string.include?("FAILURE")
+ end
+
+ def test_broadcast_silencing_does_not_break_plain_ruby_logger
+ another_output = StringIO.new
+ another_logger = ::Logger.new(another_output)
+
+ @logger.extend ActiveSupport::Logger.broadcast(another_logger)
+
+ @logger.debug "CORRECT DEBUG"
+ @logger.silence do |logger|
+ assert_kind_of ActiveSupport::Logger, logger
+ @logger.debug "FAILURE"
+ @logger.error "CORRECT ERROR"
+ end
+
+ assert @output.string.include?("CORRECT DEBUG")
+ assert @output.string.include?("CORRECT ERROR")
+ assert_not @output.string.include?("FAILURE")
+
+ assert another_output.string.include?("CORRECT DEBUG")
+ assert another_output.string.include?("CORRECT ERROR")
+ assert another_output.string.include?("FAILURE")
+ # We can't silence plain ruby Logger cause with thread safety
+ # but at least we don't break it
+ end
+
def test_logger_level_per_object_thread_safety
logger1 = Logger.new(StringIO.new)
logger2 = Logger.new(StringIO.new)
diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb
index 5df8f32e46..9fca47a985 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -28,7 +28,7 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd"
UNIDATA_FILE = '/NormalizationTest.txt'
- CACHE_DIR = File.join(Dir.tmpdir, 'cache')
+ CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance"
FileUtils.mkdir_p(CACHE_DIR)
RUN_P = begin
Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE)
diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
index 229f24990e..6e2f02abed 100644
--- a/activesupport/test/multibyte_grapheme_break_conformance_test.rb
+++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
@@ -27,7 +27,7 @@ class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase
TEST_DATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd/auxiliary"
TEST_DATA_FILE = '/GraphemeBreakTest.txt'
- CACHE_DIR = File.join(Dir.tmpdir, 'cache')
+ CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance"
def setup
FileUtils.mkdir_p(CACHE_DIR)
diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb
index 8bc91ef708..0d31c9520f 100644
--- a/activesupport/test/multibyte_normalization_conformance_test.rb
+++ b/activesupport/test/multibyte_normalization_conformance_test.rb
@@ -30,7 +30,7 @@ class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase
UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd"
UNIDATA_FILE = '/NormalizationTest.txt'
- CACHE_DIR = File.join(Dir.tmpdir, 'cache')
+ CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance"
def setup
FileUtils.mkdir_p(CACHE_DIR)
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index b3464462c8..074c872efc 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -57,6 +57,8 @@ module ActiveSupport
assert_equal("+18005551212", number_helper.number_to_phone(8005551212, :country_code => 1, :delimiter => ''))
assert_equal("22-555-1212", number_helper.number_to_phone(225551212))
assert_equal("+45-22-555-1212", number_helper.number_to_phone(225551212, :country_code => 45))
+ assert_equal("(755) 6123-4567", number_helper.number_to_phone(75561234567, pattern: /(\d{3,4})(\d{4})(\d{4})/, area_code: true))
+ assert_equal("133-1234-5678", number_helper.number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})/))
end
end
@@ -74,7 +76,6 @@ module ActiveSupport
assert_equal("1,234,567,890.50 K&#269;", number_helper.number_to_currency("1234567890.50", {:unit => "K&#269;", :format => "%n %u"}))
assert_equal("1,234,567,890.50 - K&#269;", number_helper.number_to_currency("-1234567890.50", {:unit => "K&#269;", :format => "%n %u", :negative_format => "%n - %u"}))
assert_equal("0.00", number_helper.number_to_currency(+0.0, {:unit => "", :negative_format => "(%n)"}))
- assert_equal("(0.00)", number_helper.number_to_currency(-0.0, {:unit => "", :negative_format => "(%n)"}))
end
end
diff --git a/activesupport/test/reloader_test.rb b/activesupport/test/reloader_test.rb
new file mode 100644
index 0000000000..958cb49993
--- /dev/null
+++ b/activesupport/test/reloader_test.rb
@@ -0,0 +1,85 @@
+require 'abstract_unit'
+
+class ReloaderTest < ActiveSupport::TestCase
+ def test_prepare_callback
+ prepared = false
+ reloader.to_prepare { prepared = true }
+
+ assert !prepared
+ reloader.prepare!
+ assert prepared
+
+ prepared = false
+ reloader.wrap do
+ assert prepared
+ prepared = false
+ end
+ assert !prepared
+ end
+
+ def test_only_run_when_check_passes
+ r = new_reloader { true }
+ invoked = false
+ r.to_run { invoked = true }
+ r.wrap { }
+ assert invoked
+
+ r = new_reloader { false }
+ invoked = false
+ r.to_run { invoked = true }
+ r.wrap { }
+ assert !invoked
+ end
+
+ def test_full_reload_sequence
+ called = []
+ reloader.to_prepare { called << :prepare }
+ reloader.to_run { called << :reloader_run }
+ reloader.to_complete { called << :reloader_complete }
+ reloader.executor.to_run { called << :executor_run }
+ reloader.executor.to_complete { called << :executor_complete }
+
+ reloader.wrap { }
+ assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete], called
+
+ called = []
+ reloader.reload!
+ assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete, :prepare], called
+
+ reloader.check = lambda { false }
+
+ called = []
+ reloader.wrap { }
+ assert_equal [:executor_run, :executor_complete], called
+
+ called = []
+ reloader.reload!
+ assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete, :prepare], called
+ end
+
+ def test_class_unload_block
+ called = []
+ reloader.before_class_unload { called << :before_unload }
+ reloader.after_class_unload { called << :after_unload }
+ reloader.to_run do
+ class_unload! do
+ called << :unload
+ end
+ end
+ reloader.wrap { called << :body }
+
+ assert_equal [:before_unload, :unload, :after_unload, :body], called
+ end
+
+ private
+ def new_reloader(&check)
+ Class.new(ActiveSupport::Reloader).tap do |r|
+ r.check = check
+ r.executor = Class.new(ActiveSupport::Executor)
+ end
+ end
+
+ def reloader
+ @reloader ||= new_reloader { true }
+ end
+end
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
index bd43ad0797..e42e6d2973 100644
--- a/activesupport/test/rescuable_test.rb
+++ b/activesupport/test/rescuable_test.rb
@@ -3,9 +3,6 @@ require 'abstract_unit'
class WraithAttack < StandardError
end
-class NuclearExplosion < StandardError
-end
-
class MadRonon < StandardError
end
@@ -19,6 +16,10 @@ module WeirdError
end
class Stargate
+ # Nest this so the 'NuclearExplosion' handler needs a lexical const_get
+ # to find it.
+ class NuclearExplosion < StandardError; end
+
attr_accessor :result
include ActiveSupport::Rescuable
@@ -57,6 +58,14 @@ class Stargate
raise MadRonon.new("dex")
end
+ def fall_back_to_cause
+ # This exception is the cause and has a handler.
+ ronanize
+ rescue
+ # This is the exception we'll handle that doesn't have a cause.
+ raise 'unhandled RuntimeError with a handleable cause'
+ end
+
def weird
StandardError.new.tap do |exc|
def exc.weird?
@@ -74,7 +83,6 @@ class Stargate
def sos_first
@result = 'sos_first'
end
-
end
class CoolStargate < Stargate
@@ -127,4 +135,9 @@ class RescuableTest < ActiveSupport::TestCase
result = @cool_stargate.send(:rescue_handlers).collect(&:first)
assert_equal expected, result
end
+
+ def test_rescue_falls_back_to_exception_cause
+ @stargate.dispatch :fall_back_to_cause
+ assert_equal 'unhandled RuntimeError with a handleable cause', @stargate.result
+ end
end
diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb
index 465a657308..acefa185a8 100644
--- a/activesupport/test/share_lock_test.rb
+++ b/activesupport/test/share_lock_test.rb
@@ -114,14 +114,17 @@ class ShareLockTest < ActiveSupport::TestCase
[true, false].each do |use_upgrading|
with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
begin
+ together = Concurrent::CyclicBarrier.new(2)
conflicting_exclusive_threads = [
Thread.new do
@lock.send(use_upgrading ? :sharing : :tap) do
+ together.wait
@lock.exclusive(purpose: :red, compatible: [:green, :purple]) {}
end
end,
Thread.new do
@lock.send(use_upgrading ? :sharing : :tap) do
+ together.wait
@lock.exclusive(purpose: :blue, compatible: [:green]) {}
end
end
@@ -183,11 +186,14 @@ class ShareLockTest < ActiveSupport::TestCase
load_params = [:load, [:load]]
unload_params = [:unload, [:unload, :load]]
+ all_sharing = Concurrent::CyclicBarrier.new(4)
+
[load_params, load_params, unload_params, unload_params].permutation do |thread_params|
with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
threads = thread_params.map do |purpose, compatible|
Thread.new do
@lock.sharing do
+ all_sharing.wait
@lock.exclusive(purpose: purpose, compatible: compatible) do
scratch_pad_mutex.synchronize { scratch_pad << purpose }
end
@@ -209,6 +215,245 @@ class ShareLockTest < ActiveSupport::TestCase
end
end
+ def test_new_share_attempts_block_on_waiting_exclusive
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ release_exclusive = Concurrent::CountDownLatch.new
+
+ waiting_exclusive = Thread.new do
+ @lock.sharing do
+ @lock.exclusive do
+ release_exclusive.wait
+ end
+ end
+ end
+ assert_threads_stuck waiting_exclusive
+
+ late_share_attempt = Thread.new do
+ @lock.sharing {}
+ end
+ assert_threads_stuck late_share_attempt
+
+ sharing_thread_release_latch.count_down
+ assert_threads_stuck late_share_attempt
+
+ release_exclusive.count_down
+ assert_threads_not_stuck late_share_attempt
+ end
+ end
+
+ def test_share_remains_reentrant_ignoring_a_waiting_exclusive
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ ready = Concurrent::CyclicBarrier.new(2)
+ attempt_reentrancy = Concurrent::CountDownLatch.new
+
+ sharer = Thread.new do
+ @lock.sharing do
+ ready.wait
+ attempt_reentrancy.wait
+ @lock.sharing {}
+ end
+ end
+
+ exclusive = Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive {}
+ end
+ end
+
+ assert_threads_stuck exclusive
+
+ attempt_reentrancy.count_down
+
+ assert_threads_not_stuck sharer
+ assert_threads_stuck exclusive
+ end
+ end
+
+ def test_compatible_exclusives_cooperate_to_both_proceed
+ ready = Concurrent::CyclicBarrier.new(2)
+ done = Concurrent::CyclicBarrier.new(2)
+
+ threads = 2.times.map do
+ Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive(purpose: :x, compatible: [:x], after_compatible: [:x]) {}
+ done.wait
+ end
+ end
+ end
+
+ assert_threads_not_stuck threads
+ end
+
+ def test_manual_yield
+ ready = Concurrent::CyclicBarrier.new(2)
+ done = Concurrent::CyclicBarrier.new(2)
+
+ threads = [
+ Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive(purpose: :x) {}
+ done.wait
+ end
+ end,
+
+ Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.yield_shares(compatible: [:x]) do
+ done.wait
+ end
+ end
+ end,
+ ]
+
+ assert_threads_not_stuck threads
+ end
+
+ def test_manual_incompatible_yield
+ ready = Concurrent::CyclicBarrier.new(2)
+ done = Concurrent::CyclicBarrier.new(2)
+
+ threads = [
+ Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive(purpose: :x) {}
+ done.wait
+ end
+ end,
+
+ Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.yield_shares(compatible: [:y]) do
+ done.wait
+ end
+ end
+ end,
+ ]
+
+ assert_threads_stuck threads
+ ensure
+ threads.each(&:kill) if threads
+ end
+
+ def test_manual_recursive_yield
+ ready = Concurrent::CyclicBarrier.new(2)
+ done = Concurrent::CyclicBarrier.new(2)
+ do_nesting = Concurrent::CountDownLatch.new
+
+ threads = [
+ Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive(purpose: :x) {}
+ done.wait
+ end
+ end,
+
+ Thread.new do
+ @lock.sharing do
+ @lock.yield_shares(compatible: [:x]) do
+ @lock.sharing do
+ ready.wait
+ do_nesting.wait
+ @lock.yield_shares(compatible: [:x, :y]) do
+ done.wait
+ end
+ end
+ end
+ end
+ end
+ ]
+
+ assert_threads_stuck threads
+ do_nesting.count_down
+
+ assert_threads_not_stuck threads
+ end
+
+ def test_manual_recursive_yield_cannot_expand_outer_compatible
+ ready = Concurrent::CyclicBarrier.new(2)
+ do_compatible_nesting = Concurrent::CountDownLatch.new
+ in_compatible_nesting = Concurrent::CountDownLatch.new
+
+ incompatible_thread = Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive(purpose: :x) {}
+ end
+ end
+
+ yield_shares_thread = Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.yield_shares(compatible: [:y]) do
+ do_compatible_nesting.wait
+ @lock.sharing do
+ @lock.yield_shares(compatible: [:x, :y]) do
+ in_compatible_nesting.wait
+ end
+ end
+ end
+ end
+ end
+
+ assert_threads_stuck incompatible_thread
+ do_compatible_nesting.count_down
+ assert_threads_stuck incompatible_thread
+ in_compatible_nesting.count_down
+ assert_threads_not_stuck [yield_shares_thread, incompatible_thread]
+ end
+
+ def test_manual_recursive_yield_restores_previous_compatible
+ ready = Concurrent::CyclicBarrier.new(2)
+ do_nesting = Concurrent::CountDownLatch.new
+ after_nesting = Concurrent::CountDownLatch.new
+
+ incompatible_thread = Thread.new do
+ ready.wait
+ @lock.exclusive(purpose: :z) {}
+ end
+
+ recursive_yield_shares_thread = Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.yield_shares(compatible: [:y]) do
+ do_nesting.wait
+ @lock.sharing do
+ @lock.yield_shares(compatible: [:x, :y]) {}
+ end
+ after_nesting.wait
+ end
+ end
+ end
+
+ assert_threads_stuck incompatible_thread
+ do_nesting.count_down
+ assert_threads_stuck incompatible_thread
+
+ compatible_thread = Thread.new do
+ @lock.exclusive(purpose: :y) {}
+ end
+ assert_threads_not_stuck compatible_thread
+
+ post_nesting_incompatible_thread = Thread.new do
+ @lock.exclusive(purpose: :x) {}
+ end
+ assert_threads_stuck post_nesting_incompatible_thread
+
+ after_nesting.count_down
+ assert_threads_not_stuck recursive_yield_shares_thread
+ # post_nesting_incompatible_thread can now proceed
+ assert_threads_not_stuck post_nesting_incompatible_thread
+ # assert_threads_not_stuck can now proceed
+ assert_threads_not_stuck incompatible_thread
+ end
+
def test_in_shared_section_incompatible_non_upgrading_threads_cannot_preempt_upgrading_threads
scratch_pad = []
scratch_pad_mutex = Mutex.new
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 00d40c4497..a15d5c6a0e 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -395,6 +395,17 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal(-18_000, zone.utc_offset)
end
+ def test_utc_offset_is_not_cached_when_current_period_gets_stale
+ tz = ActiveSupport::TimeZone.create('Moscow')
+ travel_to(Time.utc(2014, 10, 25, 21)) do # 1 hour before TZ change
+ assert_equal 14400, tz.utc_offset, 'utc_offset should be initialized according to current_period'
+ end
+
+ travel_to(Time.utc(2014, 10, 25, 22)) do # after TZ change
+ assert_equal 10800, tz.utc_offset, 'utc_offset should not be cached when current_period gets stale'
+ end
+ end
+
def test_seconds_to_utc_offset_with_colon
assert_equal "-06:00", ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600)
assert_equal "+00:00", ActiveSupport::TimeZone.seconds_to_utc_offset(0)
@@ -491,6 +502,11 @@ class TimeZoneTest < ActiveSupport::TestCase
assert !ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Kuala Lumpur"])
end
+ def test_country_zones
+ assert ActiveSupport::TimeZone.country_zones("ru").include?(ActiveSupport::TimeZone["Moscow"])
+ assert !ActiveSupport::TimeZone.country_zones(:ru).include?(ActiveSupport::TimeZone["Kuala Lumpur"])
+ end
+
def test_to_yaml
assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n", ActiveSupport::TimeZone["Hawaii"].to_yaml)
assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Europe/London\n", ActiveSupport::TimeZone["Europe/London"].to_yaml)
diff --git a/activesupport/test/time_zone_test_helpers.rb b/activesupport/test/time_zone_test_helpers.rb
index 9632b89d09..eb6f7d0f85 100644
--- a/activesupport/test/time_zone_test_helpers.rb
+++ b/activesupport/test/time_zone_test_helpers.rb
@@ -13,4 +13,12 @@ module TimeZoneTestHelpers
ensure
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
+
+ def with_preserve_timezone(value)
+ old_preserve_tz = ActiveSupport.to_time_preserves_timezone
+ ActiveSupport.to_time_preserves_timezone = value
+ yield
+ ensure
+ ActiveSupport.to_time_preserves_timezone = old_preserve_tz
+ end
end
diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb
index f0067ca656..6e9ce7ac11 100644
--- a/activesupport/test/xml_mini/rexml_engine_test.rb
+++ b/activesupport/test/xml_mini/rexml_engine_test.rb
@@ -22,14 +22,24 @@ class REXMLEngineTest < ActiveSupport::TestCase
morning
</root>
eoxml
- assert_equal_rexml(io)
+ hash = ActiveSupport::XmlMini.parse(io)
+ assert hash.has_key?('root')
+ assert hash['root'].has_key?('products')
+ assert_match "good", hash['root']['__content__']
+ products = hash['root']['products']
+ assert products.has_key?("__content__")
+ assert_match 'hello everyone', products['__content__']
+ end
+
+ def test_parse_from_empty_string
+ ActiveSupport::XmlMini.backend = 'REXML'
+ assert_equal({}, ActiveSupport::XmlMini.parse(""))
+ end
+
+ def test_parse_from_frozen_string
+ ActiveSupport::XmlMini.backend = 'REXML'
+ xml_string = "<root></root>".freeze
+ assert_equal({"root" => {}}, ActiveSupport::XmlMini.parse(xml_string))
end
- private
- def assert_equal_rexml(xml)
- parsed_xml = ActiveSupport::XmlMini.parse(xml)
- xml.rewind if xml.respond_to?(:rewind)
- hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) }
- assert_equal(hash, parsed_xml)
- end
end
diff --git a/ci/travis.rb b/ci/travis.rb
index e9a3626b9a..b8891d6889 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -120,9 +120,9 @@ class Build
def env
if activesupport? && !isolated?
- # There is a known issue with the listen tests that casuses files to be
- # incorrectly GC'ed even when they are still in-use. The current is to
- # only run them in isolation to avoid randomly failing our test suite.
+ # There is a known issue with the listen tests that causes files to be
+ # incorrectly GC'ed even when they are still in-use. The current solution
+ # is to only run them in isolation to avoid randomly failing our test suite.
{ 'LISTEN' => '0' }
else
{}
@@ -148,6 +148,7 @@ ENV['GEM'].split(',').each do |gem|
next if ENV['TRAVIS_PULL_REQUEST'] && ENV['TRAVIS_PULL_REQUEST'] != 'false' && isolated
next if gem == 'railties' && isolated
next if gem == 'ac' && isolated
+ next if gem == 'ac:integration' && isolated
next if gem == 'aj:integration' && isolated
next if gem == 'guides' && isolated
@@ -157,20 +158,6 @@ ENV['GEM'].split(',').each do |gem|
end
end
-# puts
-# puts "Build environment:"
-# puts " #{`cat /etc/issue`}"
-# puts " #{`uname -a`}"
-# puts " #{`ruby -v`}"
-# puts " #{`mysql --version`}"
-# puts " #{`pg_config --version`}"
-# puts " SQLite3: #{`sqlite3 -version`}"
-# `gem env`.each_line {|line| print " #{line}"}
-# puts " Bundled gems:"
-# `bundle show`.each_line {|line| print " #{line}"}
-# puts " Local gems:"
-# `gem list`.each_line {|line| print " #{line}"}
-
failures = results.select { |key, value| !value }
if failures.empty?
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index aae405d5ac..2730d2dfea 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,23 +1,2 @@
-## Rails 5.0.0.beta1 (December 18, 2015) ##
-* Add code of conduct to contributing guide
-
- *Jon Moss*
-
-* New section in Configuring: Configuring Active Job
-
- *Eliot Sykes*
-
-* New section in Active Record Association Basics: Single Table Inheritance
-
- *Andrey Nering*
-
-* New section in Active Record Querying: Understanding The Method Chaining
-
- *Andrey Nering*
-
-* New section in Configuring: Search Engines Indexing
-
- *Andrey Nering*
-
-Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/guides/CHANGELOG.md) for previous changes.
+Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/guides/CHANGELOG.md) for previous changes.
diff --git a/guides/assets/images/favicon.ico b/guides/assets/images/favicon.ico
index faa10b4580..87192a8a07 100644
--- a/guides/assets/images/favicon.ico
+++ b/guides/assets/images/favicon.ico
Binary files differ
diff --git a/guides/assets/images/getting_started/rails_welcome.png b/guides/assets/images/getting_started/rails_welcome.png
index 4d0cb417b7..baccb11322 100644
--- a/guides/assets/images/getting_started/rails_welcome.png
+++ b/guides/assets/images/getting_started/rails_welcome.png
Binary files differ
diff --git a/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css
index 6d2edb2eb8..bc7afd3898 100644
--- a/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css
@@ -90,7 +90,7 @@
}
.syntaxhighlighter .script {
color: #222 !important;
- background-color: none !important;
+ background-color: transparent !important;
}
.syntaxhighlighter .color1, .syntaxhighlighter .color1 a {
color: gray !important;
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 58ba708a39..604cf4be41 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -8,7 +8,7 @@ end
gemfile(true) do
source 'https://rubygems.org'
# Activate the gem you are reporting the issue against.
- gem 'rails', '4.2.0'
+ gem 'rails', '5.0.0.rc1'
end
require 'rack/test'
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index 3f24aa3b4d..8322707495 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -8,11 +8,6 @@ end
gemfile(true) do
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
- gem 'arel', github: 'rails/arel'
- gem 'rack', github: 'rack/rack'
- gem 'sprockets', github: 'rails/sprockets'
- gem 'sprockets-rails', github: 'rails/sprockets-rails'
- gem 'sass-rails', github: 'rails/sass-rails'
end
require 'action_controller/railtie'
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index 09d6e7b331..d4ede4e2c1 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -8,7 +8,7 @@ end
gemfile(true) do
source 'https://rubygems.org'
# Activate the gem you are reporting the issue against.
- gem 'activerecord', '4.2.0'
+ gem 'activerecord', '5.0.0.rc1'
gem 'sqlite3'
end
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index 5b742a9093..6fd401bd50 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -8,11 +8,6 @@ end
gemfile(true) do
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
- gem 'arel', github: 'rails/arel'
- gem 'rack', github: 'rack/rack'
- gem 'sprockets', github: 'rails/sprockets'
- gem 'sprockets-rails', github: 'rails/sprockets-rails'
- gem 'sass-rails', github: 'rails/sass-rails'
gem 'sqlite3'
end
@@ -25,10 +20,10 @@ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:'
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
- create_table :posts, force: true do |t|
+ create_table :posts, force: true do |t|
end
- create_table :comments, force: true do |t|
+ create_table :comments, force: true do |t|
t.integer :post_id
end
end
diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb
index a4fe51156d..bb8af3be18 100644
--- a/guides/bug_report_templates/generic_gem.rb
+++ b/guides/bug_report_templates/generic_gem.rb
@@ -8,7 +8,7 @@ end
gemfile(true) do
source 'https://rubygems.org'
# Activate the gem you are reporting the issue against.
- gem 'activesupport', '4.2.0'
+ gem 'activesupport', '5.0.0.rc1'
end
require 'active_support/core_ext/object/blank'
diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb
index 0a8048cc48..70cf931f34 100644
--- a/guides/bug_report_templates/generic_master.rb
+++ b/guides/bug_report_templates/generic_master.rb
@@ -8,20 +8,12 @@ end
gemfile(true) do
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
- gem 'arel', github: 'rails/arel'
- gem 'rack', github: 'rack/rack'
- gem 'sprockets', github: 'rails/sprockets'
- gem 'sprockets-rails', github: 'rails/sprockets-rails'
- gem 'sass-rails', github: 'rails/sass-rails'
end
require 'active_support'
require 'active_support/core_ext/object/blank'
require 'minitest/autorun'
-# Ensure backward compatibility with Minitest 4
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
class BugTest < Minitest::Test
def test_stuff
assert "zomg".present?
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index be00087f63..79634d8760 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -3,7 +3,7 @@
Ruby on Rails 2.2 Release Notes
===============================
-Rails 2.2 delivers a number of new and improved features. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/2-2-stable) in the main Rails repository on GitHub.
+Rails 2.2 delivers a number of new and improved features. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](https://github.com/rails/rails/commits/2-2-stable) in the main Rails repository on GitHub.
Along with Rails, 2.2 marks the launch of the [Ruby on Rails Guides](http://guides.rubyonrails.org/), the first results of the ongoing [Rails Guides hackfest](http://hackfest.rubyonrails.org/guide). This site will deliver high-quality documentation of the major features of Rails.
@@ -21,8 +21,8 @@ Rails 2.2 supplies an easy system for internationalization (or i18n, for those o
* Lead Contributors: Rails i18 Team
* More information :
* [Official Rails i18 website](http://rails-i18n.org)
- * [Finally. Ruby on Rails gets internationalized](http://www.artweb-design.de/2008/7/18/finally-ruby-on-rails-gets-internationalized)
- * [Localizing Rails : Demo application](http://github.com/clemens/i18n_demo_app)
+ * [Finally. Ruby on Rails gets internationalized](https://web.archive.org/web/20140407075019/http://www.artweb-design.de/2008/7/18/finally-ruby-on-rails-gets-internationalized)
+ * [Localizing Rails : Demo application](https://github.com/clemens/i18n_demo_app)
### Compatibility with Ruby 1.9 and JRuby
@@ -59,7 +59,7 @@ rake doc:guides
This will put the guides inside `Rails.root/doc/guides` and you may start surfing straight away by opening `Rails.root/doc/guides/index.html` in your favourite browser.
* Lead Contributors: [Rails Documentation Team](credits.html)
-* Major contributions from [Xavier Noria":http://advogato.org/person/fxn/diary.html and "Hongli Lai](http://izumi.plan99.net/blog/.)
+* Major contributions from [Xavier Noria](http://advogato.org/person/fxn/diary.html) and [Hongli Lai](http://izumi.plan99.net/blog/).
* More information:
* [Rails Guides hackfest](http://hackfest.rubyonrails.org/guide)
* [Help improve Rails documentation on Git branch](http://weblog.rubyonrails.org/2008/5/2/help-improve-rails-documentation-on-git-branch)
@@ -146,7 +146,7 @@ development:
* Lead Contributor: [Nick Sieger](http://blog.nicksieger.com/)
* More information:
- * [What's New in Edge Rails: Connection Pools](http://ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-connection-pools)
+ * [What's New in Edge Rails: Connection Pools](http://archives.ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-connection-pools)
### Hashes for Join Table Conditions
@@ -166,7 +166,7 @@ Product.all(:joins => :photos, :conditions => { :photos => { :copyright => false
```
* More information:
- * [What's New in Edge Rails: Easy Join Table Conditions](http://ryandaigle.com/articles/2008/7/7/what-s-new-in-edge-rails-easy-join-table-conditions)
+ * [What's New in Edge Rails: Easy Join Table Conditions](http://archives.ryandaigle.com/articles/2008/7/7/what-s-new-in-edge-rails-easy-join-table-conditions)
### New Dynamic Finders
@@ -239,7 +239,7 @@ This will enable recognition of (among others) these routes:
* Lead Contributor: [S. Brent Faulkner](http://www.unwwwired.net/)
* More information:
* [Rails Routing from the Outside In](routing.html#nested-resources)
- * [What's New in Edge Rails: Shallow Routes](http://ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-shallow-routes)
+ * [What's New in Edge Rails: Shallow Routes](http://archives.ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-shallow-routes)
### Method Arrays for Member or Collection Routes
@@ -287,7 +287,7 @@ Action Mailer
Action Mailer now supports mailer layouts. You can make your HTML emails as pretty as your in-browser views by supplying an appropriately-named layout - for example, the `CustomerMailer` class expects to use `layouts/customer_mailer.html.erb`.
* More information:
- * [What's New in Edge Rails: Mailer Layouts](http://ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-mailer-layouts)
+ * [What's New in Edge Rails: Mailer Layouts](http://archives.ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-mailer-layouts)
Action Mailer now offers built-in support for GMail's SMTP servers, by turning on STARTTLS automatically. This requires Ruby 1.8.7 to be installed.
@@ -321,7 +321,7 @@ Other features of memoization include `unmemoize`, `unmemoize_all`, and `memoize
* Lead Contributor: [Josh Peek](http://joshpeek.com/)
* More information:
- * [What's New in Edge Rails: Easy Memoization](http://ryandaigle.com/articles/2008/7/16/what-s-new-in-edge-rails-memoization)
+ * [What's New in Edge Rails: Easy Memoization](http://archives.ryandaigle.com/articles/2008/7/16/what-s-new-in-edge-rails-memoization)
* [Memo-what? A Guide to Memoization](http://www.railway.at/articles/2008/09/20/a-guide-to-memoization)
### each_with_object
@@ -389,9 +389,9 @@ To avoid deployment issues and make Rails applications more self-contained, it's
You can unpack or install a single gem by specifying `GEM=_gem_name_` on the command line.
-* Lead Contributor: [Matt Jones](http://github.com/al2o3cr)
+* Lead Contributor: [Matt Jones](https://github.com/al2o3cr)
* More information:
- * [What's New in Edge Rails: Gem Dependencies](http://ryandaigle.com/articles/2008/4/1/what-s-new-in-edge-rails-gem-dependencies)
+ * [What's New in Edge Rails: Gem Dependencies](http://archives.ryandaigle.com/articles/2008/4/1/what-s-new-in-edge-rails-gem-dependencies)
* [Rails 2.1.2 and 2.2RC1: Update Your RubyGems](http://afreshcup.com/2008/10/25/rails-212-and-22rc1-update-your-rubygems/)
* [Detailed discussion on Lighthouse](http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/1128)
@@ -411,7 +411,7 @@ Deprecated
A few pieces of older code are deprecated in this release:
* `Rails::SecretKeyGenerator` has been replaced by `ActiveSupport::SecureRandom`
-* `render_component` is deprecated. There's a [render_components plugin](http://github.com/rails/render_component/tree/master) available if you need this functionality.
+* `render_component` is deprecated. There's a [render_components plugin](https://github.com/rails/render_component/tree/master) available if you need this functionality.
* Implicit local assignments when rendering partials has been deprecated.
```ruby
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md
index 0a62f34371..06761b67bb 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -3,7 +3,7 @@
Ruby on Rails 2.3 Release Notes
===============================
-Rails 2.3 delivers a variety of new and improved features, including pervasive Rack integration, refreshed support for Rails Engines, nested transactions for Active Record, dynamic and default scopes, unified rendering, more efficient routing, application templates, and quiet backtraces. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/2-3-stable) in the main Rails repository on GitHub or review the `CHANGELOG` files for the individual Rails components.
+Rails 2.3 delivers a variety of new and improved features, including pervasive Rack integration, refreshed support for Rails Engines, nested transactions for Active Record, dynamic and default scopes, unified rendering, more efficient routing, application templates, and quiet backtraces. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](https://github.com/rails/rails/commits/2-3-stable) in the main Rails repository on GitHub or review the `CHANGELOG` files for the individual Rails components.
--------------------------------------------------------------------------------
@@ -125,14 +125,14 @@ Order.scoped_by_customer_id(12).scoped_by_status("open")
There's nothing to define to use dynamic scopes: they just work.
* Lead Contributor: [Yaroslav Markin](http://evilmartians.com/)
-* More Information: [What's New in Edge Rails: Dynamic Scope Methods](http://ryandaigle.com/articles/2008/12/29/what-s-new-in-edge-rails-dynamic-scope-methods.)
+* More Information: [What's New in Edge Rails: Dynamic Scope Methods](http://archives.ryandaigle.com/articles/2008/12/29/what-s-new-in-edge-rails-dynamic-scope-methods)
### Default Scopes
Rails 2.3 will introduce the notion of _default scopes_ similar to named scopes, but applying to all named scopes or find methods within the model. For example, you can write `default_scope :order => 'name ASC'` and any time you retrieve records from that model they'll come out sorted by name (unless you override the option, of course).
* Lead Contributor: Paweł Kondzior
-* More Information: [What's New in Edge Rails: Default Scoping](http://ryandaigle.com/articles/2008/11/18/what-s-new-in-edge-rails-default-scoping)
+* More Information: [What's New in Edge Rails: Default Scoping](http://archives.ryandaigle.com/articles/2008/11/18/what-s-new-in-edge-rails-default-scoping)
### Batch Processing
@@ -158,7 +158,7 @@ Note that you should only use this method for batch processing: for small number
* More Information (at that point the convenience method was called just `each`):
* [Rails 2.3: Batch Finding](http://afreshcup.com/2009/02/23/rails-23-batch-finding/)
- * [What's New in Edge Rails: Batched Find](http://ryandaigle.com/articles/2009/2/23/what-s-new-in-edge-rails-batched-find)
+ * [What's New in Edge Rails: Batched Find](http://archives.ryandaigle.com/articles/2009/2/23/what-s-new-in-edge-rails-batched-find)
### Multiple Conditions for Callbacks
@@ -179,7 +179,7 @@ developers = Developer.find(:all, :group => "salary",
:having => "sum(salary) > 10000", :select => "salary")
```
-* Lead Contributor: [Emilio Tagua](http://github.com/miloops)
+* Lead Contributor: [Emilio Tagua](https://github.com/miloops)
### Reconnecting MySQL Connections
@@ -235,7 +235,7 @@ If you're one of the people who has always been bothered by the special-case nam
* More Information:
* [The Death of Application.rb](http://afreshcup.com/2008/11/17/rails-2x-the-death-of-applicationrb/)
- * [What's New in Edge Rails: Application.rb Duality is no More](http://ryandaigle.com/articles/2008/11/19/what-s-new-in-edge-rails-application-rb-duality-is-no-more)
+ * [What's New in Edge Rails: Application.rb Duality is no More](http://archives.ryandaigle.com/articles/2008/11/19/what-s-new-in-edge-rails-application-rb-duality-is-no-more)
### HTTP Digest Authentication Support
@@ -261,7 +261,7 @@ end
```
* Lead Contributor: [Gregg Kellogg](http://www.kellogg-assoc.com/)
-* More Information: [What's New in Edge Rails: HTTP Digest Authentication](http://ryandaigle.com/articles/2009/1/30/what-s-new-in-edge-rails-http-digest-authentication)
+* More Information: [What's New in Edge Rails: HTTP Digest Authentication](http://archives.ryandaigle.com/articles/2009/1/30/what-s-new-in-edge-rails-http-digest-authentication)
### More Efficient Routing
@@ -377,8 +377,8 @@ You can write this view in Rails 2.3:
* Lead Contributor: [Eloy Duran](http://superalloy.nl/)
* More Information:
* [Nested Model Forms](http://weblog.rubyonrails.org/2009/1/26/nested-model-forms)
- * [complex-form-examples](http://github.com/alloy/complex-form-examples)
- * [What's New in Edge Rails: Nested Object Forms](http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes)
+ * [complex-form-examples](https://github.com/alloy/complex-form-examples)
+ * [What's New in Edge Rails: Nested Object Forms](http://archives.ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes)
### Smart Rendering of Partials
@@ -394,7 +394,7 @@ render @article
render @articles
```
-* More Information: [What's New in Edge Rails: render Stops Being High-Maintenance](http://ryandaigle.com/articles/2008/11/20/what-s-new-in-edge-rails-render-stops-being-high-maintenance)
+* More Information: [What's New in Edge Rails: render Stops Being High-Maintenance](http://archives.ryandaigle.com/articles/2008/11/20/what-s-new-in-edge-rails-render-stops-being-high-maintenance)
### Prompts for Date Select Helpers
@@ -421,7 +421,7 @@ You're likely familiar with Rails' practice of adding timestamps to static asset
Asset hosts get more flexible in edge Rails with the ability to declare an asset host as a specific object that responds to a call. This allows you to implement any complex logic you need in your asset hosting.
-* More Information: [asset-hosting-with-minimum-ssl](http://github.com/dhh/asset-hosting-with-minimum-ssl/tree/master)
+* More Information: [asset-hosting-with-minimum-ssl](https://github.com/dhh/asset-hosting-with-minimum-ssl/tree/master)
### grouped_options_for_select Helper Method
@@ -498,7 +498,7 @@ Active Support has a few interesting changes, including the introduction of `Obj
A lot of folks have adopted the notion of using try() to attempt operations on objects. It's especially helpful in views where you can avoid nil-checking by writing code like `<%= @person.try(:name) %>`. Well, now it's baked right into Rails. As implemented in Rails, it raises `NoMethodError` on private methods and always returns `nil` if the object is nil.
-* More Information: [try()](http://ozmm.org/posts/try.html.)
+* More Information: [try()](http://ozmm.org/posts/try.html)
### Object#tap Backport
@@ -533,7 +533,7 @@ If you look up the spec on the "json.org" site, you'll discover that all keys in
### Other Active Support Changes
* You can use `Enumerable#none?` to check that none of the elements match the supplied block.
-* If you're using Active Support [delegates](http://afreshcup.com/2008/10/19/coming-in-rails-22-delegate-prefixes/,) the new `:allow_nil` option lets you return `nil` instead of raising an exception when the target object is nil.
+* If you're using Active Support [delegates](http://afreshcup.com/2008/10/19/coming-in-rails-22-delegate-prefixes/) the new `:allow_nil` option lets you return `nil` instead of raising an exception when the target object is nil.
* `ActiveSupport::OrderedHash`: now implements `each_key` and `each_value`.
* `ActiveSupport::MessageEncryptor` provides a simple way to encrypt information for storage in an untrusted location (like cookies).
* Active Support's `from_xml` no longer depends on XmlSimple. Instead, Rails now includes its own XmlMini implementation, with just the functionality that it requires. This lets Rails dispense with the bundled copy of XmlSimple that it's been carting around.
@@ -555,11 +555,11 @@ Rails Metal is a new mechanism that provides superfast endpoints inside of your
* [Introducing Rails Metal](http://weblog.rubyonrails.org/2008/12/17/introducing-rails-metal)
* [Rails Metal: a micro-framework with the power of Rails](http://soylentfoo.jnewland.com/articles/2008/12/16/rails-metal-a-micro-framework-with-the-power-of-rails-m)
* [Metal: Super-fast Endpoints within your Rails Apps](http://www.railsinside.com/deployment/180-metal-super-fast-endpoints-within-your-rails-apps.html)
- * [What's New in Edge Rails: Rails Metal](http://ryandaigle.com/articles/2008/12/18/what-s-new-in-edge-rails-rails-metal)
+ * [What's New in Edge Rails: Rails Metal](http://archives.ryandaigle.com/articles/2008/12/18/what-s-new-in-edge-rails-rails-metal)
### Application Templates
-Rails 2.3 incorporates Jeremy McAnally's [rg](http://github.com/jeremymcanally/rg/tree/master) application generator. What this means is that we now have template-based application generation built right into Rails; if you have a set of plugins you include in every application (among many other use cases), you can just set up a template once and use it over and over again when you run the `rails` command. There's also a rake task to apply a template to an existing application:
+Rails 2.3 incorporates Jeremy McAnally's [rg](https://github.com/jm/rg) application generator. What this means is that we now have template-based application generation built right into Rails; if you have a set of plugins you include in every application (among many other use cases), you can just set up a template once and use it over and over again when you run the `rails` command. There's also a rake task to apply a template to an existing application:
```
rake rails:template LOCATION=~/template.rb
@@ -572,7 +572,7 @@ This will layer the changes from the template on top of whatever code the projec
### Quieter Backtraces
-Building on Thoughtbot's [Quiet Backtrace](https://github.com/thoughtbot/quietbacktrace) plugin, which allows you to selectively remove lines from `Test::Unit` backtraces, Rails 2.3 implements `ActiveSupport::BacktraceCleaner` and `Rails::BacktraceCleaner` in core. This supports both filters (to perform regex-based substitutions on backtrace lines) and silencers (to remove backtrace lines entirely). Rails automatically adds silencers to get rid of the most common noise in a new application, and builds a `config/backtrace_silencers.rb` file to hold your own additions. This feature also enables prettier printing from any gem in the backtrace.
+Building on thoughtbot's [Quiet Backtrace](https://github.com/thoughtbot/quietbacktrace) plugin, which allows you to selectively remove lines from `Test::Unit` backtraces, Rails 2.3 implements `ActiveSupport::BacktraceCleaner` and `Rails::BacktraceCleaner` in core. This supports both filters (to perform regex-based substitutions on backtrace lines) and silencers (to remove backtrace lines entirely). Rails automatically adds silencers to get rid of the most common noise in a new application, and builds a `config/backtrace_silencers.rb` file to hold your own additions. This feature also enables prettier printing from any gem in the backtrace.
### Faster Boot Time in Development Mode with Lazy Loading/Autoload
@@ -605,8 +605,8 @@ Deprecated
A few pieces of older code are deprecated in this release:
-* If you're one of the (fairly rare) Rails developers who deploys in a fashion that depends on the inspector, reaper, and spawner scripts, you'll need to know that those scripts are no longer included in core Rails. If you need them, you'll be able to pick up copies via the [irs_process_scripts](http://github.com/rails/irs_process_scripts/tree) plugin.
-* `render_component` goes from "deprecated" to "nonexistent" in Rails 2.3. If you still need it, you can install the [render_component plugin](http://github.com/rails/render_component/tree/master).
+* If you're one of the (fairly rare) Rails developers who deploys in a fashion that depends on the inspector, reaper, and spawner scripts, you'll need to know that those scripts are no longer included in core Rails. If you need them, you'll be able to pick up copies via the [irs_process_scripts](https://github.com/rails/irs_process_scripts/tree) plugin.
+* `render_component` goes from "deprecated" to "nonexistent" in Rails 2.3. If you still need it, you can install the [render_component plugin](https://github.com/rails/render_component/tree/master).
* Support for Rails components has been removed.
* If you were one of the people who got used to running `script/performance/request` to look at performance based on integration tests, you need to learn a new trick: that script has been removed from core Rails now. There's a new request_profiler plugin that you can install to get the exact same functionality back.
* `ActionController::Base#session_enabled?` is deprecated because sessions are lazy-loaded now.
@@ -614,7 +614,7 @@ A few pieces of older code are deprecated in this release:
* Some integration test helpers have been removed. `response.headers["Status"]` and `headers["Status"]` will no longer return anything. Rack does not allow "Status" in its return headers. However you can still use the `status` and `status_message` helpers. `response.headers["cookie"]` and `headers["cookie"]` will no longer return any CGI cookies. You can inspect `headers["Set-Cookie"]` to see the raw cookie header or use the `cookies` helper to get a hash of the cookies sent to the client.
* `formatted_polymorphic_url` is deprecated. Use `polymorphic_url` with `:format` instead.
* The `:http_only` option in `ActionController::Response#set_cookie` has been renamed to `:httponly`.
-* The `:connector` and `:skip_last_comma` options of `to_sentence` have been replaced by `:words_connnector`, `:two_words_connector`, and `:last_word_connector` options.
+* The `:connector` and `:skip_last_comma` options of `to_sentence` have been replaced by `:words_connector`, `:two_words_connector`, and `:last_word_connector` options.
* Posting a multipart form with an empty `file_field` control used to submit an empty string to the controller. Now it submits a nil, due to differences between Rack's multipart parser and the old Rails one.
Credits
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index 696493a3cf..49d37ba489 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -17,7 +17,7 @@ Even if you don't give a hoot about any of our internal cleanups, Rails 3.0 is g
On top of all that, we've tried our best to deprecate the old APIs with nice warnings. That means that you can move your existing application to Rails 3 without immediately rewriting all your old code to the latest best practices.
-These release notes cover the major upgrades, but don't include every little bug fix and change. Rails 3.0 consists of almost 4,000 commits by more than 250 authors! If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/3-0-stable) in the main Rails repository on GitHub.
+These release notes cover the major upgrades, but don't include every little bug fix and change. Rails 3.0 consists of almost 4,000 commits by more than 250 authors! If you want to see everything, check out the [list of commits](https://github.com/rails/rails/commits/3-0-stable) in the main Rails repository on GitHub.
--------------------------------------------------------------------------------
@@ -63,7 +63,7 @@ The `config.gem` method is gone and has been replaced by using `bundler` and a `
### Upgrade Process
-To help with the upgrade process, a plugin named [Rails Upgrade](http://github.com/rails/rails_upgrade) has been created to automate part of it.
+To help with the upgrade process, a plugin named [Rails Upgrade](https://github.com/rails/rails_upgrade) has been created to automate part of it.
Simply install the plugin, then run `rake rails:upgrade:check` to check your app for pieces that need to be updated (with links to information on how to update them). It also offers a task to generate a `Gemfile` based on your current `config.gem` calls and a task to generate a new routes file from your current one. To get the plugin, simply run the following:
@@ -86,7 +86,7 @@ $ cd myapp
### Vendoring Gems
-Rails now uses a `Gemfile` in the application root to determine the gems you require for your application to start. This `Gemfile` is processed by the [Bundler](http://github.com/carlhuda/bundler,) which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems.
+Rails now uses a `Gemfile` in the application root to determine the gems you require for your application to start. This `Gemfile` is processed by the [Bundler](https://github.com/bundler/bundler) which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems.
More information: - [bundler homepage](http://bundler.io/)
@@ -138,14 +138,14 @@ More Information: - [Rails Edge Architecture](http://yehudakatz.com/2009/06/11/r
### Arel Integration
-[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.
+[Arel](https://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](https://web.archive.org/web/20120718093140/http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/)
### Mail Extraction
-Action Mailer ever since its beginnings has had monkey patches, pre parsers and even delivery and receiver agents, all in addition to having TMail vendored in the source tree. Version 3 changes that with all email message related functionality abstracted out to the [Mail](http://github.com/mikel/mail) gem. This again reduces code duplication and helps create definable boundaries between Action Mailer and the email parser.
+Action Mailer ever since its beginnings has had monkey patches, pre parsers and even delivery and receiver agents, all in addition to having TMail vendored in the source tree. Version 3 changes that with all email message related functionality abstracted out to the [Mail](https://github.com/mikel/mail) gem. This again reduces code duplication and helps create definable boundaries between Action Mailer and the email parser.
More information: - [New Action Mailer API in Rails 3](http://lindsaar.net/2010/1/26/new-actionmailer-api-in-rails-3)
@@ -161,7 +161,7 @@ More Information: - [Rails Documentation Projects](http://weblog.rubyonrails.org
Internationalization
--------------------
-A large amount of work has been done with I18n support in Rails 3, including the latest [I18n](http://github.com/svenfuchs/i18n) gem supplying many speed improvements.
+A large amount of work has been done with I18n support in Rails 3, including the latest [I18n](https://github.com/svenfuchs/i18n) gem supplying many speed improvements.
* I18n for any object - I18n behavior can be added to any object by including `ActiveModel::Translation` and `ActiveModel::Validations`. There is also an `errors.messages` fallback for translations.
* Attributes can have default translations.
@@ -213,7 +213,6 @@ Railties now deprecates:
More information:
* [Discovering Rails 3 generators](http://blog.plataformatec.com.br/2010/01/discovering-rails-3-generators)
-* [Making Generators for Rails 3 with Thor](http://caffeinedd.com/guides/331-making-generators-for-rails-3-with-thor)
* [The Rails Module (in Rails 3)](http://litanyagainstfear.com/blog/2010/02/03/the-rails-module/)
Action Pack
@@ -250,7 +249,7 @@ Deprecations:
More Information:
-* [Render Options in Rails 3](http://www.engineyard.com/blog/2010/render-options-in-rails-3/)
+* [Render Options in Rails 3](https://blog.engineyard.com/2010/render-options-in-rails-3)
* [Three reasons to love ActionController::Responder](http://weblog.rubyonrails.org/2009/8/31/three-reasons-love-responder)
@@ -310,7 +309,7 @@ More Information:
Major re-write was done in the Action View helpers, implementing Unobtrusive JavaScript (UJS) hooks and removing the old inline AJAX commands. This enables Rails to use any compliant UJS driver to implement the UJS hooks in the helpers.
-What this means is that all previous `remote_<method>` helpers have been removed from Rails core and put into the [Prototype Legacy Helper](http://github.com/rails/prototype_legacy_helper). To get UJS hooks into your HTML, you now pass `:remote => true` instead. For example:
+What this means is that all previous `remote_<method>` helpers have been removed from Rails core and put into the [Prototype Legacy Helper](https://github.com/rails/prototype_legacy_helper). To get UJS hooks into your HTML, you now pass `:remote => true` instead. For example:
```ruby
form_for @post, :remote => true
@@ -576,7 +575,7 @@ The following methods have been removed because they are no longer used in the f
Action Mailer
-------------
-Action Mailer has been given a new API with TMail being replaced out with the new [Mail](http://github.com/mikel/mail) as the email library. Action Mailer itself has been given an almost complete re-write with pretty much every line of code touched. The result is that Action Mailer now simply inherits from Abstract Controller and wraps the Mail gem in a Rails DSL. This reduces the amount of code and duplication of other libraries in Action Mailer considerably.
+Action Mailer has been given a new API with TMail being replaced out with the new [Mail](https://github.com/mikel/mail) as the email library. Action Mailer itself has been given an almost complete re-write with pretty much every line of code touched. The result is that Action Mailer now simply inherits from Abstract Controller and wraps the Mail gem in a Rails DSL. This reduces the amount of code and duplication of other libraries in Action Mailer considerably.
* All mailers are now in `app/mailers` by default.
* Can now send email using new API with three methods: `attachments`, `headers` and `mail`.
diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md
index 327495704a..feee0f9920 100644
--- a/guides/source/3_1_release_notes.md
+++ b/guides/source/3_1_release_notes.md
@@ -558,4 +558,4 @@ Credits
See the [full list of contributors to Rails](http://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them.
-Rails 3.1 Release Notes were compiled by [Vijay Dev](https://github.com/vijaydev.)
+Rails 3.1 Release Notes were compiled by [Vijay Dev](https://github.com/vijaydev)
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index b9444510ea..4615cf18e6 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -108,7 +108,7 @@ In Rails 4.0, several features have been extracted into gems. You can simply add
* Mass assignment protection in Active Record models ([GitHub](https://github.com/rails/protected_attributes), [Pull Request](https://github.com/rails/rails/pull/7251))
* ActiveRecord::SessionStore ([GitHub](https://github.com/rails/activerecord-session_store), [Pull Request](https://github.com/rails/rails/pull/7436))
* Active Record Observers ([GitHub](https://github.com/rails/rails-observers), [Commit](https://github.com/rails/rails/commit/39e85b3b90c58449164673909a6f1893cba290b2))
-* Active Resource ([GitHub](https://github.com/rails/activeresource), [Pull Request](https://github.com/rails/rails/pull/572), [Blog](http://yetimedia.tumblr.com/post/35233051627/activeresource-is-dead-long-live-activeresource))
+* Active Resource ([GitHub](https://github.com/rails/activeresource), [Pull Request](https://github.com/rails/rails/pull/572), [Blog](http://yetimedia-blog-blog.tumblr.com/post/35233051627/activeresource-is-dead-long-live-activeresource))
* Action Caching ([GitHub](https://github.com/rails/actionpack-action_caching), [Pull Request](https://github.com/rails/rails/pull/7833))
* Page Caching ([GitHub](https://github.com/rails/actionpack-page_caching), [Pull Request](https://github.com/rails/rails/pull/7833))
* Sprockets ([GitHub](https://github.com/rails/sprockets-rails))
diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md
index 8a59007420..a30bfc458a 100644
--- a/guides/source/4_2_release_notes.md
+++ b/guides/source/4_2_release_notes.md
@@ -405,7 +405,7 @@ Please refer to the [Changelog][railties] for detailed changes.
url: http://localhost:3001
namespace: my_app_development
- # config/production.rb
+ # config/environments/production.rb
Rails.application.configure do
config.middleware.use ExceptionNotifier, config_for(:exception_notification)
end
@@ -871,7 +871,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.
+* New [guide](autoloading_and_reloading_constants.html) about constant autoloading and reloading.
Credits
-------
diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md
index 2650384df3..8bd2074e82 100644
--- a/guides/source/5_0_release_notes.md
+++ b/guides/source/5_0_release_notes.md
@@ -37,9 +37,20 @@ Major Features
--------------
### Action Cable
-[Pull Request](https://github.com/rails/rails/pull/22586)
-ToDo...
+Action Cable is a new framework in Rails 5. It seamlessly integrates
+[WebSockets](https://en.wikipedia.org/wiki/WebSocket) with the rest of your
+Rails application.
+
+Action Cable allows for real-time features to be written in Ruby in the
+same style and form as the rest of your Rails application, while still being
+performant and scalable. It's a full-stack offering that provides both a
+client-side JavaScript framework and a server-side Ruby framework. You have
+access to your full domain model written with Active Record or your ORM of
+choice.
+
+See the [Active Cable Overview](action_cable_overview.html) guide for more
+information.
### Rails API
[Pull Request](https://github.com/rails/rails/pull/19832)
@@ -48,7 +59,74 @@ ToDo...
### Active Record attributes API
-ToDo...
+Defines an attribute with a type on a 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::Base.where`, which lets use our domain objects across much of Active Record,
+without having to rely on implementation details or monkey patching.
+
+Some things that you can achieve with this:
+* The type detected by Active Record can be overridden.
+* A default can also be provided.
+* Attributes do not need to be backed by a database column.
+
+```ruby
+
+# db/schema.rb
+create_table :store_listings, force: true do |t|
+ t.decimal :price_in_cents
+ t.string :my_string, default: "original default"
+end
+
+# app/models/store_listing.rb
+class StoreListing < ActiveRecord::Base
+end
+
+store_listing = StoreListing.new(price_in_cents: '10.1')
+
+# before
+store_listing.price_in_cents # => BigDecimal.new(10.1)
+StoreListing.new.my_string # => "original default"
+
+class StoreListing < ActiveRecord::Base
+ attribute :price_in_cents, :integer # custom type
+ attribute :my_string, :string, default: "new default" # default value
+ attribute :my_default_proc, :datetime, default: -> { Time.now } # default value
+ attribute :field_without_db_column, :integer, array: true
+end
+
+# after
+store_listing.price_in_cents # => 10
+StoreListing.new.my_string # => "new default"
+StoreListing.new.my_default_proc # => 2015-05-30 11:04:48 -0600
+model = StoreListing.new(field_without_db_column: ["1", "2", "3"])
+model.attributes #=> {field_without_db_column: [1, 2, 3]}
+```
+
+**Creating Custom Types:**
+
+You can define your 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. This is useful, for example, when doing custom conversion,
+like Money data.
+
+**Querying:**
+
+When `ActiveRecord::Base.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.
+
+This gives the objects ability to specify, how to convert values when performing SQL queries.
+
+**Dirty Tracking:**
+
+The type of an attribute is given the opportunity to change how dirty
+tracking is performed.
+
+See its
+[documentation](http://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html)
+for a detailed write up.
+
### Test Runner
[Pull Request](https://github.com/rails/rails/pull/19216)
@@ -94,6 +172,10 @@ Please refer to the [Changelog][railties] for detailed changes.
* Deprecated `config.serve_static_files` in favor of `config.public_file_server.enabled`.
([Pull Request](https://github.com/rails/rails/pull/22173))
+* Deprecated the tasks in the `rails` task namespace in favor of the `app` namespace.
+ (e.g. `rails:update` and `rails:template` tasks is renamed to `app:update` and `app:template`.)
+ ([Pull Request](https://github.com/rails/rails/pull/23439))
+
### Notable changes
* Added Rails test runner `bin/rails test`.
@@ -120,6 +202,32 @@ Please refer to the [Changelog][railties] for detailed changes.
([Pull Request](https://github.com/rails/rails/pull/22457),
[Pull Request](https://github.com/rails/rails/pull/22288))
+* New applications are generated with the evented file system monitor enabled
+ on Linux and Mac OS X. The feature can be opted out by passing
+ `--skip-listen` to the generator.
+ ([commit](https://github.com/rails/rails/commit/de6ad5665d2679944a9ee9407826ba88395a1003),
+ [commit](https://github.com/rails/rails/commit/94dbc48887bf39c241ee2ce1741ee680d773f202))
+
+* Generate applications with an option to log to STDOUT in production
+ using the environment variable `RAILS_LOG_TO_STDOUT`.
+ ([Pull Request](https://github.com/rails/rails/pull/23734))
+
+* Enable HSTS with IncludeSudomains header for new applications.
+ ([Pull Request](https://github.com/rails/rails/pull/23852))
+
+* The application generator writes a new file `config/spring.rb`, which tells
+ Spring to watch additional common files.
+ ([commit](https://github.com/rails/rails/commit/b04d07337fd7bc17e88500e9d6bcd361885a45f8))
+
+
+* Added `--skip-action-mailer` to skip Action Mailer while generating new app.
+ ([Pull Request](https://github.com/rails/rails/pull/18288))
+
+* Removed `tmp/sessions` directory and the clear rake task associated with it.
+ ([Pull Request](https://github.com/rails/rails/pull/18314))
+
+* Changed `_form.html.erb` generated by scaffold generator to use local variables.
+ ([Pull Request](https://github.com/rails/rails/pull/13434))
Action Pack
-----------
@@ -206,6 +314,12 @@ Please refer to the [Changelog][action-pack] for detailed changes.
`RedirectBackError`.
([Pull Request](https://github.com/rails/rails/pull/22506))
+* `ActionDispatch::IntegrationTest` and `ActionController::TestCase` deprecate positional arguments in favor of
+ keyword arguments. ([Pull Request](https://github.com/rails/rails/pull/18323))
+
+* Deprecated `:controller` and `:action` path parameters.
+ ([Pull Request](https://github.com/rails/rails/pull/23980))
+
### Notable changes
* Added `ActionController::Renderer` to render arbitrary templates
@@ -249,10 +363,37 @@ Please refer to the [Changelog][action-pack] for detailed changes.
* Changed the `protect_from_forgery` prepend default to `false`.
([commit](https://github.com/rails/rails/commit/39794037817703575c35a75f1961b01b83791191))
-* `ActionController::TestCase` will be moved to it's own gem in Rails 5.1. Use
+* `ActionController::TestCase` will be moved to its own gem in Rails 5.1. Use
`ActionDispatch::IntegrationTest` instead.
([commit](https://github.com/rails/rails/commit/4414c5d1795e815b102571425974a8b1d46d932d))
+* Rails will only generate "weak", instead of strong ETags.
+ ([Pull Request](https://github.com/rails/rails/pull/17573))
+
+* Controller actions without an explicit `render` call and with no
+ corresponding templates will render `head :no_content` implicitly
+ instead of raising an error.
+ (Pull Request [1](https://github.com/rails/rails/pull/19377),
+ [2](https://github.com/rails/rails/pull/23827))
+
+* Added an option for per-form CSRF tokens.
+ ([Pull Request](https://github.com/rails/rails/pull/22275))
+
+* Added request encoding and response parsing to integration tests.
+ ([Pull Request](https://github.com/rails/rails/pull/21671))
+
+* Update default rendering policies when the controller action did
+ not explicitly indicate a response.
+ ([Pull Request](https://github.com/rails/rails/pull/23827))
+
+
+* Add `ActionController#helpers` to get access to the view context
+ at the controller level.
+ ([Pull Request](https://github.com/rails/rails/pull/24866))
+
+* Discarded flash messages get removed before storing into session.
+ ([Pull Request](https://github.com/rails/rails/pull/18721))
+
Action View
-------------
@@ -272,17 +413,21 @@ Please refer to the [Changelog][action-view] for detailed changes.
supported by I18n.
([Pull Request](https://github.com/rails/rails/pull/20019))
+### Deprecations
+
+* Deprecated `datetime_field` and `datetime_field_tag` helpers.
+ Datetime input type was removed from HTML specification.
+ One can use `datetime_local_field` and `datetime_local_field_tag` instead.
+ ([Pull Request](https://github.com/rails/rails/pull/24385))
+
### Notable Changes
* Changed the default template handler from `ERB` to `Raw`.
([commit](https://github.com/rails/rails/commit/4be859f0fdf7b3059a28d03c279f03f5938efc80))
-* Collection rendering automatically caches and fetches multiple partials.
- ([Pull Request](https://github.com/rails/rails/pull/18948))
-
-* Allow defining explicit collection caching using a `# Template Collection: ...`
- directive inside templates.
- ([Pull Request](https://github.com/rails/rails/pull/20781))
+* Collection rendering can cache and fetches multiple partials at once.
+ ([Pull Request](https://github.com/rails/rails/pull/18948),
+ [commit](https://github.com/rails/rails/commit/e93f0f0f133717f9b06b1eaefd3442bd0ff43985))
* Added wildcard matching to explicit dependencies.
([Pull Request](https://github.com/rails/rails/pull/20904))
@@ -291,6 +436,8 @@ Please refer to the [Changelog][action-view] for detailed changes.
button on submit to prevent double submits.
([Pull Request](https://github.com/rails/rails/pull/21135))
+* Partial template name no longer has to be a valid Ruby identifier.
+ ([commit](https://github.com/rails/rails/commit/da9038e))
Action Mailer
-------------
@@ -321,6 +468,11 @@ Please refer to the [Changelog][action-mailer] for detailed changes.
the mailer queue name.
([Pull Request](https://github.com/rails/rails/pull/18587))
+* Added support for fragment caching in Action Mailer views.
+ Added new config option `config.action_mailer.perform_caching` to determine
+ whether your templates should perform caching or not.
+ ([Pull Request](https://github.com/rails/rails/pull/22825))
+
Active Record
-------------
@@ -378,6 +530,12 @@ Please refer to the [Changelog][active-record] for detailed changes.
* Removed support for the `protected_attributes` gem.
([commit](https://github.com/rails/rails/commit/f4fbc0301021f13ae05c8e941c8efc4ae351fdf9))
+* Removed support for PostgreSQL versions below 9.1.
+ ([Pull Request](https://github.com/rails/rails/pull/23434))
+
+* Removed support for `activerecord-deprecated_finders` gem.
+ ([commit](https://github.com/rails/rails/commit/78dab2a8569408658542e462a957ea5a35aa4679))
+
### Deprecations
* Deprecated passing a class as a value in a query. Users should pass strings
@@ -390,10 +548,6 @@ Please refer to the [Changelog][active-record] for detailed changes.
* Deprecated `ActiveRecord::Base.errors_in_transactional_callbacks=`.
([commit](https://github.com/rails/rails/commit/07d3d402341e81ada0214f2cb2be1da69eadfe72))
-* Deprecated passing of `start` value to `find_in_batches` and `find_each`
- in favour of `begin_at` value.
- ([Pull Request](https://github.com/rails/rails/pull/18961))
-
* Deprecated `Relation#uniq` use `Relation#distinct` instead.
([commit](https://github.com/rails/rails/commit/adfab2dcf4003ca564d78d4425566dd2d9cd8b4f))
@@ -435,6 +589,14 @@ Please refer to the [Changelog][active-record] for detailed changes.
`offset` method on relation instead.
([Pull Request](https://github.com/rails/rails/pull/22053))
+* Deprecated `{insert|update|delete}_sql` in `DatabaseStatements`.
+ Use the `{insert|update|delete}` public methods instead.
+ ([Pull Request](https://github.com/rails/rails/pull/23086))
+
+* Deprecated `use_transactional_fixtures` in favor of
+ `use_transactional_tests` for more clarity.
+ ([Pull Request](https://github.com/rails/rails/pull/19282))
+
### Notable changes
* Added a `foreign_key` option to `references` while creating the table.
@@ -443,19 +605,13 @@ Please refer to the [Changelog][active-record] for detailed changes.
* New attributes
API. ([commit](https://github.com/rails/rails/commit/8c752c7ac739d5a86d4136ab1e9d0142c4041e58))
-* Added `:enum_prefix`/`:enum_suffix` option to `enum`
- definition. ([Pull Request](https://github.com/rails/rails/pull/19813))
+* Added `:_prefix`/`:_suffix` option to `enum` definition.
+ ([Pull Request](https://github.com/rails/rails/pull/19813),
+ [Pull Request](https://github.com/rails/rails/pull/20999))
* Added `#cache_key` to `ActiveRecord::Relation`.
([Pull Request](https://github.com/rails/rails/pull/20884))
-* Added `ActiveRecord::Relation#outer_joins`.
- ([Pull Request](https://github.com/rails/rails/pull/12071))
-
-* Require `belongs_to` by default.
- ([Pull Request](https://github.com/rails/rails/pull/18937)) - Deprecate
- `required` option in favor of `optional` for `belongs_to`
-
* Changed the default `null` value for `timestamps` to `false`.
([commit](https://github.com/rails/rails/commit/a939506f297b667291480f26fa32a373a18ae06a))
@@ -484,7 +640,8 @@ Please refer to the [Changelog][active-record] for detailed changes.
* `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`.
+ with `optional: true`. Also deprecate `required` option in favor of `optional`
+ for `belongs_to`.
([Pull Request](https://github.com/rails/rails/pull/18937))
* Added `config.active_record.dump_schemas` to configure the behavior of
@@ -536,6 +693,31 @@ Please refer to the [Changelog][active-record] for detailed changes.
model behavior.
([Pull Request](https://github.com/rails/rails/pull/22567))
+* Added ActiveRecord `#second_to_last` and `#third_to_last` methods.
+ ([Pull Request](https://github.com/rails/rails/pull/23583))
+
+* Added ability to annotate database objects (tables, columns, indexes)
+ with comments stored in database metadata for PostgreSQL & MySQL.
+ ([Pull Request](https://github.com/rails/rails/pull/22911))
+
+* Added prepared statements support to `mysql2` adapter, for mysql2 0.4.4+,
+ Previously this was only supported on the deprecated `mysql` legacy adapter.
+ To enable, set `prepared_statements: true` in config/database.yml.
+ ([Pull Request](https://github.com/rails/rails/pull/23461))
+
+* Added ability to call `ActionRecord::Relation#update` on relation objects
+ which will run validations on callbacks on all objects in the relation.
+ ([Pull Request](https://github.com/rails/rails/pull/11898))
+
+* Added `:touch` option to the `save` method so that records can be saved without
+ updating timestamps.
+ ([Pull Request](https://github.com/rails/rails/pull/18225))
+
+* Added expression indexes and operator classes support for PostgreSQL.
+ ([commit](https://github.com/rails/rails/commit/edc2b7718725016e988089b5fb6d6fb9d6e16882))
+
+* Added `:index_errors` option to add indexes to errors of nested attributes.
+ ([Pull Request](https://github.com/rails/rails/pull/19686))
Active Model
------------
@@ -552,6 +734,9 @@ Please refer to the [Changelog][active-model] for detailed changes.
[activemodel-serializers-xml](https://github.com/rails/activemodel-serializers-xml) gem.
([Pull Request](https://github.com/rails/rails/pull/21161))
+* Removed `ActionController::ModelNaming` module.
+ ([Pull Request](https://github.com/rails/rails/pull/18194))
+
### Deprecations
* Deprecated returning `false` as a way to halt Active Model and
@@ -595,23 +780,32 @@ Please refer to the [Changelog][active-job] for detailed changes.
### Notable changes
-* `ActiveJob::Base.deserialize` delegates to the job class. this allows jobs
+* `ActiveJob::Base.deserialize` delegates to the job class. This allows jobs
to attach arbitrary metadata when they get serialized and read it back when
they get performed.
([Pull Request](https://github.com/rails/rails/pull/18260))
+* Add ability to configure the queue adapter on a per job basis without
+ affecting each other.
+ ([Pull Request](https://github.com/rails/rails/pull/16992))
+
* A generated job now inherits from `app/jobs/application_job.rb` by default.
([Pull Request](https://github.com/rails/rails/pull/19034))
-* Allow `DelayedJob`, `Sidekiq`, `qu`, and `que` to report the job id back to
- `ActiveJob::Base` as `provider_job_id`.
+* Allow `DelayedJob`, `Sidekiq`, `qu`, `que`, and `queue_classic` to report
+ the job id back to `ActiveJob::Base` as `provider_job_id`.
([Pull Request](https://github.com/rails/rails/pull/20064),
- [Pull Request](https://github.com/rails/rails/pull/20056))
+ [Pull Request](https://github.com/rails/rails/pull/20056),
+ [commit](https://github.com/rails/rails/commit/68e3279163d06e6b04e043f91c9470e9259bbbe0))
* Implement a simple `AsyncJob` processor and associated `AsyncAdapter` that
queue jobs to a `concurrent-ruby` thread pool.
([Pull Request](https://github.com/rails/rails/pull/21257))
+* Change the default adapter from inline to async. It's a better default as
+ tests will then not mistakenly come to rely on behavior happening
+ synchronously.
+ ([commit](https://github.com/rails/rails/commit/625baa69d14881ac49ba2e5c7d9cac4b222d7022))
Active Support
--------------
@@ -649,6 +843,9 @@ Please refer to the [Changelog][active-support] for detailed changes.
* Removed deprecated `ThreadSafe::Cache`. Use `Concurrent::Map` instead.
([Pull Request](https://github.com/rails/rails/pull/21679))
+* Removed `Object#itself` as it is implemented in Ruby 2.2.
+ ([Pull Request](https://github.com/rails/rails/pull/18244))
+
### Deprecations
* Deprecated `MissingSourceFile` in favor of `LoadError`.
@@ -680,6 +877,13 @@ Please refer to the [Changelog][active-support] for detailed changes.
Deprecated `ActiveSupport::Cache::LocaleCache#set_cache_value` in favor of `write_cache_value`.
([Pull Request](https://github.com/rails/rails/pull/22215))
+* Deprecated passing arguments to `assert_nothing_raised`.
+ ([Pull Request](https://github.com/rails/rails/pull/23789))
+
+* Deprecated `Module.local_constants` in favor of `Module.constants(false)`.
+ ([Pull Request](https://github.com/rails/rails/pull/23936))
+
+
### Notable changes
* Added `#verified` and `#valid_message?` methods to
@@ -699,7 +903,7 @@ Please refer to the [Changelog][active-support] for detailed changes.
* Changed the default test order from `:sorted` to `:random`.
([commit](https://github.com/rails/rails/commit/5f777e4b5ee2e3e8e6fd0e2a208ec2a4d25a960d))
-* Added `#on_weekend?`, `#next_weekday`, `#prev_weekday` methods to `Date`,
+* Added `#on_weekend?`, `#on_weekday?`, `#next_weekday`, `#prev_weekday` methods to `Date`,
`Time`, and `DateTime`.
([Pull Request](https://github.com/rails/rails/pull/18335))
@@ -730,7 +934,7 @@ Please refer to the [Changelog][active-support] for detailed changes.
([commit](https://github.com/rails/rails/commit/a5e507fa0b8180c3d97458a9b86c195e9857d8f6))
* Added `Integer#positive?` and `Integer#negative?` query methods
- in the vein of `Fixnum#zero?`.
+ in the vein of `Integer#zero?`.
([commit](https://github.com/rails/rails/commit/e54277a45da3c86fecdfa930663d7692fd083daa))
* Added a bang version to `ActiveSupport::OrderedOptions` get methods which will raise
@@ -749,6 +953,23 @@ Please refer to the [Changelog][active-support] for detailed changes.
class and module variables that live per-thread.
([Pull Request](https://github.com/rails/rails/pull/22630))
+* Added `Array#second_to_last` and `Array#third_to_last` methods.
+ ([Pull Request](https://github.com/rails/rails/pull/23583))
+
+* Added `#on_weekday?` method to `Date`, `Time`, and `DateTime`.
+ ([Pull Request](https://github.com/rails/rails/pull/23687))
+
+* Publish `ActiveSupport::Executor` and `ActiveSupport::Reloader` APIs to allow
+ components and libraries to manage, and participate in, the execution of
+ application code, and the application reloading process.
+ ([Pull Request](https://github.com/rails/rails/pull/23807))
+
+* `ActiveSupport::Duration` now supports ISO8601 formatting and parsing.
+ ([Pull Request](https://github.com/rails/rails/pull/16917))
+
+* Added ability to `TaggedLogging` to allow loggers to be instantiated multiple
+ times so that they don't share tags with each other.
+ ([Pull Request](https://github.com/rails/rails/pull/9065))
Credits
-------
@@ -762,6 +983,7 @@ framework it is. Kudos to all of them.
[action-pack]: https://github.com/rails/rails/blob/5-0-stable/actionpack/CHANGELOG.md
[action-view]: https://github.com/rails/rails/blob/5-0-stable/actionview/CHANGELOG.md
[action-mailer]: https://github.com/rails/rails/blob/5-0-stable/actionmailer/CHANGELOG.md
+[action-cable]: https://github.com/rails/rails/blob/5-0-stable/actioncable/CHANGELOG.md
[active-record]: https://github.com/rails/rails/blob/5-0-stable/activerecord/CHANGELOG.md
[active-model]: https://github.com/rails/rails/blob/5-0-stable/activemodel/CHANGELOG.md
[active-support]: https://github.com/rails/rails/blob/5-0-stable/activesupport/CHANGELOG.md
diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md
new file mode 100644
index 0000000000..16aa9438a2
--- /dev/null
+++ b/guides/source/action_cable_overview.md
@@ -0,0 +1,665 @@
+Action Cable Overview
+=====================
+
+In this guide you will learn how Action Cable works and how to use WebSockets to
+incorporate real-time features into your Rails application.
+
+After reading this guide, you will know:
+
+* How to setup Action Cable
+* How to setup channels
+
+Introduction
+------------
+
+Action Cable seamlessly integrates
+[WebSockets](https://en.wikipedia.org/wiki/WebSocket) with the rest of your
+Rails application. It allows for real-time features to be written in Ruby in the
+same style and form as the rest of your Rails application, while still being
+performant and scalable. It's a full-stack offering that provides both a
+client-side JavaScript framework and a server-side Ruby framework. You have
+access to your full domain model written with Active Record or your ORM of
+choice.
+
+What is Pub/Sub
+---------------
+
+[Pub/Sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern), or
+Publish-Subscribe, refers to a message queue paradigm whereby senders of
+information (publishers), send data to an abstract class of recipients
+(subscribers), without specifying individual recipients. Action Cable uses this
+approach to communicate between the server and many clients.
+
+## Server-Side Components
+
+### Connections
+
+*Connections* form the foundation of the client-server relationship. For every
+WebSocket accepted by the server, a connection object is instantiated. This
+object becomes the parent of all the *channel subscriptions* that are created
+from there on. The connection itself does not deal with any specific application
+logic beyond authentication and authorization. The client of a WebSocket
+connection is called the connection *consumer*. An individual user will create
+one consumer-connection pair per browser tab, window, or device they have open.
+
+Connections are instances of `ApplicationCable::Connection`. In this class, you
+authorize the incoming connection, and proceed to establish it if the user can
+be identified.
+
+#### Connection Setup
+
+```ruby
+# app/channels/application_cable/connection.rb
+module ApplicationCable
+ class Connection < ActionCable::Connection::Base
+ identified_by :current_user
+
+ def connect
+ self.current_user = find_verified_user
+ end
+
+ protected
+ def find_verified_user
+ if current_user = User.find_by(id: cookies.signed[:user_id])
+ current_user
+ else
+ reject_unauthorized_connection
+ end
+ end
+ end
+end
+```
+
+Here `identified_by` is a connection identifier that can be used to find the
+specific connection later. Note that anything marked as an identifier will automatically
+create a delegate by the same name on any channel instances created off the connection.
+
+This example relies on the fact that you will already have handled authentication of the user
+somewhere else in your application, and that a successful authentication sets a signed
+cookie with the user ID.
+
+The cookie is then automatically sent to the connection instance when a new connection
+is attempted, and you use that to set the `current_user`. By identifying the connection
+by this same current user, you're also ensuring that you can later retrieve all open
+connections by a given user (and potentially disconnect them all if the user is deleted
+or unauthorized).
+
+### Channels
+
+A *channel* encapsulates a logical unit of work, similar to what a controller does in a
+regular MVC setup. By default, Rails creates a parent `ApplicationCable::Channel` class
+for encapsulating shared logic between your channels.
+
+#### Parent Channel Setup
+
+```ruby
+# app/channels/application_cable/channel.rb
+module ApplicationCable
+ class Channel < ActionCable::Channel::Base
+ end
+end
+```
+
+Then you would create your own channel classes. For example, you could have a
+`ChatChannel` and an `AppearanceChannel`:
+
+```ruby
+# app/channels/chat_channel.rb
+class ChatChannel < ApplicationCable::Channel
+end
+
+# app/channels/appearance_channel.rb
+class AppearanceChannel < ApplicationCable::Channel
+end
+```
+
+A consumer could then be subscribed to either or both of these channels.
+
+#### Subscriptions
+
+Consumers subscribe to channels, acting as *subscribers*. Their connection is
+called a *subscription*. Produced messages are then routed to these channel
+subscriptions based on an identifier sent by the cable consumer.
+
+```ruby
+# app/channels/chat_channel.rb
+class ChatChannel < ApplicationCable::Channel
+ # Called when the consumer has successfully
+ # become a subscriber of this channel.
+ def subscribed
+ end
+end
+```
+
+## Client-Side Components
+
+### Connections
+
+Consumers require an instance of the connection on their side. This can be
+established using the following JavaScript, which is generated by default by Rails:
+
+#### Connect Consumer
+
+```js
+// app/assets/javascripts/cable.js
+//= require action_cable
+//= require_self
+//= require_tree ./channels
+
+(function() {
+ this.App || (this.App = {});
+
+ App.cable = ActionCable.createConsumer();
+}).call(this);
+```
+
+This will ready a consumer that'll connect against `/cable` on your server by default.
+The connection won't be established until you've also specified at least one subscription
+you're interested in having.
+
+#### Subscriber
+
+A consumer becomes a subscriber by creating a subscription to a given channel:
+
+```coffeescript
+# app/assets/javascripts/cable/subscriptions/chat.coffee
+App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }
+
+# app/assets/javascripts/cable/subscriptions/appearance.coffee
+App.cable.subscriptions.create { channel: "AppearanceChannel" }
+```
+
+While this creates the subscription, the functionality needed to respond to
+received data will be described later on.
+
+A consumer can act as a subscriber to a given channel any number of times. For
+example, a consumer could subscribe to multiple chat rooms at the same time:
+
+```coffeescript
+App.cable.subscriptions.create { channel: "ChatChannel", room: "1st Room" }
+App.cable.subscriptions.create { channel: "ChatChannel", room: "2nd Room" }
+```
+
+## Client-Server Interactions
+
+### Streams
+
+*Streams* provide the mechanism by which channels route published content
+(broadcasts) to their subscribers.
+
+```ruby
+# app/channels/chat_channel.rb
+class ChatChannel < ApplicationCable::Channel
+ def subscribed
+ stream_from "chat_#{params[:room]}"
+ end
+end
+```
+
+If you have a stream that is related to a model, then the broadcasting used
+can be generated from the model and channel. The following example would
+subscribe to a broadcasting like `comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE`
+
+```ruby
+class CommentsChannel < ApplicationCable::Channel
+ def subscribed
+ post = Post.find(params[:id])
+ stream_for post
+ end
+end
+```
+
+You can then broadcast to this channel like this:
+
+```ruby
+CommentsChannel.broadcast_to(@post, @comment)
+```
+
+### Broadcasting
+
+A *broadcasting* is a pub/sub link where anything transmitted by a publisher
+is routed directly to the channel subscribers who are streaming that named
+broadcasting. Each channel can be streaming zero or more broadcastings.
+
+Broadcastings are purely an online queue and time dependent. If a consumer is
+not streaming (subscribed to a given channel), they'll not get the broadcast
+should they connect later.
+
+Broadcasts are called elsewhere in your Rails application:
+
+```ruby
+WebNotificationsChannel.broadcast_to(
+ current_user,
+ title: 'New things!',
+ body: 'All the news fit to print'
+)
+```
+
+The `WebNotificationsChannel.broadcast_to` call places a message in the current
+subscription adapter (Redis by default)'s pubsub queue under a separate
+broadcasting name for each user. For a user with an ID of 1, the broadcasting
+name would be `web_notifications_1`.
+
+The channel has been instructed to stream everything that arrives at
+`web_notifications_1` directly to the client by invoking the `received`
+callback.
+
+### Subscriptions
+
+When a consumer is subscribed to a channel, they act as a subscriber. This
+connection is called a subscription. Incoming messages are then routed to
+these channel subscriptions based on an identifier sent by the cable consumer.
+
+```coffeescript
+# app/assets/javascripts/cable/subscriptions/chat.coffee
+# Assumes you've already requested the right to send web notifications
+App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
+ received: (data) ->
+ @appendLine(data)
+
+ appendLine: (data) ->
+ html = @createLine(data)
+ $("[data-chat-room='Best Room']").append(html)
+
+ createLine: (data) ->
+ """
+ <article class="chat-line">
+ <span class="speaker">#{data["sent_by"]}</span>
+ <span class="body">#{data["body"]}</span>
+ </article>
+ """
+```
+
+### Passing Parameters to Channels
+
+You can pass parameters from the client side to the server side when creating a
+subscription. For example:
+
+```ruby
+# app/channels/chat_channel.rb
+class ChatChannel < ApplicationCable::Channel
+ def subscribed
+ stream_from "chat_#{params[:room]}"
+ end
+end
+```
+
+An object passed as the first argument to `subscriptions.create` becomes the
+params hash in the cable channel. The keyword `channel` is required:
+
+```coffeescript
+# app/assets/javascripts/cable/subscriptions/chat.coffee
+App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
+ received: (data) ->
+ @appendLine(data)
+
+ appendLine: (data) ->
+ html = @createLine(data)
+ $("[data-chat-room='Best Room']").append(html)
+
+ createLine: (data) ->
+ """
+ <article class="chat-line">
+ <span class="speaker">#{data["sent_by"]}</span>
+ <span class="body">#{data["body"]}</span>
+ </article>
+ """
+```
+
+```ruby
+# Somewhere in your app this is called, perhaps
+# from a NewCommentJob.
+ChatChannel.broadcast_to(
+ "chat_#{room}",
+ sent_by: 'Paul',
+ body: 'This is a cool chat app.'
+)
+```
+
+### Rebroadcasting a Message
+
+A common use case is to *rebroadcast* a message sent by one client to any
+other connected clients.
+
+```ruby
+# app/channels/chat_channel.rb
+class ChatChannel < ApplicationCable::Channel
+ def subscribed
+ stream_from "chat_#{params[:room]}"
+ end
+
+ def receive(data)
+ ChatChannel.broadcast_to("chat_#{params[:room]}", data)
+ end
+end
+```
+
+```coffeescript
+# app/assets/javascripts/cable/subscriptions/chat.coffee
+App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
+ received: (data) ->
+ # data => { sent_by: "Paul", body: "This is a cool chat app." }
+
+App.chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
+```
+
+The rebroadcast will be received by all connected clients, _including_ the
+client that sent the message. Note that params are the same as they were when
+you subscribed to the channel.
+
+## Full-Stack Examples
+
+The following setup steps are common to both examples:
+
+ 1. [Setup your connection](#connection-setup).
+ 2. [Setup your parent channel](#parent-channel-setup).
+ 3. [Connect your consumer](#connect-consumer).
+
+### Example 1: User Appearances
+
+Here's a simple example of a channel that tracks whether a user is online or not
+and what page they're on. (This is useful for creating presence features like showing
+a green dot next to a user name if they're online).
+
+Create the server-side appearance channel:
+
+```ruby
+# app/channels/appearance_channel.rb
+class AppearanceChannel < ApplicationCable::Channel
+ def subscribed
+ current_user.appear
+ end
+
+ def unsubscribed
+ current_user.disappear
+ end
+
+ def appear(data)
+ current_user.appear(on: data['appearing_on'])
+ end
+
+ def away
+ current_user.away
+ end
+end
+```
+
+When a subscription is initiated the `subscribed` callback gets fired and we
+take that opportunity to say "the current user has indeed appeared". That
+appear/disappear API could be backed by Redis, a database, or whatever else.
+
+Create the client-side appearance channel subscription:
+
+```coffeescript
+# app/assets/javascripts/cable/subscriptions/appearance.coffee
+App.cable.subscriptions.create "AppearanceChannel",
+ # Called when the subscription is ready for use on the server.
+ connected: ->
+ @install()
+ @appear()
+
+ # Called when the WebSocket connection is closed.
+ disconnected: ->
+ @uninstall()
+
+ # Called when the subscription is rejected by the server.
+ rejected: ->
+ @uninstall()
+
+ appear: ->
+ # Calls `AppearanceChannel#appear(data)` on the server.
+ @perform("appear", appearing_on: $("main").data("appearing-on"))
+
+ away: ->
+ # Calls `AppearanceChannel#away` on the server.
+ @perform("away")
+
+
+ buttonSelector = "[data-behavior~=appear_away]"
+
+ install: ->
+ $(document).on "page:change.appearance", =>
+ @appear()
+
+ $(document).on "click.appearance", buttonSelector, =>
+ @away()
+ false
+
+ $(buttonSelector).show()
+
+ uninstall: ->
+ $(document).off(".appearance")
+ $(buttonSelector).hide()
+```
+
+##### Client-Server Interaction
+
+1. **Client** connects to the **Server** via `App.cable =
+ActionCable.createConsumer("ws://cable.example.com")`. (`cable.js`). The
+**Server** identifies this connection by `current_user`.
+
+2. **Client** subscribes to the appearance channel via
+`App.cable.subscriptions.create(channel: "AppearanceChannel")`. (`appearance.coffee`)
+
+3. **Server** recognizes a new subscription has been initiated for the
+appearance channel and runs its `subscribed` callback, calling the `appear`
+method on `current_user`. (`appearance_channel.rb`)
+
+4. **Client** recognizes that a subscription has been established and calls
+`connected` (`appearance.coffee`) which in turn calls `@install` and `@appear`.
+`@appear` calls `AppearanceChannel#appear(data)` on the server, and supplies a
+data hash of `{ appearing_on: $("main").data("appearing-on") }`. This is
+possible because the server-side channel instance automatically exposes all
+public methods declared on the class (minus the callbacks), so that these can be
+reached as remote procedure calls via a subscription's `perform` method.
+
+5. **Server** receives the request for the `appear` action on the appearance
+channel for the connection identified by `current_user`
+(`appearance_channel.rb`). **Server** retrieves the data with the
+`:appearing_on` key from the data hash and sets it as the value for the `:on`
+key being passed to `current_user.appear`.
+
+### Example 2: Receiving New Web Notifications
+
+The appearance example was all about exposing server functionality to
+client-side invocation over the WebSocket connection. But the great thing
+about WebSockets is that it's a two-way street. So now let's show an example
+where the server invokes an action on the client.
+
+This is a web notification channel that allows you to trigger client-side
+web notifications when you broadcast to the right streams:
+
+Create the server-side web notifications channel:
+
+```ruby
+# app/channels/web_notifications_channel.rb
+class WebNotificationsChannel < ApplicationCable::Channel
+ def subscribed
+ stream_for current_user
+ end
+end
+```
+
+Create the client-side web notifications channel subscription:
+
+```coffeescript
+# app/assets/javascripts/cable/subscriptions/web_notifications.coffee
+# Client-side which assumes you've already requested
+# the right to send web notifications.
+App.cable.subscriptions.create "WebNotificationsChannel",
+ received: (data) ->
+ new Notification data["title"], body: data["body"]
+```
+
+Broadcast content to a web notification channel instance from elsewhere in your
+application:
+
+```ruby
+# Somewhere in your app this is called, perhaps from a NewCommentJob
+WebNotificationsChannel.broadcast_to(
+ current_user,
+ title: 'New things!',
+ body: 'All the news fit to print'
+)
+```
+
+The `WebNotificationsChannel.broadcast_to` call places a message in the current
+subscription adapter's pubsub queue under a separate broadcasting name for each
+user. For a user with an ID of 1, the broadcasting name would be
+"web_notifications_1".
+
+The channel has been instructed to stream everything that arrives at
+"web_notifications_1" directly to the client by invoking the `received`
+callback. The data passed as argument is the hash sent as the second parameter
+to the server-side broadcast call, JSON encoded for the trip across the wire,
+and unpacked for the data argument arriving to `received`.
+
+### More Complete Examples
+
+See the [rails/actioncable-examples](https://github.com/rails/actioncable-examples)
+repository for a full example of how to setup Action Cable in a Rails app and adding channels.
+
+## Configuration
+
+Action Cable has two required configurations: a subscription adapter and allowed request origins.
+
+### Subscription Adapter
+
+By default, Action Cable looks for a configuration file in `config/cable.yml`.
+The file must specify an adapter and a URL for each Rails environment. See the
+[Dependencies](#dependencies) section for additional information on adapters.
+
+```yaml
+development:
+ adapter: async
+
+test:
+ adapter: async
+
+production:
+ adapter: redis
+ url: redis://10.10.3.153:6381
+```
+
+### Allowed Request Origins
+
+Action Cable will only accept requests from specified origins, which are
+passed to the server config as an array. The origins can be instances of
+strings or regular expressions, against which a check for match will be performed.
+
+```ruby
+config.action_cable.allowed_request_origins = ['http://rubyonrails.com', %r{http://ruby.*}]
+```
+
+To disable and allow requests from any origin:
+
+```ruby
+config.action_cable.disable_request_forgery_protection = true
+```
+
+By default, Action Cable allows all requests from localhost:3000 when running
+in the development environment.
+
+### Consumer Configuration
+
+To configure the URL, add a call to `action_cable_meta_tag` in your HTML layout
+HEAD. This uses a URL or path typically set via `config.action_cable.url` in the
+environment configuration files.
+
+### Other Configurations
+
+The other common option to configure is the log tags applied to the
+per-connection logger. Here's close to what we're using in Basecamp:
+
+```ruby
+config.action_cable.log_tags = [
+ -> request { request.env['bc.account_id'] || "no-account" },
+ :action_cable,
+ -> request { request.uuid }
+]
+```
+
+For a full list of all configuration options, see the
+`ActionCable::Server::Configuration` class.
+
+Also note that your server must provide at least the same number of database
+connections as you have workers. The default worker pool size is set to 4, so
+that means you have to make at least that available. You can change that in
+`config/database.yml` through the `pool` attribute.
+
+## Running Standalone Cable Servers
+
+### In App
+
+Action Cable can run alongside your Rails application. For example, to
+listen for WebSocket requests on `/websocket`, specify that path to
+`config.action_cable.mount_path`:
+
+```ruby
+# config/application.rb
+class Application < Rails::Application
+ config.action_cable.mount_path = '/websocket'
+end
+```
+
+You can use `App.cable = ActionCable.createConsumer()` to connect to the cable
+server if `action_cable_meta_tag` is invoked in the layout. A custom path is
+specified as first argument to `createConsumer` (e.g. `App.cable =
+ActionCable.createConsumer("/websocket")`).
+
+For every instance of your server you create and for every worker your server
+spawns, you will also have a new instance of Action Cable, but the use of Redis
+keeps messages synced across connections.
+
+### Standalone
+
+The cable servers can be separated from your normal application server. It's
+still a Rack application, but it is its own Rack application. The recommended
+basic setup is as follows:
+
+```ruby
+# cable/config.ru
+require_relative '../config/environment'
+Rails.application.eager_load!
+
+run ActionCable.server
+```
+
+Then you start the server using a binstub in `bin/cable` ala:
+
+```
+#!/bin/bash
+bundle exec puma -p 28080 cable/config.ru
+```
+
+The above will start a cable server on port 28080.
+
+### Notes
+
+The WebSocket server doesn't have access to the session, but it has
+access to the cookies. This can be used when you need to handle
+authentication. You can see one way of doing that with Devise in this [article](http://www.rubytutorial.io/actioncable-devise-authentication).
+
+## Dependencies
+
+Action Cable provides a subscription adapter interface to process its
+pubsub internals. By default, asynchronous, inline, PostgreSQL, evented
+Redis, and non-evented Redis adapters are included. The default adapter
+in new Rails applications is the asynchronous (`async`) adapter.
+
+The Ruby side of things is built on top of [websocket-driver](https://github.com/faye/websocket-driver-ruby),
+[nio4r](https://github.com/celluloid/nio4r), and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).
+
+## Deployment
+
+Action Cable is powered by a combination of WebSockets and threads. Both the
+framework plumbing and user-specified channel work are handled internally by
+utilizing Ruby's native thread support. This means you can use all your regular
+Rails models with no problem, as long as you haven't committed any thread-safety sins.
+
+The Action Cable server implements the Rack socket hijacking API,
+thereby allowing the use of a multithreaded pattern for managing connections
+internally, irrespective of whether the application server is multi-threaded or not.
+
+Accordingly, Action Cable works with popular servers like Unicorn, Puma, and
+Passenger.
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 9ef2c1a441..58362fea58 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -21,7 +21,7 @@ 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, 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.
+Action Controller is the C in MVC. After the router 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.
@@ -145,7 +145,7 @@ So for example, if you are sending this JSON content:
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 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:
+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 request can be written as:
```json
{ "name": "acme", "address": "123 Carrot Street" }
@@ -199,11 +199,12 @@ practice to help prevent accidentally allowing users to update sensitive
model attributes.
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.
+predefined raise/rescue flow that will result in a 400 Bad Request being
+returned if not all required parameters are passed in.
```ruby
class PeopleController < ActionController::Base
- # This will raise an ActiveModel::ForbiddenAttributes exception
+ # This will raise an ActiveModel::ForbiddenAttributesError exception
# because it's using mass assignment without an explicit permit
# step.
def create
@@ -213,8 +214,8 @@ class PeopleController < ActionController::Base
# This will pass with flying colors as long as there's a person key
# in the parameters, otherwise it'll raise a
# ActionController::ParameterMissing exception, which will get
- # caught by ActionController::Base and turned into that 400 Bad
- # Request reply.
+ # caught by ActionController::Base and turned into a 400 Bad
+ # Request error.
def update
person = current_account.people.find(params[:id])
person.update!(person_params)
@@ -700,7 +701,7 @@ class LoginsController < ApplicationController
end
```
-Now, the `LoginsController`'s `new` and `create` actions will work as before without requiring the user to be logged in. The `:only` option is used to only skip this filter for these actions, and there is also an `:except` option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place.
+Now, the `LoginsController`'s `new` and `create` actions will work as before without requiring the user to be logged in. The `:only` option is used to skip this filter only for these actions, and there is also an `:except` option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place.
### After Filters and Around Filters
@@ -814,7 +815,7 @@ In every controller there are two accessor methods pointing to the request and t
### The `request` Object
-The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the [API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Request.html). Among the properties that you can access on this object are:
+The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the [Rails API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Request.html) and [Rack Documentation](http://www.rubydoc.info/github/rack/rack/Rack/Request). Among the properties that you can access on this object are:
| Property of `request` | Purpose |
| ----------------------------------------- | -------------------------------------------------------------------------------- |
@@ -836,7 +837,7 @@ Rails collects all of the parameters sent along with the request in the `params`
### The `response` Object
-The response object is not usually used directly, but is built up during the execution of the action and rendering of the data that is being sent back to the user, but sometimes - like in an after filter - it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values.
+The response object is not usually used directly, but is built up during the execution of the action and rendering of the data that is being sent back to the user, but sometimes - like in an after filter - it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values. To get a full list of the available methods, refer to the [Rails API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Response.html) and [Rack Documentation](http://www.rubydoc.info/github/rack/rack/Rack/Response).
| Property of `response` | Purpose |
| ---------------------- | --------------------------------------------------------------------------------------------------- |
@@ -1088,6 +1089,8 @@ You can filter out sensitive request parameters from your log files by appending
config.filter_parameters << :password
```
+NOTE: Provided parameters will be filtered out by partial matching regular expression. Rails adds default `:password` in the appropriate initializer (`initializers/filter_parameter_logging.rb`) and cares about typical application parameters `password` and `password_confirmation`.
+
### Redirects Filtering
Sometimes it's desirable to filter out from log files some sensitive locations your application is redirecting to.
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index cd2c13e8c1..7359438025 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -204,10 +204,14 @@ class UsersController < ApplicationController
end
```
-NOTE: Active Job's default behavior is to execute jobs ':inline'. So, you can use
-`deliver_later` now to send emails, and when you later decide to start sending
-them from a background job, you'll only need to set up Active Job to use a queueing
-backend (Sidekiq, Resque, etc).
+NOTE: Active Job's default behavior is to execute jobs via the `:async` adapter. So, you can use
+`deliver_later` now to send emails asynchronously.
+Active Job's default adapter runs jobs with an in-process thread pool.
+It's well-suited for the development/test environments, since it doesn't require
+any external infrastructure, but it's a poor fit for production since it drops
+pending jobs on restart.
+If you need a persistent backend, you will need to use an Active Job adapter
+that has a persistent backend (Sidekiq, Resque, etc).
If you want to send emails right away (from a cronjob for example) just call
`deliver_now`:
@@ -222,7 +226,7 @@ class SendWeeklySummary
end
```
-The method `welcome_email` returns a `ActionMailer::MessageDelivery` object which
+The method `welcome_email` returns an `ActionMailer::MessageDelivery` object which
can then just be told `deliver_now` or `deliver_later` to send itself out. The
`ActionMailer::MessageDelivery` object is just a wrapper around a `Mail::Message`. If
you want to inspect, alter or do anything else with the `Mail::Message` object you can
@@ -278,7 +282,7 @@ different, encode your content and pass in the encoded content and encoding in a
```ruby
encoded_content = SpecialEncode(File.read('/path/to/filename.jpg'))
attachments['filename.jpg'] = {
- mime_type: 'application/x-gzip',
+ mime_type: 'application/gzip',
encoding: 'SpecialEncoding',
content: encoded_content
}
@@ -407,6 +411,22 @@ use the rendered text for the text part. The render command is the same one used
inside of Action Controller, so you can use all the same options, such as
`:text`, `:inline` etc.
+#### Caching mailer view
+
+You can do cache in mailer views like in application views using `cache` method.
+
+```
+<% cache do %>
+ <%= @company.name %>
+<% end %>
+```
+
+And in order to use this feature, you need to configure your application with this:
+
+```
+ config.action_mailer.perform_caching = true
+```
+
### Action Mailer Layouts
Just like controller views, you can also have mailer layouts. The layout name
@@ -714,7 +734,7 @@ files (environment.rb, production.rb, etc...)
| Configuration | Description |
|---------------|-------------|
|`logger`|Generates information on the mailing run if available. Can be set to `nil` for no logging. Compatible with both Ruby's own `Logger` and `Log4r` loggers.|
-|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default `"localhost"` setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain` (will send the password in the clear), `:login` (will send password Base64 encoded) or `:cram_md5` (combines a Challenge/Response mechanism to exchange information and a cryptographic Message Digest 5 algorithm to hash important information)</li><li>`:enable_starttls_auto` - Detects if STARTTLS is enabled in your SMTP server and starts to use it. Defaults to `true`.</li><li>`:openssl_verify_mode` - When using TLS, you can set how OpenSSL checks the certificate. This is really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name of an OpenSSL verify constant ('none', 'peer', 'client_once', 'fail_if_no_peer_cert') or directly the constant (`OpenSSL::SSL::VERIFY_NONE`, `OpenSSL::SSL::VERIFY_PEER`, ...).</li></ul>|
+|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default `"localhost"` setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain` (will send the password in the clear), `:login` (will send password Base64 encoded) or `:cram_md5` (combines a Challenge/Response mechanism to exchange information and a cryptographic Message Digest 5 algorithm to hash important information)</li><li>`:enable_starttls_auto` - Detects if STARTTLS is enabled in your SMTP server and starts to use it. Defaults to `true`.</li><li>`:openssl_verify_mode` - When using TLS, you can set how OpenSSL checks the certificate. This is really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name of an OpenSSL verify constant ('none' or 'peer') or directly the constant (`OpenSSL::SSL::VERIFY_NONE` or `OpenSSL::SSL::VERIFY_PEER`).</li></ul>|
|`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.<ul><li>`:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.</li><li>`:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i -t`.</li></ul>|
|`raise_delivery_errors`|Whether or not errors should be raised if the email fails to be delivered. This only works if the external email server is configured for immediate delivery.|
|`delivery_method`|Defines a delivery method. Possible values are:<ul><li>`:smtp` (default), can be configured by using `config.action_mailer.smtp_settings`.</li><li>`:sendmail`, can be configured by using `config.action_mailer.sendmail_settings`.</li><li>`:file`: save emails to files; can be configured by using `config.action_mailer.file_settings`.</li><li>`:test`: save emails to `ActionMailer::Base.deliveries` array.</li></ul>See [API docs](http://api.rubyonrails.org/classes/ActionMailer/Base.html) for more info.|
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 543937f8e5..f68abbae3c 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -15,7 +15,7 @@ After reading this guide, you will know:
What is Action View?
--------------------
-In Rails, web requests are handled by [Action Controller](action_controller_overview.html) and Action View. Typically, Action Controller will be concerned with communicating with the database and performing CRUD actions where necessary. Action View is then responsible for compiling the response.
+In Rails, web requests are handled by [Action Controller](action_controller_overview.html) and Action View. Typically, Action Controller is concerned with communicating with the database and performing CRUD actions where necessary. Action View is then responsible for compiling the response.
Action View templates are written using embedded Ruby in tags mingled with HTML. To avoid cluttering the templates with boilerplate code, a number of helper classes provide common behavior for forms, dates, and strings. It's also easy to add new helpers to your application as it evolves.
@@ -173,11 +173,11 @@ would produce:
```json
{
"name": "Alex",
- "email: "alex@example.com"
+ "email": "alex@example.com"
}
```
-See the [Jbuilder documention](https://github.com/rails/jbuilder#jbuilder) for
+See the [Jbuilder documentation](https://github.com/rails/jbuilder#jbuilder) for
more examples and information.
#### Template Caching
@@ -260,7 +260,7 @@ With the `as` option we can specify a different name for the local variable. For
<%= render partial: "product", as: "item" %>
```
-The `object` option can be used to directly specify which object is rendered into the partial; useful when the template's object is elsewhere (eg. in a different instance variable or in a local variable).
+The `object` option can be used to directly specify which object is rendered into the partial; useful when the template's object is elsewhere (e.g. in a different instance variable or in a local variable).
For example, instead of:
@@ -442,7 +442,7 @@ image_path("edit.png") # => /assets/edit-2d1a2db63fc738690021fedb5a65b68e.png
#### image_url
-Computes the url to an image asset in the `app/assets/images` directory. This will call `image_path` internally and merge with your current host or your asset host.
+Computes the URL to an image asset in the `app/assets/images` directory. This will call `image_path` internally and merge with your current host or your asset host.
```ruby
image_url("edit.png") # => http://www.example.com/assets/edit.png
@@ -493,7 +493,7 @@ javascript_path "common" # => /assets/common.js
#### javascript_url
-Computes the url to a JavaScript asset in the `app/assets/javascripts` directory. This will call `javascript_path` internally and merge with your current host or your asset host.
+Computes the URL to a JavaScript asset in the `app/assets/javascripts` directory. This will call `javascript_path` internally and merge with your current host or your asset host.
```ruby
javascript_url "common" # => http://www.example.com/assets/common.js
@@ -530,7 +530,7 @@ stylesheet_path "application" # => /assets/application.css
#### stylesheet_url
-Computes the url to a stylesheet asset in the `app/assets/stylesheets` directory. This will call `stylesheet_path` internally and merge with your current host or your asset host.
+Computes the URL to a stylesheet asset in the `app/assets/stylesheets` directory. This will call `stylesheet_path` internally and merge with your current host or your asset host.
```ruby
stylesheet_url "application" # => http://www.example.com/assets/application.css
@@ -599,7 +599,7 @@ This would add something like "Process data files (0.34523)" to the log, which y
#### cache
-A method for caching fragments of a view rather than an entire action or page. This technique is useful for caching pieces like menus, lists of news topics, static HTML fragments, and so on. This method takes a block that contains the content you wish to cache. See `ActionController::Caching::Fragments` for more information.
+A method for caching fragments of a view rather than an entire action or page. This technique is useful for caching pieces like menus, lists of news topics, static HTML fragments, and so on. This method takes a block that contains the content you wish to cache. See `AbstractController::Caching::Fragments` for more information.
```erb
<% cache do %>
@@ -1247,7 +1247,7 @@ file_field_tag 'attachment'
#### form_tag
-Starts a form tag that points the action to a url configured with `url_for_options` just like `ActionController::Base#url_for`.
+Starts a form tag that points the action to a URL configured with `url_for_options` just like `ActionController::Base#url_for`.
```html+erb
<%= form_tag '/articles' do %>
@@ -1419,7 +1419,7 @@ number_to_percentage(100, precision: 0) # => 100%
#### number_to_phone
-Formats a number into a US phone number.
+Formats a number into a phone number (US by default).
```ruby
number_to_phone(1235551234) # => 123-555-1234
@@ -1524,7 +1524,7 @@ Localized Views
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.
+For example, suppose you have an `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.
You can use the same technique to localize the rescue files in your public directory. For example, setting `I18n.locale = :de` and creating `public/500.de.html` and `public/404.de.html` would allow you to have localized rescue pages.
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index 76c13f0ea9..c9f70dc87b 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -62,12 +62,12 @@ $ bin/rails generate job guests_cleanup --queue urgent
```
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`.
+`app/jobs`, just make sure that it inherits from `ApplicationJob`.
Here's what a job looks like:
```ruby
-class GuestsCleanupJob < ActiveJob::Base
+class GuestsCleanupJob < ApplicationJob
queue_as :default
def perform(*guests)
@@ -109,10 +109,12 @@ That's it!
Job Execution
-------------
-For enqueuing and executing jobs you need to set up a queuing backend, that is to
-say you need to decide for a 3rd-party queuing library that Rails should use.
-Rails itself does not provide a sophisticated queuing system and just executes the
-job immediately if no adapter is set.
+For enqueuing and executing jobs in production you need to set up a queuing backend,
+that is to say you need to decide for a 3rd-party queuing library that Rails should use.
+Rails itself only provides an in-process queuing system, which only keeps the jobs in RAM.
+If the process crashes or the machine is reset, then all outstanding jobs are lost with the
+default async back-end. This may be fine for smaller apps or non-critical jobs, but most
+production apps will need to pick a persistent backend.
### Backends
@@ -136,6 +138,18 @@ module YourApp
end
```
+You can also configure your backend on a per job basis.
+
+```ruby
+class GuestsCleanupJob < ApplicationJob
+ self.queue_adapter = :resque
+ #....
+end
+
+# Now your job will use `resque` as it's backend queue adapter overriding what
+# was configured in `config.active_job.queue_adapter`.
+```
+
### Starting the Backend
Since jobs run in parallel to your Rails application, most queuing libraries
@@ -157,7 +171,7 @@ Most of the adapters support multiple queues. With Active Job you can schedule
the job to run on a specific queue:
```ruby
-class GuestsCleanupJob < ActiveJob::Base
+class GuestsCleanupJob < ApplicationJob
queue_as :low_priority
#....
end
@@ -175,7 +189,7 @@ module YourApp
end
# app/jobs/guests_cleanup_job.rb
-class GuestsCleanupJob < ActiveJob::Base
+class GuestsCleanupJob < ApplicationJob
queue_as :low_priority
#....
end
@@ -198,7 +212,7 @@ module YourApp
end
# app/jobs/guests_cleanup_job.rb
-class GuestsCleanupJob < ActiveJob::Base
+class GuestsCleanupJob < ApplicationJob
queue_as :low_priority
#....
end
@@ -220,7 +234,7 @@ block will be executed in the job context (so you can access `self.arguments`)
and you must return the queue name:
```ruby
-class ProcessVideoJob < ActiveJob::Base
+class ProcessVideoJob < ApplicationJob
queue_as do
video = self.arguments.first
if video.owner.premium?
@@ -260,7 +274,7 @@ trigger logic during the life cycle of a job.
### Usage
```ruby
-class GuestsCleanupJob < ActiveJob::Base
+class GuestsCleanupJob < ApplicationJob
queue_as :default
before_enqueue do |job|
@@ -317,7 +331,7 @@ Active Record objects to your job instead of class/id pairs, which you then have
to manually deserialize. Before, jobs would look like this:
```ruby
-class TrashableCleanupJob < ActiveJob::Base
+class TrashableCleanupJob < ApplicationJob
def perform(trashable_class, trashable_id, depth)
trashable = trashable_class.constantize.find(trashable_id)
trashable.cleanup(depth)
@@ -328,7 +342,7 @@ end
Now you can simply do:
```ruby
-class TrashableCleanupJob < ActiveJob::Base
+class TrashableCleanupJob < ApplicationJob
def perform(trashable, depth)
trashable.cleanup(depth)
end
@@ -346,7 +360,7 @@ Active Job provides a way to catch exceptions raised during the execution of the
job:
```ruby
-class GuestsCleanupJob < ActiveJob::Base
+class GuestsCleanupJob < ApplicationJob
queue_as :default
rescue_from(ActiveRecord::RecordNotFound) do |exception|
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index c05e20aceb..e834aeadb1 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -13,7 +13,7 @@ After reading this guide, you will know:
* How an Active Record model behaves.
* How Callbacks and validations work.
* How serializers work.
-* The Rails internationalization (i18n) framework.
+* How Active Model integrates with the Rails internationalization (i18n) framework.
--------------------------------------------------------------------------------
@@ -319,7 +319,7 @@ person.serializable_hash # => {"name"=>"Bob"}
#### ActiveModel::Serializers
-Rails provides a `ActiveModel::Serializers::JSON` serializer.
+Rails provides an `ActiveModel::Serializers::JSON` serializer.
This module automatically include the `ActiveModel::Serialization`.
##### ActiveModel::Serializers::JSON
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index fba89f9d13..d9e9466a33 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -361,7 +361,7 @@ class CreatePublications < ActiveRecord::Migration[5.0]
t.string :publisher_type
t.boolean :single_issue
- t.timestamps null: false
+ t.timestamps
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 fb5d2065d3..a7975c7772 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -399,7 +399,7 @@ By using the `after_commit` callback we can account for this case.
```ruby
class PictureFile < ApplicationRecord
- after_commit :delete_picture_file_from_disk, on: [:destroy]
+ after_commit :delete_picture_file_from_disk, on: :destroy
def delete_picture_file_from_disk
if File.exist?(filepath)
@@ -409,7 +409,7 @@ class PictureFile < ApplicationRecord
end
```
-NOTE: the `:on` option specifies when a callback will be fired. If you
+NOTE: The `:on` option specifies when a callback will be fired. If you
don't supply the `:on` option the callback will fire for every action.
Since using `after_commit` callback only on create, update or delete is
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index 83f4b951ee..f914122242 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -41,7 +41,7 @@ class CreateProducts < ActiveRecord::Migration[5.0]
t.string :name
t.text :description
- t.timestamps null: false
+ t.timestamps
end
end
end
@@ -247,6 +247,7 @@ end
```
This migration will create a `user_id` column and appropriate index.
+For more `add_reference` options, visit the [API documentation](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_reference).
There is also a generator which will produce join tables if `JoinTable` is part of the name:
@@ -287,7 +288,7 @@ class CreateProducts < ActiveRecord::Migration[5.0]
t.string :name
t.text :description
- t.timestamps null: false
+ t.timestamps
end
end
end
@@ -353,7 +354,14 @@ end
```
will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table
-(when using MySQL, the default is `ENGINE=InnoDB`).
+(when using MySQL or MariaDB, the default is `ENGINE=InnoDB`).
+
+Also you can pass the `:comment` option with any description for the table
+that will be stored in database itself and can be viewed with database administration
+tools, such as MySQL Workbench or PgAdmin III. It's highly recommended to specify
+comments in migrations for applications with large databases as it helps people
+to understand data model and generate documentation.
+Currently only the MySQL and PostgreSQL adapters support comments.
### Creating a Join Table
@@ -454,6 +462,7 @@ 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.
+* `comment` Adds a comment for the column.
Some adapters may support additional options; see the adapter specific API docs
for further information.
@@ -847,7 +856,7 @@ class CreateProducts < ActiveRecord::Migration[5.0]
create_table :products do |t|
t.string :name
t.text :description
- t.timestamps null: false
+ t.timestamps
end
end
@@ -883,7 +892,7 @@ Changing Existing Migrations
----------------------------
Occasionally you will make a mistake when writing a migration. If you have
-already run the migration then you cannot just edit the migration and run the
+already run the migration, then you cannot just edit the migration and run the
migration again: Rails thinks it has already run the migration and so will do
nothing when you run `rails db:migrate`. You must rollback the migration (for
example with `bin/rails db:rollback`), edit your migration and then run
@@ -933,7 +942,7 @@ There are two ways to dump the schema. This is set in `config/application.rb`
by the `config.active_record.schema_format` setting, which may be either `:sql`
or `:ruby`.
-If `:ruby` is selected then the schema is stored in `db/schema.rb`. If you look
+If `:ruby` is selected, then the schema is stored in `db/schema.rb`. If you look
at this file you'll find that it looks an awful lot like one very big
migration:
@@ -970,7 +979,7 @@ 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`
rails task) into `db/structure.sql`. For example, for PostgreSQL, the `pg_dump`
-utility is used. For MySQL, this file will contain the output of
+utility is used. For MySQL and MariaDB, this file will contain the output of
`SHOW CREATE TABLE` for the various tables.
Loading these schemas is simply a question of executing the SQL statements they
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index 68c6a77882..d7e35490ef 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -14,7 +14,7 @@ After reading this guide, you will know:
--------------------------------------------------------------------------------
-In order to use the PostgreSQL adapter you need to have at least version 8.2
+In order to use the PostgreSQL adapter you need to have at least version 9.1
installed. Older versions are not supported.
To get started with PostgreSQL have a look at the
@@ -435,7 +435,7 @@ create_table :documents do |t|
t.string 'body'
end
-execute "CREATE INDEX documents_idx ON documents USING gin(to_tsvector('english', title || ' ' || body));"
+add_index :documents, "to_tsvector('english', title || ' ' || body)", using: :gin, name: 'documents_idx'
# app/models/document.rb
class Document < ApplicationRecord
@@ -503,9 +503,9 @@ second = Article.create! title: "Brace yourself",
status: "draft",
published_at: 1.month.ago
-Article.count # => 1
-first.archive!
Article.count # => 2
+first.archive!
+Article.count # => 1
```
NOTE: This application only cares about non-archived `Articles`. A view also
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 784be91845..928ab43b3b 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -11,7 +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 finder methods.
-* How to use method chaining to use multiple ActiveRecord methods together.
+* How to use method chaining to use multiple Active Record 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.
@@ -50,7 +50,7 @@ class Role < ApplicationRecord
end
```
-Active Record will perform queries on the database for you and is compatible with most database systems (MySQL, PostgreSQL and SQLite to name a few). Regardless of which database system you're using, the Active Record method format will always be the same.
+Active Record will perform queries on the database for you and is compatible with most database systems, including MySQL, MariaDB, PostgreSQL and SQLite. Regardless of which database system you're using, the Active Record method format will always be the same.
Retrieving Objects from the Database
------------------------------------
@@ -153,9 +153,9 @@ You can pass in a numerical argument to the `take` method to return up to that n
```ruby
client = Client.take(2)
# => [
- #<Client id: 1, first_name: "Lifo">,
- #<Client id: 220, first_name: "Sara">
-]
+# #<Client id: 1, first_name: "Lifo">,
+# #<Client id: 220, first_name: "Sara">
+# ]
```
The SQL equivalent of the above is:
@@ -192,10 +192,10 @@ You can pass in a numerical argument to the `first` method to return up to that
```ruby
client = Client.first(3)
# => [
- #<Client id: 1, first_name: "Lifo">,
- #<Client id: 2, first_name: "Fifo">,
- #<Client id: 3, first_name: "Filo">
-]
+# #<Client id: 1, first_name: "Lifo">,
+# #<Client id: 2, first_name: "Fifo">,
+# #<Client id: 3, first_name: "Filo">
+# ]
```
The SQL equivalent of the above is:
@@ -243,10 +243,10 @@ You can pass in a numerical argument to the `last` method to return up to that n
```ruby
client = Client.last(3)
# => [
- #<Client id: 219, first_name: "James">,
- #<Client id: 220, first_name: "Sara">,
- #<Client id: 221, first_name: "Russel">
-]
+# #<Client id: 219, first_name: "James">,
+# #<Client id: 220, first_name: "Sara">,
+# #<Client id: 221, first_name: "Russel">
+# ]
```
The SQL equivalent of the above is:
@@ -472,6 +472,12 @@ NOTE: Only equality, range and subset checking are possible with Hash conditions
Client.where(locked: true)
```
+This will generate SQL like this:
+
+```sql
+SELECT * FROM clients WHERE (clients.locked = 1)
+```
+
The field name can also be a string:
```ruby
@@ -517,13 +523,17 @@ SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
### NOT Conditions
-`NOT` SQL queries can be built by `where.not`.
+`NOT` SQL queries can be built by `where.not`:
```ruby
-Article.where.not(author: author)
+Client.where.not(locked: true)
```
-In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions.
+In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions. This will generate SQL like this:
+
+```sql
+SELECT * FROM clients WHERE (clients.locked != 1)
+```
Ordering
--------
@@ -1085,6 +1095,8 @@ SELECT categories.* FROM categories
INNER JOIN tags ON tags.article_id = articles.id
```
+Or, in English: "return all categories that have articles, where those articles have a comment made by a guest, and where those articles also have a tag."
+
#### Specifying Conditions on the Joined Tables
You can specify conditions on the joined tables using the regular [Array](#array-conditions) and [String](#pure-string-conditions) conditions. [Hash conditions](#hash-conditions) provide a special syntax for specifying conditions for the joined tables:
@@ -1109,7 +1121,7 @@ If you want to select a set of records whether or not they have associated
records you can use the `left_outer_joins` method.
```ruby
-Author.left_outer_joins(:posts).uniq.select('authors.*, COUNT(posts.*) AS posts_count').group('authors.id')
+Author.left_outer_joins(:posts).distinct.select('authors.*, COUNT(posts.*) AS posts_count').group('authors.id')
```
Which produces:
@@ -1294,6 +1306,28 @@ Using a class method is the preferred way to accept arguments for scopes. These
category.articles.created_before(time)
```
+### Using conditionals
+
+Your scope can utilize conditionals:
+
+```ruby
+class Article < ApplicationRecord
+ scope :created_before, ->(time) { where("created_at < ?", time) if time.present? }
+end
+```
+
+Like the other examples, this will behave similarly to a class method.
+
+```ruby
+class Article < ApplicationRecord
+ def self.created_before(time)
+ where("created_at < ?", time) if time.present?
+ end
+end
+```
+
+However, there is one important caveat: A scope will always return an `ActiveRecord::Relation` object, even if the conditional evaluates to `false`, whereas a class method, will return `nil`. This can cause `NoMethodError` when chaining class methods with conditionals, if any of the conditionals return `false`.
+
### Applying a default scope
If we wish for a scope to be applied across all queries to the model we can use the
@@ -1589,7 +1623,7 @@ now want the client named 'Nick':
```ruby
nick = Client.find_or_initialize_by(first_name: 'Nick')
-# => <Client id: nil, first_name: "Nick", orders_count: 0, locked: true, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">
+# => #<Client id: nil, first_name: "Nick", orders_count: 0, locked: true, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">
nick.persisted?
# => false
@@ -1621,10 +1655,10 @@ Client.find_by_sql("SELECT * FROM clients
INNER JOIN orders ON clients.id = orders.client_id
ORDER BY clients.created_at desc")
# => [
- #<Client id: 1, first_name: "Lucas" >,
- #<Client id: 2, first_name: "Jan" >,
- # ...
-]
+# #<Client id: 1, first_name: "Lucas" >,
+# #<Client id: 2, first_name: "Jan" >,
+# ...
+# ]
```
`find_by_sql` provides you with a simple way of making custom calls to the database and retrieving instantiated objects.
@@ -1636,9 +1670,9 @@ Client.find_by_sql("SELECT * FROM clients
```ruby
Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'")
# => [
- {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"},
- {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"}
-]
+# {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"},
+# {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"}
+# ]
```
### `pluck`
@@ -1891,7 +1925,7 @@ EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `articles` ON `articles`.`
2 rows in set (0.00 sec)
```
-under MySQL.
+under MySQL and MariaDB.
Active Record performs a pretty printing that emulates that of the
corresponding database shell. So, the same query running with the
@@ -1951,7 +1985,7 @@ EXPLAIN for: SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` IN
1 row in set (0.00 sec)
```
-under MySQL.
+under MySQL and MariaDB.
### Interpreting EXPLAIN
@@ -1962,4 +1996,6 @@ following pointers may be helpful:
* MySQL: [EXPLAIN Output Format](http://dev.mysql.com/doc/refman/5.7/en/explain-output.html)
+* MariaDB: [EXPLAIN](https://mariadb.com/kb/en/mariadb/explain/)
+
* PostgreSQL: [Using EXPLAIN](http://www.postgresql.org/docs/current/static/using-explain.html)
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index dd7adf09a2..2737237c1a 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -149,7 +149,7 @@ false` as an argument. This technique should be used with caution.
### `valid?` and `invalid?`
-Before saving an ActiveRecord object, Rails runs your validations.
+Before saving an Active Record object, Rails runs your validations.
If these validations produce any errors, Rails does not save the object.
You can also run these validations on your own. `valid?` triggers your validations
@@ -278,12 +278,6 @@ form was submitted. This is typically used when the user needs to agree to your
application's terms of service, confirm that some text is read, or any similar
concept.
-This validation is very specific to web applications and this
-'acceptance' does not need to be recorded anywhere in your database. If you
-don't have a field for it, the helper will just create a virtual attribute. If
-the field does exist in your database, the `accept` option must be set to
-`true` or else the validation will not run.
-
```ruby
class Person < ApplicationRecord
validates :terms_of_service, acceptance: true
@@ -292,16 +286,31 @@ end
This check is performed only if `terms_of_service` is not `nil`.
The default error message for this helper is _"must be accepted"_.
+You can also pass custom message via the `message` option.
-It can receive an `:accept` option, which determines the value that will be
-considered acceptance. It defaults to "1" and can be easily changed.
+```ruby
+class Person < ApplicationRecord
+ validates :terms_of_service, acceptance: true, message: 'must be abided'
+end
+```
+
+It can also receive an `:accept` option, which determines the allowed values
+that will be considered as accepted. It defaults to `['1', true]` and can be
+easily changed.
```ruby
class Person < ApplicationRecord
validates :terms_of_service, acceptance: { accept: 'yes' }
+ validates :eula, acceptance: { accept: ['TRUE', 'accepted'] }
end
```
+This validation is very specific to web applications and this
+'acceptance' does not need to be recorded anywhere in your database. If you
+don't have a field for it, the helper will just create a virtual attribute. If
+the field does exist in your database, the `accept` option must be set to
+or include `true` or else the validation will not run.
+
### `validates_associated`
You should use this helper when your model has associations with other models
@@ -383,7 +392,8 @@ The `exclusion` helper has an option `:in` that receives the set of values that
will not be accepted for the validated attributes. The `:in` option has an
alias called `:within` that you can use for the same purpose, if you'd like to.
This example uses the `:message` option to show how you can include the
-attribute's value.
+attribute's value. For full options to the message argument please see the
+[message documentation](#message).
The default error message is _"is reserved"_.
@@ -418,7 +428,8 @@ end
The `inclusion` helper has an option `:in` that receives the set of values that
will be accepted. The `:in` option has an alias called `:within` that you can
use for the same purpose, if you'd like to. The previous example uses the
-`:message` option to show how you can include the attribute's value.
+`:message` option to show how you can include the attribute's value. For full
+options please see the [message documentation](#message).
The default error message for this helper is _"is not included in the list"_.
@@ -505,6 +516,8 @@ constraints to acceptable values:
* `:less_than_or_equal_to` - Specifies the value must be less than or equal to
the supplied value. The default error message for this option is _"must be
less than or equal to %{count}"_.
+* `:other_than` - Specifies the value must be other than the supplied value.
+ The default error message for this option is _"must be other than %{count}"_.
* `:odd` - Specifies the value must be an odd number if set to true. The
default error message for this option is _"must be odd"_.
* `:even` - Specifies the value must be an even number if set to true. The
@@ -757,6 +770,9 @@ class Coffee < ApplicationRecord
end
```
+For full options to the message argument please see the
+[message documentation](#message).
+
### `:allow_blank`
The `:allow_blank` option is similar to the `:allow_nil` option. This option
@@ -781,9 +797,10 @@ for each validation helper. The `:message` option accepts a `String` or `Proc`.
A `String` `:message` value can optionally contain any/all of `%{value}`,
`%{attribute}`, and `%{model}` which will be dynamically replaced when
-validation fails.
+validation fails. This replacement is done using the I18n gem, and the
+placeholders must match exactly, no spaces are allowed.
-A `Proc` `:message` value is given two arguments: a message key for i18n, and
+A `Proc` `:message` value is given two arguments: the object being validated, and
a hash with `:model`, `:attribute`, and `:value` key-value pairs.
```ruby
@@ -799,10 +816,10 @@ class Person < ApplicationRecord
# Proc
validates :username,
uniqueness: {
- # key = "activerecord.errors.models.person.attributes.username.taken"
+ # object = person object being validated
# data = { model: "Person", attribute: "Username", value: <username> }
- message: ->(key, data) do
- "#{data[:value]} taken! Try again #{Time.zone.tomorrow}"
+ message: ->(object, data) do
+ "Hey #{object.name}!, #{data[:value]} is taken already! Try again #{Time.zone.tomorrow}"
end
}
end
@@ -830,6 +847,25 @@ class Person < ApplicationRecord
end
```
+You can also use `on:` to define custom context.
+Custom contexts need to be triggered explicitly
+by passing name of the context to `valid?`, `invalid?` or `save`.
+
+```ruby
+class Person < ApplicationRecord
+ validates :email, uniqueness: true, on: :account_setup
+ validates :age, numericality: true, on: :account_setup
+end
+
+person = Person.new
+```
+
+`person.valid?(:account_setup)` executes both the validations
+without saving the model. And `person.save(context: :account_setup)`
+validates `person` in `account_setup` context before saving.
+On explicit triggers, model is validated by
+validations of only that context and validations without context.
+
Strict Validations
------------------
@@ -1196,9 +1232,9 @@ person.errors[:name]
person.errors.clear
person.errors.empty? # => true
-p.save # => false
+person.save # => false
-p.errors[:name]
+person.errors[:name]
# => ["can't be blank", "is too short (minimum is 3 characters)"]
```
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 0aca6db9b6..565c87c4b5 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -252,7 +252,7 @@ Note that `try` will swallow no-method errors, returning nil instead. If you wan
```ruby
@number.try(:nest) # => nil
-@number.try!(:nest) # NoMethodError: undefined method `nest' for 1:Fixnum
+@number.try!(:nest) # NoMethodError: undefined method `nest' for 1:Integer
```
NOTE: Defined in `active_support/core_ext/object/try.rb`.
@@ -632,8 +632,6 @@ module ActiveSupport
mattr_accessor :load_once_paths
mattr_accessor :autoloaded_constants
mattr_accessor :explicitly_unloadable_constants
- mattr_accessor :logger
- mattr_accessor :log_activity
mattr_accessor :constant_watch_stack
mattr_accessor :constant_watch_stack_mutex
end
@@ -709,87 +707,6 @@ M.parents # => [X::Y, X, Object]
NOTE: Defined in `active_support/core_ext/module/introspection.rb`.
-### Constants
-
-The method `local_constants` returns the names of the constants that have been
-defined in the receiver module:
-
-```ruby
-module X
- X1 = 1
- X2 = 2
- module Y
- Y1 = :y1
- X1 = :overrides_X1_above
- end
-end
-
-X.local_constants # => [:X1, :X2, :Y]
-X::Y.local_constants # => [:Y1, :X1]
-```
-
-The names are returned as symbols.
-
-NOTE: Defined in `active_support/core_ext/module/introspection.rb`.
-
-#### Qualified Constant Names
-
-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.
-
-The new methods are `qualified_const_defined?`, `qualified_const_get`, and
-`qualified_const_set`. Their arguments are assumed to be qualified constant
-names relative to their receiver:
-
-```ruby
-Object.qualified_const_defined?("Math::PI") # => true
-Object.qualified_const_get("Math::PI") # => 3.141592653589793
-Object.qualified_const_set("Math::Phi", 1.618034) # => 1.618034
-```
-
-Arguments may be bare constant names:
-
-```ruby
-Math.qualified_const_get("E") # => 2.718281828459045
-```
-
-These methods are analogous to their built-in counterparts. In particular,
-`qualified_constant_defined?` accepts an optional second argument to be
-able to say whether you want the predicate to look in the ancestors.
-This flag is taken into account for each constant in the expression while
-walking down the path.
-
-For example, given
-
-```ruby
-module M
- X = 1
-end
-
-module N
- class C
- include M
- end
-end
-```
-
-`qualified_const_defined?` behaves this way:
-
-```ruby
-N.qualified_const_defined?("C::X", false) # => false
-N.qualified_const_defined?("C::X", true) # => true
-N.qualified_const_defined?("C::X") # => true
-```
-
-As the last example implies, the second argument defaults to true,
-as in `const_defined?`.
-
-For coherence with the built-in methods only relative paths are accepted.
-Absolute qualified constant names like `::Math::PI` raise `NameError`.
-
-NOTE: Defined in `active_support/core_ext/module/qualified_const.rb`.
-
### Reachable
A named module is reachable if it is stored in its corresponding constant. It means you can reach the module object via the constant.
@@ -1686,19 +1603,6 @@ Given a string with a qualified constant reference expression, `deconstantize` r
"Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel"
```
-Active Support for example uses this method in `Module#qualified_const_set`:
-
-```ruby
-def qualified_const_set(path, value)
- QualifiedConstUtils.raise_if_absolute(path)
-
- const_name = path.demodulize
- mod_name = path.deconstantize
- mod = mod_name.empty? ? self : qualified_const_get(mod_name)
- mod.const_set(const_name, value)
-end
-```
-
NOTE: Defined in `active_support/core_ext/string/inflections.rb`.
#### `parameterize`
@@ -1767,7 +1671,7 @@ NOTE: Defined in `active_support/core_ext/string/inflections.rb`.
The method `constantize` resolves the constant reference expression in its receiver:
```ruby
-"Fixnum".constantize # => Fixnum
+"Integer".constantize # => Integer
module M
X = 1
@@ -2240,7 +2144,7 @@ Similarly, `from` returns the tail from the element at the passed index to the e
[].from(0) # => []
```
-The methods `second`, `third`, `fourth`, and `fifth` return the corresponding element (`first` is built-in). Thanks to social wisdom and positive constructiveness all around, `forty_two` is also available.
+The methods `second`, `third`, `fourth`, and `fifth` return the corresponding element, as do `second_to_last` and `third_to_last` (`first` and `last` are built-in). Thanks to social wisdom and positive constructiveness all around, `forty_two` is also available.
```ruby
%w(a b c d).third # => "c"
@@ -2374,7 +2278,7 @@ Contributor.limit(2).order(:rank).to_xml
To do so it sends `to_xml` to every item in turn, and collects the results under a root node. All items must respond to `to_xml`, an exception is raised otherwise.
-By default, the name of the root element is the underscorized and dasherized plural of the name of the class of the first item, provided the rest of elements belong to that type (checked with `is_a?`) and they are not hashes. In the example above that's "contributors".
+By default, the name of the root element is the underscored and dasherized plural of the name of the class of the first item, provided the rest of elements belong to that type (checked with `is_a?`) and they are not hashes. In the example above that's "contributors".
If there's any element that does not belong to the type of the first one the root node becomes "objects":
@@ -2636,8 +2540,7 @@ To do so, the method loops over the pairs and builds nodes that depend on the _v
```ruby
XML_TYPE_NAMES = {
"Symbol" => "symbol",
- "Fixnum" => "integer",
- "Bignum" => "integer",
+ "Integer" => "integer",
"BigDecimal" => "decimal",
"Float" => "float",
"TrueClass" => "boolean",
@@ -3078,7 +2981,7 @@ INFO: The following calculation methods have edge cases in October 1582, since d
#### `Date.current`
-Active Support defines `Date.current` to be today in the current time zone. That's like `Date.today`, except that it honors the user time zone, if defined. It also defines `Date.yesterday` and `Date.tomorrow`, and the instance predicates `past?`, `today?`, and `future?`, all of them relative to `Date.current`.
+Active Support defines `Date.current` to be today in the current time zone. That's like `Date.today`, except that it honors the user time zone, if defined. It also defines `Date.yesterday` and `Date.tomorrow`, and the instance predicates `past?`, `today?`, `future?`, `on_weekday?` and `on_weekend?`, all of them relative to `Date.current`.
When making Date comparisons using methods which honor the user time zone, make sure to use `Date.current` and not `Date.today`. There are cases where the user time zone might be in the future compared to the system time zone, which `Date.today` uses by default. This means `Date.today` may equal `Date.yesterday`.
@@ -3467,6 +3370,8 @@ years_ago
years_since
prev_year (last_year)
next_year
+on_weekday?
+on_weekend?
```
The following methods are reimplemented so you do **not** need to load `active_support/core_ext/date/calculations.rb` for these ones:
@@ -3653,6 +3558,8 @@ years_ago
years_since
prev_year (last_year)
next_year
+on_weekday?
+on_weekend?
```
They are analogous. Please refer to their documentation above and take into account the following differences:
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 0fd0112c9f..03af3cf819 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -112,6 +112,7 @@ Action Controller
| `:controller` | The controller name |
| `:action` | The action |
| `:params` | Hash of request parameters without any filtered parameter |
+| `:headers` | Request headers |
| `:format` | html/js/json/xml etc |
| `:method` | HTTP request verb |
| `:path` | Request path |
@@ -121,6 +122,7 @@ Action Controller
controller: "PostsController",
action: "new",
params: { "action" => "new", "controller" => "posts" },
+ headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>,
format: :html,
method: "GET",
path: "/posts/new"
@@ -134,6 +136,7 @@ Action Controller
| `:controller` | The controller name |
| `:action` | The action |
| `:params` | Hash of request parameters without any filtered parameter |
+| `:headers` | Request headers |
| `:format` | html/js/json/xml etc |
| `:method` | HTTP request verb |
| `:path` | Request path |
@@ -146,6 +149,7 @@ Action Controller
controller: "PostsController",
action: "index",
params: {"action" => "index", "controller" => "posts"},
+ headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>,
format: :html,
method: "GET",
path: "/posts",
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
index 86baa9ee84..f373d313cc 100644
--- a/guides/source/api_app.md
+++ b/guides/source/api_app.md
@@ -8,13 +8,13 @@ In this guide you will learn:
* What Rails provides for API-only applications
* How to configure Rails to start without any browser features
-* How to decide which middlewares you will want to include
+* How to decide which middleware you will want to include
* How to decide which modules to use in your controller
--------------------------------------------------------------------------------
-What is an API app?
--------------------
+What is an API Application?
+---------------------------
Traditionally, when people said that they used Rails as an "API", they meant
providing a programmatically accessible API alongside their web application.
@@ -28,15 +28,14 @@ applications.
For example, Twitter uses its [public API](https://dev.twitter.com) in its web
application, which is built as a static site that consumes JSON resources.
-Instead of using Rails to generate dynamic HTML that will communicate with the
-server through forms and links, many developers are treating their web application
-as just another client, delivered as static HTML, CSS and JavaScript consuming
-a simple JSON API.
+Instead of using Rails to generate HTML that communicates with the server
+through forms and links, many developers are treating their web application as
+just an API client delivered as HTML with JavaScript that consumes a JSON API.
This guide covers building a Rails application that serves JSON resources to an
-API client **or** a client-side framework.
+API client, including client-side frameworks.
-Why use Rails for JSON APIs?
+Why Use Rails for JSON APIs?
----------------------------
The first question a lot of people have when thinking about building a JSON API
@@ -44,11 +43,11 @@ using Rails is: "isn't using Rails to spit out some JSON overkill? Shouldn't I
just use something like Sinatra?".
For very simple APIs, this may be true. However, even in very HTML-heavy
-applications, most of an application's logic is actually outside of the view
+applications, most of an application's logic lives outside of the view
layer.
The reason most people use Rails is that it provides a set of defaults that
-allows us to get up and running quickly without having to make a lot of trivial
+allows developers to get up and running quickly, without having to make a lot of trivial
decisions.
Let's take a look at some of the things that Rails provides out of the box that are
@@ -75,18 +74,16 @@ Handled at the middleware layer:
URL-encoded String? No problem. Rails will decode the JSON for you and make
it available in `params`. Want to use nested URL-encoded parameters? That
works too.
-- Conditional GETs: Rails handles conditional `GET`, (`ETag` and `Last-Modified`),
+- Conditional GETs: Rails handles conditional `GET` (`ETag` and `Last-Modified`)
processing request headers and returning the correct response headers and status
code. All you need to do is use the
[`stale?`](http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-stale-3F)
check in your controller, and Rails will handle all of the HTTP details for you.
-- Caching: If you use `dirty?` with public cache control, Rails will automatically
- cache your responses. You can easily configure the cache store.
- HEAD requests: Rails will transparently convert `HEAD` requests into `GET` ones,
and return just the headers on the way out. This makes `HEAD` work reliably in
all Rails APIs.
-While you could obviously build these up in terms of existing Rack middlewares,
+While you could obviously build these up in terms of existing Rack middleware,
this list demonstrates that the default Rails middleware stack provides a lot
of value, even if you're "just generating JSON".
@@ -97,28 +94,28 @@ Handled at the Action Pack layer:
means not having to spend time thinking about how to model your API in terms
of HTTP.
- URL Generation: The flip side of routing is URL generation. A good API based
- on HTTP includes URLs (see [the GitHub gist API](http://developer.github.com/v3/gists/)
+ on HTTP includes URLs (see [the GitHub Gist API](http://developer.github.com/v3/gists/)
for an example).
- Header and Redirection Responses: `head :no_content` and
`redirect_to user_url(current_user)` come in handy. Sure, you could manually
add the response headers, but why?
- Caching: Rails provides page, action and fragment caching. Fragment caching
is especially helpful when building up a nested JSON object.
-- Basic, Digest and Token Authentication: Rails comes with out-of-the-box support
+- Basic, Digest, and Token Authentication: Rails comes with out-of-the-box support
for three kinds of HTTP authentication.
-- Instrumentation: Rails has an instrumentation API that will trigger registered
+- Instrumentation: Rails has an instrumentation API that triggers registered
handlers for a variety of events, such as action processing, sending a file or
data, redirection, and database queries. The payload of each event comes with
relevant information (for the action processing event, the payload includes
the controller, action, parameters, request format, request method and the
request's full path).
-- Generators: This may be passé for advanced Rails users, but it can be nice to
- generate a resource and get your model, controller, test stubs, and routes
- created for you in a single command.
+- Generators: It is often handy to generate a resource and get your model,
+ controller, test stubs, and routes created for you in a single command for
+ further tweaking. Same for migrations and others.
- Plugins: Many third-party libraries come with support for Rails that reduce
or eliminate the cost of setting up and gluing together the library and the
web framework. This includes things like overriding default generators, adding
- rake tasks, and honoring Rails choices (like the logger and cache back-end).
+ Rake tasks, and honoring Rails choices (like the logger and cache back-end).
Of course, the Rails boot process also glues together all registered components.
For example, the Rails boot process is what uses your `config/database.yml` file
@@ -126,7 +123,7 @@ when configuring Active Record.
**The short version is**: you may not have thought about which parts of Rails
are still applicable even if you remove the view layer, but the answer turns out
-to be "most of it".
+to be most of it.
The Basic Configuration
-----------------------
@@ -135,6 +132,8 @@ If you're building a Rails application that will be an API server first and
foremost, you can start with a more limited subset of Rails and add in features
as needed.
+### Creating a new application
+
You can generate a new api Rails app:
```bash
@@ -143,16 +142,18 @@ $ rails new my_api --api
This will do three main things for you:
-- Configure your application to start with a more limited set of middlewares
+- Configure your application to start with a more limited set of middleware
than normal. Specifically, it will not include any middleware primarily useful
for browser applications (like cookies support) by default.
- Make `ApplicationController` inherit from `ActionController::API` instead of
- `ActionController::Base`. As with middlewares, this will leave out any Action
+ `ActionController::Base`. As with middleware, this will leave out any Action
Controller modules that provide functionalities primarily used by browser
applications.
- Configure the generators to skip generating views, helpers and assets when
you generate a new resource.
+### Changing an existing application
+
If you want to take an existing application and make it an API one, read the
following steps.
@@ -163,14 +164,23 @@ class definition:
config.api_only = true
```
-Optionally, in `config/environments/development.rb` add the following line
-to render error responses using the API format (JSON by default) when it
-is a local request:
+In `config/environments/development.rb`, set `config.debug_exception_response_format`
+to configure the format used in responses when errors occur in development mode.
+
+To render an HTML page with debugging information, use the value `:default`.
+
+```ruby
+config.debug_exception_response_format = :default
+```
+
+To render debugging information preserving the response format, use the value `:api`.
```ruby
config.debug_exception_response_format = :api
```
+By default, `config.debug_exception_response_format` is set to `:api`, when `config.api_only` is set to true.
+
Finally, inside `app/controllers/application_controller.rb`, instead of:
```ruby
@@ -185,35 +195,36 @@ class ApplicationController < ActionController::API
end
```
-Choosing Middlewares
+Choosing Middleware
--------------------
-An API application comes with the following middlewares by default:
+An API application comes with the following middleware by default:
- `Rack::Sendfile`
- `ActionDispatch::Static`
-- `Rack::Lock`
+- `ActionDispatch::Executor`
- `ActiveSupport::Cache::Strategy::LocalCache::Middleware`
+- `Rack::Runtime`
- `ActionDispatch::RequestId`
- `Rails::Rack::Logger`
-- `Rack::Runtime`
- `ActionDispatch::ShowExceptions`
- `ActionDispatch::DebugExceptions`
- `ActionDispatch::RemoteIp`
- `ActionDispatch::Reloader`
- `ActionDispatch::Callbacks`
+- `ActiveRecord::Migration::CheckPending`
- `Rack::Head`
- `Rack::ConditionalGet`
- `Rack::ETag`
-See the [internal middlewares](rails_on_rack.html#internal-middleware-stack)
+See the [internal middleware](rails_on_rack.html#internal-middleware-stack)
section of the Rack guide for further information on them.
-Other plugins, including Active Record, may add additional middlewares. In
-general, these middlewares are agnostic to the type of application you are
+Other plugins, including Active Record, may add additional middleware. In
+general, these middleware are agnostic to the type of application you are
building, and make sense in an API-only Rails application.
-You can get a list of all middlewares in your application via:
+You can get a list of all middleware in your application via:
```bash
$ rails middleware
@@ -262,9 +273,6 @@ subsequent inbound requests for the same URL.
Think of it as page caching using HTTP semantics.
-NOTE: This middleware is always outside of the `Rack::Lock` mutex, even in
-single-threaded applications.
-
### Using Rack::Sendfile
When you use the `send_file` method inside a Rails controller, it sets the
@@ -282,7 +290,7 @@ You can learn more about how to use `Rack::Sendfile` with popular
front-ends in [the Rack::Sendfile
documentation](http://rubydoc.info/github/rack/rack/master/Rack/Sendfile).
-Here are some values for popular servers, once they are configured, to support
+Here are some values for this header for some popular servers, once these servers are configured to support
accelerated file sending:
```ruby
@@ -296,9 +304,6 @@ config.action_dispatch.x_sendfile_header = "X-Accel-Redirect"
Make sure to configure your server to support these options following the
instructions in the `Rack::Sendfile` documentation.
-NOTE: The `Rack::Sendfile` middleware is always outside of the `Rack::Lock`
-mutex, even in single-threaded applications.
-
### Using ActionDispatch::Request
`ActionDispatch::Request#params` will take parameters from the client in the JSON
@@ -327,26 +332,26 @@ will be:
{ :person => { :firstName => "Yehuda", :lastName => "Katz" } }
```
-### Other Middlewares
+### Other Middleware
-Rails ships with a number of other middlewares that you might want to use in an
+Rails ships with a number of other middleware that you might want to use in an
API application, especially if one of your API clients is the browser:
- `Rack::MethodOverride`
- `ActionDispatch::Cookies`
- `ActionDispatch::Flash`
-- For sessions management
+- For session management
* `ActionDispatch::Session::CacheStore`
* `ActionDispatch::Session::CookieStore`
* `ActionDispatch::Session::MemCacheStore`
-Any of these middlewares can be added via:
+Any of these middleware can be added via:
```ruby
config.middleware.use Rack::MethodOverride
```
-### Removing Middlewares
+### Removing Middleware
If you don't want to use a middleware that is included by default in the API-only
middleware set, you can remove it with:
@@ -355,7 +360,7 @@ middleware set, you can remove it with:
config.middleware.delete ::Rack::Sendfile
```
-Keep in mind that removing these middlewares will remove support for certain
+Keep in mind that removing these middleware will remove support for certain
features in Action Controller.
Choosing Controller Modules
@@ -364,24 +369,24 @@ Choosing Controller Modules
An API application (using `ActionController::API`) comes with the following
controller modules by default:
-- `ActionController::UrlFor`: Makes `url_for` and friends available.
+- `ActionController::UrlFor`: Makes `url_for` and similar helpers available.
- `ActionController::Redirecting`: Support for `redirect_to`.
-- `ActionController::Rendering`: Basic support for rendering.
+- `AbstractController::Rendering` and `ActionController::ApiRendering`: Basic support for rendering.
- `ActionController::Renderers::All`: Support for `render :json` and friends.
- `ActionController::ConditionalGet`: Support for `stale?`.
+- `ActionController::BasicImplicitRender`: Makes sure to return an empty response, if there isn't an explicit one.
+- `ActionController::StrongParameters`: Support for parameters white-listing in combination with Active Model mass assignment.
- `ActionController::ForceSSL`: Support for `force_ssl`.
- `ActionController::DataStreaming`: Support for `send_file` and `send_data`.
-- `AbstractController::Callbacks`: Support for `before_action` and friends.
+- `AbstractController::Callbacks`: Support for `before_action` and
+ similar helpers.
+- `ActionController::Rescue`: Support for `rescue_from`.
- `ActionController::Instrumentation`: Support for the instrumentation
hooks defined by Action Controller (see [the instrumentation
- guide](active_support_instrumentation.html#action-controller)).
-- `ActionController::Rescue`: Support for `rescue_from`.
-- `ActionController::BasicImplicitRender`: Makes sure to return an empty response
- if there's not an explicit one.
-- `ActionController::StrongParameters`: Support for parameters white-listing in
- combination with Active Model mass assignment.
-- `ActionController::ParamsWrapper`: Wraps the parameters hash into a nested hash
- so you don't have to specify root elements sending POST requests for instance.
+ guide](active_support_instrumentation.html#action-controller) for
+more information regarding this).
+- `ActionController::ParamsWrapper`: Wraps the parameters hash into a nested hash,
+ so that you don't have to specify root elements sending POST requests for instance.
Other plugins may add additional modules. You can get a list of all modules
included into `ActionController::API` in the rails console:
@@ -389,6 +394,13 @@ included into `ActionController::API` in the rails console:
```bash
$ bin/rails c
>> ActionController::API.ancestors - ActionController::Metal.ancestors
+=> [ActionController::API,
+ ActiveRecord::Railties::ControllerRuntime,
+ ActionDispatch::Routing::RouteSet::MountedHelpers,
+ ActionController::ParamsWrapper,
+ ... ,
+ AbstractController::Rendering,
+ ActionView::ViewPaths]
```
### Adding Other Modules
@@ -403,10 +415,10 @@ Some common modules you might want to add:
and translation methods.
- `ActionController::HttpAuthentication::Basic` (or `Digest` or `Token`): Support
for basic, digest or token HTTP authentication.
-- `AbstractController::Layouts`: Support for layouts when rendering.
+- `ActionView::Layouts`: Support for layouts when rendering.
- `ActionController::MimeResponds`: Support for `respond_to`.
- `ActionController::Cookies`: Support for `cookies`, which includes
support for signed and encrypted cookies. This requires the cookies middleware.
-The best place to add a module is in your `ApplicationController` but you can
+The best place to add a module is in your `ApplicationController`, but you can
also add modules to individual controllers.
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index cd208c2e13..5b34330936 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -120,7 +120,7 @@ On the other hand, big chunks of structured documentation may have a separate "E
The results of expressions follow them and are introduced by "# => ", vertically aligned:
```ruby
-# For checking if a fixnum is even or odd.
+# For checking if an integer is even or odd.
#
# 1.even? # => false
# 1.odd? # => true
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 5bdaf600ad..e6631a513c 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -21,21 +21,20 @@ What is the Asset Pipeline?
The asset pipeline provides a framework to concatenate and minify or compress
JavaScript and CSS assets. It also adds the ability to write these assets in
other languages and pre-processors such as CoffeeScript, Sass and ERB.
+It allows assets in your application to be automatically combined with assets
+from other gems. For example, jquery-rails includes a copy of jquery.js
+and enables AJAX features in Rails.
-The asset pipeline is technically no longer a core feature of Rails 4, it has
-been extracted out of the framework into the
-[sprockets-rails](https://github.com/rails/sprockets-rails) gem.
-
-The asset pipeline is enabled by default.
-
-You can disable the asset pipeline while creating a new application by
+The asset pipeline is implemented by the
+[sprockets-rails](https://github.com/rails/sprockets-rails) gem,
+and is enabled by default. You can disable it while creating a new application by
passing the `--skip-sprockets` option.
```bash
rails new appname --skip-sprockets
```
-Rails 4 automatically adds the `sass-rails`, `coffee-rails` and `uglifier`
+Rails automatically adds the `sass-rails`, `coffee-rails` and `uglifier`
gems to your Gemfile, which are used by Sprockets for asset compression:
```ruby
@@ -44,8 +43,8 @@ gem 'uglifier'
gem 'coffee-rails'
```
-Using the `--skip-sprockets` option will prevent Rails 4 from adding
-`sass-rails` and `uglifier` to your Gemfile, so if you later want to enable
+Using the `--skip-sprockets` option will prevent Rails from adding
+them to your Gemfile, so if you later want to enable
the asset pipeline you will have to add those gems to your Gemfile. Also,
creating an application with the `--skip-sprockets` option will generate
a slightly different `config/application.rb` file, with a require statement
@@ -327,13 +326,13 @@ familiar `javascript_include_tag` and `stylesheet_link_tag`:
<%= javascript_include_tag "application" %>
```
-If using the turbolinks gem, which is included by default in Rails 4, then
+If using the turbolinks gem, which is included by default in Rails, then
include the 'data-turbolinks-track' option which causes turbolinks to check if
an asset has been updated and if so loads it into the page:
```erb
-<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
-<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
+<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => "reload" %>
+<%= javascript_include_tag "application", "data-turbolinks-track" => "reload" %>
```
In regular views you can access images in the `public/assets/images` directory
@@ -436,14 +435,14 @@ Sprockets uses manifest files to determine which assets to include and serve.
These manifest files contain _directives_ - instructions that tell Sprockets
which files to require in order to build a single CSS or JavaScript file. With
these directives, Sprockets loads the files specified, processes them if
-necessary, concatenates them into one single file and then compresses them (if
-`Rails.application.config.assets.compress` is true). By serving one file rather
-than many, the load time of pages can be greatly reduced because the browser
-makes fewer requests. Compression also reduces file size, enabling the
-browser to download them faster.
+necessary, concatenates them into one single file and then compresses them
+(based on value of `Rails.application.config.assets.js_compressor`). By serving
+one file rather than many, the load time of pages can be greatly reduced because
+the browser makes fewer requests. Compression also reduces file size, enabling
+the browser to download them faster.
-For example, a new Rails 4 application includes a default
+For example, a new Rails application includes a default
`app/assets/javascripts/application.js` file containing the following lines:
```js
@@ -484,7 +483,7 @@ which contains these lines:
*/
```
-Rails 4 creates both `app/assets/javascripts/application.js` and
+Rails creates both `app/assets/javascripts/application.js` and
`app/assets/stylesheets/application.css` regardless of whether the
--skip-sprockets option is used when creating a new rails application. This is
so you can easily add asset pipelining later if you like.
@@ -667,7 +666,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 development).
+initialization option (which defaults to `true`).
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
@@ -786,7 +785,6 @@ location ~ ^/assets/ {
add_header Cache-Control public;
add_header ETag "";
- break;
}
```
@@ -898,7 +896,7 @@ your CDN server, you need to tell browsers to use your CDN to grab assets
instead of your Rails server directly. You can do this by configuring Rails to
set your CDN as the asset host instead of using a relative path. To set your
asset host in Rails, you need to set `config.action_controller.asset_host` in
-`config/production.rb`:
+`config/environments/production.rb`:
```ruby
config.action_controller.asset_host = 'mycdnsubdomain.fictional-cdn.com'
@@ -1107,11 +1105,17 @@ 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.
-NOTE: The `config.assets.compress` initialization option is no longer used in
-Rails 4 to enable either CSS or JavaScript compression. Setting it will have no
-effect on the application. Instead, setting `config.assets.css_compressor` and
-`config.assets.js_compressor` will control compression of CSS and JavaScript
-assets.
+
+
+### Serving GZipped version of assets
+
+By default, gzipped version of compiled assets will be generated, along
+with the non-gzipped version of assets. Gzipped assets help reduce the transmission of
+data over the wire. You can configure this by setting the `gzip` flag.
+
+```ruby
+config.assets.gzip = false # disable gzipped assets generation
+```
### Using Your Own Compressor
@@ -1177,19 +1181,14 @@ TIP: For further details have a look at the docs of your production web server:
Assets Cache Store
------------------
-The default Rails cache store will be used by Sprockets to cache assets in
-development and production. This can be changed by setting
-`config.assets.cache_store`:
+By default, Sprockets caches assets in `tmp/cache/assets` in development
+and production environments. This can be changed as follows:
```ruby
-config.assets.cache_store = :memory_store
-```
-
-The options accepted by the assets cache store are the same as the application's
-cache store.
-
-```ruby
-config.assets.cache_store = :memory_store, { size: 32.megabytes }
+config.assets.configure do |env|
+ env.cache = ActiveSupport::Cache.lookup_store(:memory_store,
+ { size: 32.megabytes })
+end
```
To disable the assets cache store:
@@ -1287,7 +1286,7 @@ config.assets.js_compressor = :uglifier
# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = false
-# Generate digests for assets URLs. This is planned for deprecation.
+# Generate digests for assets URLs.
config.assets.digest = true
# Precompile additional assets (application.js, application.css, and all
@@ -1295,7 +1294,7 @@ config.assets.digest = true
# config.assets.precompile += %w( search.js )
```
-Rails 4 no longer sets default config values for Sprockets in `test.rb`, so
+Rails 4 and above no longer set default config values for Sprockets in `test.rb`, so
`test.rb` now requires Sprockets configuration. The old defaults in the test
environment are: `config.assets.compile = true`, `config.assets.compress = false`,
`config.assets.debug = false` and `config.assets.digest = false`.
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index accce5a904..3993fdb1dd 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -105,13 +105,13 @@ class CreateBooks < ActiveRecord::Migration[5.0]
def change
create_table :authors do |t|
t.string :name
- t.timestamps null: false
+ t.timestamps
end
create_table :books do |t|
t.belongs_to :author, index: true
t.datetime :published_at
- t.timestamps null: false
+ t.timestamps
end
end
end
@@ -136,13 +136,13 @@ class CreateSuppliers < ActiveRecord::Migration[5.0]
def change
create_table :suppliers do |t|
t.string :name
- t.timestamps null: false
+ t.timestamps
end
create_table :accounts do |t|
t.belongs_to :supplier, index: true
t.string :account_number
- t.timestamps null: false
+ t.timestamps
end
end
end
@@ -180,13 +180,13 @@ class CreateAuthors < ActiveRecord::Migration[5.0]
def change
create_table :authors do |t|
t.string :name
- t.timestamps null: false
+ t.timestamps
end
create_table :books do |t|
t.belongs_to :author, index: true
t.datetime :published_at
- t.timestamps null: false
+ t.timestamps
end
end
end
@@ -222,19 +222,19 @@ class CreateAppointments < ActiveRecord::Migration[5.0]
def change
create_table :physicians do |t|
t.string :name
- t.timestamps null: false
+ t.timestamps
end
create_table :patients do |t|
t.string :name
- t.timestamps null: false
+ t.timestamps
end
create_table :appointments do |t|
t.belongs_to :physician, index: true
t.belongs_to :patient, index: true
t.datetime :appointment_date
- t.timestamps null: false
+ t.timestamps
end
end
end
@@ -308,19 +308,19 @@ class CreateAccountHistories < ActiveRecord::Migration[5.0]
def change
create_table :suppliers do |t|
t.string :name
- t.timestamps null: false
+ t.timestamps
end
create_table :accounts do |t|
t.belongs_to :supplier, index: true
t.string :account_number
- t.timestamps null: false
+ t.timestamps
end
create_table :account_histories do |t|
t.belongs_to :account, index: true
t.integer :credit_rating
- t.timestamps null: false
+ t.timestamps
end
end
end
@@ -349,12 +349,12 @@ class CreateAssembliesAndParts < ActiveRecord::Migration[5.0]
def change
create_table :assemblies do |t|
t.string :name
- t.timestamps null: false
+ t.timestamps
end
create_table :parts do |t|
t.string :part_number
- t.timestamps null: false
+ t.timestamps
end
create_table :assemblies_parts, id: false do |t|
@@ -388,13 +388,13 @@ class CreateSuppliers < ActiveRecord::Migration[5.0]
def change
create_table :suppliers do |t|
t.string :name
- t.timestamps null: false
+ t.timestamps
end
create_table :accounts do |t|
t.integer :supplier_id
t.string :account_number
- t.timestamps null: false
+ t.timestamps
end
add_index :accounts, :supplier_id
@@ -472,7 +472,7 @@ class CreatePictures < ActiveRecord::Migration[5.0]
t.string :name
t.integer :imageable_id
t.string :imageable_type
- t.timestamps null: false
+ t.timestamps
end
add_index :pictures, [:imageable_type, :imageable_id]
@@ -488,7 +488,7 @@ class CreatePictures < ActiveRecord::Migration[5.0]
create_table :pictures do |t|
t.string :name
t.references :imageable, polymorphic: true, index: true
- t.timestamps null: false
+ t.timestamps
end
end
end
@@ -518,7 +518,7 @@ class CreateEmployees < ActiveRecord::Migration[5.0]
def change
create_table :employees do |t|
t.references :manager, index: true
- t.timestamps null: false
+ t.timestamps
end
end
end
@@ -545,12 +545,12 @@ author.books.size # uses the cached copy of books
author.books.empty? # uses the cached copy of books
```
-But what if you want to reload the cache, because data might have been changed by some other part of the application? Just pass `true` to the association call:
+But what if you want to reload the cache, because data might have been changed by some other part of the application? Just call `reload` on the association:
```ruby
author.books # retrieves books from the database
author.books.size # uses the cached copy of books
-author.books(true).empty? # discards the cached copy of books
+author.books.reload.empty? # discards the cached copy of books
# and goes back to the database
```
@@ -713,7 +713,7 @@ By default, Active Record doesn't know about the connection between these associ
```ruby
a = Author.first
-b = c.books.first
+b = a.books.first
a.first_name == b.author.first_name # => true
a.first_name = 'Manny'
a.first_name == b.author.first_name # => false
@@ -726,7 +726,7 @@ class Author < ApplicationRecord
has_many :books, inverse_of: :author
end
-class book < ApplicationRecord
+class Book < ApplicationRecord
belongs_to :author, inverse_of: :books
end
```
@@ -734,8 +734,8 @@ end
With these changes, Active Record will only load one copy of the author object, preventing inconsistencies and making your application more efficient:
```ruby
-a = author.first
-b = c.books.first
+a = Author.first
+b = a.books.first
a.first_name == b.author.first_name # => true
a.first_name = 'Manny'
a.first_name == b.author.first_name # => true
@@ -932,15 +932,13 @@ side of the association.
Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`.
##### `:dependent`
-If you set the `:dependent` option to:
+Controls what happens to associated objects when their owner is destroyed:
-* `:destroy`, when the object is destroyed, `destroy` will be called on its
-associated objects.
-* `:delete_all`, when the object is destroyed, all its associated objects will be
-deleted directly from the database without calling their `destroy` method.
-* `:nullify`, causes the foreign key to be set to `NULL`. Callbacks are not executed.
-* `:restrict_with_exception`, causes an exception to be raised if there is an associated record
-* `:restrict_with_error`, causes an error to be added to the owner if there is an associated object
+* `:destroy` causes the associated objects to also be destroyed.
+* `:delete_all` causes the associated objects to be deleted directly from the database (callbacks are not executed).
+* `:nullify` causes the foreign keys to be set to `NULL` (callbacks are not executed).
+* `:restrict_with_exception` causes an exception to be raised if there are associated records.
+* `:restrict_with_error` causes an error to be added to the owner if there are associated objects.
WARNING: You should not specify this option on a `belongs_to` association that is connected with a `has_many` association on the other class. Doing so can lead to orphaned records in your database.
@@ -1479,7 +1477,7 @@ WARNING: Objects will _always_ be removed from the database, ignoring the `:depe
##### `collection=(objects)`
-The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate.
+The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate. The changes are persisted to the database.
##### `collection_singular_ids`
@@ -1491,7 +1489,7 @@ The `collection_singular_ids` method returns an array of the ids of the objects
##### `collection_singular_ids=(ids)`
-The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate.
+The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate. The changes are persisted to the database.
##### `collection.clear`
@@ -2008,7 +2006,7 @@ The `collection.destroy` method removes one or more objects from the collection
##### `collection=(objects)`
-The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate.
+The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate. The changes are persisted to the database.
##### `collection_singular_ids`
@@ -2020,7 +2018,7 @@ The `collection_singular_ids` method returns an array of the ids of the objects
##### `collection_singular_ids=(ids)`
-The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate.
+The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate. The changes are persisted to the database.
##### `collection.clear`
diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md
index de0fa2fdc0..61657023e7 100644
--- a/guides/source/autoloading_and_reloading_constants.md
+++ b/guides/source/autoloading_and_reloading_constants.md
@@ -449,9 +449,10 @@ 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`.
+* All subdirectories of `app` in the application and engines present at boot
+ time. 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.
@@ -524,7 +525,7 @@ 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,
+First it 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`.
@@ -624,7 +625,7 @@ file is loaded. If the file actually defines `Post` all is fine, otherwise
### 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
+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
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 3a1a1ccfe6..6c734c1d78 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -100,8 +100,8 @@ called key-based expiration.
Cache fragments will also be expired when the view fragment changes (e.g., the
HTML in the view changes). The string of characters at the end of the key is a
-template tree digest. It is an md5 hash computed based on the contents of the
-view fragment you are caching. If you change the view fragment, the md5 hash
+template tree digest. It is an MD5 hash computed based on the contents of the
+view fragment you are caching. If you change the view fragment, the MD5 hash
will change, expiring the existing file.
TIP: Cache stores like Memcached will automatically delete old cache files.
@@ -119,25 +119,16 @@ If you want to cache a fragment under certain conditions, you can use
The `render` helper can also cache individual templates rendered for a collection.
It can even one up the previous example with `each` by reading all cache
-templates at once instead of one by one. This is done automatically if the template
-rendered by the collection includes a `cache` call. Take a collection that renders
-a `products/_product.html.erb` partial for each element:
-
-```ruby
-render products
-```
-
-If `products/_product.html.erb` starts with a `cache` call like so:
+templates at once instead of one by one. This is done by passing `cached: true` when rendering the collection:
```html+erb
-<% cache product do %>
- <%= product.name %>
-<% end %>
+<%= render partial: 'products/product', collection: @products, cached: true %>
```
-All the cached templates from previous renders will be fetched at once with much
-greater speed. There's more info on how to make your templates [eligible for
-collection caching](http://api.rubyonrails.org/classes/ActionView/Template/Handlers/ERB.html#method-i-resource_cache_call_pattern).
+All cached templates from previous renders will be fetched at once with much
+greater speed. Additionally, the templates that haven't yet been cached will be
+written to cache and multi fetched on the next render.
+
### Russian Doll Caching
@@ -267,7 +258,7 @@ comment format anywhere in the template, like:
If you use a helper method, for example, inside a cached block and you then update
that helper, you'll have to bump the cache as well. It doesn't really matter how
-you do it, but the md5 of the template file must change. One recommendation is to
+you do it, but the MD5 of the template file must change. One recommendation is to
simply be explicit in a comment, like:
```html+erb
@@ -521,6 +512,40 @@ class ProductsController < ApplicationController
end
```
+### Strong v/s Weak ETags
+
+Rails generates weak ETags by default. Weak ETags allow semantically equivalent
+responses to have the same ETags, even if their bodies do not match exactly.
+This is useful when we don't want the page to be regenerated for minor changes in
+response body.
+
+Weak ETags have a leading `W/` to differentiate them from strong ETags.
+
+```
+ W/"618bbc92e2d35ea1945008b42799b0e7" → Weak ETag
+ "618bbc92e2d35ea1945008b42799b0e7" → Strong ETag
+```
+
+Unlike weak ETag, strong ETag implies that response should be exactly the same
+and byte by byte identical. Useful when doing Range requests within a
+large video or PDF file. Some CDNs support only strong ETags, like Akamai.
+If you absolutely need to generate a strong ETag, it can be done as follows.
+
+```ruby
+ class ProductsController < ApplicationController
+ def show
+ @product = Product.find(params[:id])
+ fresh_when last_modified: @product.published_at.utc, strong_etag: @product
+ end
+ end
+```
+
+You can also set the strong ETag directly on the response.
+
+```ruby
+ response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7"
+```
+
References
----------
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index f33e729de0..f766403228 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -65,11 +65,12 @@ $ bin/rails server
=> Booting Puma
=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
-=> Ctrl-C to shutdown server
-Puma 2.15.3 starting...
-* Min threads: 0, max threads: 16
+Puma starting in single mode...
+* Version 3.0.2 (ruby 2.3.0-p0), codename: Plethora of Penguin Pinatas
+* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
+Use Ctrl-C to stop
```
With just three commands we whipped up a Rails server listening on port 3000. Go to your browser and open [http://localhost:3000](http://localhost:3000), you will see a basic Rails app running.
@@ -325,7 +326,7 @@ With the `helper` method it is possible to access Rails and your application's h
### `rails dbconsole`
-`rails dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3.
+`rails dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL (including MariaDB), PostgreSQL and SQLite3.
INFO: You can also use the alias "db" to invoke the dbconsole: `rails db`.
@@ -432,7 +433,7 @@ Ruby version 2.2.2 (x86_64-linux)
RubyGems version 2.4.6
Rack version 1.6
JavaScript Runtime Node.js (V8)
-Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag
+Middleware Rack::Sendfile, ActionDispatch::Static, ActionDispatch::Executor, #<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, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag
Application root /home/foobar/commandsapp
Environment development
Database adapter sqlite3
@@ -618,7 +619,7 @@ We had to create the **gitapp** directory and initialize an empty git repository
```bash
$ cat config/database.yml
-# PostgreSQL. Versions 8.2 and up are supported.
+# PostgreSQL. Versions 9.1 and up are supported.
#
# Install the pg driver:
# gem install pg
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 8a21d4062a..b3d3b2c681 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -60,11 +60,11 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
* `config.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints built-in in browsers using different domain aliases. Shorter version of `config.action_controller.asset_host`.
-* `config.autoload_once_paths` accepts an array of paths from which Rails will autoload constants that won't be wiped per request. Relevant if `config.cache_classes` is false, which is the case in development mode by default. Otherwise, all autoloading happens only once. All elements of this array must also be in `autoload_paths`. Default is an empty array.
+* `config.autoload_once_paths` accepts an array of paths from which Rails will autoload constants that won't be wiped per request. Relevant if `config.cache_classes` is `false`, which is the case in development mode by default. Otherwise, all autoloading happens only once. All elements of this array must also be in `autoload_paths`. Default is an empty array.
* `config.autoload_paths` accepts an array of paths from which Rails will autoload constants. Default is all directories under `app`.
-* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to false in development mode, and true in test and production modes.
+* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to `false` in development mode, and `true` in test and production modes.
* `config.action_view.cache_template_loading` controls whether or not templates should be reloaded on each request. Defaults to whatever is set for `config.cache_classes`.
@@ -73,9 +73,9 @@ application. Accepts a valid week day symbol (e.g. `:monday`).
* `config.cache_store` configures which cache store to use for Rails caching. Options include one of the symbols `:memory_store`, `:file_store`, `:mem_cache_store`, `:null_store`, or an object that implements the cache API. Defaults to `:file_store` if the directory `tmp/cache` exists, and to `:memory_store` otherwise.
-* `config.colorize_logging` specifies whether or not to use ANSI color codes when logging information. Defaults to true.
+* `config.colorize_logging` specifies whether or not to use ANSI color codes when logging information. Defaults to `true`.
-* `config.consider_all_requests_local` is a flag. If true then any error will cause detailed debugging information to be dumped in the HTTP response, and the `Rails::Info` controller will show the application runtime context in `/rails/info/properties`. True by default in development and test environments, and false in production mode. For finer-grained control, set this to false and implement `local_request?` in controllers to specify which requests should provide debugging information on errors.
+* `config.consider_all_requests_local` is a flag. If `true` then any error will cause detailed debugging information to be dumped in the HTTP response, and the `Rails::Info` controller will show the application runtime context in `/rails/info/properties`. `true` by default in development and test environments, and `false` in production mode. For finer-grained control, set this to `false` and implement `local_request?` in controllers to specify which requests should provide debugging information on errors.
* `config.console` allows you to set class that will be used as console you run `rails console`. It's best to run it in `console` block:
@@ -88,9 +88,9 @@ application. Accepts a valid week day symbol (e.g. `:monday`).
end
```
-* `config.eager_load` when true, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace.
+* `config.eager_load` when `true`, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace.
-* `config.eager_load_namespaces` registers namespaces that are eager loaded when `config.eager_load` is true. All namespaces in the list must respond to the `eager_load!` method.
+* `config.eager_load_namespaces` registers namespaces that are eager loaded when `config.eager_load` is `true`. All namespaces in the list must respond to the `eager_load!` method.
* `config.eager_load_paths` accepts an array of paths from which Rails will eager load on boot if cache classes is enabled. Defaults to every folder in the `app` directory of the application.
@@ -98,15 +98,17 @@ application. Accepts a valid week day symbol (e.g. `:monday`).
* `config.exceptions_app` sets the exceptions application invoked by the ShowException middleware when an exception happens. Defaults to `ActionDispatch::PublicExceptions.new(Rails.public_path)`.
-* `config.file_watcher` is the class used to detect file updates in the file system when `config.reload_classes_only_on_change` is true. Rails ships with `ActiveSupport::FileUpdateChecker`, the default, and `ActiveSupport::EventedFileUpdateChecker` (this one depends on the [listen](https://github.com/guard/listen) gem). Custom classes must conform to the `ActiveSupport::FileUpdateChecker` API.
+* `config.debug_exception_response_format` sets the format used in responses when errors occur in development mode. Defaults to `:api` for API only apps and `:default` for normal apps.
+
+* `config.file_watcher` is the class used to detect file updates in the file system when `config.reload_classes_only_on_change` is `true`. Rails ships with `ActiveSupport::FileUpdateChecker`, the default, and `ActiveSupport::EventedFileUpdateChecker` (this one depends on the [listen](https://github.com/guard/listen) gem). Custom classes must conform to the `ActiveSupport::FileUpdateChecker` API.
* `config.filter_parameters` used for filtering out the parameters that
you don't want shown in the logs, such as passwords or credit card
-numbers. New applications filter out passwords by adding the following `config.filter_parameters+=[:password]` in `config/initializers/filter_parameter_logging.rb`.
+numbers. By default, Rails filters out passwords by adding `Rails.application.config.filter_parameters += [:password]` in `config/initializers/filter_parameter_logging.rb`. Parameters filter works by partial matching regular expression.
-* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://edgeapi.rubyonrails.org/classes/ActionDispatch/SSL.html) for details.
+* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware, and sets `config.action_mailer.default_url_options` to be `{ protocol: 'https' }`. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://edgeapi.rubyonrails.org/classes/ActionDispatch/SSL.html) for details.
-* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`.
+* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`. If you are setting a value for `config.logger` you must manually pass the value of your formatter to your logger before it is wrapped in an `ActiveSupport::TaggedLogging` instance, Rails will not do it for you.
* `config.log_level` defines the verbosity of the Rails logger. This option
defaults to `:debug` for all environments. The available log levels are: `:debug`,
@@ -114,15 +116,29 @@ defaults to `:debug` for all environments. The available log levels are: `:debug
* `config.log_tags` accepts a list of: methods that the `request` object responds to, a `Proc` that accepts the `request` object, or something that responds to `to_s`. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications.
-* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`.
+* `config.logger` is the logger that will be used for `Rails.logger` and any related Rails logging such as `ActiveRecord::Base.logger`. It defaults to an instance of `ActiveSupport::TaggedLogging` that wraps an instance of `ActiveSupport::Logger` which outputs a log to the `log/` directory. You can supply a custom logger, to get full compatibility you must follow these guidelines:
+ * To support a formatter, you must manually assign a formatter from the `config.log_formatter` value to the logger.
+ * To support tagged logs, the log instance must be wrapped with `ActiveSupport::TaggedLogging`.
+ * To support silencing, the logger must include `LoggerSilence` and `ActiveSupport::LoggerThreadSafeLevel` modules. The `ActiveSupport::Logger` class already includes these modules.
+
+ ```ruby
+ class MyLogger < ::Logger
+ include ActiveSupport::LoggerThreadSafeLevel
+ include LoggerSilence
+ end
+
+ mylogger = MyLogger.new(STDOUT)
+ mylogger.formatter = config.log_formatter
+ config.logger = ActiveSupport::TaggedLogging.new(mylogger)
+ ```
* `config.middleware` allows you to configure the application's middleware. This is covered in depth in the [Configuring Middleware](#configuring-middleware) section below.
-* `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. If `config.cache_classes` is true, this option is ignored.
+* `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to `true`. If `config.cache_classes` is `true`, this option is ignored.
* `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.public_file_server.enabled` configures Rails to serve static files from the public directory. 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.public_file_server.enabled` configures Rails to serve static files from the public directory. 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:
@@ -137,16 +153,16 @@ defaults to `:debug` for all environments. The available log levels are: `:debug
### Configuring Assets
* `config.assets.enabled` a flag that controls whether the asset
-pipeline is enabled. It is set to true by default.
+pipeline is enabled. It is set to `true` by default.
* `config.assets.raise_runtime_errors` Set this flag to `true` to enable additional runtime error checking. Recommended in `config/environments/development.rb` to minimize unexpected behavior when deploying to `production`.
-* `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/environments/production.rb`.
-
* `config.assets.css_compressor` defines the CSS compressor to use. It is set by default by `sass-rails`. The unique alternative value at the moment is `:yui`, which uses the `yui-compressor` gem.
* `config.assets.js_compressor` defines the JavaScript compressor to use. Possible values are `:closure`, `:uglifier` and `:yui` which require the use of the `closure-compiler`, `uglifier` or `yui-compressor` gems respectively.
+* `config.assets.gzip` a flag that enables the creation of gzipped version of compiled assets, along with non-gzipped assets. Set to `true` by default.
+
* `config.assets.paths` contains the paths which are used to look for assets. Appending paths to this configuration option will cause those paths to be used in the search for assets.
* `config.assets.precompile` allows you to specify additional assets (other than `application.css` and `application.js`) which are to be precompiled when `rake assets:precompile` is run.
@@ -155,15 +171,13 @@ 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` and `development.rb`.
+* `config.assets.digest` enables the use of MD5 fingerprints in asset names. Set to `true` by default.
* `config.assets.debug` disables the concatenation and compression of assets. Set to `true` by default in `development.rb`.
-* `config.assets.cache_store` defines the cache store that Sprockets will use. The default is the Rails file store.
-
* `config.assets.compile` is a boolean that can be used to turn on live Sprockets compilation in production.
-* `config.assets.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to the same configured at `config.logger`. Setting `config.assets.logger` to false will turn off served assets logging.
+* `config.assets.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to the same configured at `config.logger`. Setting `config.assets.logger` to `false` will turn off served assets logging.
### Configuring Generators
@@ -191,6 +205,7 @@ The full set of methods that can be used in this block are as follows:
* `scaffold_controller` different from `resource_controller`, defines which generator to use for generating a _scaffolded_ controller when using `rails generate scaffold`. Defaults to `:scaffold_controller`.
* `stylesheets` turns on the hook for stylesheets in generators. Used in Rails for when the `scaffold` generator is run, but this hook can be used in other generates as well. Defaults to `true`.
* `stylesheet_engine` configures the stylesheet engine (for eg. sass) to be used when generating assets. Defaults to `:css`.
+* `scaffold_stylesheet` creates `scaffold.css` when generating a scaffolded resource. Defaults to `true`.
* `test_framework` defines which test framework to use. Defaults to `false` and will use Minitest by default.
* `template_engine` defines which template engine to use, such as ERB or Haml. Defaults to `:erb`.
@@ -200,7 +215,7 @@ Every Rails application comes with a standard set of middleware which it uses in
* `ActionDispatch::SSL` forces every request to be served using HTTPS. Enabled if `config.force_ssl` is set to `true`. Options passed to this can be configured by setting `config.ssl_options`.
* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.public_file_server.enabled` is `false`. Set `config.public_file_server.index_name` if you need to serve a static directory index file that is not named `index`. For example, to serve `main.html` instead of `index.html` for directory requests, set `config.public_file_server.index_name` to `"main"`.
-* `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when `config.cache_classes` is `false`.
+* `ActionDispatch::Executor` allows thread safe code reloading. Disabled if `config.allow_concurrency` is `false`, which causes `Rack::Lock` to be loaded. `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time.
* `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.
* `Rails::Rack::Logger` notifies the logs that the request has begun. After request is complete, flushes all the logs.
@@ -209,8 +224,6 @@ Every Rails application comes with a standard set of middleware which it uses in
* `ActionDispatch::RemoteIp` checks for IP spoofing attacks and gets valid `client_ip` from request headers. Configurable with the `config.action_dispatch.ip_spoofing_check`, and `config.action_dispatch.trusted_proxies` options.
* `Rack::Sendfile` intercepts responses whose body is being served from a file and replaces it with a server specific X-Sendfile header. Configurable with `config.action_dispatch.x_sendfile_header`.
* `ActionDispatch::Callbacks` runs the prepare callbacks before serving the request.
-* `ActiveRecord::ConnectionAdapters::ConnectionManagement` cleans active connections after each request, unless the `rack.test` key in the request environment is set to `true`.
-* `ActiveRecord::QueryCache` caches all SELECT queries generated in a request. If any INSERT or UPDATE takes place then the cache is cleaned.
* `ActionDispatch::Cookies` sets cookies for the request.
* `ActionDispatch::Session::CookieStore` is responsible for storing the session in cookies. An alternate middleware can be used for this by changing the `config.action_controller.session_store` to an alternate value. Additionally, options passed to this can be configured by using `config.action_controller.session_options`.
* `ActionDispatch::Flash` sets up the `flash` keys. Only available if `config.action_controller.session_store` is set to a value.
@@ -229,6 +242,12 @@ This will put the `Magical::Unicorns` middleware on the end of the stack. You ca
config.middleware.insert_before Rack::Head, Magical::Unicorns
```
+Or you can insert a middleware to exact position by using indexes. For example, if you want to insert `Magical::Unicorns` middleware on top of the stack, you can do it, like so:
+
+```ruby
+config.middleware.insert_before 0, Magical::Unicorns
+```
+
There's also `insert_after` which will insert a middleware after another:
```ruby
@@ -259,6 +278,26 @@ All these configuration options are delegated to the `I18n` library.
* `config.i18n.load_path` sets the path Rails uses to look for locale files. Defaults to `config/locales/*.{yml,rb}`.
+* `config.i18n.fallbacks` sets fallback behavior for missing translations. Here are 3 usage examples for this option:
+
+ * You can set the option to `true` for using default locale as fallback, like so:
+
+ ```ruby
+ config.i18n.fallbacks = true
+ ```
+
+ * Or you can set an array of locales as fallback, like so:
+
+ ```ruby
+ config.i18n.fallbacks = [:tr, :en]
+ ```
+
+ * Or you can set different fallbacks for locales individually. For example, if you want to use `:tr` for `:az` and `:de`, `:en` for `:da` as fallbacks, you can do it, like so:
+
+ ```ruby
+ config.i18n.fallbacks = { az: :tr, da: [:de, :en] }
+ ```
+
### Configuring Active Record
`config.active_record` includes a variety of configuration options:
@@ -266,8 +305,8 @@ All these configuration options are delegated to the `I18n` library.
* `config.active_record.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then passed on to any new database connections made. You can retrieve this logger by calling `logger` on either an Active Record model class or an Active Record model instance. Set to `nil` to disable logging.
* `config.active_record.primary_key_prefix_type` lets you adjust the naming for primary key columns. By default, Rails assumes that primary key columns are named `id` (and this configuration option doesn't need to be set.) There are two other choices:
- * `:table_name` would make the primary key for the Customer class `customerid`
- * `:table_name_with_underscore` would make the primary key for the Customer class `customer_id`
+ * `:table_name` would make the primary key for the Customer class `customerid`.
+ * `:table_name_with_underscore` would make the primary key for the Customer class `customer_id`.
* `config.active_record.table_name_prefix` lets you set a global string to be prepended to table names. If you set this to `northwest_`, then the Customer class will look for `northwest_customers` as its table. The default is an empty string.
@@ -275,15 +314,17 @@ All these configuration options are delegated to the `I18n` library.
* `config.active_record.schema_migrations_table_name` lets you set a string to be used as the name of the schema migrations table.
-* `config.active_record.pluralize_table_names` specifies whether Rails will look for singular or plural table names in the database. If set to true (the default), then the Customer class will use the `customers` table. If set to false, then the Customer class will use the `customer` table.
+* `config.active_record.pluralize_table_names` specifies whether Rails will look for singular or plural table names in the database. If set to `true` (the default), then the Customer class will use the `customers` table. If set to false, then the Customer class will use the `customer` table.
* `config.active_record.default_timezone` determines whether to use `Time.local` (if set to `:local`) or `Time.utc` (if set to `:utc`) when pulling dates and times from the database. The default is `:utc`.
* `config.active_record.schema_format` controls the format for dumping the database schema to a file. The options are `:ruby` (the default) for a database-independent version that depends on migrations, or `:sql` for a set of (potentially database-dependent) SQL statements.
-* `config.active_record.timestamped_migrations` controls whether migrations are numbered with serial integers or with timestamps. The default is true, to use timestamps, which are preferred if there are multiple developers working on the same application.
+* `config.active_record.error_on_ignored_order_or_limit` specifies if an error should be raised if the order or limit of a query is ignored during a batch query. The options are `true` (raise error) or `false` (warn). Default is `false`.
+
+* `config.active_record.timestamped_migrations` controls whether migrations are numbered with serial integers or with timestamps. The default is `true`, to use timestamps, which are preferred if there are multiple developers working on the same application.
-* `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is true by default.
+* `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 `:nsec`.
@@ -291,13 +332,13 @@ All these configuration options are delegated to the `I18n` library.
* `config.active_record.partial_writes` is a boolean value and controls whether or not partial writes are used (i.e. whether updates only set attributes that are dirty). Note that when using partial writes, you should also use optimistic locking `config.active_record.lock_optimistically` since concurrent updates may write attributes based on a possibly stale read state. The default value is `true`.
-* `config.active_record.maintain_test_schema` is a boolean value which controls whether Active Record should try to keep your test database schema up-to-date with `db/schema.rb` (or `db/structure.sql`) when you run your tests. The default is true.
+* `config.active_record.maintain_test_schema` is a boolean value which controls whether Active Record should try to keep your test database schema up-to-date with `db/schema.rb` (or `db/structure.sql`) when you run your tests. The default is `true`.
* `config.active_record.dump_schema_after_migration` is a flag which
controls whether or not schema dump should happen (`db/schema.rb` or
- `db/structure.sql`) when you run migrations. This is set to false in
+ `db/structure.sql`) when you run migrations. This is set to `false` in
`config/environments/production.rb` which is generated by Rails. The
- default value is true if this configuration is not set.
+ 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,
@@ -315,11 +356,11 @@ All these configuration options are delegated to the `I18n` library.
* `config.active_record.index_nested_attribute_errors` allows errors for nested
has_many relationships to be displayed with an index as well as the error.
- Defaults to false.
+ Defaults to `false`.
The MySQL adapter adds one additional configuration option:
-* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default.
+* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns as booleans. Defaults to `true`.
The schema dumper adds one additional configuration option:
@@ -331,11 +372,11 @@ The schema dumper adds one additional configuration option:
* `config.action_controller.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets rather than the application server itself.
-* `config.action_controller.perform_caching` configures whether the application should perform the caching features provided by the Action Controller component or not. Set to false in development mode, true in production.
+* `config.action_controller.perform_caching` configures whether the application should perform the caching features provided by the Action Controller component or not. Set to `false` in development mode, `true` in production.
* `config.action_controller.default_static_extension` configures the extension used for cached pages. Defaults to `.html`.
-* `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.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 configuration behavior (when this option is not explicitly set to `true` or `false`) is that all view helpers are available to each controller.
* `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.
@@ -373,6 +414,10 @@ The schema dumper adds one additional configuration option:
* `config.action_dispatch.tld_length` sets the TLD (top-level domain) length for the application. Defaults to `1`.
+* `config.action_dispatch.ignore_accept_header` is used to determine whether to ignore accept headers from a request. Defaults to `false`.
+
+* `config.action_dispatch.x_sendfile_header` specifies server specific X-Sendfile header. This is useful for accelerated file sending from server. For example it can be set to 'X-Sendfile' for Apache.
+
* `config.action_dispatch.http_auth_salt` sets the HTTP Auth salt value. Defaults
to `'http authentication'`.
@@ -387,7 +432,7 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
* `config.action_dispatch.perform_deep_munge` configures whether `deep_munge`
method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation)
- for more information. It defaults to true.
+ for more information. It defaults to `true`.
* `config.action_dispatch.rescue_responses` configures what exceptions are assigned to an HTTP status. It accepts a hash and you can specify pairs of exception/status. By default, this is defined as:
@@ -442,7 +487,7 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
* `config.action_view.embed_authenticity_token_in_remote_forms` allows you to
set the default behavior for `authenticity_token` in forms with `remote:
- true`. By default it's set to false, which means that remote forms will not
+ true`. By default it's set to `false`, which means that remote forms will not
include `authenticity_token`, which is helpful when you're fragment-caching
the form. Remote forms get the authenticity from the `meta` tag, so embedding
is unnecessary unless you support browsers without JavaScript. In such case
@@ -461,9 +506,9 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
error should be raised for missing translations.
* `config.action_view.automatically_disable_submit_tag` determines whether
- submit_tag should automatically disable on click, this defaults to true.
+ submit_tag should automatically disable on click, this defaults to `true`.
-* `config.action_view.debug_missing_translation` determins whether to wrap the missing translations key in a `<span>` tag or not. This defaults to true.
+* `config.action_view.debug_missing_translation` determines whether to wrap the missing translations key in a `<span>` tag or not. This defaults to `true`.
### Configuring Action Mailer
@@ -478,16 +523,19 @@ There are a number of settings available on `config.action_mailer`:
* `:user_name` - If your mail server requires authentication, set the username in this setting.
* `:password` - If your mail server requires authentication, set the password in this setting.
* `:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.
+ * `:enable_starttls_auto` - Detects if STARTTLS is enabled in your SMTP server and starts to use it. It defaults to `true`.
+ * `:openssl_verify_mode` - When using TLS, you can set how OpenSSL checks the certificate. This is useful if you need to validate a self-signed and/or a wildcard certificate. This can be one of the OpenSSL verify constants, `:none` or `:peer` -- or the constant directly `OpenSSL::SSL::VERIFY_NONE` or `OpenSSL::SSL::VERIFY_PEER`, respectively.
+ * `:ssl/:tls` - Enables the SMTP connection to use SMTP/TLS (SMTPS: SMTP over direct TLS connection).
* `config.action_mailer.sendmail_settings` allows detailed configuration for the `sendmail` delivery method. It accepts a hash of options, which can include any of these options:
* `:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.
* `:arguments` - The command line arguments. Defaults to `-i -t`.
-* `config.action_mailer.raise_delivery_errors` specifies whether to raise an error if email delivery cannot be completed. It defaults to true.
+* `config.action_mailer.raise_delivery_errors` specifies whether to raise an error if email delivery cannot be completed. It defaults to `true`.
* `config.action_mailer.delivery_method` defines the delivery method and defaults to `:smtp`. See the [configuration section in the Action Mailer guide](action_mailer_basics.html#action-mailer-configuration) for more info.
-* `config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to false for testing.
+* `config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to `false` for testing.
* `config.action_mailer.default_options` configures Action Mailer defaults. Use to set options like `from` or `reply_to` for every mailer. These default to:
@@ -533,6 +581,9 @@ There are a number of settings available on `config.action_mailer`:
* `config.action_mailer.deliver_later_queue_name` specifies the queue name for
mailers. By default this is `mailers`.
+* `config.action_mailer.perform_caching` specifies whether the mailer templates should perform fragment caching or not. By default this is `false` in all environments.
+
+
### Configuring Active Support
There are a few configuration options available in Active Support:
@@ -547,7 +598,7 @@ There are a few configuration options available in Active Support:
* `config.active_support.time_precision` sets the precision of JSON encoded time values. Defaults to `3`.
-* `ActiveSupport.halt_callback_chains_on_return_false` specifies whether Active Record and Active Model callback chains can be halted by returning `false` in a 'before' callback. When set to `false`, callback chains are halted only when explicitly done so with `throw(:abort)`. When set to `true`, callback chains are halted when a callback returns false (the previous behavior before Rails 5) and a deprecation warning is given. Defaults to `true` during the deprecation period. New Rails 5 apps generate an initializer file called `callback_terminator.rb` which sets the value to `false`. This file is *not* added when running `rake rails:update`, so returning `false` will still work on older apps ported to Rails 5 and display a deprecation warning to prompt users to update their code.
+* `ActiveSupport.halt_callback_chains_on_return_false` specifies whether Active Record and Active Model callback chains can be halted by returning `false` in a 'before' callback. When set to `false`, callback chains are halted only when explicitly done so with `throw(:abort)`. When set to `true`, callback chains are halted when a callback returns `false` (the previous behavior before Rails 5) and a deprecation warning is given. Defaults to `true` during the deprecation period. New Rails 5 apps generate an initializer file called `callback_terminator.rb` which sets the value to `false`. This file is *not* added when running `rails app:update`, so returning `false` will still work on older apps ported to Rails 5 and display a deprecation warning to prompt users to update their code.
* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`.
@@ -563,7 +614,7 @@ There are a few configuration options available in Active Support:
`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).
+* `config.active_job.queue_adapter` sets the adapter for the queueing backend. The default adapter is `:async`. 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
@@ -612,6 +663,17 @@ There are a few configuration options available in Active Support:
* `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 Action Cable
+
+* `config.action_cable.url` accepts a string for the URL for where
+ you are hosting your Action Cable server. You would use this option
+if you are running Action Cable servers that are separated from your
+main application.
+* `config.action_cable.mount_path` accepts a string for where to mount Action
+ Cable, as part of the main server process. Defaults to `/cable`.
+You can set this as nil to not mount Action Cable as part of your
+normal Rails server.
+
### Configuring a Database
Just about every Rails application will interact with a database. You can connect to the database by setting an environment variable `ENV['DATABASE_URL']` or by using a configuration file called `config/database.yml`.
@@ -753,11 +815,11 @@ development:
timeout: 5000
```
-NOTE: Rails uses an SQLite3 database for data storage by default because it is a zero configuration database that just works. Rails also supports MySQL and PostgreSQL "out of the box", and has plugins for many database systems. If you are using a database in a production environment Rails most likely has an adapter for it.
+NOTE: Rails uses an SQLite3 database for data storage by default because it is a zero configuration database that just works. Rails also supports MySQL (including MariaDB) and PostgreSQL "out of the box", and has plugins for many database systems. If you are using a database in a production environment Rails most likely has an adapter for it.
-#### Configuring a MySQL Database
+#### Configuring a MySQL or MariaDB Database
-If you choose to use MySQL instead of the shipped SQLite3 database, your `config/database.yml` will look a little different. Here's the development section:
+If you choose to use MySQL or MariaDB instead of the shipped SQLite3 database, your `config/database.yml` will look a little different. Here's the development section:
```yaml
development:
@@ -770,7 +832,7 @@ development:
socket: /tmp/mysql.sock
```
-If your development computer's MySQL installation includes a root user with an empty password, this configuration should work for you. Otherwise, change the username and password in the `development` section as appropriate.
+If your development database has a root user with an empty password, this configuration should work for you. Otherwise, change the username and password in the `development` section as appropriate.
#### Configuring a PostgreSQL Database
@@ -812,9 +874,9 @@ development:
database: db/development.sqlite3
```
-#### Configuring a MySQL Database for JRuby Platform
+#### Configuring a MySQL or MariaDB Database for JRuby Platform
-If you choose to use MySQL and are using JRuby, your `config/database.yml` will look a little different. Here's the development section:
+If you choose to use MySQL or MariaDB and are using JRuby, your `config/database.yml` will look a little different. Here's the development section:
```yaml
development:
@@ -867,7 +929,7 @@ Rails will now prepend "/app1" when generating links.
#### Using Passenger
-Passenger makes it easy to run your application in a subdirectory. You can find the relevant configuration in the [Passenger manual](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri).
+Passenger makes it easy to run your application in a subdirectory. You can find the relevant configuration in the [Passenger manual](https://www.phusionpassenger.com/library/deploy/apache/deploy/ruby/#deploying-an-app-to-a-sub-uri-or-subdirectory).
#### Using a Reverse Proxy
@@ -992,98 +1054,110 @@ Because `Rails::Application` inherits from `Rails::Railtie` (indirectly), you ca
Below is a comprehensive list of all the initializers found in Rails in the order that they are defined (and therefore run in, unless otherwise stated).
-* `load_environment_hook` Serves as a placeholder so that `:load_environment_config` can be defined to run before it.
+* `load_environment_hook`: Serves as a placeholder so that `:load_environment_config` can be defined to run before it.
+
+* `load_active_support`: Requires `active_support/dependencies` which sets up the basis for Active Support. Optionally requires `active_support/all` if `config.active_support.bare` is un-truthful, which is the default.
+
+* `initialize_logger`: Initializes the logger (an `ActiveSupport::Logger` object) for the application and makes it accessible at `Rails.logger`, provided that no initializer inserted before this point has defined `Rails.logger`.
+
+* `initialize_cache`: If `Rails.cache` isn't set yet, initializes the cache by referencing the value in `config.cache_store` and stores the outcome as `Rails.cache`. If this object responds to the `middleware` method, its middleware is inserted before `Rack::Runtime` in the middleware stack.
+
+* `set_clear_dependencies_hook`: This initializer - which runs only if `cache_classes` is set to `false` - uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request.
+
+* `initialize_dependency_mechanism`: If `config.cache_classes` is true, configures `ActiveSupport::Dependencies.mechanism` to `require` dependencies rather than `load` them.
+
+* `bootstrap_hook`: Runs all configured `before_initialize` blocks.
-* `load_active_support` Requires `active_support/dependencies` which sets up the basis for Active Support. Optionally requires `active_support/all` if `config.active_support.bare` is un-truthful, which is the default.
+* `i18n.callbacks`: In the development environment, sets up a `to_prepare` callback which will call `I18n.reload!` if any of the locales have changed since the last request. In production mode this callback will only run on the first request.
-* `initialize_logger` Initializes the logger (an `ActiveSupport::Logger` object) for the application and makes it accessible at `Rails.logger`, provided that no initializer inserted before this point has defined `Rails.logger`.
+* `active_support.deprecation_behavior`: Sets up deprecation reporting for environments, defaulting to `:log` for development, `:notify` for production and `:stderr` for test. If a value isn't set for `config.active_support.deprecation` then this initializer will prompt the user to configure this line in the current environment's `config/environments` file. Can be set to an array of values.
-* `initialize_cache` If `Rails.cache` isn't set yet, initializes the cache by referencing the value in `config.cache_store` and stores the outcome as `Rails.cache`. If this object responds to the `middleware` method, its middleware is inserted before `Rack::Runtime` in the middleware stack.
+* `active_support.initialize_time_zone`: Sets the default time zone for the application based on the `config.time_zone` setting, which defaults to "UTC".
-* `set_clear_dependencies_hook` Provides a hook for `active_record.set_dispatch_hooks` to use, which will run before this initializer. This initializer - which runs only if `cache_classes` is set to `false` - uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request.
+* `active_support.initialize_beginning_of_week`: Sets the default beginning of week for the application based on `config.beginning_of_week` setting, which defaults to `:monday`.
-* `initialize_dependency_mechanism` If `config.cache_classes` is true, configures `ActiveSupport::Dependencies.mechanism` to `require` dependencies rather than `load` them.
+* `active_support.set_configs`: Sets up Active Support by using the settings in `config.active_support` by `send`'ing the method names as setters to `ActiveSupport` and passing the values through.
-* `bootstrap_hook` Runs all configured `before_initialize` blocks.
+* `action_dispatch.configure`: Configures the `ActionDispatch::Http::URL.tld_length` to be set to the value of `config.action_dispatch.tld_length`.
-* `i18n.callbacks` In the development environment, sets up a `to_prepare` callback which will call `I18n.reload!` if any of the locales have changed since the last request. In production mode this callback will only run on the first request.
+* `action_view.set_configs`: Sets up Action View by using the settings in `config.action_view` by `send`'ing the method names as setters to `ActionView::Base` and passing the values through.
-* `active_support.deprecation_behavior` Sets up deprecation reporting for environments, defaulting to `:log` for development, `:notify` for production and `:stderr` for test. If a value isn't set for `config.active_support.deprecation` then this initializer will prompt the user to configure this line in the current environment's `config/environments` file. Can be set to an array of values.
+* `action_controller.assets_config`: Initializes the `config.actions_controller.assets_dir` to the app's public directory if not explicitly configured.
-* `active_support.initialize_time_zone` Sets the default time zone for the application based on the `config.time_zone` setting, which defaults to "UTC".
+* `action_controller.set_helpers_path`: Sets Action Controller's `helpers_path` to the application's `helpers_path`.
-* `active_support.initialize_beginning_of_week` Sets the default beginning of week for the application based on `config.beginning_of_week` setting, which defaults to `:monday`.
+* `action_controller.parameters_config`: Configures strong parameters options for `ActionController::Parameters`.
-* `action_dispatch.configure` Configures the `ActionDispatch::Http::URL.tld_length` to be set to the value of `config.action_dispatch.tld_length`.
+* `action_controller.set_configs`: Sets up Action Controller by using the settings in `config.action_controller` by `send`'ing the method names as setters to `ActionController::Base` and passing the values through.
-* `action_view.set_configs` Sets up Action View by using the settings in `config.action_view` by `send`'ing the method names as setters to `ActionView::Base` and passing the values through.
+* `action_controller.compile_config_methods`: Initializes methods for the config settings specified so that they are quicker to access.
-* `action_controller.logger` Sets `ActionController::Base.logger` - if it's not already set - to `Rails.logger`.
+* `active_record.initialize_timezone`: Sets `ActiveRecord::Base.time_zone_aware_attributes` to `true`, as well as setting `ActiveRecord::Base.default_timezone` to UTC. When attributes are read from the database, they will be converted into the time zone specified by `Time.zone`.
-* `action_controller.initialize_framework_caches` Sets `ActionController::Base.cache_store` - if it's not already set - to `Rails.cache`.
+* `active_record.logger`: Sets `ActiveRecord::Base.logger` - if it's not already set - to `Rails.logger`.
-* `action_controller.set_configs` Sets up Action Controller by using the settings in `config.action_controller` by `send`'ing the method names as setters to `ActionController::Base` and passing the values through.
+* `active_record.migration_error`: Configures middleware to check for pending migrations.
-* `action_controller.compile_config_methods` Initializes methods for the config settings specified so that they are quicker to access.
+* `active_record.check_schema_cache_dump`: Loads the schema cache dump if configured and available.
-* `active_record.initialize_timezone` Sets `ActiveRecord::Base.time_zone_aware_attributes` to true, as well as setting `ActiveRecord::Base.default_timezone` to UTC. When attributes are read from the database, they will be converted into the time zone specified by `Time.zone`.
+* `active_record.warn_on_records_fetched_greater_than`: Enables warnings when queries return large numbers of records.
-* `active_record.logger` Sets `ActiveRecord::Base.logger` - if it's not already set - to `Rails.logger`.
+* `active_record.set_configs`: Sets up Active Record by using the settings in `config.active_record` by `send`'ing the method names as setters to `ActiveRecord::Base` and passing the values through.
-* `active_record.set_configs` Sets up Active Record by using the settings in `config.active_record` by `send`'ing the method names as setters to `ActiveRecord::Base` and passing the values through.
+* `active_record.initialize_database`: Loads the database configuration (by default) from `config/database.yml` and establishes a connection for the current environment.
-* `active_record.initialize_database` Loads the database configuration (by default) from `config/database.yml` and establishes a connection for the current environment.
+* `active_record.log_runtime`: Includes `ActiveRecord::Railties::ControllerRuntime` which is responsible for reporting the time taken by Active Record calls for the request back to the logger.
-* `active_record.log_runtime` Includes `ActiveRecord::Railties::ControllerRuntime` which is responsible for reporting the time taken by Active Record calls for the request back to the logger.
+* `active_record.set_reloader_hooks`: Resets all reloadable connections to the database if `config.cache_classes` is set to `false`.
-* `active_record.set_dispatch_hooks` Resets all reloadable connections to the database if `config.cache_classes` is set to `false`.
+* `active_record.add_watchable_files`: Adds `schema.rb` and `structure.sql` files to watchable files.
-* `active_job.logger` Sets `ActiveJob::Base.logger` - if it's not already set -
+* `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.
+* `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.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.
+* `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.
-* `action_mailer.compile_config_methods` Initializes methods for the config settings specified so that they are quicker to access.
+* `action_mailer.compile_config_methods`: Initializes methods for the config settings specified so that they are quicker to access.
-* `set_load_path` This initializer runs before `bootstrap_hook`. Adds paths specified by `config.load_paths` and all autoload paths to `$LOAD_PATH`.
+* `set_load_path`: This initializer runs before `bootstrap_hook`. Adds paths specified by `config.load_paths` and all autoload paths to `$LOAD_PATH`.
-* `set_autoload_paths` This initializer runs before `bootstrap_hook`. Adds all sub-directories of `app` and paths specified by `config.autoload_paths`, `config.eager_load_paths` and `config.autoload_once_paths` to `ActiveSupport::Dependencies.autoload_paths`.
+* `set_autoload_paths`: This initializer runs before `bootstrap_hook`. Adds all sub-directories of `app` and paths specified by `config.autoload_paths`, `config.eager_load_paths` and `config.autoload_once_paths` to `ActiveSupport::Dependencies.autoload_paths`.
-* `add_routing_paths` Loads (by default) all `config/routes.rb` files (in the application and railties, including engines) and sets up the routes for the application.
+* `add_routing_paths`: Loads (by default) all `config/routes.rb` files (in the application and railties, including engines) and sets up the routes for the application.
-* `add_locales` Adds the files in `config/locales` (from the application, railties and engines) to `I18n.load_path`, making available the translations in these files.
+* `add_locales`: Adds the files in `config/locales` (from the application, railties and engines) to `I18n.load_path`, making available the translations in these files.
-* `add_view_paths` Adds the directory `app/views` from the application, railties and engines to the lookup path for view files for the application.
+* `add_view_paths`: Adds the directory `app/views` from the application, railties and engines to the lookup path for view files for the application.
-* `load_environment_config` Loads the `config/environments` file for the current environment.
+* `load_environment_config`: Loads the `config/environments` file for the current environment.
-* `prepend_helpers_path` Adds the directory `app/helpers` from the application, railties and engines to the lookup path for helpers for the application.
+* `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.
+* `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.
-* `engines_blank_point` Provides a point-in-initialization to hook into if you wish to do anything before engines are loaded. After this point, all railtie and engine initializers are run.
+* `engines_blank_point`: Provides a point-in-initialization to hook into if you wish to do anything before engines are loaded. After this point, all railtie and engine initializers are run.
-* `add_generator_templates` Finds templates for generators at `lib/templates` for the application, railties and engines and adds these to the `config.generators.templates` setting, which will make the templates available for all generators to reference.
+* `add_generator_templates`: Finds templates for generators at `lib/templates` for the application, railties and engines and adds these to the `config.generators.templates` setting, which will make the templates available for all generators to reference.
-* `ensure_autoload_once_paths_as_subset` Ensures that the `config.autoload_once_paths` only contains paths from `config.autoload_paths`. If it contains extra paths, then an exception will be raised.
+* `ensure_autoload_once_paths_as_subset`: Ensures that the `config.autoload_once_paths` only contains paths from `config.autoload_paths`. If it contains extra paths, then an exception will be raised.
-* `add_to_prepare_blocks` The block for every `config.to_prepare` call in the application, a railtie or engine is added to the `to_prepare` callbacks for Action Dispatch which will be run per request in development, or before the first request in production.
+* `add_to_prepare_blocks`: The block for every `config.to_prepare` call in the application, a railtie or engine is added to the `to_prepare` callbacks for Action Dispatch which will be run per request in development, or before the first request in production.
-* `add_builtin_route` If the application is running under the development environment then this will append the route for `rails/info/properties` to the application routes. This route provides the detailed information such as Rails and Ruby version for `public/index.html` in a default Rails application.
+* `add_builtin_route`: If the application is running under the development environment then this will append the route for `rails/info/properties` to the application routes. This route provides the detailed information such as Rails and Ruby version for `public/index.html` in a default Rails application.
-* `build_middleware_stack` Builds the middleware stack for the application, returning an object which has a `call` method which takes a Rack environment object for the request.
+* `build_middleware_stack`: Builds the middleware stack for the application, returning an object which has a `call` method which takes a Rack environment object for the request.
-* `eager_load!` If `config.eager_load` is true, runs the `config.before_eager_load` hooks and then calls `eager_load!` which will load all `config.eager_load_namespaces`.
+* `eager_load!`: If `config.eager_load` is `true`, runs the `config.before_eager_load` hooks and then calls `eager_load!` which will load all `config.eager_load_namespaces`.
-* `finisher_hook` Provides a hook for after the initialization of process of the application is complete, as well as running all the `config.after_initialize` blocks for the application, railties and engines.
+* `finisher_hook`: Provides a hook for after the initialization of process of the application is complete, as well as running all the `config.after_initialize` blocks for the application, railties and engines.
-* `set_routes_reloader` Configures Action Dispatch to reload the routes file using `ActionDispatch::Callbacks.to_prepare`.
+* `set_routes_reloader`: Configures Action Dispatch to reload the routes file using `ActionDispatch::Callbacks.to_prepare`.
-* `disable_dependency_loading` Disables the automatic dependency loading if the `config.eager_load` is set to true.
+* `disable_dependency_loading`: Disables the automatic dependency loading if the `config.eager_load` is set to `true`.
Database pooling
----------------
@@ -1188,7 +1262,7 @@ Evented File System Monitor
If the [listen gem](https://github.com/guard/listen) is loaded Rails uses an
evented file system monitor to detect changes when `config.cache_classes` is
-false:
+`false`:
```ruby
group :development do
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index cbc304c87f..ba8d085f79 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -25,7 +25,7 @@ Reporting an Issue
Ruby on Rails uses [GitHub Issue Tracking](https://github.com/rails/rails/issues) to track issues (primarily bugs and contributions of new code). If you've found a bug in Ruby on Rails, this is the place to start. You'll need to create a (free) GitHub account in order to submit an issue, to comment on them or to create pull requests.
-NOTE: Bugs in the most recent released version of Ruby on Rails are likely to get the most attention. Also, the Rails core team is always interested in feedback from those who can take the time to test _edge Rails_ (the code for the version of Rails that is currently under development). Later in this guide you'll find out how to get edge Rails for testing.
+NOTE: Bugs in the most recent released version of Ruby on Rails are likely to get the most attention. Also, the Rails core team is always interested in feedback from those who can take the time to test _edge Rails_ (the code for the version of Rails that is currently under development). Later in this guide, you'll find out how to get edge Rails for testing.
### Creating a Bug Report
@@ -58,7 +58,7 @@ WARNING: Please do not report security vulnerabilities with public GitHub issue
Please don't put "feature request" items into GitHub Issues. If there's a new
feature that you want to see added to Ruby on Rails, you'll need to write the
code yourself - or convince someone else to partner with you to write the code.
-Later in this guide you'll find detailed instructions for proposing a patch to
+Later in this guide, you'll find detailed instructions for proposing a patch to
Ruby on Rails. If you enter a wish list item in GitHub Issues with no code, you
can expect it to be marked "invalid" as soon as it's reviewed.
@@ -105,7 +105,7 @@ $ git checkout -b testing_branch
Then you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to a topic branch "orange" located at https://github.com/JohnSmith/rails.
```bash
-$ git remote add JohnSmith git://github.com/JohnSmith/rails.git
+$ git remote add JohnSmith https://github.com/JohnSmith/rails.git
$ git pull JohnSmith orange
```
@@ -130,11 +130,11 @@ learn about Ruby on Rails, and the API, which serves as a reference.
You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing them up to date with the latest edge Rails.
-You can either open a pull request to [Rails](http://github.com/rails/rails) or
-ask the [Rails core team](http://rubyonrails.org/core) for commit access on
+You can either open a pull request to [Rails](https://github.com/rails/rails) or
+ask the [Rails core team](http://rubyonrails.org/community/#core) for commit access on
docrails if you contribute regularly.
Please do not open pull requests in docrails, if you'd like to get feedback on your
-change, ask for it in [Rails](http://github.com/rails/rails) instead.
+change, ask for it in [Rails](https://github.com/rails/rails) instead.
Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation.
@@ -159,7 +159,7 @@ If you want to translate the Rails guides in your own language, follows these st
* Copy the contents of *guides/source* into your own language directory and translate them.
* Do NOT translate the HTML files, as they are automatically generated.
-To generate the guides in HTML format cd into the *guides* direcotry then run (eg. for it-IT):
+To generate the guides in HTML format cd into the *guides* directory then run (eg. for it-IT):
```bash
$ bundle install
@@ -174,8 +174,8 @@ Translation efforts we know about (various versions):
* **Italian**: [https://github.com/rixlabs/docrails](https://github.com/rixlabs/docrails)
* **Spanish**: [http://wiki.github.com/gramos/docrails](http://wiki.github.com/gramos/docrails)
-* **Polish**: [http://github.com/apohllo/docrails/tree/master](http://github.com/apohllo/docrails/tree/master)
-* **French** : [http://github.com/railsfrance/docrails](http://github.com/railsfrance/docrails)
+* **Polish**: [https://github.com/apohllo/docrails/tree/master](https://github.com/apohllo/docrails/tree/master)
+* **French** : [https://github.com/railsfrance/docrails](https://github.com/railsfrance/docrails)
* **Czech** : [https://github.com/rubyonrails-cz/docrails/tree/czech](https://github.com/rubyonrails-cz/docrails/tree/czech)
* **Turkish** : [https://github.com/ujk/docrails/tree/master](https://github.com/ujk/docrails/tree/master)
* **Korean** : [https://github.com/rorlakr/rails-guides](https://github.com/rorlakr/rails-guides)
@@ -189,7 +189,7 @@ Contributing to the Rails Code
### Setting Up a Development Environment
-To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to setup the tests on your own computer.
+To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide, you'll learn how to setup the tests on your own computer.
#### The Easy Way
@@ -204,7 +204,7 @@ In case you can't use the Rails development box, see [this other guide](developm
To be able to contribute code, you need to clone the Rails repository:
```bash
-$ git clone git://github.com/rails/rails.git
+$ git clone https://github.com/rails/rails.git
```
and create a dedicated branch:
@@ -299,9 +299,9 @@ Please see the benchmark/ips [README](https://github.com/evanphx/benchmark-ips/b
### Running Tests
It is not customary in Rails to run the full test suite before pushing
-changes. The railties test suite in particular takes a long time, and even
-more if the source code is mounted in `/vagrant` as happens in the recommended
-workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box).
+changes. The railties test suite in particular takes a long time, and takes an
+especially long time if the source code is mounted in `/vagrant` as happens in
+the recommended workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box).
As a compromise, test what your code obviously affects, and if the change is
not in railties, run the whole test suite of the affected component. If all
@@ -367,7 +367,7 @@ Finally,
$ bundle exec rake test
```
-will now run the four of them in turn.
+will now run the three of them in turn.
You can also run any single test separately:
@@ -506,7 +506,7 @@ Navigate to the Rails [GitHub repository](https://github.com/rails/rails) and pr
Add the new remote to your local repository on your local machine:
```bash
-$ git remote add mine git@github.com:<your user name>/rails.git
+$ git remote add mine https://github.com:<your user name>/rails.git
```
Push to your remote:
@@ -520,7 +520,7 @@ You might have cloned your forked repository into your machine and might want to
In the directory you cloned your fork:
```bash
-$ git remote add rails git://github.com/rails/rails.git
+$ git remote add rails https://github.com/rails/rails.git
```
Download new commits and branches from the official repository:
@@ -662,7 +662,7 @@ Changes that are merged into master are intended for the next major release of R
For simple fixes, the easiest way to backport your changes is to [extract a diff from your changes in master and apply them to the target branch](http://ariejan.net/2009/10/26/how-to-create-and-apply-a-patch-with-git).
-First make sure your changes are the only difference between your current branch and master:
+First, make sure your changes are the only difference between your current branch and master:
```bash
$ git log master..HEAD
diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb
index 1d995581fa..511d76041b 100644
--- a/guides/source/credits.html.erb
+++ b/guides/source/credits.html.erb
@@ -64,7 +64,7 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi
<% end %>
<%= author('Pratik Naik', 'lifo') do %>
- Pratik Naik is a Ruby on Rails developer at <a href="https://basecamp.com/">Basecamp</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through =&gt; :rails</a> and has a semi-active <a href="http://twitter.com/lifo">twitter account</a>.
+ Pratik Naik is a Ruby on Rails developer at <a href="https://basecamp.com/">Basecamp</a> and maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through =&gt; :rails</a>. He also has a semi-active <a href="http://twitter.com/lifo">twitter account</a>.
<% end %>
<%= author('Emilio Tagua', 'miloops') do %>
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 35ad6eb705..f0d0f9753a 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -109,18 +109,18 @@ It can also be useful to save information to log files at runtime. Rails maintai
Rails makes use of the `ActiveSupport::Logger` class to write log information. Other loggers, such as `Log4r`, may also be substituted.
-You can specify an alternative logger in `environment.rb` or any other environment file, for example:
+You can specify an alternative logger in `config/application.rb` or any other environment file, for example:
```ruby
-Rails.logger = Logger.new(STDOUT)
-Rails.logger = Log4r::Logger.new("Application Log")
+config.logger = Logger.new(STDOUT)
+config.logger = Log4r::Logger.new("Application Log")
```
Or in the `Initializer` section, add _any_ of the following
```ruby
-config.logger = Logger.new(STDOUT)
-config.logger = Log4r::Logger.new("Application Log")
+Rails.logger = Logger.new(STDOUT)
+Rails.logger = Log4r::Logger.new("Application Log")
```
TIP: By default, each log is created under `Rails.root/log/` and the log file is named after the environment in which the application is running.
@@ -255,7 +255,8 @@ is your best companion.
The debugger can also help you if you want to learn about the Rails source code
but don't know where to start. Just debug any request to your application and
-use this guide to learn how to move from the code you have written into the underlying Rails code.
+use this guide to learn how to move from the code you have written into the
+underlying Rails code.
### Setup
@@ -314,13 +315,12 @@ For example:
=> Booting Puma
=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
-=> Ctrl-C to shutdown server
-Puma 2.15.3 starting...
-* Min threads: 0, max threads: 16
+Puma starting in single mode...
+* Version 3.4.0 (ruby 2.3.1-p112), codename: Owl Bowl Brawl
+* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
-
-
+Use Ctrl-C to stop
Started GET "/" for 127.0.0.1 at 2014-04-11 13:11:48 +0200
ActiveRecord::SchemaMigration Load (0.2ms) SELECT "schema_migrations".* FROM "schema_migrations"
Processing by ArticlesController#index as HTML
@@ -336,7 +336,6 @@ Processing by ArticlesController#index as HTML
10: respond_to do |format|
11: format.html # index.html.erb
12: format.json { render json: @articles }
-
(byebug)
```
@@ -346,11 +345,45 @@ by asking the debugger for help. Type: `help`
```
(byebug) help
- h[elp][ <cmd>[ <subcmd>]]
+ break -- Sets breakpoints in the source code
+ catch -- Handles exception catchpoints
+ condition -- Sets conditions on breakpoints
+ continue -- Runs until program ends, hits a breakpoint or reaches a line
+ debug -- Spawns a subdebugger
+ delete -- Deletes breakpoints
+ disable -- Disables breakpoints or displays
+ display -- Evaluates expressions every time the debugger stops
+ down -- Moves to a lower frame in the stack trace
+ edit -- Edits source files
+ enable -- Enables breakpoints or displays
+ finish -- Runs the program until frame returns
+ frame -- Moves to a frame in the call stack
+ help -- Helps you using byebug
+ history -- Shows byebug's history of commands
+ info -- Shows several informations about the program being debugged
+ interrupt -- Interrupts the program
+ irb -- Starts an IRB session
+ kill -- Sends a signal to the current process
+ list -- Lists lines of source code
+ method -- Shows methods of an object, class or module
+ next -- Runs one or more lines of code
+ pry -- Starts a Pry session
+ quit -- Exits byebug
+ restart -- Restarts the debugged program
+ save -- Saves current byebug session to a file
+ set -- Modifies byebug settings
+ show -- Shows byebug settings
+ source -- Restores a previously saved byebug session
+ step -- Steps into blocks or methods one or more times
+ thread -- Commands to manipulate threads
+ tracevar -- Enables tracing of a global variable
+ undisplay -- Stops displaying all or some expressions when program stops
+ untracevar -- Stops tracing a global variable
+ up -- Moves to a higher frame in the stack trace
+ var -- Shows variables and its values
+ where -- Displays the backtrace
- help -- prints this help.
- help <cmd> -- prints help on command <cmd>.
- help <cmd> <subcmd> -- prints help on <cmd>'s subcommand <subcmd>.
+(byebug)
```
To see the previous ten lines you should type `list-` (or `l-`).
@@ -369,12 +402,11 @@ To see the previous ten lines you should type `list-` (or `l-`).
8 @articles = Article.find_recent
9
10 respond_to do |format|
-
```
-This way you can move inside the file and see the code above
-the line where you added the `byebug` call. Finally, to see where you are in
-the code again you can type `list=`
+This way you can move inside the file and see the code above the line where you
+added the `byebug` call. Finally, to see where you are in the code again you can
+type `list=`
```
(byebug) list=
@@ -390,7 +422,6 @@ the code again you can type `list=`
10: respond_to do |format|
11: format.html # index.html.erb
12: format.json { render json: @articles }
-
(byebug)
```
@@ -412,46 +443,45 @@ then `backtrace` will supply the answer.
```
(byebug) where
--> #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-5.0.0/lib/action_controller/metal/implicit_render.rb:4
+ at /PathToProject/app/controllers/articles_controller.rb:8
+ #1 ActionController::BasicImplicitRender.send_action(method#String, *args#Array)
+ at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/basic_implicit_render.rb:4
#2 AbstractController::Base.process_action(action#NilClass, *args#Array)
- at /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb:189
- #3 ActionController::Rendering.process_action(action#NilClass, *args#NilClass)
- at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/rendering.rb:10
+ at /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb:181
+ #3 ActionController::Rendering.process_action(action, *args)
+ at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/rendering.rb:30
...
```
The current frame is marked with `-->`. You can move anywhere you want in this
-trace (thus changing the context) by using the `frame _n_` command, where _n_ is
+trace (thus changing the context) by using the `frame n` command, where _n_ is
the specified frame number. If you do that, `byebug` will display your new
context.
```
(byebug) frame 2
-[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
- 187: # which is *not* necessarily the same as the action name.
- 188: def process_action(method_name, *args)
-=> 189: send_action(method_name, *args)
- 190: end
- 191:
- 192: # Actually call the method associated with the action. Override
- 193: # this method if you wish to change how action methods are called,
-
+[176, 185] in /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb
+ 176: # is the intended way to override action dispatching.
+ 177: #
+ 178: # Notice that the first argument is the method to be dispatched
+ 179: # which is *not* necessarily the same as the action name.
+ 180: def process_action(method_name, *args)
+=> 181: send_action(method_name, *args)
+ 182: end
+ 183:
+ 184: # Actually call the method associated with the action. Override
+ 185: # this method if you wish to change how action methods are called,
(byebug)
```
The available variables are the same as if you were running the code line by
line. After all, that's what debugging is.
-You can also use `up [n]` (`u` for abbreviated) and `down [n]` commands in order
-to change the context _n_ frames up or down the stack respectively. _n_ defaults
-to one. Up in this case is towards higher-numbered stack frames, and down is
-towards lower-numbered stack frames.
+You can also use `up [n]` and `down [n]` commands in order to change the context
+_n_ frames up or down the stack respectively. _n_ defaults to one. Up in this
+case is towards higher-numbered stack frames, and down is towards lower-numbered
+stack frames.
### Threads
@@ -460,11 +490,11 @@ the `thread` command (or the abbreviated `th`). This command has a handful of
options:
* `thread`: shows the current thread.
-* `thread list`: is used to list all threads and their statuses. The plus +
-character and the number indicates the current thread of execution.
-* `thread stop _n_`: stop thread _n_.
-* `thread resume _n_`: resumes thread _n_.
-* `thread switch _n_`: switches the current thread context to _n_.
+* `thread list`: is used to list all threads and their statuses. The current
+thread is marked with a plus (+) sign.
+* `thread stop n`: stops thread _n_.
+* `thread resume n`: resumes thread _n_.
+* `thread switch n`: switches the current thread context to _n_.
This command is very helpful when you are debugging concurrent threads and need
to verify that there are no race conditions in your code.
@@ -491,9 +521,9 @@ current context:
12: format.json { render json: @articles }
(byebug) instance_variables
-[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request,
- :@_response, :@_prefixes, :@_lookup_context, :@_action_name,
- :@_response_body, :@marked_for_same_origin_verification, :@_config]
+[:@_action_has_layout, :@_routes, :@_request, :@_response, :@_lookup_context,
+ :@_action_name, :@_response_body, :@marked_for_same_origin_verification,
+ :@_config]
```
As you may have figured out, all of the variables that you can access from a
@@ -503,6 +533,7 @@ command later in this guide).
```
(byebug) next
+
[5, 14] in /PathTo/project/app/controllers/articles_controller.rb
5 # GET /articles.json
6 def index
@@ -522,29 +553,35 @@ And then ask again for the instance_variables:
```
(byebug) instance_variables
-[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request,
- :@_response, :@_prefixes, :@_lookup_context, :@_action_name,
- :@_response_body, :@marked_for_same_origin_verification, :@_config,
- :@articles]
+[:@_action_has_layout, :@_routes, :@_request, :@_response, :@_lookup_context,
+ :@_action_name, :@_response_body, :@marked_for_same_origin_verification,
+ :@_config, :@articles]
```
-Now `@articles` is included in the instance variables, because the line defining it
-was executed.
+Now `@articles` is included in the instance variables, because the line defining
+it was executed.
TIP: You can also step into **irb** mode with the command `irb` (of course!).
-This will start an irb session within the context you invoked it. But
-be warned: this is an experimental feature.
+This will start an irb session within the context you invoked it.
The `var` method is the most convenient way to show variables and their values.
Let's have `byebug` help us with it.
```
(byebug) help var
-v[ar] cl[ass] show class variables of self
-v[ar] const <object> show constants of object
-v[ar] g[lobal] show global variables
-v[ar] i[nstance] <object> show instance variables of object
-v[ar] l[ocal] show local variables
+
+ [v]ar <subcommand>
+
+ Shows variables and its values
+
+
+ var all -- Shows local, global and instance variables of self.
+ var args -- Information about arguments of the current scope
+ var const -- Shows constants of an object.
+ var global -- Shows global variables.
+ var instance -- Shows instance variables of self or a specific object.
+ var local -- Shows local variables in current scope.
+
```
This is a great way to inspect the values of the current context variables. For
@@ -562,16 +599,17 @@ You can also inspect for an object method this way:
@_start_transaction_state = {}
@aggregation_cache = {}
@association_cache = {}
-@attributes = {"id"=>nil, "created_at"=>nil, "updated_at"=>nil}
-@attributes_cache = {}
-@changed_attributes = nil
-...
+@attributes = #<ActiveRecord::AttributeSet:0x007fd0682a9b18 @attributes={"id"=>#<ActiveRecord::Attribute::FromDatabase:0x007fd0682a9a00 @name="id", @value_be...
+@destroyed = false
+@destroyed_by_association = nil
+@marked_for_destruction = false
+@new_record = true
+@readonly = false
+@transaction_state = nil
+@txn = nil
```
-TIP: The commands `p` (print) and `pp` (pretty print) can be used to evaluate
-Ruby expressions and display the value of variables to the console.
-
-You can use also `display` to start watching variables. This is a good way of
+You can also use `display` to start watching variables. This is a good way of
tracking the values of a variable while the execution goes on.
```
@@ -580,7 +618,7 @@ tracking the values of a variable while the execution goes on.
```
The variables inside the displayed list will be printed with their values after
-you move in the stack. To stop displaying a variable use `undisplay _n_` where
+you move in the stack. To stop displaying a variable use `undisplay n` where
_n_ is the variable number (1 in the last example).
### Step by Step
@@ -590,32 +628,23 @@ available variables. But let's continue and move on with the application
execution.
Use `step` (abbreviated `s`) to continue running your program until the next
-logical stopping point and return control to the debugger.
-
-You may also use `next` which is similar to step, but function or method calls
-that appear within the line of code are executed without stopping.
-
-TIP: You can also use `step n` or `next n` to move forwards `n` steps at once.
-
-The difference between `next` and `step` is that `step` stops at the next line
-of code executed, doing just a single step, while `next` moves to the next line
-without descending inside methods.
+logical stopping point and return control to the debugger. `next` is similar to
+`step`, but while `step` stops at the next line of code executed, doing just a
+single step, `next` moves to the next line without descending inside methods.
For example, consider the following situation:
-```ruby
+```
Started GET "/" for 127.0.0.1 at 2014-04-11 13:39:23 +0200
Processing by ArticlesController#index as HTML
-[1, 8] in /home/davidr/Proyectos/test_app/app/models/article.rb
+[1, 6] in /PathToProject/app/models/article.rb
1: class Article < ApplicationRecord
- 2:
- 3: def self.find_recent(limit = 10)
- 4: byebug
-=> 5: where('created_at > ?', 1.week.ago).limit(limit)
- 6: end
- 7:
- 8: end
+ 2: def self.find_recent(limit = 10)
+ 3: byebug
+=> 4: where('created_at > ?', 1.week.ago).limit(limit)
+ 5: end
+ 6: end
(byebug)
```
@@ -627,11 +656,7 @@ method.
```
(byebug) next
-
-Next advances to the next line (line 6: `end`), which returns to the next line
-of the caller method:
-
-[4, 13] in /PathTo/project/test_app/app/controllers/articles_controller.rb
+[4, 13] in /PathToProject/app/controllers/articles_controller.rb
4: # GET /articles
5: # GET /articles.json
6: def index
@@ -652,23 +677,24 @@ Ruby instruction to be executed -- in this case, Active Support's `week` method.
```
(byebug) step
-[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
- 53:
- 54: def weeks
-=> 55: ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]])
- 56: end
- 57: alias :week :weeks
- 58:
- 59: def fortnights
-
+[49, 58] in /PathToGems/activesupport-5.0.0/lib/active_support/core_ext/numeric/time.rb
+ 49:
+ 50: # Returns a Duration instance matching the number of weeks provided.
+ 51: #
+ 52: # 2.weeks # => 14 days
+ 53: def weeks
+=> 54: ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]])
+ 55: end
+ 56: alias :week :weeks
+ 57:
+ 58: # Returns a Duration instance matching the number of fortnights provided.
(byebug)
```
This is one of the best ways to find bugs in your code.
+TIP: You can also use `step n` or `next n` to move forward `n` steps at once.
+
### Breakpoints
A breakpoint makes your application stop whenever a certain point in the program
@@ -677,19 +703,18 @@ is reached. The debugger shell is invoked in that line.
You can add breakpoints dynamically with the command `break` (or just `b`).
There are 3 possible ways of adding breakpoints manually:
-* `break line`: set breakpoint in the _line_ in the current source file.
-* `break file:line [if expression]`: set breakpoint in the _line_ number inside
-the _file_. If an _expression_ is given it must evaluated to _true_ to fire up
-the debugger.
+* `break n`: set breakpoint in line number _n_ in the current source file.
+* `break file:n [if expression]`: set breakpoint in line number _n_ inside
+file named _file_. If an _expression_ is given it must evaluated to _true_ to
+fire up the debugger.
* `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and
\# for class and instance method respectively) defined in _class_. The
-_expression_ works the same way as with file:line.
-
+_expression_ works the same way as with file:n.
For example, in the previous situation
```
-[4, 13] in /PathTo/project/app/controllers/articles_controller.rb
+[4, 13] in /PathToProject/app/controllers/articles_controller.rb
4: # GET /articles
5: # GET /articles.json
6: def index
@@ -702,20 +727,20 @@ For example, in the previous situation
13: end
(byebug) break 11
-Created breakpoint 1 at /PathTo/project/app/controllers/articles_controller.rb:11
+Successfully created breakpoint with id 1
```
-Use `info breakpoints _n_` or `info break _n_` to list breakpoints. If you
-supply a number, it lists that breakpoint. Otherwise it lists all breakpoints.
+Use `info breakpoints` to list breakpoints. If you supply a number, it lists
+that breakpoint. Otherwise it lists all breakpoints.
```
(byebug) info breakpoints
Num Enb What
-1 y at /PathTo/project/app/controllers/articles_controller.rb:11
+1 y at /PathToProject/app/controllers/articles_controller.rb:11
```
-To delete breakpoints: use the command `delete _n_` to remove the breakpoint
+To delete breakpoints: use the command `delete n` to remove the breakpoint
number _n_. If no number is specified, it deletes all breakpoints that are
currently active.
@@ -727,10 +752,11 @@ No breakpoints.
You can also enable or disable breakpoints:
-* `enable breakpoints`: allow a _breakpoints_ list or all of them if no list is
-specified, to stop your program. This is the default state when you create a
+* `enable breakpoints [n [m [...]]]`: allows a specific breakpoint list or all
+breakpoints to stop your program. This is the default state when you create a
breakpoint.
-* `disable breakpoints`: the _breakpoints_ will have no effect on your program.
+* `disable breakpoints [n [m [...]]]`: make certain (or all) breakpoints have
+no effect on your program.
### Catching Exceptions
@@ -745,24 +771,22 @@ To list all active catchpoints use `catch`.
There are two ways to resume execution of an application that is stopped in the
debugger:
-* `continue [line-specification]` \(or `c`): resume program execution, at the
-address where your script last stopped; any breakpoints set at that address are
-bypassed. The optional argument line-specification allows you to specify a line
-number to set a one-time breakpoint which is deleted when that breakpoint is
-reached.
-* `finish [frame-number]` \(or `fin`): execute until the selected stack frame
-returns. If no frame number is given, the application will run until the
-currently selected frame returns. The currently selected frame starts out the
-most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been
-performed. If a frame number is given it will run until the specified frame
-returns.
+* `continue [n]`: resumes program execution at the address where your script last
+stopped; any breakpoints set at that address are bypassed. The optional argument
+`n` allows you to specify a line number to set a one-time breakpoint which is
+deleted when that breakpoint is reached.
+* `finish [n]`: execute until the selected stack frame returns. If no frame
+number is given, the application will run until the currently selected frame
+returns. The currently selected frame starts out the most-recent frame or 0 if
+no frame positioning (e.g up, down or frame) has been performed. If a frame
+number is given it will run until the specified frame returns.
### Editing
Two commands allow you to open code from the debugger into an editor:
-* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR
-environment variable. A specific _line_ can also be given.
+* `edit [file:n]`: edit file named _file_ using the editor specified by the
+EDITOR environment variable. A specific line _n_ can also be given.
### Quitting
@@ -776,21 +800,43 @@ will be stopped and you will have to start it again.
`byebug` has a few available options to tweak its behavior:
-* `set autoreload`: Reload source code when changed (defaults: true).
-* `set autolist`: Execute `list` command on every breakpoint (defaults: true).
-* `set listsize _n_`: Set number of source lines to list by default to _n_
-(defaults: 10)
-* `set forcestep`: Make sure the `next` and `step` commands always move to a new
-line.
+```
+(byebug) help set
+
+ set <setting> <value>
-You can see the full list by using `help set`. Use `help set _subcommand_` to
-learn about a particular `set` command.
+ Modifies byebug settings
+
+ Boolean values take "on", "off", "true", "false", "1" or "0". If you
+ don't specify a value, the boolean setting will be enabled. Conversely,
+ you can use "set no<setting>" to disable them.
+
+ You can see these environment settings with the "show" command.
+
+ List of supported settings:
+
+ autosave -- Automatically save command history record on exit
+ autolist -- Invoke list command on every stop
+ width -- Number of characters per line in byebug's output
+ autoirb -- Invoke IRB on every stop
+ basename -- <file>:<line> information after every stop uses short paths
+ linetrace -- Enable line execution tracing
+ autopry -- Invoke Pry on every stop
+ stack_on_error -- Display stack trace when `eval` raises an exception
+ fullpath -- Display full file names in backtraces
+ histfile -- File where cmd history is saved to. Default: ./.byebug_history
+ listsize -- Set number of source lines to list by default
+ post_mortem -- Enable/disable post-mortem mode
+ callstyle -- Set how you want method call parameters to be displayed
+ histsize -- Maximum number of commands that can be stored in byebug history
+ savefile -- File where settings are saved to. Default: ~/.byebug_save
+```
TIP: You can save these settings in an `.byebugrc` file in your home directory.
The debugger reads these global settings when it starts. For example:
```bash
-set forcestep
+set callstyle short
set listsize 25
```
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 7beb8f72a9..cc24e6f666 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -30,7 +30,6 @@ Ruby on Rails uses Git for source code control. The [Git homepage](http://git-sc
* [Try Git course](http://try.github.io/) is an interactive course that will teach you the basics.
* The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git.
* [Everyday Git](http://schacon.github.io/git/everyday.html) will teach you just enough about Git to get by.
-* The [PeepCode screencast](https://peepcode.com/products/git) on Git is easier to follow.
* [GitHub](http://help.github.com) offers links to a variety of Git resources.
* [Pro Git](http://git-scm.com/book) is an entire book about Git with a Creative Commons license.
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 4473eba478..a06a53b250 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -116,7 +116,7 @@
name: The Rails Initialization Process
work_in_progress: true
url: initialization.html
- description: This guide explains the internals of the Rails initialization process as of Rails 4.
+ description: This guide explains the internals of the Rails initialization process.
-
name: Autoloading and Reloading Constants
url: autoloading_and_reloading_constants.html
@@ -135,6 +135,14 @@
work_in_progress: true
url: profiling.html
description: This guide explains how to profile your Rails applications to improve performance.
+ -
+ name: Using Rails for API-only Applications
+ url: api_app.html
+ description: This guide explains how to effectively use Rails to develop a JSON API application.
+ -
+ name: Action Cable Overview
+ url: action_cable_overview.html
+ description: This guide explains how Action Cable works, and how to use WebSockets to create real-time features.
-
name: Extending Rails
@@ -187,6 +195,11 @@
url: upgrading_ruby_on_rails.html
description: This guide helps in upgrading applications to latest Ruby on Rails versions.
-
+ name: Ruby on Rails 5.0 Release Notes
+ url: 5_0_release_notes.html
+ description: Release notes for Rails 5.0.
+ work_in_progress: true
+ -
name: Ruby on Rails 4.2 Release Notes
url: 4_2_release_notes.html
description: Release notes for Rails 4.2.
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 697938434c..f9a37e45ac 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -11,9 +11,9 @@ After reading this guide, you will know:
* What makes an engine.
* How to generate an engine.
-* Building features for the engine.
-* Hooking the engine into an application.
-* Overriding engine functionality in the application.
+* How to build features for the engine.
+* How to hook the engine into an application.
+* How to override engine functionality in the application.
--------------------------------------------------------------------------------
@@ -25,7 +25,7 @@ their host applications. A Rails application is actually just a "supercharged"
engine, with the `Rails::Application` class inheriting a lot of its behavior
from `Rails::Engine`.
-Therefore, engines and applications can be thought of almost the same thing,
+Therefore, engines and applications can be thought of as almost the same thing,
just with subtle differences, as you'll see throughout this guide. Engines and
applications also share a common structure.
@@ -402,8 +402,8 @@ module Blorgh
end
```
-NOTE: The `ApplicationController` class being inherited from here is the
-`Blorgh::ApplicationController`, not an application's `ApplicationController`.
+NOTE: The `ArticlesController` class inherits from
+`Blorgh::ApplicationController`, not the application's `ApplicationController`.
The helper inside `app/helpers/blorgh/articles_helper.rb` is also namespaced:
@@ -461,7 +461,7 @@ model, a comment controller and then modify the articles scaffold to display
comments and allow people to create new ones.
From the application root, run the model generator. Tell it to generate a
-`Comment` model, with the related table having two columns: a `article_id` integer
+`Comment` model, with the related table having two columns: an `article_id` integer
and `text` text column.
```bash
@@ -799,7 +799,7 @@ before the article is saved. It will also need to have an `attr_accessor` set up
for this field, so that the setter and getter methods are defined for it.
To do all this, you'll need to add the `attr_accessor` for `author_name`, the
-association for the author and the `before_save` call into
+association for the author and the `before_validation` call into
`app/models/blorgh/article.rb`. The `author` association will be hard-coded to the
`User` class for the time being.
@@ -807,7 +807,7 @@ association for the author and the `before_save` call into
attr_accessor :author_name
belongs_to :author, class_name: "User"
-before_save :set_author
+before_validation :set_author
private
def set_author
@@ -1034,6 +1034,8 @@ typical `GET` to a controller in a controller's functional test like this:
```ruby
module Blorgh
class FooControllerTest < ActionDispatch::IntegrationTest
+ include Engine.routes.url_helpers
+
def test_index
get foos_url
...
@@ -1050,6 +1052,8 @@ in your setup code:
```ruby
module Blorgh
class FooControllerTest < ActionDispatch::IntegrationTest
+ include Engine.routes.url_helpers
+
setup do
@routes = Engine.routes
end
@@ -1205,7 +1209,7 @@ module Blorgh::Concerns::Models::Article
attr_accessor :author_name
belongs_to :author, class_name: "User"
- before_save :set_author
+ before_validation :set_author
private
def set_author
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 2a289dd33a..048fe190e8 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -174,7 +174,6 @@ URL fields, email fields, number fields and range fields:
<%= search_field(:user, :name) %>
<%= telephone_field(:user, :phone) %>
<%= date_field(:user, :born_on) %>
-<%= datetime_field(:user, :meeting_time) %>
<%= datetime_local_field(:user, :graduation_day) %>
<%= month_field(:user, :birthday_month) %>
<%= week_field(:user, :birthday_week) %>
@@ -195,7 +194,6 @@ Output:
<input id="user_name" name="user[name]" type="search" />
<input id="user_phone" name="user[phone]" type="tel" />
<input id="user_born_on" name="user[born_on]" type="date" />
-<input id="user_meeting_time" name="user[meeting_time]" type="datetime" />
<input id="user_graduation_day" name="user[graduation_day]" type="datetime-local" />
<input id="user_birthday_month" name="user[birthday_month]" type="month" />
<input id="user_birthday_week" name="user[birthday_week]" type="week" />
@@ -317,7 +315,7 @@ The Article model is directly available to users of the application, so - follow
resources :articles
```
-TIP: Declaring a resource has a number of side-affects. See [Rails Routing From the Outside In](routing.html#resource-routing-the-rails-default) for more information on setting up and using resources.
+TIP: Declaring a resource has a number of side effects. See [Rails Routing From the Outside In](routing.html#resource-routing-the-rails-default) for more information on setting up and using resources.
When dealing with RESTful resources, calls to `form_for` can get significantly easier if you rely on **record identification**. In short, you can just pass the model instance and have Rails figure out model name and the rest:
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 9677ab1583..65fdd7ca0d 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -93,7 +93,7 @@ current version of Ruby installed:
```bash
$ ruby -v
-ruby 2.2.2p95
+ruby 2.3.1p112
```
TIP: A number of tools exist to help you quickly install Ruby and Ruby
@@ -164,7 +164,7 @@ of the files and folders that Rails created by default:
| File/Folder | Purpose |
| ----------- | ------- |
-|app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.|
+|app/|Contains the controllers, models, views, helpers, mailers, channels, jobs and assets for your application. You'll focus on this folder for the remainder of this guide.|
|bin/|Contains the rails script that starts your app and can contain other scripts you use to setup, update, deploy or run your application.|
|config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).|
|config.ru|Rack configuration for Rack based servers used to start the application.|
@@ -223,8 +223,7 @@ the server.
The "Welcome aboard" page is the _smoke test_ for a new Rails application: it
makes sure that you have your software configured correctly enough to serve a
-page. You can also click on the _About your application's environment_ link to
-see a summary of your application's environment.
+page.
### Say "Hello", Rails
@@ -245,11 +244,11 @@ Ruby) which is processed by the request cycle in Rails before being sent to the
user.
To create a new controller, you will need to run the "controller" generator and
-tell it you want a controller called "welcome" with an action called "index",
+tell it you want a controller called "Welcome" with an action called "index",
just like this:
```bash
-$ bin/rails generate controller welcome index
+$ bin/rails generate controller Welcome index
```
Rails will create several files and a route for you.
@@ -264,6 +263,7 @@ invoke test_unit
create test/controllers/welcome_controller_test.rb
invoke helper
create app/helpers/welcome_helper.rb
+invoke test_unit
invoke assets
invoke coffee
create app/assets/javascripts/welcome.coffee
@@ -298,33 +298,37 @@ Open the file `config/routes.rb` in your editor.
Rails.application.routes.draw do
get 'welcome/index'
- # The priority is based upon order of creation:
- # first created -> highest priority.
- # See how all your routes lay out with "bin/rails routes".
- #
- # You can have the root of your site routed with "root"
- # root 'welcome#index'
- #
- # ...
+ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+
+ # Serve websocket cable requests in-process
+ # mount ActionCable.server => '/cable'
+end
```
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
-uncomment it. It should look something like the following:
+controllers and actions.
+Edit this file by adding the line of code `root 'welcome#index'`.
+It should look something like the following:
```ruby
-root 'welcome#index'
+Rails.application.routes.draw do
+ get 'welcome/index'
+
+ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+
+ # Serve websocket cable requests in-process
+ # mount ActionCable.server => '/cable'
+ root 'welcome#index'
+end
```
`root 'welcome#index'` tells Rails to map requests to the root of the
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 (`bin/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 (`bin/rails
server`) and navigate to <http://localhost:3000> in your browser. You'll see the
@@ -348,7 +352,7 @@ operations are referred to as _CRUD_ operations.
Rails provides a `resources` method which can be used to declare a standard REST
resource. You need to add the _article resource_ to the
-`config/routes.rb` as follows:
+`config/routes.rb` so the file will look as follows:
```ruby
Rails.application.routes.draw do
@@ -387,7 +391,7 @@ create and read. The form for doing this will look like this:
It will look a little basic for now, but that's ok. We'll look at improving the
styling for it afterwards.
-### Laying down the ground work
+### Laying down the groundwork
Firstly, you need a place within the application to create a new article. A
great place for that would be at `/articles/new`. With the route already
@@ -403,7 +407,7 @@ a controller called `ArticlesController`. You can do this by running this
command:
```bash
-$ bin/rails generate controller articles
+$ bin/rails generate controller Articles
```
If you open up the newly generated `app/controllers/articles_controller.rb`
@@ -471,7 +475,7 @@ 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 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
+format of the 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
us what _template handlers_ could be used to render our template. `:erb` is most
commonly used for HTML templates, `:builder` is used for XML templates, and
@@ -625,7 +629,7 @@ end
The `render` method here is taking a very simple hash with a key of `:plain` and
value of `params[:article].inspect`. The `params` method is the object which
represents the parameters (or fields) coming in from the form. The `params`
-method returns an `ActiveSupport::HashWithIndifferentAccess` object, which
+method returns an `ActionController::Parameters` object, which
allows you to access the keys of the hash using either strings or symbols. In
this situation, the only parameters that matter are the ones from the form.
@@ -635,7 +639,7 @@ If you re-submit the form one more time you'll now no longer get the missing
template error. Instead, you'll see something that looks like the following:
```ruby
-{"title"=>"First article!", "text"=>"This is my first article."}
+<ActionController::Parameters {"title"=>"First Article!", "text"=>"This is my first article."} permitted: false>
```
This action is now displaying the parameters for the article that are coming in
@@ -653,7 +657,7 @@ run this command in your terminal:
$ bin/rails generate model Article title:string text:text
```
-With that command we told Rails that we want a `Article` model, together
+With that command we told Rails that we want an `Article` model, together
with a _title_ attribute of type string, and a _text_ attribute
of type text. Those attributes are automatically added to the `articles`
table in the database and mapped to the `Article` model.
@@ -686,7 +690,7 @@ class CreateArticles < ActiveRecord::Migration[5.0]
t.string :title
t.text :text
- t.timestamps null: false
+ t.timestamps
end
end
end
@@ -767,7 +771,7 @@ Why do you have to bother? The ability to grab and automatically assign all
controller parameters to your model in one shot makes the programmer's job
easier, but this convenience also allows malicious use. What if a request to
the server was crafted to look like a new article form submit but also included
-extra fields with values that violated your applications integrity? They would
+extra fields with values that violated your application's integrity? They would
be 'mass assigned' into your model and then into the database along with the
good stuff - potentially breaking your application or worse.
@@ -1540,7 +1544,7 @@ This is very similar to the `Article` model that you saw earlier. The difference
is the line `belongs_to :article`, which sets up an Active Record _association_.
You'll learn a little about associations in the next section of this guide.
-The (`:references`) keyword used in the bash command is a special data type for models.
+The (`:references`) keyword used in the bash command is a special data type for models.
It creates a new column on your database table with the provided model name appended with an `_id`
that can hold integer values. You can get a better understanding after analyzing the
`db/schema.rb` file below.
@@ -1554,9 +1558,9 @@ class CreateComments < ActiveRecord::Migration[5.0]
create_table :comments do |t|
t.string :commenter
t.text :body
- t.references :article, index: true, foreign_key: true
+ t.references :article, foreign_key: true
- t.timestamps null: false
+ t.timestamps
end
end
end
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 5bbd4048b9..f3802a142f 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -25,7 +25,7 @@ After reading this guide, you will know:
* How I18n works in Ruby on Rails
* How to correctly use I18n into a RESTful application in various ways
-* How to use I18n to translate ActiveRecord errors or ActionMailer E-mail subjects
+* How to use I18n to translate Active Record errors or Action Mailer E-mail subjects
* Some other tools to go further with the translation process of your application
--------------------------------------------------------------------------------
@@ -109,12 +109,11 @@ The **translations load path** (`I18n.load_path`) is an array of paths to files
NOTE: The backend lazy-loads these translations when a translation is looked up for the first time. This backend can be swapped with something else even after translations have already been announced.
-The default `config/application.rb` file has instructions on how to add locales from another directory and how to set a different default locale.
+You can change the default locale as well as configure the translations load paths in `config/application.rb` as follows:
```ruby
-# 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
+ config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
+ config.i18n.default_locale = :de
```
The load path must be specified before any translations are looked up. To change the default locale from an initializer instead of `config/application.rb`:
@@ -1113,7 +1112,7 @@ Conclusion
At this point you should have a good overview about how I18n support in Ruby on Rails works and are ready to start translating your project.
-If you find anything missing or wrong in this guide, please file a ticket on our [issue tracker](http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview). If you want to discuss certain portions or have questions, please sign up to our [mailing list](http://groups.google.com/group/rails-i18n).
+If you want to discuss certain portions or have questions, please sign up to the [rails-i18n mailing list](http://groups.google.com/group/rails-i18n).
Contributing to Rails I18n
@@ -1130,10 +1129,8 @@ Resources
---------
* [Google group: rails-i18n](http://groups.google.com/group/rails-i18n) - The project's mailing list.
-* [GitHub: rails-i18n](https://github.com/svenfuchs/rails-i18n/tree/master) - Code repository for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases.
-* [GitHub: i18n](https://github.com/svenfuchs/i18n/tree/master) - Code repository for the i18n gem.
-* [Lighthouse: rails-i18n](http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview) - Issue tracker for the rails-i18n project.
-* [Lighthouse: i18n](http://i18n.lighthouseapp.com/projects/14947-ruby-i18n/overview) - Issue tracker for the i18n gem.
+* [GitHub: rails-i18n](https://github.com/svenfuchs/rails-i18n) - Code repository and issue tracker for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases.
+* [GitHub: i18n](https://github.com/svenfuchs/i18n) - Code repository and issue tracker for the i18n gem.
Authors
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 6232ef4c57..89e5346d86 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -3,8 +3,8 @@
The Rails Initialization Process
================================
-This guide explains the internals of the initialization process in Rails
-as of Rails 4. It is an extremely in-depth guide and recommended for advanced Rails developers.
+This guide explains the internals of the initialization process in Rails.
+It is an extremely in-depth guide and recommended for advanced Rails developers.
After reading this guide, you will know:
@@ -157,7 +157,7 @@ snippet.
If we had used `s` rather than `server`, Rails would have used the `aliases`
defined here to find the matching command.
-### `rails/commands/command_tasks.rb`
+### `rails/commands/commands_tasks.rb`
When one types a valid Rails command, `run_command!` a method of the same name
is called. If Rails doesn't recognize the command, it tries to run a Rake task
@@ -356,8 +356,6 @@ private
def print_boot_information
...
puts "=> Run `rails server -h` for more startup options"
- ...
- puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
end
def create_tmp_directories
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 1f81ea4694..9abb863da6 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -24,7 +24,17 @@
<% end %>
<div id="topNav">
<div class="wrapper">
- <strong class="more-info-label">←<a href="http://rubyonrails.org/">Back to rubyonrails.org:</a> </strong>
+ <strong class="more-info-label">More at <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
+ <span class="red-button more-info-button">
+ More Ruby on Rails
+ </span>
+ <ul class="more-info-links s-hidden">
+ <li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li>
+ <li class="more-info"><a href="http://guides.rubyonrails.org/">Guides</a></li>
+ <li class="more-info"><a href="http://api.rubyonrails.org/">API</a></li>
+ <li class="more-info"><a href="http://stackoverflow.com/questions/tagged/ruby-on-rails">Ask for help</a></li>
+ <li class="more-info"><a href="https://github.com/rails/rails">Contribute on GitHub</a></li>
+ </ul>
</div>
</div>
<div id="header">
@@ -101,7 +111,7 @@
<%= link_to 'open an issue', 'https://github.com/rails/rails/issues' %>.
</p>
<p>And last but not least, any kind of discussion regarding Ruby on Rails
- documentation is very welcome in the <%= link_to 'rubyonrails-docs mailing list', 'http://groups.google.com/group/rubyonrails-docs' %>.
+ documentation is very welcome in the <%= link_to 'rubyonrails-docs mailing list', 'https://groups.google.com/forum/#!forum/rubyonrails-docs' %>.
</p>
</div>
</div>
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index d55e1007ee..2722789c49 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -149,23 +149,22 @@ render template: "products/show"
#### Rendering an Arbitrary File
-The `render` method can also use a view that's entirely outside of your application (perhaps you're sharing views between two Rails applications):
-
-```ruby
-render "/u/apps/warehouse_app/current/app/views/products/show"
-```
-
-Rails determines that this is a file render because of the leading slash character. To be explicit, you can use the `:file` option (which was required on Rails 2.2 and earlier):
+The `render` method can also use a view that's entirely outside of your application:
```ruby
render file: "/u/apps/warehouse_app/current/app/views/products/show"
```
-The `:file` option takes an absolute file-system path. Of course, you need to have rights to the view that you're using to render the content.
+The `:file` option takes an absolute file-system path. Of course, you need to have rights
+to the view that you're using to render the content.
+
+NOTE: Using the `:file` option in combination with users input can lead to security problems
+since an attacker could use this action to access security sensitive files in your file system.
NOTE: By default, the file is rendered using the current layout.
-TIP: If you're running Rails on Microsoft Windows, you should use the `:file` option to render a file, because Windows filenames do not have the same format as Unix filenames.
+TIP: If you're running Rails on Microsoft Windows, you should use the `:file` option to
+render a file, because Windows filenames do not have the same format as Unix filenames.
#### Wrapping it up
@@ -238,7 +237,7 @@ TIP: This is useful when you're rendering a small snippet of HTML code.
However, you might want to consider moving it to a template file if the markup
is complex.
-NOTE: When using `html:` option, HTML entities will be escaped if the string is not marked as HTML safe by using `html_safe` method.
+NOTE: When using `html:` option, HTML entities will be escaped if the string is not marked as HTML safe by using `html_safe` method.
#### Rendering JSON
@@ -555,7 +554,7 @@ class Admin::ProductsController < AdminController
end
```
-The lookup order for a `admin/products#index` action will be:
+The lookup order for an `admin/products#index` action will be:
* `app/views/admin/products/`
* `app/views/admin/`
@@ -700,7 +699,7 @@ This would detect that there are no books with the specified ID, populate the `@
### Using `head` To Build Header-Only Responses
-The `head` method can be used to send responses with only headers to the browser. It provides a more obvious alternative to calling `render :nothing`. The `head` method accepts a number or symbol (see [reference table](#the-status-option)) representing a HTTP status code. The options argument is interpreted as a hash of header names and values. For example, you can return only an error header:
+The `head` method can be used to send responses with only headers to the browser. The `head` method accepts a number or symbol (see [reference table](#the-status-option)) representing an HTTP status code. The options argument is interpreted as a hash of header names and values. For example, you can return only an error header:
```ruby
head :bad_request
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index 5a46baff2d..3e99ee7021 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -15,18 +15,18 @@ After reading this guide, you will know:
Usage
-----
-To apply a template, you need to provide the Rails generator with the location of the template you wish to apply using the -m option. This can either be a path to a file or a URL.
+To apply a template, you need to provide the Rails generator with the location of the template you wish to apply using the `-m` option. This can either be a path to a file or a URL.
```bash
$ rails new blog -m ~/template.rb
$ rails new blog -m http://example.com/template.rb
```
-You can use the rake task `rails:template` to apply templates to an existing Rails application. The location of the template needs to be passed in to an environment variable named LOCATION. Again, this can either be path to a file or a URL.
+You can use the `app:template` Rake task to apply templates to an existing Rails application. The location of the template needs to be passed in via the LOCATION environment variable. Again, this can either be path to a file or a URL.
```bash
-$ bin/rails rails:template LOCATION=~/template.rb
-$ bin/rails rails:template LOCATION=http://example.com/template.rb
+$ bin/rails app:template LOCATION=~/template.rb
+$ bin/rails app:template LOCATION=http://example.com/template.rb
```
Template API
@@ -38,7 +38,7 @@ The Rails templates API is easy to understand. Here's an example of a typical Ra
# template.rb
generate(:scaffold, "person name:string")
route "root to: 'people#index'"
-rake("db:migrate")
+rails_command("db:migrate")
after_bundle do
git :init
@@ -175,18 +175,24 @@ Executes an arbitrary command. Just like the backticks. Let's say you want to re
run "rm README.rdoc"
```
-### rake(command, options = {})
+### rails_command(command, options = {})
-Runs the supplied rake tasks in the Rails application. Let's say you want to migrate the database:
+Runs the supplied task in the Rails application. Let's say you want to migrate the database:
```ruby
-rake "db:migrate"
+rails_command "db:migrate"
```
-You can also run rake tasks with a different Rails environment:
+You can also run tasks with a different Rails environment:
```ruby
-rake "db:migrate", env: 'production'
+rails_command "db:migrate", env: 'production'
+```
+
+You can also run tasks as a super-user:
+
+```ruby
+rails_command "log:clear", sudo: true
```
### route(routing_code)
@@ -226,7 +232,7 @@ CODE
These methods let you ask questions from templates and decide the flow based on the user's answer. Let's say you want to Freeze Rails only if the user wants to:
```ruby
-rake("rails:freeze:gems") if yes?("Freeze rails gems?")
+rails_command("rails:freeze:gems") if yes?("Freeze rails gems?")
# no?(question) acts just the opposite.
```
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 934693252e..8148f70c31 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -93,7 +93,7 @@ NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`,
### Inspecting Middleware Stack
-Rails has a handy rake task for inspecting the middleware stack in use:
+Rails has a handy task for inspecting the middleware stack in use:
```bash
$ bin/rails middleware
@@ -104,20 +104,19 @@ For a freshly generated Rails application, this might produce something like:
```ruby
use Rack::Sendfile
use ActionDispatch::Static
-use Rack::Lock
-use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000029a0838>
+use ActionDispatch::Executor
+use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
+use WebConsole::Middleware
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
-use ActiveRecord::ConnectionAdapters::ConnectionManagement
-use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
@@ -149,9 +148,9 @@ You can add a new middleware to the middleware stack using any of the following
# Push Rack::BounceFavicon at the bottom
config.middleware.use Rack::BounceFavicon
-# Add Lifo::Cache after ActiveRecord::QueryCache.
+# Add Lifo::Cache after ActionDispatch::Executor.
# Pass { page_cache: false } argument to Lifo::Cache.
-config.middleware.insert_after ActiveRecord::QueryCache, Lifo::Cache, page_cache: false
+config.middleware.insert_after ActionDispatch::Executor, Lifo::Cache, page_cache: false
```
#### Swapping a Middleware
@@ -171,10 +170,10 @@ Add the following lines to your application configuration:
```ruby
# config/application.rb
-config.middleware.delete Rack::Lock
+config.middleware.delete Rack::Runtime
```
-And now if you inspect the middleware stack, you'll find that `Rack::Lock` is
+And now if you inspect the middleware stack, you'll find that `Rack::Runtime` is
not a part of it.
```bash
@@ -219,6 +218,10 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
* Sets `env["rack.multithread"]` flag to `false` and wraps the application within a Mutex.
+**`ActionDispatch::Executor`**
+
+* Used for thread safe code reloading during development.
+
**`ActiveSupport::Cache::Strategy::LocalCache::Middleware`**
* Used for memory caching. This cache is not thread safe.
@@ -263,14 +266,6 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
* Checks pending migrations and raises `ActiveRecord::PendingMigrationError` if any migrations are pending.
-**`ActiveRecord::ConnectionAdapters::ConnectionManagement`**
-
-* Cleans active connections after each request, unless the `rack.test` key in the request environment is set to `true`.
-
-**`ActiveRecord::QueryCache`**
-
-* Enables the Active Record query cache.
-
**`ActionDispatch::Cookies`**
* Sets cookies for the request.
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 5a745b10cd..756e0fefd7 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -9,16 +9,16 @@ After reading this guide, you will know:
* How to interpret the code in `config/routes.rb`.
* How to construct your own routes, using either the preferred resourceful style or the `match` method.
-* What parameters to expect an action to receive.
+* How to declare route parameters, which are passed onto controller actions.
* How to automatically create paths and URLs using route helpers.
-* Advanced techniques such as constraints and Rack endpoints.
+* Advanced techniques such as creating constraints and mounting Rack endpoints.
--------------------------------------------------------------------------------
The Purpose of the Rails Router
-------------------------------
-The Rails router recognizes URLs and dispatches them to a controller's action. It can also generate paths and URLs, avoiding the need to hardcode strings in your views.
+The Rails router recognizes URLs and dispatches them to a controller's action, or to a Rack application. It can also generate paths and URLs, avoiding the need to hardcode strings in your views.
### Connecting URLs to Code
@@ -812,10 +812,10 @@ In all of these cases, if you don't provide the leading host (`http://www.exampl
Instead of a String like `'articles#index'`, which corresponds to the `index` action in the `ArticlesController`, you can specify any [Rack application](rails_on_rack.html) as the endpoint for a matcher:
```ruby
-match '/application.js', to: Sprockets, via: :all
+match '/application.js', to: MyRackApp, via: :all
```
-As long as `Sprockets` responds to `call` and returns a `[status, headers, body]`, the router won't know the difference between the Rack application and an action. This is an appropriate use of `via: :all`, as you will want to allow your Rack application to handle all verbs as it considers appropriate.
+As long as `MyRackApp` responds to `call` and returns a `[status, headers, body]`, the router won't know the difference between the Rack application and an action. This is an appropriate use of `via: :all`, as you will want to allow your Rack application to handle all verbs as it considers appropriate.
NOTE: For the curious, `'articles#index'` actually expands out to `ArticlesController.action(:index)`, which returns a valid Rack application.
@@ -1136,10 +1136,21 @@ For example, here's a small section of the `rails routes` output for a RESTful r
edit_user GET /users/:id/edit(.:format) users#edit
```
-You may restrict the listing to the routes that map to a particular controller setting the `CONTROLLER` environment variable:
+You can search through your routes with the grep option: -g. This outputs any routes that partially match the URL helper method name, the HTTP verb, or the URL path.
-```bash
-$ CONTROLLER=users bin/rails routes
+```
+$ bin/rails routes -g new_comment
+$ bin/rails routes -g POST
+$ bin/rails routes -g admin
+```
+
+If you only want to see the routes that map to a specific controller, there's the -c option.
+
+```
+$ bin/rails routes -c users
+$ bin/rails routes -c admin/users
+$ bin/rails routes -c Comments
+$ bin/rails routes -c Articles::CommentsController
```
TIP: You'll find that the output from `rails routes` is much more readable if you widen your terminal window until the output lines don't wrap.
diff --git a/guides/source/security.md b/guides/source/security.md
index 1d0e87d831..ca985134e6 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -23,7 +23,7 @@ Web application frameworks are made to help developers build web applications. S
In general there is no such thing as plug-n-play security. Security depends on the people using the framework, and sometimes on the development method. And it depends on all layers of a web application environment: The back-end storage, the web server and the web application itself (and possibly other layers or applications).
-The Gartner Group however estimates that 75% of attacks are at the web application layer, and found out "that out of 300 audited sites, 97% are vulnerable to attack". This is because web applications are relatively easy to attack, as they are simple to understand and manipulate, even by the lay person.
+The Gartner Group, however, estimates that 75% of attacks are at the web application layer, and found out "that out of 300 audited sites, 97% are vulnerable to attack". This is because web applications are relatively easy to attack, as they are simple to understand and manipulate, even by the lay person.
The threats against web applications include user account hijacking, bypass of access control, reading or modifying sensitive data, or presenting fraudulent content. Or an attacker might be able to install a Trojan horse program or unsolicited e-mail sending software, aim at financial enrichment or cause brand name damage by modifying company resources. In order to prevent attacks, minimize their impact and remove points of attack, first of all, you have to fully understand the attack methods in order to find the correct countermeasures. That is what this guide aims at.
@@ -41,28 +41,28 @@ NOTE: _HTTP is a stateless protocol. Sessions make it stateful._
Most applications need to keep track of certain state of a particular user. This could be the contents of a shopping basket or the user id of the currently logged in user. Without the idea of sessions, the user would have to identify, and probably authenticate, on every request.
Rails will create a new session automatically if a new user accesses the application. It will load an existing session if the user has already used the application.
-A session usually consists of a hash of values and a session id, usually a 32-character string, to identify the hash. Every cookie sent to the client's browser includes the session id. And the other way round: the browser will send it to the server on every request from the client. In Rails you can save and retrieve values using the session method:
+A session usually consists of a hash of values and a session ID, usually a 32-character string, to identify the hash. Every cookie sent to the client's browser includes the session ID. And the other way round: the browser will send it to the server on every request from the client. In Rails you can save and retrieve values using the session method:
```ruby
session[:user_id] = @current_user.id
User.find(session[:user_id])
```
-### Session id
+### Session ID
-NOTE: _The session id is a 32 byte long MD5 hash value._
+NOTE: _The session ID is a 32-character random hex string._
-A session id consists of the hash value of a random string. The random string is the current time, a random number between 0 and 1, the process id number of the Ruby interpreter (also basically a random number) and a constant string. Currently it is not feasible to brute-force Rails' session ids. To date MD5 is uncompromised, but there have been collisions, so it is theoretically possible to create another input text with the same hash value. But this has had no security impact to date.
+The session ID is generated using `SecureRandom.hex` which generates a random hex string using platform specific methods (such as OpenSSL, /dev/urandom or Win32) for generating cryptographically secure random numbers. Currently it is not feasible to brute-force Rails' session IDs.
### Session Hijacking
-WARNING: _Stealing a user's session id lets an attacker use the web application in the victim's name._
+WARNING: _Stealing a user's session ID lets an attacker use the web application in the victim's name._
-Many web applications have an authentication system: a user provides a user name and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session id in the cookie identifies the session.
+Many web applications have an authentication system: a user provides a user name and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session ID in the cookie identifies the session.
Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user - with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures:
-* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file:
+* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN, it is especially easy to listen to the traffic of all connected clients. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file:
```ruby
config.force_ssl = true
@@ -89,7 +89,7 @@ This will also be a good idea, if you modify the structure of an object and old
NOTE: _Rails provides several storage mechanisms for the session hashes. The most important is `ActionDispatch::Session::CookieStore`._
-Rails 2 introduced a new default session storage, CookieStore. CookieStore saves the session hash directly in a cookie on the client-side. The server retrieves the session hash from the cookie and eliminates the need for a session id. That will greatly increase the speed of the application, but it is a controversial storage option and you have to think about the security implications of it:
+Rails 2 introduced a new default session storage, CookieStore. CookieStore saves the session hash directly in a cookie on the client-side. The server retrieves the session hash from the cookie and eliminates the need for a session ID. That will greatly increase the speed of the application, but it is a controversial storage option and you have to think about the security implications of it:
* Cookies imply a strict size limit of 4kB. This is fine as you should not store large amounts of data in a session anyway, as described before. _Storing the current user's database id in a session is usually ok_.
@@ -102,7 +102,7 @@ Thus the session becomes a more secure place to store data. The encryption is
done using a server-side secret key `secrets.secret_key_base` stored in
`config/secrets.yml`.
-That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters, use `rake secret` instead_.
+That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters, use `rails secret` instead_.
`secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`, e.g.:
@@ -137,16 +137,16 @@ The best _solution against it is not to store this kind of data in a session, bu
### Session Fixation
-NOTE: _Apart from stealing a user's session id, the attacker may fix a session id known to them. This is called session fixation._
+NOTE: _Apart from stealing a user's session ID, the attacker may fix a session ID known to them. This is called session fixation._
![Session fixation](images/session_fixation.png)
-This attack focuses on fixing a user's session id known to the attacker, and forcing the user's browser into using this id. It is therefore not necessary for the attacker to steal the session id afterwards. Here is how this attack works:
+This attack focuses on fixing a user's session ID known to the attacker, and forcing the user's browser into using this ID. It is therefore not necessary for the attacker to steal the session ID afterwards. Here is how this attack works:
-* The attacker creates a valid session id: They load the login page of the web application where they want to fix the session, and take the session id in the cookie from the response (see number 1 and 2 in the image).
+* The attacker creates a valid session ID: They load the login page of the web application where they want to fix the session, and take the session ID in the cookie from the response (see number 1 and 2 in the image).
* They maintain the session by accessing the web application periodically in order to keep an expiring session alive.
-* The attacker forces the user's browser into using this session id (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: `<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>`. Read more about XSS and injection later on.
-* The attacker lures the victim to the infected page with the JavaScript code. By viewing the page, the victim's browser will change the session id to the trap session id.
+* The attacker forces the user's browser into using this session ID (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: `<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>`. Read more about XSS and injection later on.
+* The attacker lures the victim to the infected page with the JavaScript code. By viewing the page, the victim's browser will change the session ID to the trap session ID.
* As the new trap session is unused, the web application will require the user to authenticate.
* From now on, the victim and the attacker will co-use the web application with the same session: The session became valid and the victim didn't notice the attack.
@@ -160,7 +160,7 @@ The most effective countermeasure is to _issue a new session identifier_ and dec
reset_session
```
-If you use the popular RestfulAuthentication plugin for user management, add reset_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_.
+If you use the popular [Devise](https://rubygems.org/gems/devise) gem for user management, it will automatically expire sessions on sign in and sign out for you. If you roll your own, remember to expire the session after your sign in action (when the session is created). This will remove values from the session, therefore _you will have to transfer them to the new session_.
Another countermeasure is to _save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way.
@@ -168,7 +168,7 @@ Another countermeasure is to _save user-specific properties in the session_, ver
NOTE: _Sessions that never expire extend the time-frame for attacks such as cross-site request forgery (CSRF), session hijacking and session fixation._
-One possibility is to set the expiry time-stamp of the cookie with the session id. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _expire sessions in a database table_. Call `Session.sweep("20 minutes")` to expire sessions that were used longer than 20 minutes ago.
+One possibility is to set the expiry time-stamp of the cookie with the session ID. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _expire sessions in a database table_. Call `Session.sweep("20 minutes")` to expire sessions that were used longer than 20 minutes ago.
```ruby
class Session < ApplicationRecord
@@ -196,11 +196,11 @@ This attack method works by including malicious code or a link in a page that ac
![](images/csrf.png)
-In the [session chapter](#sessions) you have learned that most Rails applications use cookie-based sessions. Either they store the session id in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is that if the request comes from a site of a different domain, it will also send the cookie. Let's start with an example:
+In the [session chapter](#sessions) you have learned that most Rails applications use cookie-based sessions. Either they store the session ID in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is that if the request comes from a site of a different domain, it will also send the cookie. Let's start with an example:
* Bob browses a message board and views a post from a hacker where there is a crafted HTML image element. The element references a command in Bob's project management application, rather than an image file: `<img src="http://www.webapp.com/project/1/destroy">`
* Bob's session at `www.webapp.com` is still alive, because he didn't log out a few minutes ago.
-* By viewing the post, the browser finds an image tag. It tries to load the suspected image from `www.webapp.com`. As explained before, it will also send along the cookie with the valid session id.
+* By viewing the post, the browser finds an image tag. It tries to load the suspected image from `www.webapp.com`. As explained before, it will also send along the cookie with the valid session ID.
* The web application at `www.webapp.com` verifies the user information in the corresponding session hash and destroys the project with the ID 1. It then returns a result page which is an unexpected result for the browser, so it will not display the image.
* Bob doesn't notice the attack - but a few days later he finds out that project number one is gone.
@@ -381,7 +381,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/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 the user, 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 the user, but it also contained an image tag that resulted in an 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. If the victim was logged into Google Adsense, the administration interface for Google advertisement campaigns, an attacker could change the credentials of the victim.

@@ -453,7 +453,7 @@ However, the attacker may also take over the account by changing the e-mail addr
#### Other
-Depending on your web application, there may be more ways to hijack the user's account. In many cases CSRF and XSS will help to do so. For example, as in a CSRF vulnerability in [Google Mail](http://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/). In this proof-of-concept attack, the victim would have been lured to a web site controlled by the attacker. On that site is a crafted IMG-tag which results in a HTTP GET request that changes the filter settings of Google Mail. If the victim was logged in to Google Mail, the attacker would change the filters to forward all e-mails to their e-mail address. This is nearly as harmful as hijacking the entire account. As a countermeasure, _review your application logic and eliminate all XSS and CSRF vulnerabilities_.
+Depending on your web application, there may be more ways to hijack the user's account. In many cases CSRF and XSS will help to do so. For example, as in a CSRF vulnerability in [Google Mail](http://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/). In this proof-of-concept attack, the victim would have been lured to a web site controlled by the attacker. On that site is a crafted IMG-tag which results in an HTTP GET request that changes the filter settings of Google Mail. If the victim was logged in to Google Mail, the attacker would change the filters to forward all e-mails to their e-mail address. This is nearly as harmful as hijacking the entire account. As a countermeasure, _review your application logic and eliminate all XSS and CSRF vulnerabilities_.
### CAPTCHAs
@@ -466,7 +466,7 @@ The problem with CAPTCHAs is that they have a negative impact on the user experi
Most bots are really dumb. They crawl the web and put their spam into every form's field they can find. Negative CAPTCHAs take advantage of that and include a "honeypot" field in the form which will be hidden from the human user by CSS or JavaScript.
-Note that negative CAPTCHAs are only effective against dumb bots and won't suffice to protect critical applications from targeted bots. Still, the negative and positive CAPTCHAs can be combined to increase the performance, e.g., if the "honeypot" field is not empty (bot detected), you won't need to verify the positive CAPTCHA, which would require a HTTPS request to Google ReCaptcha before computing the response.
+Note that negative CAPTCHAs are only effective against dumb bots and won't suffice to protect critical applications from targeted bots. Still, the negative and positive CAPTCHAs can be combined to increase the performance, e.g., if the "honeypot" field is not empty (bot detected), you won't need to verify the positive CAPTCHA, which would require an HTTPS request to Google ReCaptcha before computing the response.
Here are some ideas how to hide honeypot fields by JavaScript and/or CSS:
@@ -494,6 +494,8 @@ By default, Rails logs all requests being made to the web application. But log f
config.filter_parameters << :password
```
+NOTE: Provided parameters will be filtered out by partial matching regular expression. Rails adds default `:password` in the appropriate initializer (`initializers/filter_parameter_logging.rb`) and cares about typical application parameters `password` and `password_confirmation`.
+
### Good Passwords
INFO: _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._
@@ -565,7 +567,7 @@ This is alright for some web applications, but certainly not if the user is not
Depending on your web application, there will be many more parameters the user can tamper with. As a rule of thumb, _no user input data is secure, until proven otherwise, and every parameter from the user is potentially manipulated_.
-Don't be fooled by security by obfuscation and JavaScript security. The Web Developer Toolbar for Mozilla Firefox lets you review and change every form's hidden fields. _JavaScript can be used to validate user input data, but certainly not to prevent attackers from sending malicious requests with unexpected values_. The Live Http Headers plugin for Mozilla Firefox logs every request and may repeat and change them. That is an easy way to bypass any JavaScript validations. And there are even client-side proxies that allow you to intercept any request and response from and to the Internet.
+Don't be fooled by security by obfuscation and JavaScript security. Developer tools let you review and change every form's hidden fields. _JavaScript can be used to validate user input data, but certainly not to prevent attackers from sending malicious requests with unexpected values_. The Firebug addon for Mozilla Firefox logs every request and may repeat and change them. That is an easy way to bypass any JavaScript validations. And there are even client-side proxies that allow you to intercept any request and response from and to the Internet.
Injection
---------
@@ -675,14 +677,12 @@ INFO: _The most widespread, and one of the most devastating security vulnerabili
An entry point is a vulnerable URL and its parameters where an attacker can start an attack.
-The most common entry points are message posts, user comments, and guest books, but project titles, document names and search result pages have also been vulnerable - just about everywhere where the user can input data. But the input does not necessarily have to come from input boxes on web sites, it can be in any URL parameter - obvious, hidden or internal. Remember that the user may intercept any traffic. Applications, such as the [Live HTTP Headers Firefox plugin](http://livehttpheaders.mozdev.org/), or client-site proxies make it easy to change requests.
+The most common entry points are message posts, user comments, and guest books, but project titles, document names and search result pages have also been vulnerable - just about everywhere where the user can input data. But the input does not necessarily have to come from input boxes on web sites, it can be in any URL parameter - obvious, hidden or internal. Remember that the user may intercept any traffic. Applications or client-site proxies make it easy to change requests. There are also other attack vectors like banner advertisements.
XSS attacks work like this: An attacker injects some code, the web application saves it and displays it on a page, later presented to a victim. Most XSS examples simply display an alert box, but it is more powerful than that. XSS can steal the cookie, hijack the session, redirect the victim to a fake website, display advertisements for the benefit of the attacker, change elements on the web site to get confidential information or install malicious software through security holes in the web browser.
During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. The [Symantec Global Internet Security threat report](http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf) also documented 239 browser plug-in vulnerabilities in the last six months of 2007. [Mpack](http://pandalabs.pandasecurity.com/mpack-uncovered/) is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites were hacked like this, among them the British government, United Nations, and many more high targets.
-A relatively new, and unusual, form of entry points are banner advertisements. In earlier 2008, malicious code appeared in banner ads on popular sites, such as MySpace and Excite, according to [Trend Micro](http://blog.trendmicro.com/myspace-excite-and-blick-serve-up-malicious-banner-ads/).
-
#### HTML/JavaScript Injection
The most common XSS language is of course the most popular client-side scripting language JavaScript, often in combination with HTML. _Escaping user input is essential_.
@@ -720,7 +720,7 @@ The log files on www.attacker.com will read like this:
GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2
```
-You can mitigate these attacks (in the obvious way) by adding the **httpOnly** flag to cookies, so that document.cookie may not be read by JavaScript. Http only cookies can be used from IE v6.SP1, Firefox v2.0.0.5 and Opera 9.5. Safari is still considering, it ignores the option. But other, older browsers (such as WebTV and IE 5.5 on Mac) can actually cause the page to fail to load. Be warned that cookies [will still be visible using Ajax](https://www.owasp.org/index.php/HTTPOnly#Browsers_Supporting_HttpOnly), 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, Opera 9.5, Safari 4 and Chrome 1.0.154 onwards. But other, older browsers (such as WebTV and IE 5.5 on Mac) can actually cause the page to fail to load. Be warned that cookies [will still be visible using Ajax](https://www.owasp.org/index.php/HTTPOnly#Browsers_Supporting_HttpOnly), though.
##### Defacement
@@ -787,7 +787,7 @@ The following is an excerpt from the [Js.Yamanner@m](http://www.symantec.com/sec
var IDList = ''; var CRumb = ''; function makeRequest(url, Func, Method,Param) { ...
```
-The worms exploits a hole in Yahoo's HTML/JavaScript filter, which usually filters all target and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application.
+The worms exploit a hole in Yahoo's HTML/JavaScript filter, which usually filters all targets and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application.
Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on [Rosario Valotta's paper](http://www.xssed.com/news/37/Nduja_Connection_A_cross_webmail_worm_XWW/). Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with.
diff --git a/guides/source/testing.md b/guides/source/testing.md
index b5e49a41f4..050bdda9e3 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -265,7 +265,7 @@ By now you've caught a glimpse of some of the assertions that are available. Ass
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.
+specify to make your test failure messages clearer.
| Assertion | Purpose |
| ---------------------------------------------------------------- | ------- |
@@ -287,7 +287,7 @@ specify to make your test failure messages clearer.
| `assert_not_in_delta( expected, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.|
| `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.|
| `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.|
-| `assert_nothing_raised( exception1, exception2, ... ) { block }` | Ensures that the given block doesn't raise one of the given exceptions.|
+| `assert_nothing_raised { block }` | Ensures that the given block doesn't raise any exceptions.|
| `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.|
| `assert_not_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.|
| `assert_kind_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class` or is descending from it.|
@@ -316,12 +316,12 @@ Rails adds some custom assertions of its own to the `minitest` framework:
| Assertion | Purpose |
| --------------------------------------------------------------------------------- | ------- |
-| `assert_difference(expressions, difference = 1, message = nil) {...}` | Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.|
-| `assert_no_difference(expressions, message = nil, &block)` | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.|
-| `assert_recognizes(expected_options, path, extras={}, message=nil)` | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.|
-| `assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)` | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.|
-| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.|
-| `assert_redirected_to(options = {}, message=nil)` | Asserts that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.|
+| [`assert_difference(expressions, difference = 1, message = nil) {...}`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_difference) | Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.|
+| [`assert_no_difference(expressions, message = nil, &block)`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_no_difference) | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.|
+| [`assert_recognizes(expected_options, path, extras={}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_recognizes) | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.|
+| [`assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_generates) | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.|
+| [`assert_response(type, message = nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_response) | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.|
+| [`assert_redirected_to(options = {}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_redirected_to) | Asserts that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.|
You'll see the usage of some of these assertions in the next chapter.
@@ -329,11 +329,11 @@ You'll see the usage of some of these assertions in the next chapter.
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`
-* `ActionMailer::TestCase`
-* `ActionView::TestCase`
-* `ActionDispatch::IntegrationTest`
-* `ActiveJob::TestCase`
+* [`ActiveSupport::TestCase`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html)
+* [`ActionMailer::TestCase`](http://api.rubyonrails.org/classes/ActionMailer/TestCase.html)
+* [`ActionView::TestCase`](http://api.rubyonrails.org/classes/ActionView/TestCase.html)
+* [`ActionDispatch::IntegrationTest`](http://api.rubyonrails.org/classes/ActionDispatch/IntegrationTest.html)
+* [`ActiveJob::TestCase`](http://api.rubyonrails.org/classes/ActiveJob/TestCase.html)
Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests.
@@ -415,6 +415,8 @@ You can find comprehensive documentation in the [Fixtures API documentation](htt
_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.
+NOTE: Fixtures are not designed to create every object that your tests need, and are best managed when only used for default data that can be applied to the common case.
+
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
@@ -493,7 +495,8 @@ users(:david)
users(:david).id
# one can also access methods available on the User class
-email(david.partner.email, david.location_tonight)
+david = users(:david)
+david.call(david.partner)
```
To get multiple fixtures at once, you can pass in a list of fixture names. For example:
@@ -518,7 +521,7 @@ create test/models/article_test.rb
create test/fixtures/articles.yml
```
-Model tests don't have their own superclass like `ActionMailer::TestCase` instead they inherit from `ActiveSupport::TestCase`.
+Model tests don't have their own superclass like `ActionMailer::TestCase` instead they inherit from [`ActiveSupport::TestCase`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html).
Integration Testing
@@ -589,7 +592,7 @@ class BlogFlowTest < ActionDispatch::IntegrationTest
end
```
-We will take a look at `assert_select` to query the resulting HTML of a request in the "Testing Views" section below. It is used for testing the response of our request by asserting the presence of key HTML elements and their content.
+We will take a look at `assert_select` to query the resulting HTML of a request in the "Testing Views" section below. It is used for testing the response of our request by asserting the presence of key HTML elements and their content.
When we visit our root path, we should see `welcome/index.html.erb` rendered for the view. So this assertion should pass.
@@ -638,9 +641,9 @@ We were able to successfully test a very small workflow for visiting our blog an
Functional Tests for Your Controllers
-------------------------------------
-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.
+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 are 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
+### What to include in your Functional Tests
You should test for things such as:
@@ -650,8 +653,7 @@ You should test for things such as:
* was the correct object stored in the response template?
* was the appropriate message displayed to the user in the view?
-The easiest way to see functional tests in action is to generate a controller
-scaffold:
+The easiest way to see functional tests in action is to generate a controller using the scaffold generator:
```bash
$ bin/rails generate scaffold_controller article title:string body:text
@@ -664,7 +666,7 @@ create test/controllers/articles_controller_test.rb
```
This will generate the controller code and tests for an `Article` resource.
-You can take look at the file `articles_controller_test.rb` in the `test/controllers` directory.
+You can take a look at the file `articles_controller_test.rb` in the `test/controllers` directory.
If you already have a controller and just want to generate the test scaffold code for
each of the seven default actions, you can use the following command:
@@ -677,7 +679,7 @@ create test/controllers/articles_controller_test.rb
...
```
-Let me take you through one such test, `test_should_get_index` from the file `articles_controller_test.rb`.
+Let's take a look at one such test, `test_should_get_index` from the file `articles_controller_test.rb`.
```ruby
# articles_controller_test.rb
@@ -693,7 +695,7 @@ end
In the `test_should_get_index` test, Rails simulates a request on the action called `index`, making sure the request was successful
and also ensuring that the right response body has been generated.
-The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments:
+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 route (i.e. `articles_url`).
@@ -705,7 +707,7 @@ The `get` method kicks off the web request and populates the results into the re
* `flash`: option with a hash of flash values.
-All the keyword arguments are optional.
+All of these 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:
@@ -753,7 +755,7 @@ NOTE: Functional tests do not verify whether the specified request type is accep
### Testing XHR (AJAX) requests
To test AJAX requests, you can specify the `xhr: true` option to `get`, `post`,
-`patch`, `put`, and `delete` methods:
+`patch`, `put`, and `delete` methods. For example:
```ruby
test "ajax request" do
@@ -797,7 +799,7 @@ and
can be set directly on the `@request` instance variable:
```ruby
-# setting a HTTP Header
+# setting an HTTP Header
@request.headers["Accept"] = "text/plain, text/html"
get articles_url # simulate the request with custom header
@@ -808,7 +810,7 @@ post article_url # simulate the request with custom env variable
### Testing `flash` notices
-If you remember from earlier one of the Three Hashes of the Apocalypse was `flash`.
+If you remember from earlier, one of the Three Hashes of the Apocalypse was `flash`.
We want to add a `flash` message to our blog application whenever someone
successfully creates a new Article.
@@ -829,7 +831,7 @@ end
If we run our test now, we should see a failure:
```bash
-$ bin/rails test test/controllers/articles_controller_test.rb test_should_create_article
+$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Run options: -n test_should_create_article --seed 32266
# Running:
@@ -867,7 +869,7 @@ end
Now if we run our tests, we should see it pass:
```bash
-$ bin/rails test test/controllers/articles_controller_test.rb test_should_create_article
+$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Run options: -n test_should_create_article --seed 18981
# Running:
@@ -893,7 +895,7 @@ test "should show article" do
end
```
-Remember from our discussion earlier on fixtures the `articles()` method will give us access to our Articles fixtures.
+Remember from our discussion earlier on fixtures, the `articles()` method will give us access to our Articles fixtures.
How about deleting an existing Article?
@@ -913,14 +915,19 @@ We can also add a test for updating an existing Article.
```ruby
test "should update article" do
article = articles(:one)
+
patch '/article', params: { id: article.id, article: { title: "updated" } }
+
assert_redirected_to article_path(article)
+ # Reload association to fetch updated data and assert that title is updated.
+ article.reload
+ assert_equal "updated", article.title
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.
+Our test should now look something as what follows. Disregard the other tests for now, we're leaving them out for brevity.
```ruby
require 'test_helper'
@@ -952,8 +959,12 @@ class ArticlesControllerTest < ActionDispatch::IntegrationTest
end
test "should update article" do
- patch article_url(@article), params: { article: { title: "updated" } }
+ patch '/article', params: { id: @article.id, article: { title: "updated" } }
+
assert_redirected_to article_path(@article)
+ # Reload association to fetch updated data and assert that title is updated.
+ @article.reload
+ assert_equal "updated", @article.title
end
end
```
@@ -966,7 +977,7 @@ To avoid code duplication, you can add your own test helpers.
Sign in helper can be a good example:
```ruby
-test/test_helper.rb
+#test/test_helper.rb
module SignInHelper
def sign_in(user)
@@ -999,6 +1010,8 @@ Testing Routes
Like everything else in your Rails application, you can test your routes.
+NOTE: If your application has complex routes, Rails provides a number of useful helpers to test them.
+
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
@@ -1045,7 +1058,7 @@ assert_select "ol" do
end
```
-This assertion is quite powerful. For more advanced usage, refer to its [documentation](http://www.rubydoc.info/github/rails/rails-dom-testing).
+This 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).
#### Additional View-Based Assertions
@@ -1068,27 +1081,31 @@ end
Testing Helpers
---------------
+A helper is just a simple module where you can define methods which are
+available into your views.
+
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:
+Given we have the following helper:
```ruby
-require 'test_helper'
-
-class UserHelperTest < ActionView::TestCase
+module UserHelper
+ def link_to_user(user)
+ link_to "#{user.first_name} #{user.last_name}", user
+ 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:
+We can test the output of this method like this:
```ruby
class UserHelperTest < ActionView::TestCase
- test "should return the user name" do
- # ...
+ test "should return the user's full name" do
+ user = users(:david)
+
+ assert_dom_equal %{<a href="/user/#{user.id}">David Heinemeier Hansson</a>}, link_to_user(user)
end
end
```
@@ -1123,7 +1140,7 @@ In order to test that your mailer is working as expected, you can use unit tests
For the purposes of unit testing a mailer, fixtures are used to provide an example of how the output _should_ look. Because these are example emails, and not Active Record data like the other fixtures, they are kept in their own subdirectory apart from the other fixtures. The name of the directory within `test/fixtures` directly corresponds to the name of the mailer. So, for a mailer named `UserMailer`, the fixtures should reside in `test/fixtures/user_mailer` directory.
-When you generated your mailer, the generator creates stub fixtures for each of the mailers actions. If you didn't use the generator you'll have to make those files yourself.
+When you generated your mailer, the generator creates stub fixtures for each of the mailers actions. If you didn't use the generator, you'll have to create those files yourself.
#### The Basic Test Case
@@ -1175,9 +1192,9 @@ testing) but instead it will be appended to an array
(`ActionMailer::Base.deliveries`).
NOTE: The `ActionMailer::Base.deliveries` array is only reset automatically in
-`ActionMailer::TestCase` tests. If you want to have a clean slate outside Action
-Mailer tests, you can reset it manually with:
-`ActionMailer::Base.deliveries.clear`
+`ActionMailer::TestCase` and `ActionDispatch::IntegrationTest` tests.
+If you want to have a clean slate outside these test cases, you can reset it
+manually with: `ActionMailer::Base.deliveries.clear`
### Functional Testing
@@ -1204,7 +1221,7 @@ Testing Jobs
------------
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)
+you'll need to test both, the jobs themselves (their behavior when they get enqueued)
and that other entities correctly enqueue them.
### A Basic Test Case
@@ -1252,10 +1269,12 @@ class ProductTest < ActiveJob::TestCase
end
```
-Testing Time-Dependent Code
----------------------------
+Additional Testing Resources
+----------------------------
+
+### Testing Time-Dependent Code
-Rails provides inbuilt helper methods that enable you to assert that your time-sensitve code works as expected.
+Rails provides built-in helper methods that enable you to assert that your time-sensitive code works as expected.
Here is an example using the [`travel_to`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html#method-i-travel_to) helper:
@@ -1270,5 +1289,5 @@ end
assert_equal Date.new(2004, 10, 24), user.activation_date # The change was visible only inside the `travel_to` block.
```
-Please see [`ActiveSupport::TimeHelpers` API Documentation](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html)
+Please see [`ActiveSupport::Testing::TimeHelpers` API Documentation](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html)
for in-depth information about the available time helpers.
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 202e5b5cb9..82080c4def 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -16,6 +16,21 @@ Before attempting to upgrade an existing application, you should be sure you hav
The best way to be sure that your application still works after upgrading is to have good test coverage before you start the process. If you don't have automated tests that exercise the bulk of your application, you'll need to spend time manually exercising all the parts that have changed. In the case of a Rails upgrade, that will mean every single piece of functionality in the application. Do yourself a favor and make sure your test coverage is good _before_ you start an upgrade.
+### The Upgrade Process
+
+When changing Rails versions, it's best to move slowly, one minor version at a time, in order to make good use of the deprecation warnings. Rails version numbers are in the form Major.Minor.Patch. Major and Minor versions are allowed to make changes to the public API, so this may cause errors in your application. Patch versions only include bug fixes, and don't change any public API.
+
+The process should go as follows:
+
+1. Write tests and make sure they pass.
+2. Move to the latest patch version after your current version.
+3. Fix tests and deprecated features.
+4. Move to the latest patch version of the next minor version.
+
+Repeat this process until you reach your target Rails version. Each time you move versions, you will need to change the Rails version number in the Gemfile (and possibly other gem versions) and run `bundle update`. Then run the Update task mentioned below to update configuration files, then run your tests.
+
+You can find a list of all released Rails versions [here](https://rubygems.org/gems/rails/versions).
+
### Ruby Versions
Rails generally stays close to the latest released Ruby version when it's released:
@@ -27,15 +42,15 @@ Rails generally stays close to the latest released Ruby version when it's releas
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.
-### The Rake Task
+### The Update Task
-Rails provides the `rails:update` rake task. After updating the Rails version
-in the Gemfile, run this rake task.
+Rails provides the `app:update` task (`rails:update` on 4.2 and earlier). After updating the Rails version
+in the Gemfile, run this task.
This will help you with the creation of new files and changes of old files in an
interactive session.
```bash
-$ rake rails:update
+$ rails app:update
identical config/boot.rb
exist config
conflict config/routes.rb
@@ -55,7 +70,8 @@ Upgrading from Rails 4.2 to Rails 5.0
### Ruby 2.2.2+
-ToDo...
+From Ruby on Rails 5.0 onwards, Ruby 2.2.2+ is the only supported version.
+Make sure you are on Ruby 2.2.2 version or greater, before you proceed.
### Active Record models now inherit from ApplicationRecord by default
@@ -168,7 +184,7 @@ 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.
-When you define a `after_rollback` or `after_commit` callback, you
+When you define an `after_rollback` or `after_commit` callback, you
will receive a deprecation warning about this upcoming change. When
you are ready, you can opt into the new behavior and remove the
deprecation warning by adding following configuration to your
@@ -865,7 +881,7 @@ Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must rep
* Rails 4.0 has removed the identity map from Active Record, due to [some inconsistencies with associations](https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6). If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: `config.active_record.identity_map`.
-* The `delete` method in collection associations can now receive `Fixnum` or `String` arguments as record ids, besides records, pretty much like the `destroy` method does. Previously it raised `ActiveRecord::AssociationTypeMismatch` for such arguments. From Rails 4.0 on `delete` automatically tries to find the records matching the given ids before deleting them.
+* The `delete` method in collection associations can now receive `Integer` or `String` arguments as record ids, besides records, pretty much like the `destroy` method does. Previously it raised `ActiveRecord::AssociationTypeMismatch` for such arguments. From Rails 4.0 on `delete` automatically tries to find the records matching the given ids before deleting them.
* In Rails 4.0 when a column or a table is renamed the related indexes are also renamed. If you have migrations which rename the indexes, they are no longer needed.
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 48fc6bc9c0..c1dfcab6f3 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -148,10 +148,10 @@ and Rails has got your back in those cases.
Because of Unobtrusive JavaScript, the Rails "Ajax helpers" are actually in two
parts: the JavaScript half and the Ruby half.
+Unless you have disabled the Asset Pipeline,
[rails.js](https://github.com/rails/jquery-ujs/blob/master/src/rails.js)
provides the JavaScript half, and the regular Ruby view helpers add appropriate
-tags to your DOM. The CoffeeScript in rails.js then listens for these
-attributes, and attaches appropriate handlers.
+tags to your DOM.
### form_for
@@ -328,7 +328,7 @@ this:
respond_to do |format|
if @user.save
format.html { redirect_to @user, notice: 'User was successfully created.' }
- format.js {}
+ format.js
format.json { render json: @user, status: :created, location: @user }
else
format.html { render action: "new" }
@@ -350,8 +350,8 @@ $("<%= escape_javascript(render @user) %>").appendTo("#users");
Turbolinks
----------
-Rails 4 ships with the [Turbolinks gem](https://github.com/rails/turbolinks).
-This gem uses Ajax to speed up page rendering in most applications.
+Rails ships with the [Turbolinks library](https://github.com/turbolinks/turbolinks),
+which uses Ajax to speed up page rendering in most applications.
### How Turbolinks Works
@@ -364,14 +364,14 @@ will then use PushState to change the URL to the correct one, preserving
refresh semantics and giving you pretty URLs.
The only thing you have to do to enable Turbolinks is have it in your Gemfile,
-and put `//= require turbolinks` in your CoffeeScript manifest, which is usually
+and put `//= require turbolinks` in your JavaScript manifest, which is usually
`app/assets/javascripts/application.js`.
-If you want to disable Turbolinks for certain links, add a `data-no-turbolink`
+If you want to disable Turbolinks for certain links, add a `data-turbolinks="false"`
attribute to the tag:
```html
-<a href="..." data-no-turbolink>No turbolinks here</a>.
+<a href="..." data-turbolinks="false">No turbolinks here</a>.
```
### Page Change Events
@@ -389,13 +389,13 @@ event that this relies on will not be fired. If you have code that looks like
this, you must change your code to do this instead:
```coffeescript
-$(document).on "page:change", ->
+$(document).on "turbolinks:load", ->
alert "page has loaded!"
```
For more details, including other events you can bind to, check out [the
Turbolinks
-README](https://github.com/rails/turbolinks/blob/master/README.md).
+README](https://github.com/turbolinks/turbolinks/blob/master/README.md).
Other Resources
---------------
diff --git a/load_paths.rb b/load_paths.rb
deleted file mode 100644
index 0ad8fcfeda..0000000000
--- a/load_paths.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-# bust gem prelude
-require 'bundler'
-Bundler.setup
diff --git a/rails.gemspec b/rails.gemspec
index d4e78b6aa1..8f9cc181a0 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.homepage = 'http://rubyonrails.org'
s.files = ['README.md']
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 2c363c55da..b8f1cda329 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,451 +1,5 @@
-* Bring back `TEST=` env for `rake test` task.
+* Added a shared section to `config/secrets.yml` that will be loaded for all environments.
- *Yves Senn*
+ *DHH*
-* Specify log file names or all logs to clear `rake log:clear`
-
- Specify which logs to clear when using the `rake log:clear` task, e.g. `rake log:clear LOGS=test,staging`
-
- Clear all logs from log/*.log e.g. `rake log:clear LOGS=all`
-
- By default `rake log:clear` clears standard environment log files i.e. 'development,test,production'
-
- *Pramod Shinde*
-
-* Fix using `add_source` with a block after using `gem` in a custom generator.
-
- *Will Fisher*
-
-## Rails 5.0.0.beta1 (December 18, 2015) ##
-
-* Newly generated plugins get a `README.md` in Markdown.
-
- *Yuji Yaginuma*
-
-* The generated config file for the development environment includes a new
- config line, commented out, showing how to enable the evented file watcher.
-
- *Xavier Noria*
-
-* `config.debug_exception_response_format` configures the format used
- in responses when errors occur in development mode.
-
- Set `config.debug_exception_response_format` to render an HTML page with
- debug info (using the value `:default`) or render debug info preserving
- the response format (using the value `:api`).
-
- *Jorge Bejar*
-
-* Fix setting exit status code for rake test tasks. The exit status code
- was not set when tests were fired with `rake`. Now, it is being set and it matches
- behavior of running tests via `rails` command (`rails test`), so no matter if
- `rake test` or `rails test` command is used the exit code will be set.
-
- *Arkadiusz Fal*
-
-* Add Command infrastructure to replace rake.
-
- Also move `rake dev:cache` to new infrastructure. You'll need to use
- `rails dev:cache` to toggle development caching from now on.
-
- *Chuck Callebs*
-
-* Allow use of minitest-rails gem with Rails test runner.
-
- Fixes #22455.
-
- *Chris Kottom*
-
-* Add `bin/test` script to rails plugin.
-
- `bin/test` can use the same API as `bin/rails test`.
-
- *Yuji Yaginuma*
-
-* Make `static_index` part of the `config.public_file_server` config and
- call it `public_file_server.index_name`.
-
- *Yuki Nishijima*
-
-* Deprecate `serve_static_files` in favor of `public_file_server.enabled`.
-
- Unifies the static asset options under `public_file_server`.
-
- To upgrade, replace occurrences of:
-
- ```
- config.serve_static_files = # false or true
- ```
-
- in your environment files, with:
-
- ```
- config.public_file_server.enabled = # false or true
- ```
-
- *Kasper Timm Hansen*
-
-* Deprecate `config.static_cache_control` in favor of
- `config.public_file_server.headers`.
-
- To upgrade, replace occurrences of:
-
- ```
- config.static_cache_control = 'public, max-age=60'
- ```
-
- in your environment files, with:
-
- ```
- config.public_file_server.headers = {
- 'Cache-Control' => 'public, max-age=60'
- }
- ```
-
- `config.public_file_server.headers` can set arbitrary headers, sent along when
- a response is delivered.
-
- *Yuki Nishijima*
-
-* Route generator should be idempotent
- running generators several times no longer require you to cleanup routes.rb
-
- *Thiago Pinto*
-
-* Allow passing an environment to `config_for`.
-
- *Simon Eskildsen*
-
-* Allow rake:stats to account for rake tasks in lib/tasks
-
- *Kevin Deisz*
-
-* Added javascript to update the URL on mailer previews with the currently
- selected email format. Reloading the page now keeps you on your selected
- format rather than going back to the default html version.
-
- *James Kerr*
-
-* Add fail fast to `bin/rails test`
-
- Adding `--fail-fast` or `-f` when running tests will interrupt the run on
- the first failure:
-
- ```
- # Running:
-
- ................................................S......E
-
- ArgumentError: Wups! Bet you didn't expect this!
- test/models/bunny_test.rb:19:in `block in <class:BunnyTest>'
-
- bin/rails test test/models/bunny_test.rb:18
-
- ....................................F
-
- This failed
-
- bin/rails test test/models/bunny_test.rb:14
-
- Interrupted. Exiting...
-
-
- Finished in 0.051427s, 1808.3872 runs/s, 1769.4972 assertions/s.
-
- ```
-
- Note that any unexpected errors don't abort the run.
-
- *Kasper Timm Hansen*
-
-* Add inline output to `bin/rails test`
-
- Any failures or errors (and skips if running in verbose mode) are output
- during a test run:
-
- ```
- # Running:
-
- .....S..........................................F
-
- This failed
-
- bin/rails test test/models/bunny_test.rb:14
-
- .................................E
-
- ArgumentError: Wups! Bet you didn't expect this!
- test/models/bunny_test.rb:19:in `block in <class:BunnyTest>'
-
- bin/rails test test/models/bunny_test.rb:18
-
- ....................
-
- Finished in 0.069708s, 1477.6019 runs/s, 1448.9106 assertions/s.
- ```
-
- Output can be deferred to after a run with the `--defer-output` option.
-
- *Kasper Timm Hansen*
-
-* Fix displaying mailer previews on non local requests when config
- `action_mailer.show_previews` is set
-
- *Wojciech Wnętrzak*
-
-* `rails server` will now honour the `PORT` environment variable
-
- *David Cornu*
-
-* Plugins generated using `rails plugin new` are now generated with the
- version number set to 0.1.0.
-
- *Daniel Morris*
-
-* `I18n.load_path` is now reloaded under development so there's no need to
- restart the server to make new locale files available. Also, I18n will no
- longer raise for deleted locale files.
-
- *Kir Shatrov*
-
-* Add `bin/update` script to update development environment automatically.
-
- *Mehmet Emin İNAÇ*
-
-* Fix STATS_DIRECTORIES already defined warning when running rake from within
- the top level directory of an engine that has a test app.
-
- Fixes #20510.
-
- *Ersin Akinci*
-
-* Make enabling or disabling caching in development mode possible with
- rake dev:cache.
-
- Running rake dev:cache will create or remove tmp/caching-dev.txt. When this
- file exists config.action_controller.perform_caching will be set to true in
- config/environments/development.rb.
-
- Additionally, a server can be started with either --dev-caching or
- --no-dev-caching included to toggle caching on startup.
-
- *Jussi Mertanen*, *Chuck Callebs*
-
-* Add a `--api` option in order to generate plugins that can be added
- inside an API application.
-
- *Robin Dupret*
-
-* Fix `NoMethodError` when generating a scaffold inside a full engine.
-
- *Yuji Yaginuma*
-
-* Adding support for passing a block to the `add_source` action of a custom generator
-
- *Mike Dalton*, *Hirofumi Wakasugi*
-
-* `assert_file` understands paths with special characters
- (eg. `v0.1.4~alpha+nightly`).
-
- *Diego Carrion*
-
-* Remove ContentLength middleware from the defaults. If you want it, just
- add it as a middleware in your config.
-
- *Egg McMuffin*
-
-* Make it possible to customize the executable inside rerun snippets.
-
- *Yves Senn*
-
-* Add support for API only apps.
- Middleware stack was slimmed down and it has only the needed
- middleware for API apps & generators generates the right files,
- folders and configurations.
-
- *Santiago Pastorino*, *Jorge Bejar*
-
-* Make generated scaffold functional tests work inside engines.
-
- *Yuji Yaginuma*
-
-* Generate a `.keep` file in the `tmp` folder by default as many scripts
- assume the existence of this folder and most would fail if it is absent.
-
- See #20299.
-
- *Yoong Kang Lim*, *Sunny Juneja*
-
-* `config.static_index` configures directory `index.html` filename
-
- Set `config.static_index` to serve a static directory index file not named
- `index`. E.g. to serve `main.html` instead of `index.html` for directory
- requests, set `config.static_index` to `"main"`.
-
- *Eliot Sykes*
-
-* `bin/setup` uses built-in rake tasks (`log:clear`, `tmp:clear`).
-
- *Mohnish Thallavajhula*
-
-* Fix mailer previews with attachments by using the mail gem's own API to
- locate the first part of the correct mime type.
-
- Fixes #14435.
-
- *Andrew White*
-
-* Remove sqlite support from `rails dbconsole`.
-
- *Andrew White*
-
-* Rename `railties/bin` to `railties/exe` to match the new Bundler executables
- convention.
-
- *Islam Wazery*
-
-* Print `bundle install` output in `rails new` as soon as it's available.
-
- Running `rails new` will now print the output of `bundle install` as
- it is available, instead of waiting until all gems finish installing.
-
- *Max Holder*
-
-* Respect `pluralize_table_names` when generating fixture file.
-
- Fixes #19519.
-
- *Yuji Yaginuma*
-
-* Add a new-line to the end of route method generated code.
-
- We need to add a `\n`, because we cannot have two routes
- in the same line.
-
- *arthurnn*
-
-* Add `rake initializers`.
-
- This task prints out all defined initializers in the order they are invoked
- by Rails. This is helpful for debugging issues related to the initialization
- process.
-
- *Naoto Kaneko*
-
-* Created rake restart task. Restarts your Rails app by touching the
- `tmp/restart.txt`.
-
- See #18876.
-
- *Hyonjee Joo*
-
-* Add `config/initializers/active_record_belongs_to_required_by_default.rb`.
-
- Newly generated Rails apps have a new initializer called
- `active_record_belongs_to_required_by_default.rb` which sets the value of
- the configuration option `config.active_record.belongs_to_required_by_default`
- to `true` when ActiveRecord is not skipped.
-
- As a result, new Rails apps require `belongs_to` association on model
- to be valid.
-
- This initializer is *not* added when running `rake rails:update`, so
- old apps ported to Rails 5 will work without any change.
-
- *Josef Šimánek*
-
-* `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.
-
- Fixes #16433.
-
- *Guo Xiang Tan*
-
-* Newly generated applications get a `README.md` in Markdown.
-
- *Xavier Noria*
-
-* Remove the documentation tasks `doc:app`, `doc:rails`, and `doc:guides`.
-
- *Xavier Noria*
-
-* Force generated routes to be inserted into `config/routes.rb`.
-
- *Andrew White*
-
-* Don't remove all line endings from `config/routes.rb` when revoking scaffold.
-
- Fixes #15913.
-
- *Andrew White*
-
-* Rename `--skip-test-unit` option to `--skip-test` in app generator
-
- *Melanie Gilman*
-
-* Add the `method_source` gem to the default Gemfile for apps.
-
- *Sean Griffin*
-
-* Drop old test locations from `rake stats`:
-
- - test/functional
- - test/unit
-
- *Ravil Bayramgalin*
-
-* Update `rake stats` to correctly count declarative tests
- as methods in `_test.rb` files.
-
- *Ravil Bayramgalin*
-
-* Remove deprecated `test:all` and `test:all:db` tasks.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `Rails::Rack::LogTailer`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `RAILS_CACHE` constant.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `serve_static_assets` configuration.
-
- *Rafael Mendonça França*
-
-* Use local variables in `_form.html.erb` partial generated by scaffold.
-
- *Andrew Kozlov*
-
-* Add `config/initializers/callback_terminator.rb`.
-
- Newly generated Rails apps have a new initializer called
- `callback_terminator.rb` which sets the value of the configuration option
- `ActiveSupport.halt_callback_chains_on_return_false` to `false`.
-
- As a result, new Rails apps do not halt Active Record and Active Model
- callback chains when a callback returns `false`; only when they are
- explicitly halted with `throw(:abort)`.
-
- 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.
-
- *claudiob*
-
-* Generated fixtures won't use the id when generated with references attributes.
-
- *Pablo Olmos de Aguilera Corradini*
-
-* Add `--skip-action-mailer` option to the app generator.
-
- *claudiob*
-
-* Autoload any second level directories called `app/*/concerns`.
-
- *Alex Robbin*
-
-Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/railties/CHANGELOG.md) for previous changes.
+Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/railties/CHANGELOG.md) for previous changes.
diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc
index ce024563c4..ef9bbf3d7e 100644
--- a/railties/RDOC_MAIN.rdoc
+++ b/railties/RDOC_MAIN.rdoc
@@ -35,23 +35,23 @@ can read more about Action Pack in its {README}[link:files/actionpack/README_rdo
1. Install \Rails at the command prompt if you haven't yet:
- gem install rails
+ $ gem install rails
2. At the command prompt, create a new \Rails application:
- rails new myapp
+ $ rails new myapp
where "myapp" is the application name.
3. Change directory to +myapp+ and start the web server:
- cd myapp; rails server
+ $ cd myapp; rails server
Run with <tt>--help</tt> or <tt>-h</tt> for options.
4. Go to http://localhost:3000 and you'll see:
- "Welcome aboard: You're riding Ruby on Rails!"
+ "Yay! You’re on Rails!"
5. Follow the guidelines to start developing your application. You may find the following resources handy:
diff --git a/railties/Rakefile b/railties/Rakefile
index cf130a5f14..db23bbabaa 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -2,6 +2,8 @@ require 'rake/testtask'
task :default => :test
+task :package
+
desc "Run all unit tests"
task :test => 'test:isolated'
diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb
index 11f4d5c4bc..1a7f7855f1 100644
--- a/railties/lib/rails/all.rb
+++ b/railties/lib/rails/all.rb
@@ -1,4 +1,4 @@
-require "rails"
+require 'rails'
%w(
active_record/railtie
@@ -11,7 +11,7 @@ require "rails"
sprockets/railtie
).each do |railtie|
begin
- require "#{railtie}"
+ require railtie
rescue LoadError
end
end
diff --git a/railties/lib/rails/app_loader.rb b/railties/lib/rails/app_loader.rb
index a9fe21824e..af004d85bf 100644
--- a/railties/lib/rails/app_loader.rb
+++ b/railties/lib/rails/app_loader.rb
@@ -16,7 +16,7 @@ like any other source code, rather than stubs that are generated on demand.
Here's how to upgrade:
bundle config --delete bin # Turn off Bundler's stub generator
- rake rails:update:bin # Use the new Rails 4 executables
+ rails app:update:bin # Use the new Rails 5 executables
git add bin # Add bin/ to source control
You may need to remove bin/ from your .gitignore as well.
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index cac31e1eed..c383de3e06 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,4 +1,3 @@
-require 'fileutils'
require 'yaml'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/object/blank'
@@ -74,8 +73,7 @@ module Rails
# the configuration.
#
# If you decide to define rake tasks, runners, or initializers in an
- # application other than +Rails.application+, then you must run those
- # these manually.
+ # application other than +Rails.application+, then you must run them manually.
class Application < Engine
autoload :Bootstrap, 'rails/application/bootstrap'
autoload :Configuration, 'rails/application/configuration'
@@ -113,7 +111,7 @@ module Rails
attr_accessor :assets, :sandbox
alias_method :sandbox?, :sandbox
- attr_reader :reloaders
+ attr_reader :reloaders, :reloader, :executor
delegate :default_url_options, :default_url_options=, to: :routes
@@ -131,6 +129,10 @@ module Rails
@message_verifiers = {}
@ran_load_hooks = false
+ @executor = Class.new(ActiveSupport::Executor)
+ @reloader = Class.new(ActiveSupport::Reloader)
+ @reloader.executor = @executor
+
# are these actually used?
@initial_variable_values = initial_variable_values
@block = block
@@ -214,7 +216,7 @@ module Rails
# url: http://localhost:3001
# namespace: my_app_development
#
- # # config/production.rb
+ # # config/environments/production.rb
# Rails.application.configure do
# config.middleware.use ExceptionNotifier, config_for(:exception_notification)
# end
@@ -243,7 +245,7 @@ module Rails
@app_env_config ||= begin
validate_secret_key_config!
- super.merge({
+ super.merge(
"action_dispatch.parameter_filter" => config.filter_parameters,
"action_dispatch.redirect_filter" => config.filter_redirect,
"action_dispatch.secret_token" => secrets.secret_token,
@@ -259,7 +261,7 @@ module Rails
"action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt,
"action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer,
"action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest
- })
+ )
end
end
@@ -383,11 +385,16 @@ module Rails
def secrets
@secrets ||= begin
secrets = ActiveSupport::OrderedOptions.new
- yaml = config.paths["config/secrets"].first
+ yaml = config.paths["config/secrets"].first
+
if File.exist?(yaml)
require "erb"
- all_secrets = YAML.load(ERB.new(IO.read(yaml)).result) || {}
- env_secrets = all_secrets[Rails.env]
+
+ all_secrets = YAML.load(ERB.new(IO.read(yaml)).result) || {}
+ shared_secrets = all_secrets['shared']
+ env_secrets = all_secrets[Rails.env]
+
+ secrets.merge!(shared_secrets.symbolize_keys) if shared_secrets
secrets.merge!(env_secrets.symbolize_keys) if env_secrets
end
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 9baf8aa742..f615f22b26 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -1,6 +1,7 @@
-require "active_support/notifications"
-require "active_support/dependencies"
-require "active_support/descendants_tracker"
+require 'fileutils'
+require 'active_support/notifications'
+require 'active_support/dependencies'
+require 'active_support/descendants_tracker'
module Rails
class Application
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 65cff1561a..f415a20833 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -58,7 +58,7 @@ module Rails
def static_cache_control=(value)
ActiveSupport::Deprecation.warn <<-eow.strip_heredoc
- `static_cache_control` is deprecated and will be removed in Rails 5.1.
+ `config.static_cache_control` is deprecated and will be removed in Rails 5.1.
Please use
`config.public_file_server.headers = { 'Cache-Control' => '#{value}' }`
instead.
@@ -69,8 +69,8 @@ module Rails
def serve_static_files
ActiveSupport::Deprecation.warn <<-eow.strip_heredoc
- `serve_static_files` is deprecated and will be removed in Rails 5.1.
- Please use `public_file_server.enabled` instead.
+ `config.serve_static_files` is deprecated and will be removed in Rails 5.1.
+ Please use `config.public_file_server.enabled` instead.
eow
@public_file_server.enabled
@@ -78,8 +78,8 @@ module Rails
def serve_static_files=(value)
ActiveSupport::Deprecation.warn <<-eow.strip_heredoc
- `serve_static_files` is deprecated and will be removed in Rails 5.1.
- Please use `public_file_server.enabled = #{value}` instead.
+ `config.serve_static_files` is deprecated and will be removed in Rails 5.1.
+ Please use `config.public_file_server.enabled = #{value}` instead.
eow
@public_file_server.enabled = value
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index 4f1cc0703d..381e548730 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -34,22 +34,10 @@ module Rails
# handling: presumably their code is not threadsafe
middleware.use ::Rack::Lock
-
- elsif config.allow_concurrency == :unsafe
- # Do nothing, even if we know this is dangerous. This is the
- # historical behaviour for true.
-
- else
- # Default concurrency setting: enabled, but safe
-
- unless config.cache_classes && config.eager_load
- # Without cache_classes + eager_load, the load interlock
- # is required for proper operation
-
- middleware.use ::ActionDispatch::LoadInterlock
- end
end
+ middleware.use ::ActionDispatch::Executor, app.executor
+
middleware.use ::Rack::Runtime
middleware.use ::Rack::MethodOverride unless config.api_only
middleware.use ::ActionDispatch::RequestId
@@ -61,7 +49,7 @@ module Rails
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
unless config.cache_classes
- middleware.use ::ActionDispatch::Reloader, lambda { reload_dependencies? }
+ middleware.use ::ActionDispatch::Reloader, app.reloader
end
middleware.use ::ActionDispatch::Callbacks
@@ -83,10 +71,6 @@ module Rails
private
- def reload_dependencies?
- config.reload_classes_only_on_change != true || app.reloaders.map(&:updated?).any?
- end
-
def load_rack_cache
rack_cache = config.action_dispatch.rack_cache
return unless rack_cache
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 404e3c3e23..4e24640510 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -22,10 +22,10 @@ module Rails
initializer :add_builtin_route do |app|
if Rails.env.development?
app.routes.append do
- get '/rails/info/properties' => "rails/info#properties"
- get '/rails/info/routes' => "rails/info#routes"
- get '/rails/info' => "rails/info#index"
- get '/' => "rails/welcome#index"
+ get '/rails/info/properties' => "rails/info#properties", internal: true
+ get '/rails/info/routes' => "rails/info#routes", internal: true
+ get '/rails/info' => "rails/info#index", internal: true
+ get '/' => "rails/welcome#index", internal: true
end
end
end
@@ -38,16 +38,16 @@ module Rails
app.routes.define_mounted_helper(:main_app)
end
- initializer :add_to_prepare_blocks do
+ initializer :add_to_prepare_blocks do |app|
config.to_prepare_blocks.each do |block|
- ActionDispatch::Reloader.to_prepare(&block)
+ app.reloader.to_prepare(&block)
end
end
# This needs to happen before eager load so it happens
# in exactly the same point regardless of config.cache_classes
- initializer :run_prepare_callbacks do
- ActionDispatch::Reloader.prepare!
+ initializer :run_prepare_callbacks do |app|
+ app.reloader.prepare!
end
initializer :eager_load! do
@@ -62,13 +62,60 @@ module Rails
ActiveSupport.run_load_hooks(:after_initialize, self)
end
+ class MutexHook
+ def initialize(mutex = Mutex.new)
+ @mutex = mutex
+ end
+
+ def run
+ @mutex.lock
+ end
+
+ def complete(_state)
+ @mutex.unlock
+ end
+ end
+
+ module InterlockHook
+ def self.run
+ ActiveSupport::Dependencies.interlock.start_running
+ end
+
+ def self.complete(_state)
+ ActiveSupport::Dependencies.interlock.done_running
+ end
+ end
+
+ initializer :configure_executor_for_concurrency do |app|
+ if config.allow_concurrency == false
+ # User has explicitly opted out of concurrent request
+ # handling: presumably their code is not threadsafe
+
+ app.executor.register_hook(MutexHook.new, outer: true)
+
+ elsif config.allow_concurrency == :unsafe
+ # Do nothing, even if we know this is dangerous. This is the
+ # historical behavior for true.
+
+ else
+ # Default concurrency setting: enabled, but safe
+
+ unless config.cache_classes && config.eager_load
+ # Without cache_classes + eager_load, the load interlock
+ # is required for proper operation
+
+ app.executor.register_hook(InterlockHook, outer: true)
+ end
+ end
+ end
+
# Set routes reload after the finisher hook to ensure routes added in
# the hook are taken into account.
- initializer :set_routes_reloader_hook do
+ initializer :set_routes_reloader_hook do |app|
reloader = routes_reloader
reloader.execute_if_updated
self.reloaders << reloader
- ActionDispatch::Reloader.to_prepare do
+ app.reloader.to_run do
# We configure #execute rather than #execute_if_updated because if
# autoloaded constants are cleared we need to reload routes also in
# case any was used there, as in
@@ -78,18 +125,27 @@ module Rails
# This means routes are also reloaded if i18n is updated, which
# might not be necessary, but in order to be more precise we need
# some sort of reloaders dependency support, to be added.
+ require_unload_lock!
reloader.execute
end
end
# Set clearing dependencies after the finisher hook to ensure paths
# added in the hook are taken into account.
- initializer :set_clear_dependencies_hook, group: :all do
+ initializer :set_clear_dependencies_hook, group: :all do |app|
callback = lambda do
- ActiveSupport::Dependencies.interlock.attempt_unloading do
- ActiveSupport::DescendantsTracker.clear
- ActiveSupport::Dependencies.clear
+ ActiveSupport::DescendantsTracker.clear
+ ActiveSupport::Dependencies.clear
+ end
+
+ if config.cache_classes
+ app.reloader.check = lambda { false }
+ elsif config.reload_classes_only_on_change
+ app.reloader.check = lambda do
+ app.reloaders.map(&:updated?).any?
end
+ else
+ app.reloader.check = lambda { true }
end
if config.reload_classes_only_on_change
@@ -99,15 +155,19 @@ module Rails
# Prepend this callback to have autoloaded constants cleared before
# any other possible reloading, in case they need to autoload fresh
# constants.
- ActionDispatch::Reloader.to_prepare(prepend: true) do
+ app.reloader.to_run(prepend: true) do
# In addition to changes detected by the file watcher, if routes
# or i18n have been updated we also need to clear constants,
# that's why we run #execute rather than #execute_if_updated, this
# callback has to clear autoloaded constants after any update.
- reloader.execute
+ class_unload! do
+ reloader.execute
+ end
end
else
- ActionDispatch::Reloader.to_cleanup(&callback)
+ app.reloader.to_complete do
+ class_unload!(&callback)
+ end
end
end
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 0997414482..7a8f42fe94 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -1,4 +1,5 @@
require 'rails/code_statistics_calculator'
+require 'active_support/core_ext/enumerable'
class CodeStatistics #:nodoc:
@@ -9,6 +10,8 @@ class CodeStatistics #:nodoc:
'Job tests',
'Integration tests']
+ HEADERS = {lines: ' Lines', code_lines: ' LOC', classes: 'Classes', methods: 'Methods'}
+
def initialize(*pairs)
@pairs = pairs
@statistics = calculate_statistics
@@ -67,27 +70,37 @@ class CodeStatistics #:nodoc:
test_loc
end
+ def width_for(label)
+ [@statistics.values.sum {|s| s.send(label) }.to_s.size, HEADERS[label].length].max
+ end
+
def print_header
print_splitter
- puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |"
+ print '| Name '
+ HEADERS.each do |k, v|
+ print " | #{v.rjust(width_for(k))}"
+ end
+ puts ' | M/C | LOC/M |'
print_splitter
end
def print_splitter
- puts "+----------------------+--------+--------+---------+---------+-----+-------+"
+ print '+----------------------'
+ HEADERS.each_key do |k|
+ print "+#{'-' * (width_for(k) + 2)}"
+ end
+ puts '+-----+-------+'
end
def print_line(name, statistics)
m_over_c = (statistics.methods / statistics.classes) rescue m_over_c = 0
loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue loc_over_m = 0
- puts "| #{name.ljust(20)} " \
- "| #{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)} " \
- "| #{loc_over_m.to_s.rjust(5)} |"
+ print "| #{name.ljust(20)} "
+ HEADERS.each_key do |k|
+ print "| #{statistics.send(k).to_s.rjust(width_for(k))} "
+ end
+ puts "| #{m_over_c.to_s.rjust(3)} | #{loc_over_m.to_s.rjust(5)} |"
end
def print_code_test_stats
diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb
deleted file mode 100644
index f7753cbb83..0000000000
--- a/railties/lib/rails/command.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-require 'rails/commands/commands_tasks'
-
-module Rails
- class Command #:nodoc:
- attr_reader :argv
-
- def initialize(argv = [])
- @argv = argv
-
- @option_parser = build_option_parser
- @options = {}
- end
-
- def self.run(task_name, argv)
- command_name = command_name_for(task_name)
-
- if command = command_for(command_name)
- command.new(argv).run(command_name)
- else
- Rails::CommandsTasks.new(argv).run_command!(task_name)
- end
- end
-
- def run(command_name)
- parse_options_for(command_name)
- @option_parser.parse! @argv
-
- public_send(command_name)
- end
-
- def self.options_for(command_name, &options_to_parse)
- @@command_options[command_name] = options_to_parse
- end
-
- def self.set_banner(command_name, banner)
- options_for(command_name) { |opts, _| opts.banner = banner }
- end
-
- private
- @@commands = []
- @@command_options = {}
-
- def parse_options_for(command_name)
- @@command_options.fetch(command_name, proc {}).call(@option_parser, @options)
- end
-
- def build_option_parser
- OptionParser.new do |opts|
- opts.on('-h', '--help', 'Show this help.') do
- puts opts
- exit
- end
- end
- end
-
- def self.inherited(command)
- @@commands << command
- end
-
- def self.command_name_for(task_name)
- task_name.gsub(':', '_').to_sym
- end
-
- def self.command_for(command_name)
- @@commands.find do |command|
- command.public_instance_methods.include?(command_name)
- end
- end
- end
-end
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index 7627fcf5a0..5a66b78a92 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -13,7 +13,6 @@ aliases = {
command = ARGV.shift
command = aliases[command] || command
-require 'rails/command'
-require 'rails/commands/dev_cache'
+require 'rails/commands/commands_tasks'
-Rails::Command.run(command, ARGV)
+Rails::CommandsTasks.new(ARGV).run_command!(command)
diff --git a/railties/lib/rails/commands/dev_cache.rb b/railties/lib/rails/commands/dev_cache.rb
deleted file mode 100644
index ec96e8f630..0000000000
--- a/railties/lib/rails/commands/dev_cache.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'rails/command'
-
-module Rails
- module Commands
- # This is a wrapper around the Rails dev:cache command
- class DevCache < Command
- set_banner :dev_cache, 'Toggle development mode caching on/off'
- def dev_cache
- if File.exist? 'tmp/caching-dev.txt'
- File.delete 'tmp/caching-dev.txt'
- puts 'Development mode is no longer being cached.'
- else
- FileUtils.touch 'tmp/caching-dev.txt'
- puts 'Development mode is now being cached.'
- end
-
- FileUtils.touch 'tmp/restart.txt'
- end
- end
- end
-end
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index babb197ba1..f9c183ac86 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -2,6 +2,7 @@ require 'optparse'
options = { environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup }
code_or_file = nil
+command = 'bin/rails runner'
if ARGV.first.nil?
ARGV.push "-h"
@@ -34,7 +35,7 @@ ARGV.clone.options do |opts|
opts.separator ""
opts.separator "You can also use runner as a shebang line for your executables:"
opts.separator " -------------------------------------------------------------"
- opts.separator " #!/usr/bin/env #{File.expand_path($0)} runner"
+ opts.separator " #!/usr/bin/env #{File.expand_path(command)}"
opts.separator ""
opts.separator " Product.all.each { |p| p.price *= 2 ; p.save! }"
opts.separator " -------------------------------------------------------------"
@@ -52,17 +53,17 @@ Rails.application.require_environment!
Rails.application.load_runner
if code_or_file.nil?
- $stderr.puts "Run '#{$0} -h' for help."
+ $stderr.puts "Run '#{command} -h' for help."
exit 1
elsif File.exist?(code_or_file)
$0 = code_or_file
Kernel.load code_or_file
else
begin
- eval(code_or_file, binding, __FILE__, __LINE__)
- rescue SyntaxError,NameError => err
+ eval(code_or_file, binding, __FILE__, __LINE__)
+ rescue SyntaxError, NameError
$stderr.puts "Please specify a valid ruby command or the path of a script to run."
- $stderr.puts "Run '#{$0} -h' for help."
+ $stderr.puts "Run '#{command} -h' for help."
exit 1
end
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 27cbaf360a..7418dff18b 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -2,6 +2,7 @@ require 'fileutils'
require 'optparse'
require 'action_dispatch'
require 'rails'
+require 'rails/dev_caching'
module Rails
class Server < ::Rack::Server
@@ -92,20 +93,17 @@ module Rails
DoNotReverseLookup: true,
environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup,
daemonize: false,
- caching: false,
- pid: Options::DEFAULT_PID_PATH
+ caching: nil,
+ pid: Options::DEFAULT_PID_PATH,
+ restart_cmd: restart_command
})
end
private
def setup_dev_caching
- return unless options[:environment] == "development"
-
- if options[:caching] == false
- delete_cache_file
- elsif options[:caching]
- create_cache_file
+ if options[:environment] == "development"
+ Rails::DevCaching.enable_by_argument(options[:caching])
end
end
@@ -114,16 +112,6 @@ module Rails
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
puts "=> Run `rails server -h` for more startup options"
-
- puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
- end
-
- def create_cache_file
- FileUtils.touch("tmp/caching-dev.txt")
- end
-
- def delete_cache_file
- FileUtils.rm("tmp/caching-dev.txt") if File.exist?("tmp/caching-dev.txt")
end
def create_tmp_directories
@@ -143,5 +131,9 @@ module Rails
Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
end
end
+
+ def restart_command
+ "bin/rails server #{ARGV.join(' ')}"
+ end
end
end
diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb
index ac5836a588..9ad77e0a80 100644
--- a/railties/lib/rails/console/app.rb
+++ b/railties/lib/rails/console/app.rb
@@ -29,8 +29,7 @@ module Rails
# reloads the environment
def reload!(print=true)
puts "Reloading..." if print
- ActionDispatch::Reloader.cleanup!
- ActionDispatch::Reloader.prepare!
+ Rails.application.reloader.reload!
true
end
end
diff --git a/railties/lib/rails/dev_caching.rb b/railties/lib/rails/dev_caching.rb
new file mode 100644
index 0000000000..f2a53d6417
--- /dev/null
+++ b/railties/lib/rails/dev_caching.rb
@@ -0,0 +1,43 @@
+require 'fileutils'
+
+module Rails
+ module DevCaching # :nodoc:
+ class << self
+ FILE = 'tmp/caching-dev.txt'
+
+ def enable_by_file
+ FileUtils.mkdir_p('tmp')
+
+ if File.exist?(FILE)
+ delete_cache_file
+ puts 'Development mode is no longer being cached.'
+ else
+ create_cache_file
+ puts 'Development mode is now being cached.'
+ end
+
+ FileUtils.touch 'tmp/restart.txt'
+ FileUtils.rm_f('tmp/pids/server.pid')
+ end
+
+ def enable_by_argument(caching)
+ FileUtils.mkdir_p('tmp')
+
+ if caching
+ create_cache_file
+ elsif caching == false && File.exist?(FILE)
+ delete_cache_file
+ end
+ end
+
+ private
+ def create_cache_file
+ FileUtils.touch FILE
+ end
+
+ def delete_cache_file
+ File.delete FILE
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 5757d235d2..9701409755 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -219,7 +219,7 @@ module Rails
# The next thing that changes in isolated engines is the behavior of routes.
# Normally, when you namespace your controllers, you also need to namespace
# the related routes. With an isolated engine, the engine's namespace is
- # automatically applied, so you don't need to specify it explicity in your
+ # automatically applied, so you don't need to specify it explicitly in your
# routes:
#
# MyEngine::Engine.routes.draw do
diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb
index a6d87b78e4..7bbd9ef744 100644
--- a/railties/lib/rails/engine/commands.rb
+++ b/railties/lib/rails/engine/commands.rb
@@ -1,3 +1,5 @@
+require 'rails/engine/commands_tasks'
+
ARGV << '--help' if ARGV.empty?
aliases = {
@@ -9,35 +11,4 @@ aliases = {
command = ARGV.shift
command = aliases[command] || command
-require ENGINE_PATH
-engine = ::Rails::Engine.find(ENGINE_ROOT)
-
-case command
-when 'generate', 'destroy', 'test'
- require 'rails/generators'
- Rails::Generators.namespace = engine.railtie_namespace
- engine.load_generators
- require "rails/commands/#{command}"
-
-when '--version', '-v'
- ARGV.unshift '--version'
- require 'rails/commands/application'
-
-else
- puts "Error: Command not recognized" unless %w(-h --help).include?(command)
- puts <<-EOT
-Usage: rails COMMAND [ARGS]
-
-The common Rails commands available for engines are:
- generate Generate new code (short-cut alias: "g")
- destroy Undo code generated with "generate" (short-cut alias: "d")
- test Run tests (short-cut alias: "t")
-
-All commands can be run with -h for more information.
-
-If you want to run any commands that need to be run in context
-of the application, like `rails server` or `rails console`,
-you should do it from application's directory (typically test/dummy).
- EOT
- exit(1)
-end
+Rails::Engine::CommandsTasks.new(ARGV).run_command!(command)
diff --git a/railties/lib/rails/engine/commands_tasks.rb b/railties/lib/rails/engine/commands_tasks.rb
new file mode 100644
index 0000000000..fa3ee59b7d
--- /dev/null
+++ b/railties/lib/rails/engine/commands_tasks.rb
@@ -0,0 +1,116 @@
+require 'rails/commands/rake_proxy'
+
+module Rails
+ class Engine
+ class CommandsTasks # :nodoc:
+ include Rails::RakeProxy
+
+ attr_reader :argv
+
+ HELP_MESSAGE = <<-EOT
+Usage: rails COMMAND [ARGS]
+
+The common Rails commands available for engines are:
+ generate Generate new code (short-cut alias: "g")
+ destroy Undo code generated with "generate" (short-cut alias: "d")
+ test Run tests (short-cut alias: "t")
+
+All commands can be run with -h for more information.
+
+If you want to run any commands that need to be run in context
+of the application, like `rails server` or `rails console`,
+you should do it from application's directory (typically test/dummy).
+
+In addition to those commands, there are:
+ EOT
+
+ COMMAND_WHITELIST = %w(generate destroy version help test)
+
+ def initialize(argv)
+ @argv = argv
+ end
+
+ def run_command!(command)
+ command = parse_command(command)
+
+ if COMMAND_WHITELIST.include?(command)
+ send(command)
+ else
+ run_rake_task(command)
+ end
+ end
+
+ def generate
+ generate_or_destroy(:generate)
+ end
+
+ def destroy
+ generate_or_destroy(:destroy)
+ end
+
+ def test
+ require_command!("test")
+ end
+
+ def version
+ argv.unshift '--version'
+ require_command!("application")
+ end
+
+ def help
+ write_help_message
+ write_commands(formatted_rake_tasks)
+ end
+
+ private
+
+ def require_command!(command)
+ require "rails/commands/#{command}"
+ end
+
+ def generate_or_destroy(command)
+ load_generators
+ require_command!(command)
+ end
+
+ def load_generators
+ require 'rails/generators'
+ require ENGINE_PATH
+
+ engine = ::Rails::Engine.find(ENGINE_ROOT)
+ Rails::Generators.namespace = engine.railtie_namespace
+ engine.load_generators
+ end
+
+ def write_help_message
+ puts HELP_MESSAGE
+ end
+
+ def write_commands(commands)
+ width = commands.map { |name, _| name.size }.max || 10
+ commands.each { |command| printf(" %-#{width}s %s\n", *command) }
+ end
+
+ def parse_command(command)
+ case command
+ when '--version', '-v'
+ 'version'
+ when '--help', '-h'
+ 'help'
+ else
+ command
+ end
+ end
+
+ def rake_tasks
+ return @rake_tasks if defined?(@rake_tasks)
+
+ load_generators
+ Rake::TaskManager.record_task_metadata = true
+ Rake.application.init('rails')
+ Rake.application.load_rakefile
+ @rake_tasks = Rake.application.tasks.select(&:comment)
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 8cadbc3ddd..294d07446f 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -39,6 +39,7 @@ module Rails
paths.add "app", eager_load: true, glob: "{*,*/concerns}"
paths.add "app/assets", glob: "*"
paths.add "app/controllers", eager_load: true
+ paths.add "app/channels", eager_load: true, glob: "**/*_channel.rb"
paths.add "app/helpers", eager_load: true
paths.add "app/models", eager_load: true
paths.add "app/mailers", eager_load: true
diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb
index f4d5b660dc..9c49e0655a 100644
--- a/railties/lib/rails/gem_version.rb
+++ b/railties/lib/rails/gem_version.rb
@@ -6,9 +6,9 @@ module Rails
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta1.1"
+ 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 e3d79521e7..330bd7ec5d 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -105,7 +105,7 @@ module Rails
# Configure generators for API only applications. It basically hides
# everything that is usually browser related, such as assets and session
- # migration generators, and completely disable views, helpers and assets
+ # migration generators, and completely disable helpers and assets
# so generators such as scaffold won't create them.
def self.api_only!
hide_namespaces "assets", "helper", "css", "js"
@@ -116,6 +116,10 @@ module Rails
helper: false,
template_engine: nil
)
+
+ if ARGV.first == 'mailer'
+ options[:rails].merge!(template_engine: :erb)
+ end
end
# Remove the color from output.
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 9ca731347a..c947c062fa 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -1,5 +1,3 @@
-require 'open-uri'
-
module Rails
module Generators
module Actions
@@ -207,16 +205,22 @@ module Rails
in_root { run_ruby_script("bin/rails generate #{what} #{argument}", verbose: false) }
end
- # Runs the supplied rake task
+ # Runs the supplied rake task (invoked with 'rake ...')
#
# rake("db:migrate")
# rake("db:migrate", env: "production")
# rake("gems:install", sudo: true)
def rake(command, options={})
- log :rake, command
- env = options[:env] || ENV["RAILS_ENV"] || 'development'
- sudo = options[:sudo] && RbConfig::CONFIG['host_os'] !~ /mswin|mingw/ ? 'sudo ' : ''
- in_root { run("#{sudo}#{extify(:rake)} #{command} RAILS_ENV=#{env}", verbose: false) }
+ execute_command :rake, command, options
+ end
+
+ # Runs the supplied rake task (invoked with 'rails ...')
+ #
+ # rails("db:migrate")
+ # rails("db:migrate", env: "production")
+ # rails("gems:install", sudo: true)
+ def rails_command(command, options={})
+ execute_command :rails, command, options
end
# Just run the capify command in root
@@ -270,6 +274,16 @@ module Rails
end
end
+
+ # Runs the supplied command using either "rake ..." or "rails ..."
+ # based on the executor parameter provided.
+ def execute_command(executor, command, options={})
+ log executor, command
+ env = options[:env] || ENV["RAILS_ENV"] || 'development'
+ sudo = options[:sudo] && RbConfig::CONFIG['host_os'] !~ /mswin|mingw/ ? 'sudo ' : ''
+ in_root { run("#{sudo}#{extify(executor)} #{command} RAILS_ENV=#{env}", verbose: false) }
+ end
+
# Add an extension to the given name based on the platform.
def extify(name)
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb
index d664b07652..6c5b55466d 100644
--- a/railties/lib/rails/generators/actions/create_migration.rb
+++ b/railties/lib/rails/generators/actions/create_migration.rb
@@ -1,3 +1,4 @@
+require 'fileutils'
require 'thor/actions'
module Rails
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index c629459d95..f0a3289563 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -1,3 +1,4 @@
+require 'fileutils'
require 'digest/md5'
require 'active_support/core_ext/string/strip'
require 'rails/version' unless defined?(Rails::VERSION)
@@ -51,6 +52,9 @@ module Rails
class_option :skip_active_record, type: :boolean, aliases: '-O', default: false,
desc: 'Skip Active Record files'
+ class_option :skip_puma, type: :boolean, aliases: '-P', default: false,
+ desc: 'Skip Puma related files'
+
class_option :skip_action_cable, type: :boolean, aliases: '-C', default: false,
desc: 'Skip Action Cable files'
@@ -60,6 +64,9 @@ module Rails
class_option :skip_spring, type: :boolean, default: false,
desc: "Don't install Spring application preloader"
+ class_option :skip_listen, type: :boolean, default: false,
+ desc: "Don't generate configuration that depends on the listen gem"
+
class_option :skip_javascript, type: :boolean, aliases: '-J', default: false,
desc: 'Skip JavaScript files'
@@ -113,6 +120,7 @@ module Rails
def gemfile_entries
[rails_gemfile_entry,
database_gemfile_entry,
+ webserver_gemfile_entry,
assets_gemfile_entry,
javascript_gemfile_entry,
jbuilder_gemfile_entry,
@@ -171,6 +179,12 @@ module Rails
"Use #{options[:database]} as the database for Active Record"
end
+ def webserver_gemfile_entry
+ return [] if options[:skip_puma]
+ comment = 'Use Puma as the app server'
+ GemfileEntry.new('puma', '~> 3.0', comment)
+ end
+
def include_all_railties?
options.values_at(:skip_active_record, :skip_action_mailer, :skip_test, :skip_sprockets, :skip_action_cable).none?
end
@@ -282,7 +296,7 @@ module Rails
return [] if options[:skip_sprockets]
gems = []
- gems << GemfileEntry.version('sass-rails', '~> 5.0',
+ gems << GemfileEntry.github('sass-rails', 'rails/sass-rails', nil,
'Use SCSS for stylesheets')
gems << GemfileEntry.version('uglifier',
@@ -294,20 +308,15 @@ module Rails
def jbuilder_gemfile_entry
comment = 'Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder'
- GemfileEntry.new 'jbuilder', '~> 2.0', comment, {}, options[:api]
+ GemfileEntry.new 'jbuilder', '~> 2.5', comment, {}, options[:api]
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', nil, comment
- else
- GemfileEntry.version 'coffee-rails', '~> 4.1.0', comment
- end
+ GemfileEntry.github 'coffee-rails', 'rails/coffee-rails', nil, 'Use CoffeeScript for .coffee assets and views'
end
def javascript_gemfile_entry
- if options[:skip_javascript]
+ if options[:skip_javascript] || options[:skip_sprockets]
[]
else
gems = [coffee_gemfile_entry, javascript_runtime_gemfile_entry]
@@ -315,8 +324,8 @@ module Rails
"Use #{options[:javascript]} as the JavaScript library")
unless options[:skip_turbolinks]
- gems << GemfileEntry.version("turbolinks", nil,
- "Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks")
+ gems << GemfileEntry.version("turbolinks", "~> 5.x",
+ "Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks")
end
gems
@@ -342,10 +351,9 @@ module Rails
def cable_gemfile_entry
return [] if options[:skip_action_cable]
- comment = 'Action Cable dependencies for the Redis adapter'
+ comment = 'Use Redis adapter to run Action Cable in production'
gems = []
- gems << GemfileEntry.new("em-hiredis", '~> 0.3.0', comment)
- gems << GemfileEntry.new("redis", '~> 3.0', comment)
+ gems << GemfileEntry.new("redis", '~> 3.0', comment, {}, true)
gems
end
@@ -381,6 +389,14 @@ module Rails
!options[:skip_spring] && !options.dev? && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin")
end
+ def depend_on_listen?
+ !options[:skip_listen] && os_supports_listen_out_of_the_box?
+ end
+
+ def os_supports_listen_out_of_the_box?
+ RbConfig::CONFIG['host_os'] =~ /darwin|linux/
+ end
+
def run_bundle
bundle_command('install') if bundle_install?
end
diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
index bc249aa5e5..97f3657070 100644
--- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
@@ -9,6 +9,13 @@ module Erb # :nodoc:
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', class_path, filename_with_extensions('mailer', format))
+ template filename_with_extensions(:layout, format), layout_path
+ end
+ end
+
actions.each do |action|
@action = action
@@ -26,7 +33,7 @@ module Erb # :nodoc:
end
def file_name
- @_file_name ||= super.gsub(/\_mailer/i, '')
+ @_file_name ||= super.gsub(/_mailer/i, '')
end
end
end
diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb.tt b/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb.tt
new file mode 100644
index 0000000000..55f3675d49
--- /dev/null
+++ b/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb.tt
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <style>
+ /* Email styles need to be inline */
+ </style>
+ </head>
+
+ <body>
+ <%%= yield %>
+ </body>
+</html>
diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb.tt b/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb.tt
new file mode 100644
index 0000000000..6363733e6e
--- /dev/null
+++ b/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb.tt
@@ -0,0 +1 @@
+<%%= yield %>
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index efbf51ddfb..ee076eb711 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -26,6 +26,10 @@ module Rails
super
end
end
+
+ def js_template(source, destination)
+ template(source + '.js', destination + '.js')
+ end
end
protected
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index a4758857f2..448dce06af 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -79,6 +79,8 @@ module Rails
template "environment.rb"
template "secrets.yml"
template "cable.yml" unless options[:skip_action_cable]
+ template "puma.rb" unless options[:skip_puma]
+ template "spring.rb" if spring_install?
directory "environments"
directory "initializers"
@@ -88,21 +90,23 @@ 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')
+ action_cable_config_exist = File.exist?('config/cable.yml')
+ rack_cors_config_exist = File.exist?('config/initializers/cors.rb')
config
- unless callback_terminator_config_exist
- remove_file 'config/initializers/callback_terminator.rb'
- end
+ gsub_file 'config/environments/development.rb', /^(\s+)config\.file_watcher/, '\1# config.file_watcher'
unless cookie_serializer_config_exist
- gsub_file 'config/initializers/cookies_serializer.rb', /json/, 'marshal'
+ gsub_file 'config/initializers/cookies_serializer.rb', /json(?!,)/, 'marshal'
+ end
+
+ unless action_cable_config_exist
+ template 'config/cable.yml'
end
- unless active_record_belongs_to_required_by_default_config_exist
- remove_file 'config/initializers/active_record_belongs_to_required_by_default.rb'
+ unless rack_cors_config_exist
+ remove_file 'config/initializers/cors.rb'
end
end
@@ -275,9 +279,20 @@ module Rails
end
end
- def delete_app_views_if_api_option
+ def delete_application_layout_file_if_api_option
if options[:api]
- remove_dir 'app/views'
+ remove_file 'app/views/layouts/application.html.erb'
+ end
+ end
+
+ def delete_public_files_if_api_option
+ if options[:api]
+ remove_file 'public/404.html'
+ remove_file 'public/422.html'
+ remove_file 'public/500.html'
+ remove_file 'public/apple-touch-icon-precomposed.png'
+ remove_file 'public/apple-touch-icon.png'
+ remove_file 'public/favicon.ico'
end
end
@@ -307,18 +322,11 @@ 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 delete_action_cable_files_skipping_action_cable
if options[:skip_action_cable]
remove_file 'config/cable.yml'
- remove_file 'app/assets/javascripts/cable.coffee'
+ remove_file 'app/assets/javascripts/cable.js'
remove_dir 'app/channels'
- gsub_file 'app/views/layouts/application.html.erb', /action_cable_meta_tag/, '' unless options[:api]
end
end
@@ -326,8 +334,6 @@ module Rails
if options[:api]
remove_file 'config/initializers/session_store.rb'
remove_file 'config/initializers/cookies_serializer.rb'
- remove_file 'config/initializers/request_forgery_protection.rb'
- remove_file 'config/initializers/per_form_csrf_tokens.rb'
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index da5af4eefc..86143ca1f1 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -12,9 +12,6 @@ source 'https://rubygems.org'
<% end -%>
<% end -%>
-# Use Puma as the app server
-gem 'puma'
-
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
@@ -29,21 +26,27 @@ gem 'puma'
<% if RUBY_ENGINE == 'ruby' -%>
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
- gem 'byebug'
+ gem 'byebug', platform: :mri
end
group :development do
<%- unless options.api? -%>
- # Access an IRB console on exception pages or by using <%%= console %> in views
+ # Access an IRB console on exception pages or by using <%%= console %> anywhere in the code.
<%- if options.dev? || options.edge? -%>
gem 'web-console', github: 'rails/web-console'
<%- else -%>
- gem 'web-console', '~> 3.0'
+ gem 'web-console'
<%- end -%>
<%- end -%>
+<% if depend_on_listen? -%>
+ gem 'listen', '~> 3.0.5'
+<% 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'
+<% if depend_on_listen? -%>
+ gem 'spring-watcher-listen', '~> 2.0.0'
+<% end -%>
<% end -%>
end
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/README.md b/railties/lib/rails/generators/rails/app/templates/README.md
index 55e144da18..7db80e4ca1 100644
--- a/railties/lib/rails/generators/rails/app/templates/README.md
+++ 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.
diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile
index ba6b733dd2..e85f913914 100644
--- a/railties/lib/rails/generators/rails/app/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/app/templates/Rakefile
@@ -1,6 +1,6 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
-require File.expand_path('../config/application', __FILE__)
+require_relative 'config/application'
Rails.application.load_tasks
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
deleted file mode 100644
index 07934d026f..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
+++ /dev/null
@@ -1,11 +0,0 @@
-# Action Cable provides the framework to deal with WebSockets in Rails.
-# You can generate new channels where WebSocket features live using the rails generate channel command.
-#
-# Turn on the cable connection by removing the comments after the require statements (and ensure it's also on in config/routes.rb).
-#
-#= require action_cable
-#= require_self
-#= require_tree ./channels
-#
-# @App ||= {}
-# App.cable = ActionCable.createConsumer()
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js
new file mode 100644
index 0000000000..71ee1e66de
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js
@@ -0,0 +1,13 @@
+// Action Cable provides the framework to deal with WebSockets in Rails.
+// You can generate new channels where WebSocket features live using the rails generate channel command.
+//
+//= require action_cable
+//= require_self
+//= require_tree ./channels
+
+(function() {
+ this.App || (this.App = {});
+
+ App.cable = ActionCable.createConsumer();
+
+}).call(this);
diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb
index d56fa30f4d..d672697283 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb
@@ -1,4 +1,3 @@
-# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb
index b4f41389ad..0ff5442f47 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb
@@ -1,4 +1,3 @@
-# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
module ApplicationCable
class Connection < ActionCable::Connection::Base
end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt
index f726fd6305..413354186d 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt
@@ -1,7 +1,5 @@
class ApplicationController < ActionController::<%= options[:api] ? "API" : "Base" %>
<%- unless options[:api] -%>
- # Prevent CSRF attacks by raising an exception.
- # For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
<%- end -%>
end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
index 68b5c051b2..d51f79bd49 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
@@ -3,16 +3,13 @@
<head>
<title><%= camelized %></title>
<%%= csrf_meta_tags %>
- <%- unless options[:skip_action_cable] -%>
- <%%= action_cable_meta_tag %>
- <%- end -%>
<%- if options[:skip_javascript] -%>
<%%= stylesheet_link_tag 'application', media: 'all' %>
<%- else -%>
<%- if gemfile_entries.any? { |m| m.name == 'turbolinks' } -%>
- <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
- <%%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
+ <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
+ <%%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<%- else -%>
<%%= stylesheet_link_tag 'application', media: 'all' %>
<%%= javascript_include_tag 'application' %>
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/rails b/railties/lib/rails/generators/rails/app/templates/bin/rails
index 80ec8080ab..513a2e0183 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', __dir__)
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 df88bfd3bc..acae810c1a 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup
@@ -15,7 +15,7 @@ chdir APP_ROOT do
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
- system('bundle check') or system!('bundle install')
+ system('bundle check') || system!('bundle install')
# puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml')
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update b/railties/lib/rails/generators/rails/app/templates/bin/update
index c6ed3ae64b..770a605fed 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/update
+++ b/railties/lib/rails/generators/rails/app/templates/bin/update
@@ -15,7 +15,7 @@ chdir APP_ROOT do
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
- system 'bundle check' or system! 'bundle install'
+ system('bundle check') || system!('bundle install')
puts "\n== Updating database =="
system! 'bin/rails db:migrate'
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru
new file mode 100644
index 0000000000..f7ba0b527b
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config.ru
@@ -0,0 +1,5 @@
+# This file is used by Rack-based servers to start the application.
+
+require_relative 'config/environment'
+
+run Rails.application
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru.tt b/railties/lib/rails/generators/rails/app/templates/config.ru.tt
deleted file mode 100644
index 70556fcc99..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/config.ru.tt
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file is used by Rack-based servers to start the application.
-
-require ::File.expand_path('../config/environment', __FILE__)
-<%- unless options[:skip_action_cable] -%>
-
-# Action Cable uses EventMachine which requires that all classes are loaded in advance
-Rails.application.eager_load!
-require 'action_cable/process/logging'
-<%- end -%>
-
-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 cb83364360..c0a0bd0a3e 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../boot', __FILE__)
+require_relative 'boot'
<% if include_all_railties? -%>
require 'rails/all'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
index 6b750f00b1..30f5120df6 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
@@ -1,3 +1,3 @@
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require 'bundler/setup' # Set up gems listed in the Gemfile.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/cable.yml
index 004adb7b3c..0bbde6f74f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/cable.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/cable.yml
@@ -1,12 +1,9 @@
-# Action Cable uses Redis by default to administer connections, channels, and sending/receiving messages over the WebSocket.
-production:
- adapter: redis
- url: redis://localhost:6379/1
-
development:
- adapter: redis
- url: redis://localhost:6379/2
+ adapter: async
test:
+ adapter: async
+
+production:
adapter: redis
- url: redis://localhost:6379/3
+ url: redis://localhost:6379/1
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml
index 34fc0e3465..917b52e535 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml
@@ -8,6 +8,7 @@
#
default: &default
adapter: frontbase
+ pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
host: localhost
username: <%= app_name %>
password: ''
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
index 187ff01bac..d40117a27f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
@@ -34,6 +34,7 @@
#
default: &default
adapter: ibm_db
+ pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: db2inst1
password:
#schema: db2inst1
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml
index db0a429753..563be77710 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml
@@ -38,6 +38,7 @@
default: &default
adapter: jdbc
+ pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: <%= app_name %>
password:
driver:
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 5ca549a8c8..a2b2a64ba6 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
@@ -1,4 +1,4 @@
-# MySQL. Versions 4.1 and 5.0 are recommended.
+# MySQL. Versions 5.0 and up are supported.
#
# Install the MySQL driver:
# gem install activerecord-jdbcmysql-adapter
@@ -11,6 +11,7 @@
#
default: &default
adapter: mysql
+ pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
password:
host: localhost
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
index 9e99264d33..70df04079d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
@@ -1,4 +1,4 @@
-# PostgreSQL. Versions 8.2 and up are supported.
+# PostgreSQL. Versions 9.1 and up are supported.
#
# Configure Using Gemfile
# gem 'activerecord-jdbcpostgresql-adapter'
@@ -6,6 +6,7 @@
default: &default
adapter: postgresql
encoding: unicode
+ pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml
index 28c36eb82f..371415e6a8 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml
@@ -6,6 +6,7 @@
#
default: &default
adapter: sqlite3
+ pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
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 119c2fe2c3..d987cf303b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
@@ -1,4 +1,4 @@
-# MySQL. Versions 5.0+ are recommended.
+# MySQL. Versions 5.0 and up are supported.
#
# Install the MySQL driver
# gem install mysql2
@@ -12,7 +12,7 @@
default: &default
adapter: mysql2
encoding: utf8
- pool: 5
+ pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
password:
<% if mysql_socket -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
index 9aedcc15cb..d2499ea4fb 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
@@ -18,6 +18,7 @@
#
default: &default
adapter: oracle
+ pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: <%= app_name %>
password:
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
index feb25bbc6b..bd5c0b10f6 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
@@ -1,4 +1,4 @@
-# PostgreSQL. Versions 8.2 and up are supported.
+# PostgreSQL. Versions 9.1 and up are supported.
#
# Install the pg driver:
# gem install pg
@@ -19,7 +19,7 @@ default: &default
encoding: unicode
# For details on connection pooling, see rails configuration guide
# http://guides.rubyonrails.org/configuring.html#database-pooling
- pool: 5
+ pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
index 1c1a37ca8d..9510568124 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
@@ -6,7 +6,7 @@
#
default: &default
adapter: sqlite3
- pool: 5
+ pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
development:
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
index 30b0df34a8..c223d6bc62 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
@@ -25,6 +25,7 @@
default: &default
adapter: sqlserver
encoding: utf8
+ pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
reconnect: false
username: <%= app_name %>
password:
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb
index ee8d90dc65..426333bb46 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb
@@ -1,5 +1,5 @@
# Load the Rails application.
-require File.expand_path('../application', __FILE__)
+require_relative 'application'
# Initialize the Rails application.
Rails.application.initialize!
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 fd41372d9c..f3ccf95045 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
@@ -15,18 +15,22 @@ Rails.application.configure do
# Enable/disable caching. By default caching is disabled.
if Rails.root.join('tmp/caching-dev.txt').exist?
config.action_controller.perform_caching = true
+
config.cache_store = :memory_store
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=172800'
}
else
config.action_controller.perform_caching = false
+
config.cache_store = :null_store
end
<%- unless options.skip_action_mailer? -%>
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
+
+ config.action_mailer.perform_caching = false
<%- end -%>
# Print deprecation notices to the Rails logger.
@@ -43,14 +47,8 @@ Rails.application.configure do
# number of complex assets.
config.assets.debug = true
- # Asset digests allow you to set far-future HTTP expiration dates on all assets,
- # yet still be able to expire them through the digest params.
- config.assets.digest = true
-
- # Adds additional error checking when serving assets at runtime.
- # Checks for improperly declared sprockets dependencies.
- # Raises helpful error messages.
- config.assets.raise_runtime_errors = true
+ # Suppress logger output for asset requests.
+ config.assets.quiet = true
<%- end -%>
# Raises error for missing translations
@@ -58,5 +56,5 @@ Rails.application.configure do
# Use an evented file watcher to asynchronously detect changes in source code,
# routes, locales, etc. This feature depends on the listen gem.
- # config.file_watcher = ActiveSupport::EventedFileUpdateChecker
+ <%= '# ' unless depend_on_listen? %>config.file_watcher = ActiveSupport::EventedFileUpdateChecker
end
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 82509f5ef5..363af05459 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
@@ -26,10 +26,6 @@ Rails.application.configure do
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
- # Asset digests allow you to set far-future HTTP expiration dates on all assets,
- # yet still be able to expire them through the digest params.
- config.assets.digest = true
-
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
<%- end -%>
@@ -41,7 +37,8 @@ Rails.application.configure do
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
<%- unless options[:skip_action_cable] -%>
- # Action Cable endpoint configuration
+ # Mount Action Cable outside main process or domain
+ # config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable'
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
<%- end -%>
@@ -56,10 +53,6 @@ Rails.application.configure do
# Prepend all log lines with the following tags.
config.log_tags = [ :request_id ]
- # Use a different logger for distributed setups.
- # require 'syslog/logger'
- # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
-
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
@@ -67,6 +60,7 @@ Rails.application.configure do
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "<%= app_name %>_#{Rails.env}"
<%- unless options.skip_action_mailer? -%>
+ config.action_mailer.perform_caching = false
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
@@ -82,6 +76,16 @@ Rails.application.configure do
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
+
+ # Use a different logger for distributed setups.
+ # require 'syslog/logger'
+ # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
+
+ if ENV["RAILS_LOG_TO_STDOUT"].present?
+ logger = ActiveSupport::Logger.new(STDOUT)
+ logger.formatter = config.log_formatter
+ config.logger = ActiveSupport::TaggedLogging.new(logger)
+ end
<%- unless options.skip_active_record? -%>
# Do not dump schema after migrations.
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 8133917591..42fee3b036 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
@@ -28,6 +28,7 @@ Rails.application.configure do
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
<%- unless options.skip_action_mailer? -%>
+ config.action_mailer.perform_caching = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
@@ -35,9 +36,6 @@ Rails.application.configure do
config.action_mailer.delivery_method = :test
<%- end -%>
- # Randomize the order test cases are executed.
- config.active_support.test_order = :random
-
# Print deprecation notices to the stderr.
config.active_support.deprecation = :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
deleted file mode 100644
index f613b40f80..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# Require `belongs_to` associations by default. This is a new Rails 5.0
-# default, so it is introduced as a configuration option to ensure that apps
-# made on earlier versions of Rails are not affected when upgrading.
-Rails.application.config.active_record.belongs_to_required_by_default = true
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
deleted file mode 100644
index 649e82280e..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# Do not halt callback chains when a callback returns false. This is a new
-# Rails 5.0 default, so it is introduced as a configuration option to ensure
-# that apps made with earlier versions of Rails are not affected when upgrading.
-ActiveSupport.halt_callback_chains_on_return_false = false
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt
new file mode 100644
index 0000000000..991963b65e
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt
@@ -0,0 +1,34 @@
+# Be sure to restart your server when you modify this file.
+#
+# This file contains migration options to ease your Rails 5.0 upgrade.
+#
+<%- if options[:update] -%>
+# Once upgraded flip defaults one by one to migrate to the new default.
+#
+<%- end -%>
+# Read the Rails 5.0 release notes for more info on each option.
+<%- unless options[:api] -%>
+
+# Enable per-form CSRF tokens. Previous versions had false.
+Rails.application.config.action_controller.per_form_csrf_tokens = <%= options[:update] ? false : true %>
+
+# Enable origin-checking CSRF mitigation. Previous versions had false.
+Rails.application.config.action_controller.forgery_protection_origin_check = <%= options[:update] ? false : true %>
+<%- end -%>
+
+# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
+# Previous versions had false.
+ActiveSupport.to_time_preserves_timezone = <%= options[:update] ? false : true %>
+<%- unless options[:skip_active_record] -%>
+
+# Require `belongs_to` associations by default. Previous versions had false.
+Rails.application.config.active_record.belongs_to_required_by_default = <%= options[:update] ? false : true %>
+<%- end -%>
+
+# Do not halt callback chains when a callback returns false. Previous versions had true.
+ActiveSupport.halt_callback_chains_on_return_false = <%= options[:update] ? true : false %>
+<%- unless options[:update] -%>
+
+# Configure SSL options to enable HSTS with subdomains. Previous versions had false.
+Rails.application.config.ssl_options = { hsts: { subdomains: true } }
+<%- end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb
deleted file mode 100644
index 1f569dedfd..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/per_form_csrf_tokens.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# Enable per-form CSRF tokens.
-Rails.application.config.action_controller.per_form_csrf_tokens = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb
deleted file mode 100644
index 3eab78a885..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/request_forgery_protection.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# Enable origin-checking CSRF mitigation.
-Rails.application.config.action_controller.forgery_protection_origin_check = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb b/railties/lib/rails/generators/rails/app/templates/config/puma.rb
new file mode 100644
index 0000000000..c7f311f811
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb
@@ -0,0 +1,47 @@
+# Puma can serve each request in a thread from an internal thread pool.
+# The `threads` method setting takes two numbers a minimum and maximum.
+# Any libraries that use thread pools should be configured to match
+# the maximum value specified for Puma. Default is set to 5 threads for minimum
+# and maximum, this matches the default thread size of Active Record.
+#
+threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
+threads threads_count, threads_count
+
+# Specifies the `port` that Puma will listen on to receive requests, default is 3000.
+#
+port ENV.fetch("PORT") { 3000 }
+
+# Specifies the `environment` that Puma will run in.
+#
+environment ENV.fetch("RAILS_ENV") { "development" }
+
+# Specifies the number of `workers` to boot in clustered mode.
+# Workers are forked webserver processes. If using threads and workers together
+# the concurrency of the application would be max `threads` * `workers`.
+# Workers do not work on JRuby or Windows (both of which do not support
+# processes).
+#
+# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
+
+# Use the `preload_app!` method when specifying a `workers` number.
+# This directive tells Puma to first boot the application and load code
+# before forking the application. This takes advantage of Copy On Write
+# process behavior so workers use less memory. If you use this option
+# you need to make sure to reconnect any threads in the `on_worker_boot`
+# block.
+#
+# preload_app!
+
+# The code in the `on_worker_boot` will be called if you are using
+# clustered mode by specifying a number of `workers`. After each worker
+# process is booted this block will be run, if you are using `preload_app!`
+# option you will want to use this block to reconnect to any threads
+# or connections that may have been created at application boot, Ruby
+# cannot share connections between processes.
+#
+# on_worker_boot do
+# ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
+# end
+
+# Allow puma to be restarted by `rails restart` command.
+plugin :tmp_restart
diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
index 8293c8a483..787824f888 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -1,6 +1,3 @@
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
-
- # Serve websocket cable requests in-process
- # mount ActionCable.server => '/cable'
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
index cdea2fd060..8e995a5df1 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
@@ -10,6 +10,13 @@
# Make sure the secrets in this file are kept private
# if you're sharing your code publicly.
+# Shared secrets are available across all environments.
+
+shared:
+ api_key: 123
+
+# Environmental secrets are only available for that specific environment.
+
development:
secret_key_base: <%= app_secret %>
@@ -18,5 +25,6 @@ test:
# Do not keep production secrets in the repository,
# instead read values from the environment.
+
production:
secret_key_base: <%%= ENV["SECRET_KEY_BASE"] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/spring.rb b/railties/lib/rails/generators/rails/app/templates/config/spring.rb
new file mode 100644
index 0000000000..c9119b40c0
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/spring.rb
@@ -0,0 +1,6 @@
+%w(
+ .ruby-version
+ .rbenv-vars
+ tmp/restart.txt
+ tmp/caching-dev.txt
+).each { |path| Spring.watch(path) }
diff --git a/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon-precomposed.png b/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon-precomposed.png
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon-precomposed.png
diff --git a/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon.png b/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon.png
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon.png
diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb
index 0a4c509a31..6c583e5811 100644
--- a/railties/lib/rails/generators/rails/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb
@@ -19,8 +19,7 @@ module Rails
end
end
- hook_for :template_engine, :test_framework
- hook_for :helper, :assets, hide: true
+ hook_for :template_engine, :test_framework, :helper, :assets
private
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 0c7a73a54e..56efd35a95 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -90,6 +90,7 @@ task default: :test
opts[:force] = force
opts[:skip_bundle] = true
opts[:api] = options.api?
+ opts[:skip_listen] = true
invoke Rails::Generators::AppGenerator,
[ File.expand_path(dummy_path, destination_root) ], opts
@@ -259,6 +260,12 @@ task default: :test
public_task :apply_rails_template, :run_bundle
+ def run_after_bundle_callbacks
+ @after_bundle_callbacks.each do |callback|
+ callback.call
+ end
+ end
+
def name
@name ||= begin
# same as ActiveSupport::Inflector#underscore except not replacing '-'
@@ -281,10 +288,6 @@ task default: :test
protected
- def app_templates_dir
- "../../app/templates"
- end
-
def create_dummy_app(path = nil)
dummy_path(path) if path
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
index f1943644e4..383d2fb2d1 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
@@ -25,5 +25,5 @@ load 'rails/tasks/statistics.rake'
<% unless options[:skip_gemspec] -%>
-Bundler::GemHelper.install_tasks
+require 'bundler/gem_tasks'
<% end %>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt
index 7fe4e5034d..abbacd9bec 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt
@@ -1,5 +1,6 @@
<%= wrap_in_modules <<-rb.strip_heredoc
class ApplicationController < ActionController::#{api? ? "API" : "Base"}
+ #{ api? ? '# ' : '' }protect_from_forgery with: :exception
end
rb
%>
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 3edaac35c9..56e7925c6b 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
@@ -1,4 +1,5 @@
-# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
+# This command will automatically be run when you run "rails" with Rails gems
+# installed from the root of your application.
ENGINE_ROOT = File.expand_path('../..', __FILE__)
ENGINE_PATH = File.expand_path('../../lib/<%= namespaced_name -%>/engine', __FILE__)
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 b1038c839e..d03b1be878 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../boot', __FILE__)
+require_relative 'boot'
<% if include_all_railties? -%>
require 'rails/all'
@@ -6,10 +6,12 @@ require 'rails/all'
# Pick the frameworks you want:
<%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie"
-<%= comment_if :skip_action_mailer %>require "action_mailer/railtie"
require "action_view/railtie"
-<%= comment_if :skip_sprockets %>require "sprockets/railtie"
+<%= comment_if :skip_action_mailer %>require "action_mailer/railtie"
+require "active_job/railtie"
+<%= comment_if :skip_action_cable %>require "action_cable/engine"
<%= comment_if :skip_test %>require "rails/test_unit/railtie"
+<%= comment_if :skip_sprockets %>require "sprockets/railtie"
<% end -%>
Bundler.require(*Rails.groups)
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb
index 6266cfc509..c9aef85d40 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb
@@ -1,5 +1,5 @@
# Set up gems listed in the Gemfile.
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
-$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
+$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)
diff --git a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
index 79f8b7f96f..cd4f3de38d 100644
--- a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
+++ b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
@@ -1,13 +1,13 @@
body {
background-color: #fff;
color: #333;
+ margin: 33px;
}
body, p, ol, ul, td {
font-family: verdana, arial, helvetica, sans-serif;
font-size: 13px;
line-height: 18px;
- margin: 33px;
}
pre {
@@ -34,9 +34,7 @@ th {
}
td {
- padding-bottom: 7px;
- padding-left: 5px;
- padding-right: 5px;
+ padding: 0 5px 7px;
}
div.field,
@@ -57,8 +55,7 @@ div.actions {
#error_explanation {
width: 450px;
border: 2px solid red;
- padding: 7px;
- padding-bottom: 0;
+ padding: 7px 7px 0;
margin-bottom: 20px;
background-color: #f0f0f0;
}
@@ -68,8 +65,7 @@ div.actions {
font-weight: bold;
padding: 5px 5px 5px 15px;
font-size: 12px;
- margin: -7px;
- margin-bottom: 0;
+ margin: -7px -7px 0;
background-color: #c00;
color: #fff;
}
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 343c8a3949..76a0b79654 100644
--- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
@@ -19,7 +19,7 @@ module TestUnit # :nodoc:
protected
def file_name
- @_file_name ||= super.gsub(/\_mailer/i, '')
+ @_file_name ||= super.gsub(/_mailer/i, '')
end
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 2656767eb4..0681780c97 100644
--- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
+++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
@@ -6,7 +6,7 @@
<%- if attribute.password_digest? -%>
password_digest: <%%= BCrypt::Password.create('secret') %>
<%- elsif attribute.reference? -%>
- <%= yaml_key_value(attribute.column_name.sub(/_id$/, ''), attribute.default) %>
+ <%= yaml_key_value(attribute.column_name.sub(/_id$/, ''), attribute.default || name) %>
<%- else -%>
<%= yaml_key_value(attribute.column_name, attribute.default) %>
<%- end -%>
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
index 0d18478043..c469c188e6 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
@@ -11,31 +11,31 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe
end
test "should get index" do
- get <%= index_helper %>_url
+ get <%= index_helper %>_url, as: :json
assert_response :success
end
test "should create <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count') do
- post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
+ post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }, as: :json
end
assert_response 201
end
test "should show <%= singular_table_name %>" do
- get <%= show_helper %>
+ get <%= show_helper %>, as: :json
assert_response :success
end
test "should update <%= singular_table_name %>" do
- patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
+ patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }, as: :json
assert_response 200
end
test "should destroy <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count', -1) do
- delete <%= show_helper %>
+ delete <%= show_helper %>, as: :json
end
assert_response 204
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 0e6bef12fc..c33375b7b4 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
@@ -25,7 +25,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe
post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
end
- assert_redirected_to <%= singular_table_name %>_path(<%= class_name %>.last)
+ assert_redirected_to <%= singular_table_name %>_url(<%= class_name %>.last)
end
test "should show <%= singular_table_name %>" do
@@ -40,7 +40,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe
test "should update <%= singular_table_name %>" do
patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
- assert_redirected_to <%= singular_table_name %>_path(<%= "@#{singular_table_name}" %>)
+ assert_redirected_to <%= singular_table_name %>_url(<%= "@#{singular_table_name}" %>)
end
test "should destroy <%= singular_table_name %>" do
@@ -48,7 +48,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe
delete <%= show_helper %>
end
- assert_redirected_to <%= index_helper %>_path
+ assert_redirected_to <%= index_helper %>_url
end
end
<% end -%>
diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb
index 12676b18bc..b63d3a58d2 100644
--- a/railties/lib/rails/rack/logger.rb
+++ b/railties/lib/rails/rack/logger.rb
@@ -30,12 +30,6 @@ module Rails
protected
def call_app(request, env)
- # Put some space between requests in development logs.
- if development?
- logger.debug ''
- logger.debug ''
- end
-
instrumenter = ActiveSupport::Notifications.instrumenter
instrumenter.start 'request.action_dispatch', request: request
logger.info { started_request_message(request) }
@@ -78,10 +72,6 @@ module Rails
instrumenter.finish 'request.action_dispatch', request: request
end
- def development?
- Rails.env.development?
- end
-
def logger
Rails.logger
end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 8c24d1d56d..492c519222 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -1,38 +1,37 @@
require 'rails/initializable'
-require 'rails/configuration'
require 'active_support/inflector'
require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/module/delegation'
module Rails
- # Railtie is the core of the Rails framework and provides several hooks to extend
- # Rails and/or modify the initialization process.
+ # <tt>Rails::Railtie</tt> is the core of the Rails framework and provides
+ # several hooks to extend Rails and/or modify the initialization process.
#
- # Every major component of Rails (Action Mailer, Action Controller,
- # Action View and Active Record) is a Railtie. Each of
- # them is responsible for their own initialization. This makes Rails itself
- # absent of any component hooks, allowing other components to be used in
- # place of any of the Rails defaults.
+ # Every major component of Rails (Action Mailer, Action Controller, Active
+ # Record, etc.) implements a railtie. Each of them is responsible for their
+ # own initialization. This makes Rails itself absent of any component hooks,
+ # allowing other components to be used in place of any of the Rails defaults.
#
- # Developing a Rails extension does _not_ require any implementation of
- # Railtie, but if you need to interact with the Rails framework during
- # or after boot, then Railtie is needed.
+ # Developing a Rails extension does _not_ require implementing a railtie, but
+ # if you need to interact with the Rails framework during or after boot, then
+ # a railtie is needed.
#
- # For example, an extension doing any of the following would require Railtie:
+ # For example, an extension doing any of the following would need a railtie:
#
# * creating initializers
# * configuring a Rails framework for the application, like setting a generator
# * adding <tt>config.*</tt> keys to the environment
- # * setting up a subscriber with ActiveSupport::Notifications
- # * adding rake tasks
+ # * setting up a subscriber with <tt>ActiveSupport::Notifications</tt>
+ # * adding Rake tasks
#
- # == Creating your Railtie
+ # == Creating a Railtie
#
- # To extend Rails using Railtie, create a Railtie class which inherits
- # from Rails::Railtie within your extension's namespace. This class must be
- # loaded during the Rails boot process.
+ # To extend Rails using a railtie, create a subclass of <tt>Rails::Railtie</tt>.
+ # This class must be loaded during the Rails boot process, and is conventionally
+ # called <tt>MyNamespace::Railtie</tt>.
#
- # The following example demonstrates an extension which can be used with or without Rails.
+ # The following example demonstrates an extension which can be used with or
+ # without Rails.
#
# # lib/my_gem/railtie.rb
# module MyGem
@@ -45,8 +44,8 @@ module Rails
#
# == Initializers
#
- # To add an initialization step from your Railtie to Rails boot process, you just need
- # to create an initializer block:
+ # To add an initialization step to the Rails boot process from your railtie, just
+ # define the initialization code with the +initializer+ macro:
#
# class MyRailtie < Rails::Railtie
# initializer "my_railtie.configure_rails_initialization" do
@@ -55,7 +54,7 @@ module Rails
# end
#
# If specified, the block can also receive the application object, in case you
- # need to access some application specific configuration, like middleware:
+ # need to access some application-specific configuration, like middleware:
#
# class MyRailtie < Rails::Railtie
# initializer "my_railtie.configure_rails_initialization" do |app|
@@ -63,56 +62,56 @@ module Rails
# end
# end
#
- # Finally, you can also pass <tt>:before</tt> and <tt>:after</tt> as option to initializer,
- # in case you want to couple it with a specific step in the initialization process.
+ # Finally, you can also pass <tt>:before</tt> and <tt>:after</tt> as options to
+ # +initializer+, in case you want to couple it with a specific step in the
+ # initialization process.
#
# == Configuration
#
- # Inside the Railtie class, you can access a config object which contains configuration
- # shared by all railties and the application:
+ # Railties can access a config object which contains configuration shared by all
+ # railties and the application:
#
# class MyRailtie < Rails::Railtie
# # Customize the ORM
# config.app_generators.orm :my_railtie_orm
#
# # Add a to_prepare block which is executed once in production
- # # and before each request in development
+ # # and before each request in development.
# config.to_prepare do
# MyRailtie.setup!
# end
# end
#
- # == Loading rake tasks and generators
+ # == Loading Rake Tasks and Generators
#
- # If your railtie has rake tasks, you can tell Rails to load them through the method
- # rake_tasks:
+ # If your railtie has Rake tasks, you can tell Rails to load them through the method
+ # +rake_tasks+:
#
# class MyRailtie < Rails::Railtie
# rake_tasks do
- # load "path/to/my_railtie.tasks"
+ # load 'path/to/my_railtie.tasks'
# end
# end
#
# 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
+ # your generators at a different location, you can specify in your railtie a block which
# will load them during normal generators lookup:
#
# class MyRailtie < Rails::Railtie
# generators do
- # require "path/to/my_railtie_generator"
+ # require 'path/to/my_railtie_generator'
# end
# end
#
# == Application and Engine
#
- # A Rails::Engine is nothing more than a Railtie with some initializers already set.
- # And since Rails::Application is an engine, the same configuration described here
- # can be used in both.
+ # An engine is nothing more than a railtie with some initializers already set. And since
+ # <tt>Rails::Application</tt> is an engine, the same configuration described here can be
+ # used in both.
#
# Be sure to look at the documentation of those specific classes for more information.
- #
class Railtie
- autoload :Configuration, "rails/railtie/configuration"
+ autoload :Configuration, 'rails/railtie/configuration'
include Initializable
@@ -183,7 +182,7 @@ module Rails
end
protected
- def generate_railtie_name(string)
+ def generate_railtie_name(string) #:nodoc:
ActiveSupport::Inflector.underscore(string).tr("/", "_")
end
@@ -200,21 +199,24 @@ module Rails
delegate :railtie_name, to: :class
- def initialize
+ def initialize #:nodoc:
if self.class.abstract_railtie?
raise "#{self.class.name} is abstract, you cannot instantiate it directly."
end
end
- def configure(&block)
+ def configure(&block) #:nodoc:
instance_eval(&block)
end
+ # This is used to create the <tt>config</tt> object on Railties, an instance of
+ # Railtie::Configuration, that is used by Railties and Application to store
+ # related configuration.
def config
@config ||= Railtie::Configuration.new
end
- def railtie_namespace
+ def railtie_namespace #:nodoc:
@railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:railtie_namespace) }
end
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index d60eaf6f4f..d3e33584d7 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -3,6 +3,7 @@ require 'rake'
# Load Rails Rakefile extensions
%w(
annotations
+ dev
framework
initializers
log
diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake
new file mode 100644
index 0000000000..d2ceaacc0c
--- /dev/null
+++ b/railties/lib/rails/tasks/dev.rake
@@ -0,0 +1,8 @@
+require 'rails/dev_caching'
+
+namespace :dev do
+ desc 'Toggle development mode caching on/off'
+ task :cache do
+ Rails::DevCaching.enable_by_file
+ end
+end
diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake
index c51524f8f6..e678103f63 100644
--- a/railties/lib/rails/tasks/engine.rake
+++ b/railties/lib/rails/tasks/engine.rake
@@ -4,8 +4,8 @@ task "load_app" do
end
task :environment => "app:environment"
- if !defined?(ENGINE_PATH) || !ENGINE_PATH
- ENGINE_PATH = find_engine_path(APP_RAKEFILE)
+ if !defined?(ENGINE_ROOT) || !ENGINE_ROOT
+ ENGINE_ROOT = find_engine_path(APP_RAKEFILE)
end
end
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 904b9d9ad6..51d9daaaa9 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -1,4 +1,6 @@
-namespace :rails do
+require 'active_support/deprecation'
+
+namespace :app do
desc "Update configs and some other initially generated files (or use just update:configs or update:bin)"
task update: [ "update:configs", "update:bin" ]
@@ -24,12 +26,12 @@ namespace :rails do
default_templates.each do |type, names|
local_template_type_dir = File.join(project_templates, type)
- FileUtils.mkdir_p local_template_type_dir
+ mkdir_p local_template_type_dir, verbose: false
names.each do |name|
dst_name = File.join(local_template_type_dir, name)
src_name = File.join(generators_lib, type, name, "templates")
- FileUtils.cp_r src_name, dst_name
+ cp_r src_name, dst_name, verbose: false
end
end
end
@@ -45,8 +47,9 @@ namespace :rails do
@app_generator ||= begin
require 'rails/generators'
require 'rails/generators/rails/app/app_generator'
- gen = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true },
- destination_root: Rails.root
+ gen = Rails::Generators::AppGenerator.new ["rails"],
+ { api: !!Rails.application.config.api_only, update: true },
+ destination_root: Rails.root
File.exist?(Rails.root.join("config", "application.rb")) ?
gen.send(:app_const) : gen.send(:valid_const?)
gen
@@ -66,3 +69,15 @@ namespace :rails do
end
end
end
+
+namespace :rails do
+ %i(update template templates:copy update:configs update:bin).each do |task_name|
+ task "#{task_name}" do
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Running #{task_name} with the rails: namespace is deprecated in favor of app: namespace.
+ Run bin/rails app:#{task_name} instead.
+ MSG
+ Rake.application.invoke_task("app:#{task_name}")
+ end
+ end
+end
diff --git a/railties/lib/rails/tasks/misc.rake b/railties/lib/rails/tasks/misc.rake
index 4195106961..e6b13cc077 100644
--- a/railties/lib/rails/tasks/misc.rake
+++ b/railties/lib/rails/tasks/misc.rake
@@ -10,29 +10,46 @@ task about: :environment do
end
namespace :time do
+ desc 'List all time zones, list by two-letter country code (`rails time:zones[US]`), or list by UTC offset (`rails time:zones[-8]`)'
+ task :zones, :country_or_offset do |t, args|
+ zones, offset = ActiveSupport::TimeZone.all, nil
+
+ if country_or_offset = args[:country_or_offset]
+ begin
+ zones = ActiveSupport::TimeZone.country_zones(country_or_offset)
+ rescue TZInfo::InvalidCountryCode
+ offset = country_or_offset
+ end
+ end
+
+ build_time_zone_list zones, offset
+ end
+
namespace :zones do
- desc 'Displays all time zones, also available: time:zones:us, time:zones:local -- filter with OFFSET parameter, e.g., OFFSET=-6'
+ # desc 'Displays all time zones, also available: time:zones:us, time:zones:local -- filter with OFFSET parameter, e.g., OFFSET=-6'
task :all do
- build_time_zone_list(:all)
+ build_time_zone_list ActiveSupport::TimeZone.all
end
# desc 'Displays names of US time zones recognized by the Rails TimeZone class, grouped by offset. Results can be filtered with optional OFFSET parameter, e.g., OFFSET=-6'
task :us do
- build_time_zone_list(:us_zones)
+ build_time_zone_list ActiveSupport::TimeZone.us_zones
end
# desc 'Displays names of time zones recognized by the Rails TimeZone class with the same offset as the system local time'
task :local do
require 'active_support'
require 'active_support/time'
+
jan_offset = Time.now.beginning_of_year.utc_offset
jul_offset = Time.now.beginning_of_year.change(month: 7).utc_offset
offset = jan_offset < jul_offset ? jan_offset : jul_offset
- build_time_zone_list(:all, offset)
+
+ build_time_zone_list(ActiveSupport::TimeZone.all, offset)
end
# to find UTC -06:00 zones, OFFSET can be set to either -6, -6:00 or 21600
- def build_time_zone_list(method, offset = ENV['OFFSET'])
+ def build_time_zone_list(zones, offset = ENV['OFFSET'])
require 'active_support'
require 'active_support/time'
if offset
@@ -47,7 +64,7 @@ namespace :time do
end
end
previous_offset = nil
- ActiveSupport::TimeZone.__send__(method).each do |zone|
+ zones.each do |zone|
if offset.nil? || offset == zone.utc_offset
puts "\n* UTC #{zone.formatted_offset} *" unless zone.utc_offset == previous_offset
puts zone.name
diff --git a/railties/lib/rails/tasks/restart.rake b/railties/lib/rails/tasks/restart.rake
index f36c86d81b..3f98cbe51f 100644
--- a/railties/lib/rails/tasks/restart.rake
+++ b/railties/lib/rails/tasks/restart.rake
@@ -1,5 +1,8 @@
-desc "Restart app by touching tmp/restart.txt"
+desc 'Restart app by touching tmp/restart.txt'
task :restart do
- FileUtils.mkdir_p('tmp')
- FileUtils.touch('tmp/restart.txt')
+ verbose(false) do
+ mkdir_p 'tmp'
+ touch 'tmp/restart.txt'
+ rm_f 'tmp/pids/server.pid'
+ end
end
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index 1815c2fdc7..ff7233cae9 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -1,7 +1,38 @@
-desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
+require 'active_support/deprecation'
+require 'active_support/core_ext/string/strip' # for strip_heredoc
+require 'optparse'
+
+desc 'Print out all defined routes in match order, with names. Target specific controller with -c option, or grep routes using -g option'
task routes: :environment do
all_routes = Rails.application.routes.routes
require 'action_dispatch/routing/inspector'
inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
- puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, ENV['CONTROLLER'])
+ if ARGV.any?{ |argv| argv.start_with? 'CONTROLLER' }
+ puts <<-eow.strip_heredoc
+ Passing `CONTROLLER` to `bin/rails routes` is deprecated and will be removed in Rails 5.1.
+ Please use `bin/rails routes -c controller_name` instead.
+ eow
+ end
+
+ routes_filter = nil
+ routes_filter = { controller: ENV['CONTROLLER'] } if ENV['CONTROLLER']
+
+ OptionParser.new do |opts|
+ opts.banner = "Usage: rails routes [options]"
+
+ Rake.application.standard_rake_options.each { |args| opts.on(*args) }
+
+ opts.on("-c CONTROLLER") do |controller|
+ routes_filter = { controller: controller }
+ end
+
+ opts.on("-g PATTERN") do |pattern|
+ routes_filter = pattern
+ end
+
+ end.parse!(ARGV.reject { |x| x == "routes" })
+
+ puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, routes_filter)
+
+ exit 0 # ensure extra arguments aren't interpreted as Rake tasks
end
diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index a919d36939..3e40d3b037 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -7,6 +7,7 @@ STATS_DIRECTORIES = [
%w(Jobs app/jobs),
%w(Models app/models),
%w(Mailers app/mailers),
+ %w(Channels app/channels),
%w(Javascripts app/assets/javascripts),
%w(Libraries lib/),
%w(Tasks lib/tasks),
diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake
index 9162ef234a..c74a17a0ca 100644
--- a/railties/lib/rails/tasks/tmp.rake
+++ b/railties/lib/rails/tasks/tmp.rake
@@ -5,9 +5,7 @@ namespace :tmp do
tmp_dirs = [ 'tmp/cache',
'tmp/sockets',
'tmp/pids',
- 'tmp/cache/assets/development',
- 'tmp/cache/assets/test',
- 'tmp/cache/assets/production' ]
+ 'tmp/cache/assets' ]
tmp_dirs.each { |d| directory d }
@@ -17,21 +15,21 @@ namespace :tmp do
namespace :cache do
# desc "Clears all files and directories in tmp/cache"
task :clear do
- FileUtils.rm_rf(Dir['tmp/cache/[^.]*'])
+ rm_rf Dir['tmp/cache/[^.]*'], verbose: false
end
end
namespace :sockets do
# desc "Clears all files in tmp/sockets"
task :clear do
- FileUtils.rm(Dir['tmp/sockets/[^.]*'])
+ rm Dir['tmp/sockets/[^.]*'], verbose: false
end
end
namespace :pids do
# desc "Clears all files in tmp/pids"
task :clear do
- FileUtils.rm(Dir['tmp/pids/[^.]*'])
+ rm Dir['tmp/pids/[^.]*'], verbose: false
end
end
end
diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb
index acf04af416..5cdb7e6a20 100644
--- a/railties/lib/rails/templates/rails/welcome/index.html.erb
+++ b/railties/lib/rails/templates/rails/welcome/index.html.erb
@@ -1,268 +1,64 @@
<!DOCTYPE html>
<html>
- <head>
- <title>Ruby on Rails: Welcome aboard</title>
- <style media="screen">
- body {
- margin: 0;
- margin-bottom: 25px;
- padding: 0;
- background-color: #f0f0f0;
- font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana";
- font-size: 13px;
- color: #333;
- }
-
- h1 {
- font-size: 28px;
- color: #000;
- }
-
- a {
- color: #03c;
- }
-
- a:hover {
- background-color: #03c;
- color: white;
- text-decoration: none;
- }
-
- #page {
- background-color: #f0f0f0;
- width: 750px;
- margin: 0;
- margin-left: auto;
- margin-right: auto;
- }
-
- #content {
- float: left;
- background-color: white;
- border: 3px solid #aaa;
- border-top: none;
- padding: 25px;
- width: 500px;
- }
-
- #sidebar {
- float: right;
- width: 175px;
- }
-
- #footer {
- clear: both;
- }
-
- #header, #about, #getting-started {
- padding-left: 75px;
- padding-right: 30px;
- }
-
- #header {
- background-image: url(data:image/png;base64,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);
- background-repeat: no-repeat;
- background-position: top left;
- height: 64px;
- }
-
- #header h1,
- #header h2 {
- margin: 0;
- }
-
- #header h2 {
- color: #888;
- font-weight: normal;
- font-size: 16px;
- }
-
- #about h3 {
- margin: 0;
- margin-bottom: 10px;
- font-size: 14px;
- }
-
- #about-content {
- background-color: #ffd;
- border: 1px solid #fc0;
- margin-left: -55px;
- margin-right: -10px;
- }
-
- #about-content table {
- margin-top: 10px;
- margin-bottom: 10px;
- font-size: 11px;
- border-collapse: collapse;
- }
-
- #about-content td {
- padding: 10px;
- padding-top: 3px;
- padding-bottom: 3px;
- }
-
- #about-content td.name {
- font-weight: bold;
- vertical-align: top;
- color: #555;
- }
-
- #about-content td.value {
- color: #000;
- }
-
- #about-content ul {
- padding: 0;
- list-style-type: none;
- }
-
- #about-content.failure {
- background-color: #fcc;
- border: 1px solid #f00;
- }
-
- #about-content.failure p {
- margin: 0;
- padding: 10px;
- }
-
- #getting-started {
- border-top: 1px solid #ccc;
- margin-top: 25px;
- padding-top: 15px;
- }
-
- #getting-started h1 {
- margin: 0;
- font-size: 20px;
- }
-
- #getting-started h2 {
- margin: 0;
- font-size: 14px;
- font-weight: normal;
- color: #333;
- margin-bottom: 25px;
- }
-
- #getting-started ol {
- margin-left: 0;
- padding-left: 0;
- }
-
- #getting-started li {
- font-size: 18px;
- color: #888;
- margin-bottom: 25px;
- }
-
- #getting-started li h2 {
- margin: 0;
- font-weight: normal;
- font-size: 18px;
- color: #333;
- }
-
- #getting-started li p {
- color: #555;
- font-size: 13px;
- }
-
- #sidebar ul {
- margin-left: 0;
- padding-left: 0;
- }
-
- #sidebar ul h3 {
- margin-top: 25px;
- font-size: 16px;
- padding-bottom: 10px;
- border-bottom: 1px solid #ccc;
- }
-
- #sidebar li {
- list-style-type: none;
- }
-
- #sidebar ul.links li {
- margin-bottom: 5px;
- }
-
- .filename {
- font-style: italic;
- }
- </style>
- <script>
- function about() {
- var info = document.getElementById('about-content'),
- xhr;
-
- if (info.innerHTML === '') {
- xhr = new XMLHttpRequest();
- xhr.open("GET", "/rails/info/properties", false);
- xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
- xhr.send("");
- info.innerHTML = xhr.responseText;
- }
-
- info.style.display = info.style.display === 'none' ? 'block' : 'none';
- }
- </script>
- </head>
- <body>
- <div id="page">
- <div id="sidebar">
- <ul id="sidebar-items">
- <li>
- <h3>Browse the documentation</h3>
- <ul class="links">
- <li><a href="http://guides.rubyonrails.org/">Rails Guides</a></li>
- <li><a href="http://api.rubyonrails.org/">Rails API</a></li>
- <li><a href="http://www.ruby-doc.org/core/">Ruby core</a></li>
- <li><a href="http://www.ruby-doc.org/stdlib/">Ruby standard library</a></li>
- </ul>
- </li>
- </ul>
- </div>
-
- <div id="content">
- <div id="header">
- <h1>Welcome aboard</h1>
- <h2>You&rsquo;re riding Ruby on Rails!</h2>
- </div>
-
- <div id="about">
- <h3><a href="/rails/info/properties" onclick="about(); return false">About your application&rsquo;s environment</a></h3>
- <div id="about-content" style="display: none"></div>
- </div>
-
- <div id="getting-started">
- <h1>Getting started</h1>
- <h2>Here&rsquo;s how to get rolling:</h2>
-
- <ol>
- <li>
- <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>
-
- <li>
- <h2>Set up a root route to replace this page</h2>
- <p>You're seeing this page because you're running in development mode and you haven't set a root route yet.</p>
- <p>Routes are set up in <span class="filename">config/routes.rb</span>.</p>
- </li>
-
- <li>
- <h2>Configure your database</h2>
- <p>If you're not using SQLite (the default), edit <span class="filename">config/database.yml</span> with your username and password.</p>
- </li>
- </ol>
- </div>
- </div>
-
- <div id="footer">&nbsp;</div>
- </div>
- </body>
+<head>
+ <title>Ruby on Rails</title>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width">
+ <style type="text/css" media="screen" charset="utf-8">
+ body {
+ font-family: Georgia, sans-serif;
+ line-height: 2rem;
+ font-size: 1.3rem;
+ background-color: white;
+ margin: 0;
+ padding: 0;
+ color: #000;
+ }
+
+ h1 {
+ font-weight: normal;
+ line-height: 2.8rem;
+ font-size: 2.5rem;
+ letter-spacing: -1px;
+ color: black;
+ }
+
+ p { font-family: monospace; }
+
+ .container {
+ width: 960px;
+ margin: 0 auto 40px;
+ overflow: hidden;
+ }
+
+
+ section {
+ margin: 0 auto 2rem;
+ padding: 1rem 0 0;
+ width: 700px;
+ text-align: center;
+ }
+ </style>
+</head>
+
+<body>
+ <div class="container">
+ <section>
+ <p>
+ <a href="http://rubyonrails.org">
+ <img width="130" height="46" alt="Ruby on Rails" border="0" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCA0MDAgMTQwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA0MDAgMTQwIiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxzdHlsZT4uYXtmaWxsOiNjMDA7fTwvc3R5bGU+DQo8dGl0bGU+cmFpbHMtbG9nbzwvdGl0bGU+DQo8Zz4NCgk8cGF0aCBjbGFzcz0iYSIgZD0iTTM0Ni42LDEyMS41djE4LjFjMCwwLDIzLjQsMCwzMi43LDBjNi43LDAsMTguMi00LjksMTguNi0xOC42YzAtMC42LDAtNi41LDAtN2MwLTExLjctOS42LTE4LjYtMTguNi0xOC42DQoJCWMtNC4yLDAtMTYuMywwLTE2LjMsMFY4N2wzMi4zLDBWNjguOGMwLDAtMjIuMiwwLTMxLDBjLTgsMC0xOC43LDYuNi0xOC43LDE4LjljMCwxLjIsMCw1LjIsMCw2LjNjMCwxMi4zLDEwLjYsMTguNiwxOC43LDE4LjYNCgkJYzIyLjUsMC4xLTUuNCwwLDE1LjQsMGMwLDguOCwwLDguOCwwLDguOCIvPg0KCTxwYXRoIGNsYXNzPSJhIiBkPSJNMTcxLjQsMTE3LjFjMCwwLDE3LjUtMS41LDE3LjUtMjQuMXMtMjEuMi0yNC43LTIxLjItMjQuN2gtMzguMnY3MS4zaDE5LjJ2LTE3LjJsMTYuNiwxNy4yaDI4LjQNCgkJTDE3MS40LDExNy4xeiBNMTY0LDEwMi41aC0xNS4zVjg2LjJoMTUuNGMwLDAsNC4zLDEuNiw0LjMsOC4xUzE2NCwxMDIuNSwxNjQsMTAyLjV6Ii8+DQoJPHBhdGggY2xhc3M9ImEiIGQ9Ik0yMzYuMyw2OC44Yy00LjksMC01LjYsMC0xOS41LDBjLTEzLjksMC0xOC42LDEyLjYtMTguNiwxOC42YzAsMTMsMCw1Mi4yLDAsNTIuMmgxOS41di0xMi41SDIzNnYxMi41aDE4LjkNCgkJYzAsMCwwLTM4LjUsMC01Mi4yQzI1NC45LDcyLjIsMjQxLjEsNjguOCwyMzYuMyw2OC44eiBNMjM2LDEwNi45aC0xOC40Vjg5LjZjMCwwLDAtMy45LDYuMS0zLjljNS42LDAsMSwwLDYuNywwDQoJCWM1LjQsMCw1LjUsMy45LDUuNSwzLjlWMTA2Ljl6Ii8+DQoJPHJlY3QgeD0iMjYzLjgiIHk9IjY4LjgiIGNsYXNzPSJhIiB3aWR0aD0iMjAuMyIgaGVpZ2h0PSI3MC44Ii8+DQoJPHBvbHlnb24gY2xhc3M9ImEiIHBvaW50cz0iMzEyLjYsMTIxLjMgMzEyLjYsNjguOCAyOTIuNCw2OC44IDI5Mi40LDEyMS4zIDI5Mi40LDEzOS42IDMxMi42LDEzOS42IDMzOS45LDEzOS42IDMzOS45LDEyMS4zIAkNCgkJIi8+DQoJPHBhdGggY2xhc3M9ImEiIGQ9Ik05LDEzOS42aDc5YzAsMC0xNS4xLTY4LjksMzQuOS05Ni44YzEwLjktNS4zLDQ1LjYtMjUuMSwxMDIuNCwxNi45YzEuOC0xLjUsMy41LTIuNywzLjUtMi43DQoJCVMxNzYuOCw1LjEsMTE4LjksMTAuOUM4OS44LDEzLjUsNTQsNDAsMzMsNzVTOSwxMzkuNiw5LDEzOS42eiIvPg0KCTxwYXRoIGNsYXNzPSJhIiBkPSJNOSwxMzkuNmg3OWMwLDAtMTUuMS02OC45LDM0LjktOTYuOGMxMC45LTUuMyw0NS42LTI1LjEsMTAyLjQsMTYuOWMxLjgtMS41LDMuNS0yLjcsMy41LTIuNw0KCQlTMTc2LjgsNS4xLDExOC45LDEwLjlDODkuOCwxMy41LDU0LDQwLDMzLDc1UzksMTM5LjYsOSwxMzkuNnoiLz4NCgk8cGF0aCBjbGFzcz0iYSIgZD0iTTksMTM5LjZoNzljMCwwLTE1LjEtNjguOSwzNC45LTk2LjhjMTAuOS01LjMsNDUuNi0yNS4xLDEwMi40LDE2LjljMS44LTEuNSwzLjUtMi43LDMuNS0yLjcNCgkJUzE3Ni44LDUuMSwxMTguOSwxMC45Qzg5LjcsMTMuNSw1My45LDQwLDMyLjksNzVTOSwxMzkuNiw5LDEzOS42eiIvPg0KCTxwYXRoIGNsYXNzPSJhIiBkPSJNMTczLjYsMTYuNWwwLjQtNi43Yy0wLjktMC41LTMuNC0xLjctOS43LTMuNWwtMC40LDYuNkMxNjcuMiwxNCwxNzAuNCwxNS4yLDE3My42LDE2LjV6Ii8+DQoJPHBhdGggY2xhc3M9ImEiIGQ9Ik0xNjQuMSwzNy43bC0wLjQsNi4zYzMuMywwLjEsNi42LDAuNSw5LjksMS4ybDAuNC02LjJDMTcwLjYsMzguMywxNjcuMywzNy45LDE2NC4xLDM3Ljd6Ii8+DQoJPHBhdGggY2xhc3M9ImEiIGQ9Ik0xMjcuMSw2LjVjMC4zLDAsMC43LDAsMSwwbC0yLTYuMWMtMy4xLDAtNi4zLDAuMi05LjYsMC42bDEuOSw1LjlDMTIxLjMsNi42LDEyNC4yLDYuNSwxMjcuMSw2LjV6Ii8+DQoJPHBhdGggY2xhc3M9ImEiIGQ9Ik0xMzEuOSw0My4zbDIuMyw2LjljMi45LTEuNCw1LjgtMi42LDguNy0zLjVsLTIuMi02LjZDMTM3LjMsNDEuMSwxMzQuNCw0Mi4yLDEzMS45LDQzLjN6Ii8+DQoJPHBhdGggY2xhc3M9ImEiIGQ9Ik04Ni41LDE3TDgyLDEwLjFjLTIuNSwxLjMtNS4xLDIuNy03LjgsNC4zbDQuNiw3QzgxLjQsMTkuOCw4My45LDE4LjMsODYuNSwxN3oiLz4NCgk8cGF0aCBjbGFzcz0iYSIgZD0iTTEwNyw2Mmw0LjgsNy4yYzEuNy0yLjUsMy43LTQuOCw1LjktNy4xbC00LjUtNi44QzExMC45LDU3LjQsMTA4LjgsNTkuNywxMDcsNjJ6Ii8+DQoJPHBhdGggY2xhc3M9ImEiIGQ9Ik05Mi41LDk0LjJsOC4xLDYuNGMwLjQtMy45LDEuMS03LjgsMi4xLTExLjdsLTcuMi01LjdDOTQuMiw4Ni45LDkzLjMsOTAuNiw5Mi41LDk0LjJ6Ii8+DQoJPHBhdGggY2xhc3M9ImEiIGQ9Ik00OC43LDQ2LjdsLTcuMS02LjJjLTIuNiwyLjUtNS4xLDUtNy40LDcuNWw3LjcsNi42QzQ0LDUxLjksNDYuMyw0OS4yLDQ4LjcsNDYuN3oiLz4NCgk8cGF0aCBjbGFzcz0iYSIgZD0iTTE4LjUsOTEuNEw3LDg3LjJjLTEuOSw0LjMtNCw5LjMtNSwxMmwxMS41LDQuMkMxNC44LDEwMCwxNi45LDk1LjEsMTguNSw5MS40eiIvPg0KCTxwYXRoIGNsYXNzPSJhIiBkPSJNOTEsMTE5LjZjMC4yLDUuMywwLjcsOS42LDEuMiwxMi42bDEyLDQuM2MtMC45LTMuOS0xLjgtOC4zLTIuNC0xM0w5MSwxMTkuNnoiLz4NCjwvZz4NCjwvc3ZnPg0K" />
+ </a>
+ </p>
+
+ <h1>Yay! You&rsquo;re on Rails!</h1>
+
+ <img alt="Welcome" width="600" height="350" src="data:image/jpg;base64,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" />
+
+ <p class="version">
+ <strong>Rails version:</strong> <%= Rails.version %><br />
+ <strong>Ruby version:</strong> <%= RUBY_VERSION %> (<%= RUBY_PLATFORM %>)
+ </p>
+ </section>
+ </div>
+</body>
</html>
diff --git a/railties/lib/rails/test_unit/line_filtering.rb b/railties/lib/rails/test_unit/line_filtering.rb
index dab4d3631d..dd9732bb12 100644
--- a/railties/lib/rails/test_unit/line_filtering.rb
+++ b/railties/lib/rails/test_unit/line_filtering.rb
@@ -13,9 +13,12 @@ module Rails
end
class CompositeFilter # :nodoc:
+ attr_reader :named_filter
+
def initialize(runnable, filter, patterns)
@runnable = runnable
- @filters = [ derive_regexp(filter), *derive_line_filters(patterns) ].compact
+ @named_filter = derive_named_filter(filter)
+ @filters = [ @named_filter, *derive_line_filters(patterns) ].compact
end
# Minitest uses === to find matching filters.
@@ -24,9 +27,14 @@ module Rails
end
private
- def derive_regexp(filter)
- # Regexp filtering copied from Minitest.
- filter =~ %r%/(.*)/% ? Regexp.new($1) : filter
+ def derive_named_filter(filter)
+ if filter.respond_to?(:named_filter)
+ filter.named_filter
+ elsif filter =~ %r%/(.*)/% # Regexp filtering copied from Minitest.
+ Regexp.new $1
+ elsif filter.is_a?(String)
+ filter
+ end
end
def derive_line_filters(patterns)
diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb
index 29a3d991b8..076ab536be 100644
--- a/railties/lib/rails/test_unit/minitest_plugin.rb
+++ b/railties/lib/rails/test_unit/minitest_plugin.rb
@@ -1,6 +1,7 @@
require "active_support/core_ext/module/attribute_accessors"
require "rails/test_unit/reporter"
require "rails/test_unit/test_requirer"
+require 'shellwords'
module Minitest
class SuppressedSummaryReporter < SummaryReporter
@@ -42,7 +43,7 @@ module Minitest
end
opts.on("-f", "--fail-fast",
- "Abort test run on first failure") do
+ "Abort test run on first failure or error") do
options[:fail_fast] = true
end
@@ -53,27 +54,26 @@ module Minitest
options[:color] = true
options[:output_inline] = true
- options[:patterns] = opts.order!
+ options[:patterns] = defined?(@rake_patterns) ? @rake_patterns : opts.order!
end
# Running several Rake tasks in a single command would trip up the runner,
# as the patterns would also contain the other Rake tasks.
def self.rake_run(patterns) # :nodoc:
@rake_patterns = patterns
- passed = run
+ passed = run(Shellwords.split(ENV['TESTOPTS'] || ''))
exit passed unless passed
passed
end
+ # Owes great inspiration to test runner trailblazers like RSpec,
+ # minitest-reporters, maxitest and others.
def self.plugin_rails_init(options)
self.run_with_rails_extension = true
ENV["RAILS_ENV"] = options[:environment] || "test"
- unless run_with_autorun
- patterns = defined?(@rake_patterns) ? @rake_patterns : options[:patterns]
- ::Rails::TestRequirer.require_files(patterns)
- end
+ ::Rails::TestRequirer.require_files(options[:patterns]) unless run_with_autorun
unless options[:full_backtrace] || ENV["BACKTRACE"]
# Plugin can run without Rails loaded, check before filtering.
@@ -81,14 +81,18 @@ module Minitest
end
# Replace progress reporter for colors.
- self.reporter.reporters.delete_if { |reporter| reporter.kind_of?(SummaryReporter) || reporter.kind_of?(ProgressReporter) }
- self.reporter << SuppressedSummaryReporter.new(options[:io], options)
- self.reporter << ::Rails::TestUnitReporter.new(options[:io], options)
+ reporter.reporters.delete_if { |reporter| reporter.kind_of?(SummaryReporter) || reporter.kind_of?(ProgressReporter) }
+ reporter << SuppressedSummaryReporter.new(options[:io], options)
+ reporter << ::Rails::TestUnitReporter.new(options[:io], options)
end
mattr_accessor(:run_with_autorun) { false }
mattr_accessor(:run_with_rails_extension) { false }
end
+# Put Rails as the first plugin minitest initializes so other plugins
+# can override or replace our default reporter setup.
+# Since minitest only loads plugins if its extensions are empty we have
+# to call `load_plugins` first.
Minitest.load_plugins
-Minitest.extensions << 'rails'
+Minitest.extensions.unshift 'rails'
diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb
index 73b8d7d27b..4086d5b731 100644
--- a/railties/lib/rails/test_unit/reporter.rb
+++ b/railties/lib/rails/test_unit/reporter.rb
@@ -18,13 +18,13 @@ module Rails
if output_inline? && result.failure && (!result.skipped? || options[:verbose])
io.puts
io.puts
- io.puts format_failures(result).map { |line| color_output(line, by: result) }
+ io.puts color_output(result, by: result)
io.puts
io.puts format_rerun_snippet(result)
io.puts
end
- if fail_fast? && result.failure && !result.error? && !result.skipped?
+ if fail_fast? && result.failure && !result.skipped?
raise Interrupt
end
end
@@ -66,21 +66,9 @@ module Rails
"%s#%s = %.2f s = %s" % [result.class, result.name, result.time, result.result_code]
end
- def format_failures(result)
- result.failures.map do |failure|
- "#{failure.result_label}:\n#{result.class}##{result.name}:\n#{failure.message}\n"
- end
- end
-
def format_rerun_snippet(result)
- # Try to extract path to assertion from backtrace.
- if result.location =~ /\[(.*)\]\z/
- assertion_path = $1
- else
- assertion_path = result.method(result.name).source_location.join(':')
- end
-
- "#{self.executable} #{relative_path_for(assertion_path)}"
+ location, line = result.method(result.name).source_location
+ "#{self.executable} #{relative_path_for(location)}:#{line}"
end
def app_root
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index a06336f698..fa417816eb 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.homepage = 'http://rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'RDOC_MAIN.rdoc', 'exe/**/*', 'lib/**/{*,.[a-z]*}']
s.require_path = 'lib'
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index 794d180e5d..2a5a731fe2 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -1,7 +1,5 @@
ENV["RAILS_ENV"] ||= "test"
-require File.expand_path("../../../load_paths", __FILE__)
-
require 'stringio'
require 'active_support/testing/autorun'
require 'active_support/testing/stream'
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 2670fad618..9e8531b482 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -186,6 +186,26 @@ module ApplicationTests
assert_file_exists("#{app_path}/public/assets/something-*.js")
end
+ test 'sprockets cache is not shared between environments' do
+ app_file "app/assets/images/rails.png", "notactuallyapng"
+ app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
+ add_to_env_config 'production', 'config.assets.prefix = "production_assets"'
+
+ precompile!
+
+ assert_file_exists("#{app_path}/public/assets/application-*.css")
+
+ file = Dir["#{app_path}/public/assets/application-*.css"].first
+ assert_match(/assets\/rails-([0-z]+)\.png/, File.read(file))
+
+ precompile! RAILS_ENV: 'production'
+
+ assert_file_exists("#{app_path}/public/production_assets/application-*.css")
+
+ file = Dir["#{app_path}/public/production_assets/application-*.css"].first
+ assert_match(/production_assets\/rails-([0-z]+)\.png/, File.read(file))
+ end
+
test 'precompile use assets defined in app config and reassigned in app env config' do
add_to_config 'config.assets.precompile = [ "something_manifest.js" ]'
add_to_env_config 'production', 'config.assets.precompile += [ "another_manifest.js" ]'
@@ -287,7 +307,7 @@ module ApplicationTests
assert_not_equal asset_path, assets["assets"]["application.css"]
end
- test "precompile appends the md5 hash to files referenced with asset_path and run in production with digest true" do
+ test "precompile appends the MD5 hash to files referenced with asset_path and run in production with digest true" do
app_file "app/assets/images/rails.png", "notactuallyapng"
app_file "app/assets/stylesheets/application.css.erb", "p { background-image: url(<%= asset_path('rails.png') %>) }"
@@ -324,8 +344,7 @@ module ApplicationTests
clean_assets!
- files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/assets/development/*",
- "#{app_path}/tmp/cache/assets/test/*", "#{app_path}/tmp/cache/assets/production/*"]
+ files = Dir["#{app_path}/public/assets/**/*"]
assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}"
end
diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb
index 8c3ab65c51..ba700df1d6 100644
--- a/railties/test/application/bin_setup_test.rb
+++ b/railties/test/application/bin_setup_test.rb
@@ -28,7 +28,7 @@ module ApplicationTests
assert_not File.exist?("tmp/restart.txt")
`bin/setup 2>&1`
assert_equal 0, File.size("log/test.log")
- assert_equal '["articles", "schema_migrations", "active_record_internal_metadatas"]', list_tables.call
+ assert_equal '["articles", "schema_migrations", "ar_internal_metadata"]', list_tables.call
assert File.exist?("tmp/restart.txt")
end
end
@@ -43,6 +43,8 @@ module ApplicationTests
The Gemfile's dependencies are satisfied
== Preparing database ==
+Created database 'db/development.sqlite3'
+Created database 'db/test.sqlite3'
== Removing old logs and tempfiles ==
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 7bcfc86d03..7ec25aeca1 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -348,6 +348,17 @@ module ApplicationTests
end
end
+ test "In production mode, STDOUT logging is enabled when RAILS_LOG_TO_STDOUT is set" do
+ restore_default_config
+
+ with_rails_env "production" do
+ switch_env "RAILS_LOG_TO_STDOUT", "1" do
+ app 'production'
+ assert ActiveSupport::Logger.logger_outputs_to?(app.config.logger, STDOUT)
+ end
+ end
+ end
+
test "In production mode, config.public_file_server.enabled is disabled when RAILS_SERVE_STATIC_FILES is blank" do
restore_default_config
@@ -544,6 +555,31 @@ module ApplicationTests
assert_equal 'myamazonsecretaccesskey', app.secrets.aws_secret_access_key
end
+ test "shared secrets saved in config/secrets.yml are loaded in app secrets" do
+ app_file 'config/secrets.yml', <<-YAML
+ shared:
+ api_key: 3b7cd727
+ YAML
+
+ app 'development'
+
+ assert_equal '3b7cd727', app.secrets.api_key
+ end
+
+ test "shared secrets will yield to environment specific secrets" do
+ app_file 'config/secrets.yml', <<-YAML
+ shared:
+ api_key: 3b7cd727
+
+ development:
+ api_key: abc12345
+ YAML
+
+ app 'development'
+
+ assert_equal 'abc12345', app.secrets.api_key
+ end
+
test "blank config/secrets.yml does not crash the loading process" do
app_file 'config/secrets.yml', <<-YAML
YAML
@@ -675,7 +711,7 @@ module ApplicationTests
private
- def form_authenticity_token(*args); token; end # stub the authenticy token
+ def form_authenticity_token(*args); token; end # stub the authenticity token
end
RUBY
@@ -988,7 +1024,7 @@ module ApplicationTests
app 'development'
post "/posts.json", '{ "title": "foo", "name": "bar" }', "CONTENT_TYPE" => "application/json"
- assert_equal '{"title"=>"foo"}', last_response.body
+ assert_equal '<ActionController::Parameters {"title"=>"foo"} permitted: false>', last_response.body
end
test "config.action_controller.permit_all_parameters = true" do
@@ -1326,7 +1362,7 @@ module ApplicationTests
assert_equal 'custom key', Rails.application.config.my_custom_config['key']
end
- test "config_for use the Pathname object if it is provided" do
+ test "config_for uses the Pathname object if it is provided" do
app_file 'config/custom.yml', <<-RUBY
development:
key: 'custom key'
@@ -1444,7 +1480,7 @@ module ApplicationTests
assert Rails.configuration.api_only
end
- test "debug_exception_response_format is :api by default if only_api is enabled" do
+ test "debug_exception_response_format is :api by default if api_only is enabled" do
add_to_config <<-RUBY
config.api_only = true
RUBY
@@ -1453,7 +1489,7 @@ module ApplicationTests
assert_equal :api, Rails.configuration.debug_exception_response_format
end
- test "debug_exception_response_format can be override" do
+ test "debug_exception_response_format can be overridden" do
add_to_config <<-RUBY
config.api_only = true
RUBY
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index 7bf123d12b..ea68e63f8f 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -52,12 +52,11 @@ class ConsoleTest < ActiveSupport::TestCase
a = b = c = nil
# TODO: These should be defined on the initializer
- ActionDispatch::Reloader.to_cleanup { a = b = c = 1 }
- ActionDispatch::Reloader.to_cleanup { b = c = 2 }
- ActionDispatch::Reloader.to_prepare { c = 3 }
+ ActiveSupport::Reloader.to_complete { a = b = c = 1 }
+ ActiveSupport::Reloader.to_complete { b = c = 2 }
+ ActiveSupport::Reloader.to_prepare { c = 3 }
- # Hide Reloading... output
- silence_stream(STDOUT) { irb_context.reload! }
+ irb_context.reload!(false)
assert_equal 1, a
assert_equal 2, b
@@ -81,7 +80,7 @@ class ConsoleTest < ActiveSupport::TestCase
MODEL
assert !User.new.respond_to?(:age)
- silence_stream(STDOUT) { irb_context.reload! }
+ irb_context.reload!(false)
assert User.new.respond_to?(:age)
end
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index 84cc6e120b..644af0e737 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -160,5 +160,15 @@ module ApplicationTests
assert Rails::Generators.options[:rails][:helper]
assert_equal :my_template, Rails::Generators.options[:rails][:template_engine]
end
+
+ test "api only generator generate mailer views" do
+ add_to_config <<-RUBY
+ config.api_only = true
+ RUBY
+
+ FileUtils.cd(rails_root){ `bin/rails generate mailer notifier foo` }
+ assert File.exist?(File.join(rails_root, "app/views/notifier_mailer/foo.text.erb"))
+ assert File.exist?(File.join(rails_root, "app/views/notifier_mailer/foo.html.erb"))
+ end
end
end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 4c06b6324c..44209a52f7 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -16,7 +16,7 @@ module ApplicationTests
# AC & AM
test "set load paths set only if action controller or action mailer are in use" do
- assert_nothing_raised NameError do
+ assert_nothing_raised do
add_to_config <<-RUBY
config.root = "#{app_path}"
RUBY
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index ab7f29b0f2..0f9bb41053 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -245,7 +245,7 @@ fr:
assert_fallbacks de: [:de, :'en-US', :en]
end
- test "[shortcut] config.i18n.fallbacks = [{ :ca => :'es-ES' }] initializes fallbacks with a mapping de-AT => de-DE" do
+ test "[shortcut] config.i18n.fallbacks = [{ :ca => :'es-ES' }] initializes fallbacks with a mapping ca => es-ES" do
I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' }
load_app
assert_fallbacks ca: [:ca, :"es-ES", :es, :en]
@@ -257,6 +257,12 @@ fr:
assert_fallbacks ca: [:ca, :"es-ES", :es, :'en-US', :en]
end
+ test "[shortcut] config.i18n.fallbacks = { ca: :en } initializes fallbacks with a mapping ca => :en" do
+ I18n::Railtie.config.i18n.fallbacks = { ca: :en }
+ load_app
+ assert_fallbacks ca: [:ca, :en]
+ end
+
test "disable config.i18n.enforce_available_locales" do
add_to_config <<-RUBY
config.i18n.enforce_available_locales = false
diff --git a/railties/test/application/integration_test_case_test.rb b/railties/test/application/integration_test_case_test.rb
new file mode 100644
index 0000000000..d106d5159a
--- /dev/null
+++ b/railties/test/application/integration_test_case_test.rb
@@ -0,0 +1,46 @@
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ class IntegrationTestCaseTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ setup do
+ build_app
+ boot_rails
+ end
+
+ teardown do
+ teardown_app
+ end
+
+ test "resets Action Mailer test deliveries" do
+ script('generate mailer BaseMailer welcome')
+
+ app_file 'test/integration/mailer_integration_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class MailerIntegrationTest < ActionDispatch::IntegrationTest
+ setup do
+ @old_delivery_method = ActionMailer::Base.delivery_method
+ ActionMailer::Base.delivery_method = :test
+ end
+
+ teardown do
+ ActionMailer::Base.delivery_method = @old_delivery_method
+ end
+
+ 2.times do |i|
+ define_method "test_resets_deliveries_\#{i}" do
+ BaseMailer.welcome.deliver_now
+ assert_equal 1, ActionMailer::Base.deliveries.count
+ end
+ end
+ end
+ RUBY
+
+ output = Dir.chdir(app_path) { `bin/rails test 2>&1` }
+ assert_equal 0, $?.to_i, output
+ assert_match(/0 failures, 0 errors/, output)
+ end
+ end
+end
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index 40abaf860d..efb21ae473 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -56,10 +56,10 @@ class LoadingTest < ActiveSupport::TestCase
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 }
+ assert_nothing_raised { Trackable }
+ assert_nothing_raised { EmailLoggable }
+ assert_nothing_raised { Orderable }
+ assert_nothing_raised { Matchable }
end
test "models without table do not panic on scope definitions when loaded" do
diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb
index 7b4babb13b..639b01b562 100644
--- a/railties/test/application/middleware/exceptions_test.rb
+++ b/railties/test/application/middleware/exceptions_test.rb
@@ -85,7 +85,7 @@ module ApplicationTests
test "unspecified route when action_dispatch.show_exceptions is set shows 404" do
app.config.action_dispatch.show_exceptions = true
- assert_nothing_raised(ActionController::RoutingError) do
+ assert_nothing_raised do
get '/foo'
assert_match "The page you were looking for doesn't exist.", last_response.body
end
@@ -95,7 +95,7 @@ module ApplicationTests
app.config.action_dispatch.show_exceptions = true
app.config.consider_all_requests_local = true
- assert_nothing_raised(ActionController::RoutingError) do
+ assert_nothing_raised do
get '/foo'
assert_match "No route matches", last_response.body
end
diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb
index 97d5b5c698..37bd8a25c1 100644
--- a/railties/test/application/middleware/remote_ip_test.rb
+++ b/railties/test/application/middleware/remote_ip_test.rb
@@ -36,10 +36,10 @@ module ApplicationTests
test "works with both headers individually" do
make_basic_app
- assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do
+ assert_nothing_raised do
assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1")
end
- assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do
+ assert_nothing_raised do
assert_equal "1.1.1.2", remote_ip("HTTP_CLIENT_IP" => "1.1.1.2")
end
end
@@ -49,7 +49,7 @@ module ApplicationTests
app.config.action_dispatch.ip_spoofing_check = false
end
- assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do
+ assert_nothing_raised do
assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2")
end
end
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index f847e80471..85e7761727 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -345,5 +345,33 @@ module ApplicationTests
get '/foo/read_raw_cookie'
assert_equal 2, verifier.verify(last_response.body)['foo']
end
+
+ test 'calling reset_session on request does not trigger an error for API apps' do
+ add_to_config 'config.api_only = true'
+
+ controller :test, <<-RUBY
+ class TestController < ApplicationController
+ def dump_flash
+ request.reset_session
+ render plain: 'It worked!'
+ end
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ get '/dump_flash' => "test#dump_flash"
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get '/dump_flash'
+
+ assert_equal 200, last_response.status
+ assert_equal 'It worked!', last_response.body
+
+ refute Rails.application.middleware.include?(ActionDispatch::Flash)
+ end
end
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 1434522cce..7a86a96e19 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -26,7 +26,7 @@ module ApplicationTests
assert_equal [
"Rack::Sendfile",
"ActionDispatch::Static",
- "ActionDispatch::LoadInterlock",
+ "ActionDispatch::Executor",
"ActiveSupport::Cache::Strategy::LocalCache",
"Rack::Runtime",
"Rack::MethodOverride",
@@ -38,8 +38,6 @@ module ApplicationTests
"ActionDispatch::Reloader",
"ActionDispatch::Callbacks",
"ActiveRecord::Migration::CheckPending",
- "ActiveRecord::ConnectionAdapters::ConnectionManagement",
- "ActiveRecord::QueryCache",
"ActionDispatch::Cookies",
"ActionDispatch::Session::CookieStore",
"ActionDispatch::Flash",
@@ -57,7 +55,7 @@ module ApplicationTests
assert_equal [
"Rack::Sendfile",
"ActionDispatch::Static",
- "ActionDispatch::LoadInterlock",
+ "ActionDispatch::Executor",
"ActiveSupport::Cache::Strategy::LocalCache",
"Rack::Runtime",
"ActionDispatch::RequestId",
@@ -67,8 +65,6 @@ module ApplicationTests
"ActionDispatch::RemoteIp",
"ActionDispatch::Reloader",
"ActionDispatch::Callbacks",
- "ActiveRecord::ConnectionAdapters::ConnectionManagement",
- "ActiveRecord::QueryCache",
"Rack::Head",
"Rack::ConditionalGet",
"Rack::ETag"
@@ -114,23 +110,12 @@ module ApplicationTests
test "removing Active Record omits its middleware" do
use_frameworks []
boot!
- assert !middleware.include?("ActiveRecord::ConnectionAdapters::ConnectionManagement")
- assert !middleware.include?("ActiveRecord::QueryCache")
assert !middleware.include?("ActiveRecord::Migration::CheckPending")
end
- test "includes interlock if cache_classes is set but eager_load is not" do
- add_to_config "config.cache_classes = true"
- boot!
- assert_not_includes middleware, "Rack::Lock"
- assert_includes middleware, "ActionDispatch::LoadInterlock"
- end
-
- test "includes interlock if cache_classes is off" do
- add_to_config "config.cache_classes = false"
+ test "includes executor" do
boot!
- assert_not_includes middleware, "Rack::Lock"
- assert_includes middleware, "ActionDispatch::LoadInterlock"
+ assert_includes middleware, "ActionDispatch::Executor"
end
test "does not include lock if cache_classes is set and so is eager_load" do
@@ -138,21 +123,18 @@ module ApplicationTests
add_to_config "config.eager_load = true"
boot!
assert_not_includes middleware, "Rack::Lock"
- assert_not_includes middleware, "ActionDispatch::LoadInterlock"
end
test "does not include lock if allow_concurrency is set to :unsafe" do
add_to_config "config.allow_concurrency = :unsafe"
boot!
assert_not_includes middleware, "Rack::Lock"
- assert_not_includes middleware, "ActionDispatch::LoadInterlock"
end
test "includes lock if allow_concurrency is disabled" do
add_to_config "config.allow_concurrency = false"
boot!
assert_includes middleware, "Rack::Lock"
- assert_not_includes middleware, "ActionDispatch::LoadInterlock"
end
test "removes static asset server if public_file_server.enabled is disabled" do
@@ -253,7 +235,7 @@ module ApplicationTests
end
end
- etag = "W/" + "5af83e3196bf99f440f31f2e1a6c9afe".inspect
+ etag = "W/" + "c00862d1c6c1cf7c1b49388306e7b3c1".inspect
get "/"
assert_equal 200, last_response.status
diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb
index 3198e12662..dfe3fc9354 100644
--- a/railties/test/application/per_request_digest_cache_test.rb
+++ b/railties/test/application/per_request_digest_cache_test.rb
@@ -29,6 +29,8 @@ class PerRequestDigestCacheTest < ActiveSupport::TestCase
app_file 'app/controllers/customers_controller.rb', <<-RUBY
class CustomersController < ApplicationController
+ self.perform_caching = true
+
def index
render [ Customer.new('david', 1), Customer.new('dingus', 2) ]
end
@@ -50,12 +52,13 @@ class PerRequestDigestCacheTest < ActiveSupport::TestCase
get '/customers'
assert_equal 200, last_response.status
- assert_equal [ '8ba099b7749542fe765ff34a6824d548' ], ActionView::Digestor.cache.values
+ values = ActionView::LookupContext::DetailsKey.digest_caches.first.values
+ assert_equal [ '8ba099b7749542fe765ff34a6824d548' ], values
assert_equal %w(david dingus), last_response.body.split.map(&:strip)
end
test "template digests are cleared before a request" do
- assert_called(ActionView::Digestor.cache, :clear) do
+ assert_called(ActionView::LookupContext::DetailsKey, :clear) do
get '/customers'
assert_equal 200, last_response.status
end
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index c000a70382..cee9db5535 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -29,11 +29,11 @@ module ApplicationTests
def db_create_and_drop(expected_database)
Dir.chdir(app_path) do
output = `bin/rails db:create`
- assert_empty output
+ assert_match(/Created database/, output)
assert File.exist?(expected_database)
assert_equal expected_database, ActiveRecord::Base.connection_config[:database]
output = `bin/rails db:drop`
- assert_empty output
+ assert_match(/Dropped database/, output)
assert !File.exist?(expected_database)
end
end
@@ -222,14 +222,14 @@ module ApplicationTests
assert_equal '["posts"]', list_tables[]
`bin/rails db:schema:load`
- assert_equal '["posts", "comments", "schema_migrations", "active_record_internal_metadatas"]', list_tables[]
+ assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata"]', list_tables[]
app_file 'db/structure.sql', <<-SQL
CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255));
SQL
`bin/rails db:structure:load`
- assert_equal '["posts", "comments", "schema_migrations", "active_record_internal_metadatas", "users"]', list_tables[]
+ assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata", "users"]', list_tables[]
end
end
diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb
new file mode 100644
index 0000000000..2330ad3535
--- /dev/null
+++ b/railties/test/application/rake/dev_test.rb
@@ -0,0 +1,43 @@
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ module RakeTests
+ class RakeDevTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test 'dev:cache creates file and outputs message' do
+ Dir.chdir(app_path) do
+ output = `rails dev:cache`
+ assert File.exist?('tmp/caching-dev.txt')
+ assert_match(/Development mode is now being cached/, output)
+ end
+ end
+
+ test 'dev:cache deletes file and outputs message' do
+ Dir.chdir(app_path) do
+ `rails dev:cache` # Create caching file.
+ output = `rails dev:cache` # Delete caching file.
+ assert_not File.exist?('tmp/caching-dev.txt')
+ assert_match(/Development mode is no longer being cached/, output)
+ end
+ end
+
+ test 'dev:cache removes server.pid also' do
+ Dir.chdir(app_path) do
+ FileUtils.mkdir_p("tmp/pids")
+ FileUtils.touch("tmp/pids/server.pid")
+ `rails dev:cache`
+ assert_not File.exist?("tmp/pids/server.pid")
+ end
+ end
+ end
+ end
+end
diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb
index 4cae199e6b..30f662a9be 100644
--- a/railties/test/application/rake/restart_test.rb
+++ b/railties/test/application/rake/restart_test.rb
@@ -34,6 +34,15 @@ module ApplicationTests
assert File.exist?('tmp/restart.txt')
end
end
+
+ test 'rake restart removes server.pid also' do
+ Dir.chdir(app_path) do
+ FileUtils.mkdir_p("tmp/pids")
+ FileUtils.touch("tmp/pids/server.pid")
+ `rake restart`
+ assert_not File.exist?("tmp/pids/server.pid")
+ end
+ end
end
end
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index b979ad64d1..badb9ecdd6 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -24,7 +24,7 @@ module ApplicationTests
assert $task_loaded
end
- def test_the_test_rake_task_is_protected_when_previous_migration_was_production
+ test "task is protected when previous migration was production" do
Dir.chdir(app_path) do
output = `bin/rails generate model product name:string;
env RAILS_ENV=production bin/rails db:create db:migrate;
@@ -118,11 +118,11 @@ module ApplicationTests
end
def test_code_statistics_sanity
- assert_match "Code LOC: 14 Test LOC: 0 Code to Test Ratio: 1:0.0",
- Dir.chdir(app_path){ `bin/rails stats` }
+ assert_match "Code LOC: 26 Test LOC: 0 Code to Test Ratio: 1:0.0",
+ Dir.chdir(app_path) { `bin/rails stats` }
end
- def test_rake_routes_calls_the_route_inspector
+ def test_rails_routes_calls_the_route_inspector
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
get '/cart', to: 'cart#show'
@@ -133,7 +133,7 @@ module ApplicationTests
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end
- def test_rake_routes_with_controller_environment
+ def test_rails_routes_with_controller_environment
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
get '/cart', to: 'cart#show'
@@ -141,12 +141,79 @@ module ApplicationTests
end
RUBY
- ENV['CONTROLLER'] = 'cart'
- output = Dir.chdir(app_path){ `bin/rails routes` }
+ output = Dir.chdir(app_path){ `bin/rails routes CONTROLLER=cart` }
+ assert_equal ["Passing `CONTROLLER` to `bin/rails routes` is deprecated and will be removed in Rails 5.1.",
+ "Please use `bin/rails routes -c controller_name` instead.",
+ "Prefix Verb URI Pattern Controller#Action",
+ " cart GET /cart(.:format) cart#show\n"].join("\n"), output
+
+ output = Dir.chdir(app_path){ `bin/rails routes -c cart` }
+ assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
+ end
+
+ def test_rails_routes_with_namespaced_controller_environment
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ namespace :admin do
+ resource :post
+ end
+ end
+ RUBY
+ expected_output = [" Prefix Verb URI Pattern Controller#Action",
+ " admin_post POST /admin/post(.:format) admin/posts#create",
+ " new_admin_post GET /admin/post/new(.:format) admin/posts#new",
+ "edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit",
+ " GET /admin/post(.:format) admin/posts#show",
+ " PATCH /admin/post(.:format) admin/posts#update",
+ " PUT /admin/post(.:format) admin/posts#update",
+ " DELETE /admin/post(.:format) admin/posts#destroy\n"].join("\n")
+
+ output = Dir.chdir(app_path){ `bin/rails routes -c Admin::PostController` }
+ assert_equal expected_output, output
+
+ output = Dir.chdir(app_path){ `bin/rails routes -c PostController` }
+ assert_equal expected_output, output
+ end
+
+ def test_rails_routes_with_global_search_key
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get '/cart', to: 'cart#show'
+ post '/cart', to: 'cart#create'
+ get '/basketballs', to: 'basketball#index'
+ end
+ RUBY
+
+ output = Dir.chdir(app_path){ `bin/rails routes -g show` }
+ assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
+
+ output = Dir.chdir(app_path){ `bin/rails routes -g POST` }
+ assert_equal "Prefix Verb URI Pattern Controller#Action\n POST /cart(.:format) cart#create\n", output
+
+ output = Dir.chdir(app_path){ `bin/rails routes -g basketballs` }
+ assert_equal " Prefix Verb URI Pattern Controller#Action\n" \
+ "basketballs GET /basketballs(.:format) basketball#index\n", output
+ end
+
+ def test_rails_routes_with_controller_search_key
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get '/cart', to: 'cart#show'
+ get '/basketball', to: 'basketball#index'
+ end
+ RUBY
+
+ output = Dir.chdir(app_path){ `bin/rails routes -c cart` }
+ assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
+
+ output = Dir.chdir(app_path){ `bin/rails routes -c Cart` }
+ assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
+
+ output = Dir.chdir(app_path){ `bin/rails routes -c CartController` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end
- def test_rake_routes_displays_message_when_no_routes_are_defined
+ def test_rails_routes_displays_message_when_no_routes_are_defined
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
end
@@ -161,6 +228,17 @@ module ApplicationTests
MESSAGE
end
+ def test_rake_routes_with_rake_options
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get '/cart', to: 'cart#show'
+ end
+ RUBY
+
+ output = Dir.chdir(app_path){ `bin/rake --rakefile Rakefile routes` }
+ assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
+ end
+
def test_logger_is_flushed_when_exiting_production_rake_tasks
add_to_config <<-RUBY
rake_tasks do
@@ -209,7 +287,7 @@ module ApplicationTests
RAILS_ENV=test bin/rails db:migrate test`
end
- assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output)
+ assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output)
assert_no_match(/Errors running/, output)
end
@@ -232,16 +310,15 @@ module ApplicationTests
assert_no_match(/Errors running/, output)
end
- 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"
-
+ def test_scaffold_with_references_columns_tests_pass_by_default
output = Dir.chdir(app_path) do
- `bin/rails generate scaffold LineItems product:references cart:belongs_to;
+ `bin/rails generate model Product;
+ bin/rails generate model Cart;
+ bin/rails generate scaffold LineItems product:references cart:belongs_to;
RAILS_ENV=test bin/rails db:migrate test`
end
- assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output)
+ assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output)
assert_no_match(/Errors running/, output)
end
@@ -303,7 +380,7 @@ module ApplicationTests
def test_copy_templates
Dir.chdir(app_path) do
- `bin/rails rails:templates:copy`
+ `bin/rails app:templates:copy`
%w(controller mailer scaffold).each do |dir|
assert File.exist?(File.join(app_path, 'lib', 'templates', 'erb', dir))
end
@@ -318,7 +395,7 @@ module ApplicationTests
app_file "template.rb", ""
output = Dir.chdir(app_path) do
- `bin/rails rails:template LOCATION=template.rb`
+ `bin/rails app:template LOCATION=template.rb`
end
assert_match(/Hello, World!/, output)
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index 0777714d35..e51f32aaed 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -42,8 +42,7 @@ module ApplicationTests
test "root takes precedence over internal welcome controller" do
app("development")
- get '/'
- assert_match %r{<h1>Getting started</h1>} , last_response.body
+ assert_welcome get('/')
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 a7eb0feb11..08759ab5a4 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -363,7 +363,7 @@ module ApplicationTests
end
RUBY
- run_test_command('test/models/account_test.rb:4:9 test/models/post_test:4:9').tap do |output|
+ run_test_command('test/models/account_test.rb:4:9 test/models/post_test.rb:4:9').tap do |output|
assert_match 'AccountTest:FirstFilter', output
assert_match 'AccountTest:SecondFilter', output
assert_match 'PostTest:FirstFilter', output
@@ -382,6 +382,54 @@ module ApplicationTests
end
end
+ def test_line_filters_trigger_only_one_runnable
+ app_file 'test/models/post_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class PostTest < ActiveSupport::TestCase
+ test 'truth' do
+ assert true
+ end
+ end
+
+ class SecondPostTest < ActiveSupport::TestCase
+ test 'truth' do
+ assert false, 'ran second runnable'
+ end
+ end
+ RUBY
+
+ # Pass seed guaranteeing failure.
+ run_test_command('test/models/post_test.rb:4 --seed 30410').tap do |output|
+ assert_no_match 'ran second runnable', output
+ assert_match '1 runs, 1 assertions', output
+ end
+ end
+
+ def test_line_filter_with_minitest_string_filter
+ app_file 'test/models/post_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class PostTest < ActiveSupport::TestCase
+ test 'by line' do
+ puts 'by line'
+ assert true
+ end
+
+ test 'by name' do
+ puts 'by name'
+ assert true
+ end
+ end
+ RUBY
+
+ run_test_command('test/models/post_test.rb:4 -n test_by_name').tap do |output|
+ assert_match 'by line', output
+ assert_match 'by name', output
+ assert_match '2 runs, 2 assertions', output
+ end
+ end
+
def test_shows_filtered_backtrace_by_default
create_backtrace_test
@@ -419,7 +467,7 @@ module ApplicationTests
create_test_file :models, 'post', pass: false
output = run_test_command('test/models/post_test.rb')
- expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth:\nwups!\n\nbin/rails test test/models/post_test.rb:6\n\n\n\n}
+ expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/models/post_test.rb:6\]:\nwups!\n\nbin/rails test test/models/post_test.rb:4\n\n\n\n}
assert_match expect, output
end
@@ -454,6 +502,40 @@ module ApplicationTests
assert_match '1 runs, 1 assertions', output
end
+ def test_pass_rake_options
+ create_test_file :models, 'account'
+ output = Dir.chdir(app_path) { `bin/rake --rakefile Rakefile --trace=stdout test` }
+
+ assert_match '1 runs, 1 assertions', output
+ assert_match 'Execute test', output
+ end
+
+ def test_rails_db_create_all_restores_db_connection
+ create_test_file :models, 'account'
+ output = Dir.chdir(app_path) { `bin/rails db:create:all db:migrate && echo ".tables" | rails dbconsole` }
+ assert_match "ar_internal_metadata", output, "tables should be dumped"
+ end
+
+ def test_rails_db_create_all_restores_db_connection_after_drop
+ create_test_file :models, 'account'
+ Dir.chdir(app_path) { `bin/rails db:create:all` } # create all to avoid warnings
+ output = Dir.chdir(app_path) { `bin/rails db:drop:all db:create:all db:migrate && echo ".tables" | rails dbconsole` }
+ assert_match "ar_internal_metadata", output, "tables should be dumped"
+ end
+
+ def test_rake_passes_TESTOPTS_to_minitest
+ create_test_file :models, 'account'
+ output = Dir.chdir(app_path) { `bin/rake test TESTOPTS=-v` }
+ assert_match "AccountTest#test_truth", output, "passing TEST= should run selected test"
+ end
+
+ def test_rake_passes_multiple_TESTOPTS_to_minitest
+ create_test_file :models, 'account'
+ output = Dir.chdir(app_path) { `bin/rake test TESTOPTS='-v --seed=1234'` }
+ assert_match "AccountTest#test_truth", output, "passing TEST= should run selected test"
+ assert_match "seed=1234", output, "passing TEST= should run selected test"
+ end
+
private
def run_test_command(arguments = 'test/unit/test_test.rb')
Dir.chdir(app_path) { `bin/rails t #{arguments}` }
diff --git a/railties/test/commands/dev_cache_test.rb b/railties/test/commands/dev_cache_test.rb
deleted file mode 100644
index 1b7a72e7fc..0000000000
--- a/railties/test/commands/dev_cache_test.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require_relative '../isolation/abstract_unit'
-
-module CommandsTests
- class DevCacheTest < ActiveSupport::TestCase
- include ActiveSupport::Testing::Isolation
-
- def setup
- build_app
- end
-
- def teardown
- teardown_app
- end
-
- test 'dev:cache creates file and outputs message' do
- Dir.chdir(app_path) do
- output = `rails dev:cache`
- assert File.exist?('tmp/caching-dev.txt')
- assert_match(%r{Development mode is now being cached}, output)
- end
- end
-
- test 'dev:cache deletes file and outputs message' do
- Dir.chdir(app_path) do
- `rails dev:cache` # Create caching file.
- output = `rails dev:cache` # Delete caching file.
- assert_not File.exist?('tmp/caching-dev.txt')
- assert_match(%r{Development mode is no longer being cached}, output)
- end
- end
- end
-end
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index 0c49bd9c53..38a1605d1f 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -54,7 +54,8 @@ class Rails::ServerTest < ActiveSupport::TestCase
def test_caching_without_option
args = []
options = Rails::Server::Options.new.parse!(args)
- assert_equal nil, options[:caching]
+ merged_options = Rails::Server.new.default_options.merge(options)
+ assert_equal nil, merged_options[:caching]
end
def test_caching_with_option
@@ -117,4 +118,18 @@ class Rails::ServerTest < ActiveSupport::TestCase
assert_equal old_default_options, server.default_options
end
end
+
+ def test_restart_command_contains_customized_options
+ original_args = ARGV.dup
+ args = ["-p", "4567"]
+ ARGV.replace args
+
+ options = Rails::Server::Options.new.parse! args
+ server = Rails::Server.new options
+ expected = "bin/rails server -p 4567"
+
+ assert_equal expected, server.default_options[:restart_cmd]
+ ensure
+ ARGV.replace original_args
+ end
end
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 3300850604..3b2b3c37d0 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -201,7 +201,7 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_rake_should_run_rake_command_with_default_env
+ def test_rails_should_run_rake_command_with_default_env
assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=development", verbose: false]) do
with_rails_env nil do
action :rake, 'log:clear'
@@ -209,13 +209,13 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_rake_with_env_option_should_run_rake_command_in_env
+ def test_rails_with_env_option_should_run_rake_command_in_env
assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do
action :rake, 'log:clear', env: 'production'
end
end
- def test_rake_with_rails_env_variable_should_run_rake_command_in_env
+ test "rails command with RAILS_ENV variable should run rake command in env" do
assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do
with_rails_env "production" do
action :rake, 'log:clear'
@@ -223,7 +223,7 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_env_option_should_win_over_rails_env_variable_when_running_rake
+ test "env option should win over RAILS_ENV variable when running rake" do
assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do
with_rails_env "staging" do
action :rake, 'log:clear', env: 'production'
@@ -231,7 +231,7 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_rake_with_sudo_option_should_run_rake_command_with_sudo
+ test "rails command with sudo option should run rake command with sudo" do
assert_called_with(generator, :run, ["sudo rake log:clear RAILS_ENV=development", verbose: false]) do
with_rails_env nil do
action :rake, 'log:clear', sudo: true
@@ -239,6 +239,44 @@ class ActionsTest < Rails::Generators::TestCase
end
end
+ test "rails command should run rails_command with default env" do
+ assert_called_with(generator, :run, ["rails log:clear RAILS_ENV=development", verbose: false]) do
+ with_rails_env nil do
+ action :rails_command, 'log:clear'
+ end
+ end
+ end
+
+ test "rails command with env option should run rails_command with same env" do
+ assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do
+ action :rails_command, 'log:clear', env: 'production'
+ end
+ end
+
+ test "rails command with RAILS_ENV variable should run rails_command in env" do
+ assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do
+ with_rails_env "production" do
+ action :rails_command, 'log:clear'
+ end
+ end
+ end
+
+ def test_env_option_should_win_over_rails_env_variable_when_running_rails
+ assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do
+ with_rails_env "staging" do
+ action :rails_command, 'log:clear', env: 'production'
+ end
+ end
+ end
+
+ test "rails command with sudo option should run rails_command with sudo" do
+ assert_called_with(generator, :run, ["sudo rails log:clear RAILS_ENV=development", verbose: false]) do
+ with_rails_env nil do
+ action :rails_command, 'log:clear', sudo: true
+ end
+ end
+ end
+
def test_capify_should_run_the_capify_command
assert_called_with(generator, :run, ['capify .', verbose: false]) do
action :capify!
diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb
index d9eb7770f3..92779452e1 100644
--- a/railties/test/generators/api_app_generator_test.rb
+++ b/railties/test/generators/api_app_generator_test.rb
@@ -52,6 +52,25 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
assert_file "app/controllers/application_controller.rb", /ActionController::API/
end
+ def test_generator_if_skip_action_cable_is_given
+ run_generator [destination_root, "--skip-action-cable"]
+ assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/
+ assert_no_file "config/cable.yml"
+ assert_no_file "app/channels"
+ assert_file "Gemfile" do |content|
+ assert_no_match(/redis/, content)
+ end
+ end
+
+ def test_generator_skips_per_form_csrf_token_and_origin_check_configs_for_api_apps
+ run_generator
+
+ assert_file "config/initializers/new_framework_defaults.rb" do |initializer_content|
+ assert_no_match(/per_form_csrf_tokens/, initializer_content)
+ assert_no_match(/forgery_protection_origin_check/, initializer_content)
+ end
+ end
+
private
def default_files
@@ -63,6 +82,8 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
app/controllers
app/mailers
app/models
+ app/views/layouts/mailer.html.erb
+ app/views/layouts/mailer.text.erb
config/environments
config/initializers
config/locales
@@ -84,15 +105,19 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
def skipped_files
%w(app/assets
app/helpers
- app/views
+ app/views/layouts/application.html.erb
config/initializers/assets.rb
config/initializers/cookies_serializer.rb
config/initializers/session_store.rb
- config/initializers/request_forgery_protection.rb
- config/initializers/per_form_csrf_tokens.rb
lib/assets
vendor/assets
test/helpers
- tmp/cache/assets)
+ tmp/cache/assets
+ public/404.html
+ public/422.html
+ public/500.html
+ public/apple-touch-icon-precomposed.png
+ public/apple-touch-icon.png
+ public/favicon.ico)
end
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 136bdd1694..058308aa13 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -27,6 +27,8 @@ DEFAULT_APP_FILES = %w(
config/initializers
config/locales
config/cable.yml
+ config/puma.rb
+ config/spring.rb
db
lib
lib/tasks
@@ -63,8 +65,8 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_assets
run_generator
- assert_file("app/views/layouts/application.html.erb", /stylesheet_link_tag\s+'application', media: 'all', 'data-turbolinks-track' => true/)
- assert_file("app/views/layouts/application.html.erb", /javascript_include_tag\s+'application', 'data-turbolinks-track' => true/)
+ assert_file("app/views/layouts/application.html.erb", /stylesheet_link_tag\s+'application', media: 'all', 'data-turbolinks-track': 'reload'/)
+ assert_file("app/views/layouts/application.html.erb", /javascript_include_tag\s+'application', 'data-turbolinks-track': 'reload'/)
assert_file("app/assets/stylesheets/application.css")
assert_file("app/assets/javascripts/application.js")
end
@@ -124,7 +126,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
# make sure we are in correct dir
FileUtils.cd(app_moved_root)
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true },
+ generator = Rails::Generators::AppGenerator.new ["rails"], [],
destination_root: app_moved_root, shell: @shell
generator.send(:app_const)
quietly { generator.send(:update_config_files) }
@@ -139,7 +141,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [app_root]
stub_rails_application(app_root) do
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell
generator.send(:app_const)
quietly { generator.send(:update_config_files) }
assert_file "myapp/config/initializers/session_store.rb", /_myapp_session/
@@ -163,80 +165,84 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [app_root]
stub_rails_application(app_root) do
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell
generator.send(:app_const)
quietly { generator.send(:update_config_files) }
assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/)
end
end
- def test_rails_update_does_not_create_callback_terminator_initializer
+ def test_rails_update_set_the_cookie_serializer_to_marshal_if_it_is_not_already_configured
app_root = File.join(destination_root, 'myapp')
run_generator [app_root]
- FileUtils.rm("#{app_root}/config/initializers/callback_terminator.rb")
+ FileUtils.rm("#{app_root}/config/initializers/cookies_serializer.rb")
stub_rails_application(app_root) do
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator = Rails::Generators::AppGenerator.new ["rails"], [], 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"
+ assert_file("#{app_root}/config/initializers/cookies_serializer.rb",
+ /Valid options are :json, :marshal, and :hybrid\.\nRails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/)
end
end
- def test_rails_update_does_not_remove_callback_terminator_initializer_if_already_present
+ def test_rails_update_dont_set_file_watcher
app_root = File.join(destination_root, 'myapp')
run_generator [app_root]
- FileUtils.touch("#{app_root}/config/initializers/callback_terminator.rb")
-
stub_rails_application(app_root) do
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator = Rails::Generators::AppGenerator.new ["rails"], [], 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"
+ assert_file "#{app_root}/config/environments/development.rb" do |content|
+ assert_match(/# config.file_watcher/, content)
+ end
end
end
- def test_rails_update_set_the_cookie_serializer_to_marchal_if_it_is_not_already_configured
+ def test_rails_update_does_not_create_new_framework_defaults_by_default
app_root = File.join(destination_root, 'myapp')
run_generator [app_root]
- FileUtils.rm("#{app_root}/config/initializers/cookies_serializer.rb")
+ FileUtils.rm("#{app_root}/config/initializers/new_framework_defaults.rb")
stub_rails_application(app_root) do
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator = Rails::Generators::AppGenerator.new ["rails"], { update: true }, destination_root: app_root, shell: @shell
generator.send(:app_const)
quietly { generator.send(:update_config_files) }
- assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/)
+
+ assert_file "#{app_root}/config/initializers/new_framework_defaults.rb" do |content|
+ assert_match(/ActiveSupport\.halt_callback_chains_on_return_false = true/, content)
+ assert_match(/Rails\.application\.config.active_record\.belongs_to_required_by_default = false/, content)
+ assert_no_match(/Rails\.application\.config\.ssl_options/, content)
+ end
end
end
- def test_rails_update_does_not_create_active_record_belongs_to_required_by_default
+ def test_rails_update_does_not_create_rack_cors
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")
-
stub_rails_application(app_root) do
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator = Rails::Generators::AppGenerator.new ["rails"], [], 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"
+ assert_no_file "#{app_root}/config/initializers/cors.rb"
end
end
- def test_rails_update_does_not_remove_active_record_belongs_to_required_by_default_if_already_present
+ def test_rails_update_does_not_remove_rack_cors_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")
+ FileUtils.touch("#{app_root}/config/initializers/cors.rb")
stub_rails_application(app_root) do
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator = Rails::Generators::AppGenerator.new ["rails"], [], 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"
+ assert_file "#{app_root}/config/initializers/cors.rb"
end
end
@@ -337,15 +343,31 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_generator_defaults_to_puma_version
+ run_generator [destination_root]
+ assert_gem "puma", "'~> 3.0'"
+ end
+
+ def test_generator_if_skip_puma_is_given
+ run_generator [destination_root, "--skip-puma"]
+ assert_no_file "config/puma.rb"
+ assert_file "Gemfile" do |content|
+ assert_no_match(/puma/, 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_no_file "app/models/application_record.rb"
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
assert_file "test/test_helper.rb" do |helper_content|
assert_no_match(/fixtures :all/, helper_content)
end
+
+ assert_file "config/initializers/new_framework_defaults.rb" do |initializer_content|
+ assert_no_match(/belongs_to_required_by_default/, initializer_content)
+ end
end
def test_generator_if_skip_action_mailer_is_given
@@ -376,9 +398,10 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content)
end
assert_file "Gemfile" do |content|
+ assert_no_match(/jquery-rails/, content)
assert_no_match(/sass-rails/, content)
assert_no_match(/uglifier/, content)
- assert_match(/coffee-rails/, content)
+ assert_no_match(/coffee-rails/, content)
end
assert_file "config/environments/development.rb" do |content|
assert_no_match(/config\.assets\.debug = true/, content)
@@ -394,33 +417,16 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--skip-action-cable"]
assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/
assert_no_file "config/cable.yml"
- assert_no_file "app/assets/javascripts/cable.coffee"
- assert_no_file "app/channels"
- assert_file "app/views/layouts/application.html.erb" do |content|
- assert_no_match(/action_cable_meta_tag/, content)
- end
- assert_file "Gemfile" do |content|
- assert_no_match(/em-hiredis/, content)
- assert_no_match(/redis/, content)
- end
- end
-
- def test_generator_if_skip_action_cable_is_given_for_an_api_app
- run_generator [destination_root, "--skip-action-cable", "--api"]
- assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/
- assert_no_file "config/cable.yml"
- assert_no_file "app/assets/javascripts/cable.coffee"
+ assert_no_file "app/assets/javascripts/cable.js"
assert_no_file "app/channels"
assert_file "Gemfile" do |content|
- assert_no_match(/em-hiredis/, content)
assert_no_match(/redis/, content)
end
end
def test_action_cable_redis_gems
run_generator
- assert_gem 'em-hiredis'
- assert_gem 'redis'
+ assert_file "Gemfile", /^# gem 'redis'/
end
def test_inclusion_of_javascript_runtime
@@ -483,6 +489,31 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_inclusion_of_listen_related_configuration_by_default
+ run_generator
+ if RbConfig::CONFIG['host_os'] =~ /darwin|linux/
+ assert_listen_related_configuration
+ else
+ assert_no_listen_related_configuration
+ end
+ end
+
+ def test_non_inclusion_of_listen_related_configuration_if_skip_listen
+ run_generator [destination_root, '--skip-listen']
+ assert_no_listen_related_configuration
+ end
+
+ def test_evented_file_update_checker_config
+ run_generator
+ assert_file 'config/environments/development.rb' do |content|
+ if RbConfig::CONFIG['host_os'] =~ /darwin|linux/
+ assert_match(/^\s*config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content)
+ else
+ assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content)
+ end
+ end
+ 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"]))
@@ -553,7 +584,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile" do |content|
assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content)
- assert_no_match(/gem 'web-console', '~> 3.0'/, content)
+ assert_no_match(/\Agem 'web-console'\z/, content)
end
end
@@ -562,7 +593,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile" do |content|
assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content)
- assert_no_match(/gem 'web-console', '~> 3.0'/, content)
+ assert_no_match(/\Agem 'web-console'\z/, content)
end
end
@@ -592,7 +623,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_spring_no_fork
jruby_skip "spring doesn't run on JRuby"
- assert_called_with(Process, :respond_to?, [:fork], returns: false) do
+ assert_called_with(Process, :respond_to?, [[:fork], [:fork]], returns: false) do
run_generator
assert_file "Gemfile" do |content|
@@ -604,6 +635,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_skip_spring
run_generator [destination_root, "--skip-spring"]
+ assert_no_file 'config/spring.rb'
assert_file "Gemfile" do |content|
assert_no_match(/spring/, content)
end
@@ -703,9 +735,8 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
sequence = ['install', 'exec spring binstub --all', 'echo ran after_bundle']
- ensure_bundler_first = -> command do
@sequence_step ||= 0
-
+ ensure_bundler_first = -> command do
assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}"
@sequence_step += 1
end
@@ -717,6 +748,8 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
end
+
+ assert_equal 3, @sequence_step
end
protected
@@ -739,4 +772,23 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile", /^\s*gem\s+["']#{gem}["']$*/
end
end
+
+ def assert_listen_related_configuration
+ assert_gem 'listen'
+ assert_gem 'spring-watcher-listen'
+
+ assert_file 'config/environments/development.rb' do |content|
+ assert_match(/^\s*config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content)
+ end
+ end
+
+ def assert_no_listen_related_configuration
+ assert_file 'Gemfile' do |content|
+ assert_no_match(/listen/, content)
+ end
+
+ assert_file 'config/environments/development.rb' do |content|
+ assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content)
+ end
+ end
end
diff --git a/railties/test/generators/channel_generator_test.rb b/railties/test/generators/channel_generator_test.rb
index c1f0c03fbf..e3edde681f 100644
--- a/railties/test/generators/channel_generator_test.rb
+++ b/railties/test/generators/channel_generator_test.rb
@@ -5,6 +5,18 @@ class ChannelGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
tests Rails::Generators::ChannelGenerator
+ def test_application_cable_skeleton_is_created
+ run_generator ['books']
+
+ assert_file "app/channels/application_cable/channel.rb" do |cable|
+ assert_match(/module ApplicationCable\n class Channel < ActionCable::Channel::Base\n/, cable)
+ end
+
+ assert_file "app/channels/application_cable/connection.rb" do |cable|
+ assert_match(/module ApplicationCable\n class Connection < ActionCable::Connection::Base\n/, cable)
+ end
+ end
+
def test_channel_is_created
run_generator ['chat']
@@ -12,8 +24,24 @@ class ChannelGeneratorTest < Rails::Generators::TestCase
assert_match(/class ChatChannel < ApplicationCable::Channel/, channel)
end
- assert_file "app/assets/javascripts/channels/chat.coffee" do |channel|
- assert_match(/App.cable.subscriptions.create "ChatChannel"/, channel)
+ assert_file "app/assets/javascripts/channels/chat.js" do |channel|
+ assert_match(/App.chat = App.cable.subscriptions.create\("ChatChannel/, channel)
+ end
+ end
+
+ def test_channel_with_multiple_actions_is_created
+ run_generator ['chat', 'speak', 'mute']
+
+ assert_file "app/channels/chat_channel.rb" do |channel|
+ assert_match(/class ChatChannel < ApplicationCable::Channel/, channel)
+ assert_match(/def speak/, channel)
+ assert_match(/def mute/, channel)
+ end
+
+ assert_file "app/assets/javascripts/channels/chat.js" do |channel|
+ assert_match(/App.chat = App.cable.subscriptions.create\("ChatChannel/, channel)
+ assert_match(/,\n\n speak/, channel)
+ assert_match(/,\n\n mute: function\(\) \{\n return this\.perform\('mute'\);\n \}\n\}\);/, channel)
end
end
@@ -24,6 +52,26 @@ class ChannelGeneratorTest < Rails::Generators::TestCase
assert_match(/class ChatChannel < ApplicationCable::Channel/, channel)
end
- assert_no_file "app/assets/javascripts/channels/chat.coffee"
+ assert_no_file "app/assets/javascripts/channels/chat.js"
+ end
+
+ def test_cable_js_is_created_if_not_present_already
+ run_generator ['chat']
+ FileUtils.rm("#{destination_root}/app/assets/javascripts/cable.js")
+ run_generator ['camp']
+
+ assert_file "app/assets/javascripts/cable.js"
+ end
+
+ def test_channel_on_revoke
+ run_generator ['chat']
+ run_generator ['chat'], behavior: :revoke
+
+ assert_no_file "app/channels/chat_channel.rb"
+ assert_no_file "app/assets/javascripts/channels/chat.js"
+
+ assert_file "app/channels/application_cable/channel.rb"
+ assert_file "app/channels/application_cable/connection.rb"
+ assert_file "app/assets/javascripts/cable.js"
end
end
diff --git a/railties/test/generators/job_generator_test.rb b/railties/test/generators/job_generator_test.rb
index 7fd8f2062f..dbff0ab704 100644
--- a/railties/test/generators/job_generator_test.rb
+++ b/railties/test/generators/job_generator_test.rb
@@ -26,4 +26,11 @@ class JobGeneratorTest < Rails::Generators::TestCase
assert_match(/queue_as :admin/, job)
end
end
+
+ def test_application_job_skeleton_is_created
+ run_generator ["refresh_counters"]
+ assert_file "app/jobs/application_job.rb" do |job|
+ assert_match(/class ApplicationJob < ActiveJob::Base/, job)
+ end
+ end
end
diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb
index f8d9ccacb4..6a4951840d 100644
--- a/railties/test/generators/mailer_generator_test.rb
+++ b/railties/test/generators/mailer_generator_test.rb
@@ -12,6 +12,12 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_no_match(/default from: "from@example.com"/, mailer)
assert_no_match(/layout :mailer_notifier/, mailer)
end
+
+ assert_file 'app/mailers/application_mailer.rb' do |mailer|
+ assert_match(/class ApplicationMailer < ActionMailer::Base/, mailer)
+ assert_match(/default from: 'from@example.com'/, mailer)
+ assert_match(/layout 'mailer'/, mailer)
+ end
end
def test_mailer_with_i18n_helper
@@ -78,6 +84,10 @@ class MailerGeneratorTest < Rails::Generators::TestCase
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
@@ -91,6 +101,10 @@ class MailerGeneratorTest < Rails::Generators::TestCase
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{<body>\n <%= yield %>\n </body>}, view)
+ end
end
def test_invokes_default_template_engine_even_with_no_action
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index 814f4c050e..6b30c40476 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -6,6 +6,14 @@ class ModelGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
arguments %w(Account name:string age:integer)
+ def test_application_record_skeleton_is_created
+ run_generator
+ assert_file "app/models/application_record.rb" do |record|
+ assert_match(/class ApplicationRecord < ActiveRecord::Base/, record)
+ assert_match(/self.abstract_class = true/, record)
+ end
+ end
+
def test_help_shows_invoked_generators_options
content = run_generator ["--help"]
assert_match(/ActiveRecord options:/, content)
@@ -26,7 +34,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_invokes_default_orm
run_generator
- assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/
+ assert_file "app/models/account.rb", /class Account < ApplicationRecord/
end
def test_model_with_parent_option
@@ -48,7 +56,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_plural_names_are_singularized
content = run_generator ["accounts".freeze]
- assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/
+ assert_file "app/models/account.rb", /class Account < ApplicationRecord/
assert_file "test/models/account_test.rb", /class AccountTest/
assert_match(/\[WARNING\] The model name 'accounts' was recognized as a plural, using the singular 'account' instead\. Override with --force-plural or setup custom inflection rules for this noun before running the generator\./, content)
end
@@ -63,7 +71,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_file "app/models/admin.rb", /module Admin/
assert_file "app/models/admin.rb", /def self\.table_name_prefix/
assert_file "app/models/admin.rb", /'admin_'/
- assert_file "app/models/admin/account.rb", /class Admin::Account < ActiveRecord::Base/
+ assert_file "app/models/admin/account.rb", /class Admin::Account < ApplicationRecord/
end
def test_migration
@@ -298,18 +306,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: \n cart: /
+ assert_file "test/fixtures/line_items.yml", /product: one\n cart: one/
assert_generated_fixture("test/fixtures/line_items.yml",
- {"one"=>{"product"=>nil, "cart"=>nil}, "two"=>{"product"=>nil, "cart"=>nil}})
+ {"one"=>{"product"=>"one", "cart"=>"one"}, "two"=>{"product"=>"two", "cart"=>"two"}})
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: \n product_type: Product\n cart: /
+ assert_file "test/fixtures/line_items.yml", /product: one\n product_type: Product\n cart: one/
assert_generated_fixture("test/fixtures/line_items.yml",
- {"one"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil},
- "two"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil}})
+ {"one"=>{"product"=>"one", "product_type"=>"Product", "cart"=>"one"},
+ "two"=>{"product"=>"two", "product_type"=>"Product", "cart"=>"two"}})
end
def test_fixtures_respect_reserved_yml_keywords
@@ -378,7 +386,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
run_generator ["account", "supplier:references{required}"]
expected_file = <<-FILE.strip_heredoc
- class Account < ActiveRecord::Base
+ class Account < ApplicationRecord
belongs_to :supplier, required: true
end
FILE
@@ -389,7 +397,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
run_generator ["account", "supplier:references{required,polymorphic}"]
expected_file = <<-FILE.strip_heredoc
- class Account < ActiveRecord::Base
+ class Account < ApplicationRecord
belongs_to :supplier, polymorphic: true, required: true
end
FILE
@@ -400,7 +408,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
run_generator ["account", "supplier:references{polymorphic.required}"]
expected_file = <<-FILE.strip_heredoc
- class Account < ActiveRecord::Base
+ class Account < ApplicationRecord
belongs_to :supplier, polymorphic: true, required: true
end
FILE
@@ -451,7 +459,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
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
+ class User < ApplicationRecord
has_secure_token
has_secure_token :auth_token
end
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index d76759a7d1..902c340321 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -91,7 +91,7 @@ class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase
def test_adds_namespace_to_model
run_generator
- assert_file "app/models/test_app/account.rb", /module TestApp/, / class Account < ActiveRecord::Base/
+ assert_file "app/models/test_app/account.rb", /module TestApp/, / class Account < ApplicationRecord/
end
def test_model_with_namespace
@@ -99,7 +99,7 @@ class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase
assert_file "app/models/test_app/admin.rb", /module TestApp/, /module Admin/
assert_file "app/models/test_app/admin.rb", /def self\.table_name_prefix/
assert_file "app/models/test_app/admin.rb", /'test_app_admin_'/
- assert_file "app/models/test_app/admin/account.rb", /module TestApp/, /class Admin::Account < ActiveRecord::Base/
+ assert_file "app/models/test_app/admin/account.rb", /module TestApp/, /class Admin::Account < ApplicationRecord/
end
def test_migration
@@ -201,7 +201,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
run_generator
# Model
- assert_file "app/models/test_app/product_line.rb", /module TestApp\n class ProductLine < ActiveRecord::Base/
+ assert_file "app/models/test_app/product_line.rb", /module TestApp\n class ProductLine < ApplicationRecord/
assert_file "test/models/test_app/product_line_test.rb", /module TestApp\n class ProductLineTest < ActiveSupport::TestCase/
assert_file "test/fixtures/test_app/product_lines.yml"
assert_migration "db/migrate/create_test_app_product_lines.rb"
@@ -268,7 +268,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
# Model
assert_file "app/models/test_app/admin.rb", /module TestApp\n module Admin/
- assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ActiveRecord::Base/
+ assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ApplicationRecord/
assert_file "test/models/test_app/admin/role_test.rb", /module TestApp\n class Admin::RoleTest < ActiveSupport::TestCase/
assert_file "test/fixtures/test_app/admin/roles.yml"
assert_migration "db/migrate/create_test_app_admin_roles.rb"
@@ -336,7 +336,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
# Model
assert_file "app/models/test_app/admin/user/special.rb", /module TestApp\n module Admin/
- assert_file "app/models/test_app/admin/user/special/role.rb", /module TestApp\n class Admin::User::Special::Role < ActiveRecord::Base/
+ assert_file "app/models/test_app/admin/user/special/role.rb", /module TestApp\n class Admin::User::Special::Role < ApplicationRecord/
assert_file "test/models/test_app/admin/user/special/role_test.rb", /module TestApp\n class Admin::User::Special::RoleTest < ActiveSupport::TestCase/
assert_file "test/fixtures/test_app/admin/user/special/roles.yml"
assert_migration "db/migrate/create_test_app_admin_user_special_roles.rb"
@@ -402,7 +402,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
# Model
assert_file "app/models/test_app/admin.rb", /module TestApp\n module Admin/
- assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ActiveRecord::Base/
+ assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ApplicationRecord/
assert_file "test/models/test_app/admin/role_test.rb", /module TestApp\n class Admin::RoleTest < ActiveSupport::TestCase/
assert_file "test/fixtures/test_app/admin/roles.yml"
assert_migration "db/migrate/create_test_app_admin_roles.rb"
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 874bda17b7..5dd4cce28a 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -240,7 +240,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--mountable"]
FileUtils.cd destination_root
quietly { system 'bundle install' }
- output = `bundle exec rake db:migrate 2>&1`
+ output = `bin/rails db:migrate 2>&1`
assert $?.success?, "Command failed: #{output}"
end
@@ -335,7 +335,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
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/controllers/hyphenated/name/application_controller.rb", /module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery with: :exception\n end\n end\nend\n/
assert_file "hyphenated-name/app/models/hyphenated/name/application_record.rb", /module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/
assert_file "hyphenated-name/app/jobs/hyphenated/name/application_job.rb", /module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
assert_file "hyphenated-name/app/mailers/hyphenated/name/application_mailer.rb", /module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\nend/
@@ -357,7 +357,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
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/controllers/my_hyphenated/name/application_controller.rb", /module MyHyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery with: :exception\n end\n end\nend\n/
assert_file "my_hyphenated-name/app/models/my_hyphenated/name/application_record.rb", /module MyHyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/
assert_file "my_hyphenated-name/app/jobs/my_hyphenated/name/application_job.rb", /module MyHyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
assert_file "my_hyphenated-name/app/mailers/my_hyphenated/name/application_mailer.rb", /module MyHyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\nend/
@@ -379,7 +379,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
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/controllers/deep/hyphenated/name/application_controller.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery with: :exception\n end\n end\n end\nend\n/
assert_file "deep-hyphenated-name/app/models/deep/hyphenated/name/application_record.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\n end\nend/
assert_file "deep-hyphenated-name/app/jobs/deep/hyphenated/name/application_job.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
assert_file "deep-hyphenated-name/app/mailers/deep/hyphenated/name/application_mailer.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\n end\nend/
@@ -444,6 +444,14 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_dummy_appplication_skip_listen_by_default
+ run_generator
+
+ assert_file 'test/dummy/config/environments/development.rb' do |contents|
+ assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, contents)
+ end
+ end
+
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"])
@@ -634,6 +642,89 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "app/models/bukkits/article.rb", /class Article < ApplicationRecord/
end
+ def test_generate_application_record_when_does_not_exist_in_mountable_engine
+ run_generator [destination_root, '--mountable']
+ FileUtils.rm "#{destination_root}/app/models/bukkits/application_record.rb"
+ capture(:stdout) do
+ `#{destination_root}/bin/rails g model article`
+ end
+
+ assert_file "#{destination_root}/app/models/bukkits/application_record.rb" do |record|
+ assert_match(/module Bukkits/, record)
+ assert_match(/class ApplicationRecord < ActiveRecord::Base/, record)
+ assert_match(/self.abstract_class = true/, record)
+ end
+ end
+
+ def test_generate_application_mailer_when_does_not_exist_in_mountable_engine
+ run_generator [destination_root, '--mountable']
+ FileUtils.rm "#{destination_root}/app/mailers/bukkits/application_mailer.rb"
+ capture(:stdout) do
+ `#{destination_root}/bin/rails g mailer User`
+ end
+
+ assert_file "#{destination_root}/app/mailers/bukkits/application_mailer.rb" do |mailer|
+ assert_match(/module Bukkits/, mailer)
+ assert_match(/class ApplicationMailer < ActionMailer::Base/, mailer)
+ end
+ end
+
+ def test_generate_mailer_layouts_when_does_not_exist_in_mountable_engine
+ run_generator [destination_root, '--mountable']
+ capture(:stdout) do
+ `#{destination_root}/bin/rails g mailer User`
+ end
+
+ assert_file "#{destination_root}/app/views/layouts/bukkits/mailer.text.erb" do |view|
+ assert_match(/<%= yield %>/, view)
+ end
+
+ assert_file "#{destination_root}/app/views/layouts/bukkits/mailer.html.erb" do |view|
+ assert_match(%r{<body>\n <%= yield %>\n </body>}, view)
+ end
+ end
+
+ def test_generate_application_job_when_does_not_exist_in_mountable_engine
+ run_generator [destination_root, '--mountable']
+ FileUtils.rm "#{destination_root}/app/jobs/bukkits/application_job.rb"
+ capture(:stdout) do
+ `#{destination_root}/bin/rails g job refresh_counters`
+ end
+
+ assert_file "#{destination_root}/app/jobs/bukkits/application_job.rb" do |record|
+ assert_match(/module Bukkits/, record)
+ assert_match(/class ApplicationJob < ActiveJob::Base/, record)
+ end
+ end
+
+ def test_after_bundle_callback
+ path = 'http://example.org/rails_template'
+ template = %{ after_bundle { run 'echo ran after_bundle' } }
+ template.instance_eval "def read; self; end" # Make the string respond to read
+
+ check_open = -> *args do
+ assert_equal [ path, 'Accept' => 'application/x-thor-template' ], args
+ template
+ end
+
+ sequence = ['install', 'echo ran after_bundle']
+ @sequence_step ||= 0
+ ensure_bundler_first = -> command do
+ assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}"
+ @sequence_step += 1
+ end
+
+ generator([destination_root], template: path).stub(:open, check_open, template) do
+ generator.stub(:bundle_command, ensure_bundler_first) do
+ generator.stub(:run, ensure_bundler_first) do
+ quietly { generator.invoke_all }
+ end
+ end
+ end
+
+ assert_equal 2, @sequence_step
+ end
+
protected
def action(*args, &block)
silence(:stdout){ generator.send(*args, &block) }
diff --git a/railties/test/generators/plugin_test_runner_test.rb b/railties/test/generators/plugin_test_runner_test.rb
index f492cd49ef..ef6359fece 100644
--- a/railties/test/generators/plugin_test_runner_test.rb
+++ b/railties/test/generators/plugin_test_runner_test.rb
@@ -63,7 +63,7 @@ class PluginTestRunnerTest < ActiveSupport::TestCase
create_test_file 'post', pass: false
output = run_test_command('test/post_test.rb')
- expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth:\nwups!\n\nbin/test (/private)?#{plugin_path}/test/post_test.rb:6}
+ expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/post_test.rb:6\]:\nwups!\n\nbin/test (/private)?#{plugin_path}/test/post_test.rb:4}
assert_match expect, output
end
diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb
index addaf83bc8..53dcfc4024 100644
--- a/railties/test/generators/resource_generator_test.rb
+++ b/railties/test/generators/resource_generator_test.rb
@@ -60,14 +60,14 @@ class ResourceGeneratorTest < Rails::Generators::TestCase
def test_plural_names_are_singularized
content = run_generator ["accounts".freeze]
- assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/
+ assert_file "app/models/account.rb", /class Account < ApplicationRecord/
assert_file "test/models/account_test.rb", /class AccountTest/
assert_match(/\[WARNING\] The model name 'accounts' was recognized as a plural, using the singular 'account' instead\. Override with --force-plural or setup custom inflection rules for this noun before running the generator\./, content)
end
def test_plural_names_can_be_forced
content = run_generator ["accounts", "--force-plural"]
- assert_file "app/models/accounts.rb", /class Accounts < ActiveRecord::Base/
+ assert_file "app/models/accounts.rb", /class Accounts < ApplicationRecord/
assert_file "test/models/accounts_test.rb", /class AccountsTest/
assert_no_match(/\[WARNING\]/, content)
end
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index c37e289f4b..736ff0b41f 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -238,8 +238,8 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
assert_file "test/controllers/users_controller_test.rb" do |content|
assert_match(/class UsersControllerTest < ActionDispatch::IntegrationTest/, content)
assert_match(/test "should get index"/, content)
- assert_match(/post users_url, params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
- assert_match(/patch user_url\(@user\), params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
+ assert_match(/post users_url, params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}, as: :json/, content)
+ assert_match(/patch user_url\(@user\), params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}, as: :json/, content)
assert_no_match(/assert_redirected_to/, content)
end
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 6f7a83cae0..bd69906b9d 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -11,7 +11,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
run_generator
# Model
- assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/
+ assert_file "app/models/product_line.rb", /class ProductLine < ApplicationRecord/
assert_file "test/models/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/
assert_file "test/fixtures/product_lines.yml"
assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product/
@@ -91,7 +91,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
run_generator %w(product_line title:string product:belongs_to user:references --api --no-template-engine --no-helper --no-assets)
# Model
- assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/
+ assert_file "app/models/product_line.rb", /class ProductLine < ApplicationRecord/
assert_file "test/models/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/
assert_file "test/fixtures/product_lines.yml"
assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product/
@@ -205,7 +205,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
# Model
assert_file "app/models/admin.rb", /module Admin/
- assert_file "app/models/admin/role.rb", /class Admin::Role < ActiveRecord::Base/
+ assert_file "app/models/admin/role.rb", /class Admin::Role < ApplicationRecord/
assert_file "test/models/admin/role_test.rb", /class Admin::RoleTest < ActiveSupport::TestCase/
assert_file "test/fixtures/admin/roles.yml"
assert_migration "db/migrate/create_admin_roles.rb"
@@ -486,9 +486,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
Dir.chdir(engine_path) do
quietly do
`bin/rails g scaffold User name:string age:integer;
- bundle exec rake db:migrate`
+ bin/rails db:migrate`
end
- assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
+ assert_match(/8 runs, 10 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
end
end
@@ -500,9 +500,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
Dir.chdir(engine_path) do
quietly do
`bin/rails g scaffold User name:string age:integer;
- bundle exec rake db:migrate`
+ bin/rails db:migrate`
end
- assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
+ assert_match(/8 runs, 10 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
end
end
@@ -514,7 +514,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
Dir.chdir(engine_path) do
quietly do
`bin/rails g scaffold User name:string age:integer;
- bundle exec rake db:migrate`
+ bin/rails db:migrate`
end
assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
end
@@ -528,7 +528,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
Dir.chdir(engine_path) do
quietly do
`bin/rails g scaffold User name:string age:integer;
- bundle exec rake db:migrate`
+ bin/rails db:migrate`
end
assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
end
diff --git a/railties/test/generators/test_runner_in_engine_test.rb b/railties/test/generators/test_runner_in_engine_test.rb
index 69906b962b..d37e261fbb 100644
--- a/railties/test/generators/test_runner_in_engine_test.rb
+++ b/railties/test/generators/test_runner_in_engine_test.rb
@@ -17,7 +17,7 @@ class TestRunnerInEngineTest < ActiveSupport::TestCase
create_test_file 'post', pass: false
output = run_test_command('test/post_test.rb')
- expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth:\nwups!\n\nbin/rails test test/post_test.rb:6}
+ expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/post_test.rb:6\]:\nwups!\n\nbin/rails test test/post_test.rb:4}
assert_match expect, output
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index df3c2ca66d..e427614dfa 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -18,6 +18,7 @@ RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..")
# These files do not require any others and are needed
# to run the tests
+require "active_support/core_ext/object/blank"
require "active_support/testing/isolation"
require "active_support/core_ext/kernel/reporting"
require 'tmpdir'
@@ -74,10 +75,12 @@ module TestHelpers
end
def assert_welcome(resp)
+ resp = Array(resp)
+
assert_equal 200, resp[0]
assert_match 'text/html', resp[1]["Content-Type"]
assert_match 'charset=utf-8', resp[1]["Content-Type"]
- assert extract_body(resp).match(/Welcome aboard/)
+ assert extract_body(resp).match(/Yay! You.*re on Rails!/)
end
def assert_success(resp)
@@ -109,7 +112,7 @@ module TestHelpers
# Delete the initializers unless requested
unless options[:initializers]
- Dir["#{app_path}/config/initializers/*.rb"].each do |initializer|
+ Dir["#{app_path}/config/initializers/**/*.rb"].each do |initializer|
File.delete(initializer)
end
end
@@ -122,7 +125,7 @@ module TestHelpers
routes = File.read("#{app_path}/config/routes.rb")
if routes =~ /(\n\s*end\s*)\Z/
File.open("#{app_path}/config/routes.rb", 'w') do |f|
- f.puts $` + "\nmatch ':controller(/:action(/:id))(.:format)', via: :all\n" + $1
+ f.puts $` + "\nActiveSupport::Deprecation.silence { match ':controller(/:action(/:id))(.:format)', via: :all }\n" + $1
end
end
@@ -270,10 +273,17 @@ module TestHelpers
end
def remove_from_config(str)
- file = "#{app_path}/config/application.rb"
+ remove_from_file("#{app_path}/config/application.rb", str)
+ end
+
+ def remove_from_env_config(env, str)
+ remove_from_file("#{app_path}/config/environments/#{env}.rb", str)
+ end
+
+ def remove_from_file(file, str)
contents = File.read(file)
- contents.sub!(/#{str}/, "")
- File.open(file, "w+") { |f| f.puts contents }
+ contents.sub!(/#{str}/, '')
+ File.write(file, contents)
end
def app_file(path, contents, mode = 'w')
@@ -302,10 +312,6 @@ module TestHelpers
end
def boot_rails
- # FIXME: shush Sass warning spam, not relevant to testing Railties
- Kernel.silence_warnings do
- require File.expand_path('../../../../load_paths', __FILE__)
- end
end
end
end
@@ -317,7 +323,6 @@ class ActiveSupport::TestCase
include ActiveSupport::Testing::Stream
self.test_order = :sorted
-
end
# Create a scope and build a fixture rails app
@@ -328,12 +333,8 @@ Module.new do
FileUtils.rm_rf(app_template_path)
FileUtils.mkdir(app_template_path)
- environment = File.expand_path('../../../../load_paths', __FILE__)
- require_environment = "-r #{environment}"
-
- `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-gemfile --no-rc`
+ `#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-gemfile --skip-listen --no-rc`
File.open("#{app_template_path}/config/boot.rb", 'w') do |f|
- f.puts "require '#{environment}'"
f.puts "require 'rails/all'"
end
end unless defined?(RAILS_ISOLATED_ENGINE)
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index c51503c2b7..2e10d63599 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -78,4 +78,10 @@ class InfoControllerTest < ActionController::TestCase
get :routes, params: { path: 'rails/info/routes.html' }
assert fuzzy_count.call == 0, 'should match optional parts of route literally'
end
+
+ test "internal routes do not have a default params[:internal] value" do
+ get :properties
+ assert_response :success
+ assert_nil @controller.params[:internal]
+ end
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 4a47ab32b4..b6fdd33898 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -481,7 +481,7 @@ YAML
end
RUBY
- add_to_config "config.middleware.use \"Bukkits\""
+ add_to_config "config.middleware.use Bukkits"
boot_rails
end
diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb
index 5f4171d44b..b85e16c040 100644
--- a/railties/test/railties/generators_test.rb
+++ b/railties/test/railties/generators_test.rb
@@ -26,11 +26,7 @@ module RailtiesTests
end
def rails(cmd)
- environment = File.expand_path('../../../../load_paths', __FILE__)
- if File.exist?("#{environment}.rb")
- require_environment = "-r #{environment}"
- end
- `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails #{cmd}`
+ `#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails #{cmd}`
end
def build_engine(is_mountable=false)
diff --git a/railties/test/test_unit/reporter_test.rb b/railties/test/test_unit/reporter_test.rb
index 2d08d4ec30..0d64b48550 100644
--- a/railties/test/test_unit/reporter_test.rb
+++ b/railties/test/test_unit/reporter_test.rb
@@ -62,7 +62,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
@reporter.record(failed_test)
@reporter.report
- expect = %r{\AF\n\nFailure:\nTestUnitReporterTest::ExampleTest#woot:\nboo\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z}
+ expect = %r{\AF\n\nFailure:\nTestUnitReporterTest::ExampleTest#woot \[[^\]]+\]:\nboo\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z}
assert_match expect, @output.string
end
@@ -79,7 +79,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
verbose.record(skipped_test)
verbose.report
- expect = %r{\ATestUnitReporterTest::ExampleTest#woot = 10\.00 s = S\n\n\nSkipped:\nTestUnitReporterTest::ExampleTest#woot:\nskipchurches, misstemples\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z}
+ expect = %r{\ATestUnitReporterTest::ExampleTest#woot = 10\.00 s = S\n\n\nSkipped:\nTestUnitReporterTest::ExampleTest#woot \[[^\]]+\]:\nskipchurches, misstemples\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z}
assert_match expect, @output.string
end
@@ -104,11 +104,22 @@ class TestUnitReporterTest < ActiveSupport::TestCase
end
end
- test "fail fast does not interrupt run errors or skips" do
+ test "fail fast interrupts run on error" do
fail_fast = Rails::TestUnitReporter.new @output, fail_fast: true
+ interrupt_raised = false
- fail_fast.record(errored_test)
- assert_no_match 'Failed tests:', @output.string
+ # Minitest passes through Interrupt, catch it manually.
+ begin
+ fail_fast.record(errored_test)
+ rescue Interrupt
+ interrupt_raised = true
+ ensure
+ assert interrupt_raised, 'Expected Interrupt to be raised.'
+ end
+ end
+
+ test "fail fast does not interrupt run skips" do
+ fail_fast = Rails::TestUnitReporter.new @output, fail_fast: true
fail_fast.record(skipped_test)
assert_no_match 'Failed tests:', @output.string
diff --git a/tasks/release.rb b/tasks/release.rb
index c7704aa865..4a1ed04478 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -43,22 +43,54 @@ directory "pkg"
raise "Could not insert PRE in #{file}" unless $1
File.open(file, 'w') { |f| f.write ruby }
+
+ if File.exist?("#{framework}/package.json")
+ Dir.chdir("#{framework}") do
+ # This "npm-ifies" the current version
+ # With npm, versions such as "5.0.0.rc1" or "5.0.0.beta1.1" are not compliant with its
+ # versioning system, so they must be transformed to "5.0.0-rc1" and "5.0.0-beta1-1" respectively.
+
+ # In essence, the code below runs through all "."s that appear in the version,
+ # and checks to see if their index in the version string is greater than or equal to 2,
+ # and if so, it will change the "." to a "-".
+
+ # Sample version transformations:
+ # irb(main):001:0> version = "5.0.1.1"
+ # => "5.0.1.1"
+ # irb(main):002:0> version.gsub(/\./).with_index { |s, i| i >= 2 ? '-' : s }
+ # => "5.0.1-1"
+ # irb(main):003:0> version = "5.0.0.rc1"
+ # => "5.0.0.rc1"
+ # irb(main):004:0> version.gsub(/\./).with_index { |s, i| i >= 2 ? '-' : s }
+ # => "5.0.0-rc1"
+ version = version.gsub(/\./).with_index { |s, i| i >= 2 ? '-' : s }
+
+ # Check if npm is installed, and raise an error if not
+ if sh 'which npm'
+ sh "npm version #{version} --no-git-tag-version"
+ else
+ raise 'You must have npm installed to release Rails.'
+ end
+ end
+ end
end
task gem => %w(update_versions pkg) do
cmd = ""
cmd << "cd #{framework} && " unless framework == "rails"
+ cmd << "bundle exec rake package && " unless framework == "rails"
cmd << "gem build #{gemspec} && mv #{framework}-#{version}.gem #{root}/pkg/"
sh cmd
end
task :build => [:clean, gem]
task :install => :build do
- sh "gem install #{gem}"
+ sh "gem install --pre #{gem}"
end
task :push => :build do
sh "gem push #{gem}"
+ sh "npm publish" if File.exist?("#{framework}/package.json")
end
end
end
@@ -133,7 +165,7 @@ namespace :all do
end
task :tag do
- sh "git tag -m '#{tag} release' #{tag}"
+ sh "git tag -s -m '#{tag} release' #{tag}"
sh "git push --tags"
end
diff --git a/tools/README.md b/tools/README.md
index 25ab798bd5..b2e7e4b0ae 100644
--- a/tools/README.md
+++ b/tools/README.md
@@ -1,7 +1,8 @@
-## Rails dev tools
+# Rails dev tools
This is a collection of utilities used for Rails internal development.
They aren't used by Rails apps directly.
* `console` drops you in irb and loads local Rails repos
* `profile` profiles `Kernel#require` to help reduce startup time
+ * `line_statistics` provides CodeTools module and LineStatistics class to count lines
diff --git a/tools/console b/tools/console
index ea995a1a54..98a848ff6b 100755
--- a/tools/console
+++ b/tools/console
@@ -1,5 +1,7 @@
#!/usr/bin/env ruby
-require File.expand_path('../../load_paths', __FILE__)
+require 'bundler'
+Bundler.setup
+
require 'rails/all'
require 'active_support/all'
require 'irb'
diff --git a/tools/test.rb b/tools/test.rb
index 70f295b554..62e0faa3db 100644
--- a/tools/test.rb
+++ b/tools/test.rb
@@ -1,5 +1,8 @@
$: << File.expand_path("test", COMPONENT_ROOT)
-require File.expand_path("../../load_paths", __FILE__)
+
+require 'bundler'
+Bundler.setup
+
require "rails/test_unit/minitest_plugin"
module Rails
diff --git a/version.rb b/version.rb
index f4d5b660dc..9c49e0655a 100644
--- a/version.rb
+++ b/version.rb
@@ -6,9 +6,9 @@ module Rails
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta1.1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end