From d6bf4de7dc2c285d4b60d0c6da1c92668b074ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 25 Jan 2024 19:51:51 +0000 Subject: [PATCH] Transform actionpack documentation to Markdown --- actionpack/lib/abstract_controller.rb | 2 + .../lib/abstract_controller/asset_paths.rb | 2 + actionpack/lib/abstract_controller/base.rb | 195 +-- actionpack/lib/abstract_controller/caching.rb | 2 + .../abstract_controller/caching/fragments.rb | 107 +- .../lib/abstract_controller/callbacks.rb | 123 +- .../lib/abstract_controller/collector.rb | 2 + .../lib/abstract_controller/deprecator.rb | 2 + actionpack/lib/abstract_controller/error.rb | 2 + actionpack/lib/abstract_controller/helpers.rb | 131 +- actionpack/lib/abstract_controller/logger.rb | 2 + .../railties/routes_helpers.rb | 2 + .../lib/abstract_controller/rendering.rb | 25 +- .../lib/abstract_controller/translation.rb | 16 +- actionpack/lib/abstract_controller/url_for.rb | 14 +- actionpack/lib/action_controller.rb | 4 +- actionpack/lib/action_controller/api.rb | 139 +- .../action_controller/api/api_rendering.rb | 2 + actionpack/lib/action_controller/base.rb | 281 ++-- actionpack/lib/action_controller/caching.rb | 27 +- .../lib/action_controller/deprecator.rb | 2 + .../lib/action_controller/form_builder.rb | 37 +- .../lib/action_controller/log_subscriber.rb | 2 + actionpack/lib/action_controller/metal.rb | 115 +- .../action_controller/metal/allow_browser.rb | 51 +- .../metal/basic_implicit_render.rb | 2 + .../metal/conditional_get.rb | 340 ++--- .../metal/content_security_policy.rb | 49 +- .../lib/action_controller/metal/cookies.rb | 6 +- .../action_controller/metal/data_streaming.rb | 117 +- .../metal/default_headers.rb | 8 +- .../metal/etag_with_flash.rb | 4 +- .../metal/etag_with_template_digest.rb | 32 +- .../lib/action_controller/metal/exceptions.rb | 20 +- .../lib/action_controller/metal/flash.rb | 22 +- .../lib/action_controller/metal/head.rb | 22 +- .../lib/action_controller/metal/helpers.rb | 124 +- .../metal/http_authentication.rb | 434 +++--- .../metal/implicit_render.rb | 32 +- .../metal/instrumentation.rb | 27 +- .../lib/action_controller/metal/live.rb | 203 +-- .../lib/action_controller/metal/logging.rb | 10 +- .../action_controller/metal/mime_responds.rb | 283 ++-- .../metal/parameter_encoding.rb | 70 +- .../action_controller/metal/params_wrapper.rb | 107 +- .../metal/permissions_policy.rb | 25 +- .../action_controller/metal/rate_limiting.rb | 50 +- .../action_controller/metal/redirecting.rb | 188 +-- .../lib/action_controller/metal/renderers.rb | 99 +- .../lib/action_controller/metal/rendering.rb | 188 +-- .../metal/request_forgery_protection.rb | 257 ++-- .../lib/action_controller/metal/rescue.rb | 20 +- .../lib/action_controller/metal/streaming.rb | 268 ++-- .../metal/strong_parameters.rb | 900 ++++++------ .../lib/action_controller/metal/testing.rb | 2 + .../lib/action_controller/metal/url_for.rb | 32 +- actionpack/lib/action_controller/railtie.rb | 2 + .../lib/action_controller/railties/helpers.rb | 2 + actionpack/lib/action_controller/renderer.rb | 78 +- .../action_controller/template_assertions.rb | 2 + actionpack/lib/action_controller/test_case.rb | 270 ++-- actionpack/lib/action_dispatch.rb | 43 +- actionpack/lib/action_dispatch/constants.rb | 2 + actionpack/lib/action_dispatch/deprecator.rb | 2 + actionpack/lib/action_dispatch/http/cache.rb | 53 +- .../http/content_disposition.rb | 2 + .../http/content_security_policy.rb | 82 +- .../action_dispatch/http/filter_parameters.rb | 14 +- .../action_dispatch/http/filter_redirect.rb | 2 + .../lib/action_dispatch/http/headers.rb | 44 +- .../action_dispatch/http/mime_negotiation.rb | 57 +- .../lib/action_dispatch/http/mime_type.rb | 42 +- .../lib/action_dispatch/http/mime_types.rb | 2 + .../lib/action_dispatch/http/parameters.rb | 20 +- .../http/permissions_policy.rb | 36 +- .../lib/action_dispatch/http/rack_cache.rb | 2 + .../lib/action_dispatch/http/request.rb | 136 +- .../lib/action_dispatch/http/response.rb | 122 +- actionpack/lib/action_dispatch/http/upload.rb | 34 +- actionpack/lib/action_dispatch/http/url.rb | 148 +- actionpack/lib/action_dispatch/journey.rb | 2 + .../lib/action_dispatch/journey/formatter.rb | 14 +- .../action_dispatch/journey/gtg/builder.rb | 7 +- .../action_dispatch/journey/gtg/simulator.rb | 2 + .../journey/gtg/transition_table.rb | 18 +- .../lib/action_dispatch/journey/nfa/dot.rb | 2 + .../lib/action_dispatch/journey/nodes/node.rb | 11 +- .../lib/action_dispatch/journey/parser.rb | 7 +- .../action_dispatch/journey/parser_extras.rb | 2 + .../action_dispatch/journey/path/pattern.rb | 5 +- .../lib/action_dispatch/journey/route.rb | 16 +- .../lib/action_dispatch/journey/router.rb | 6 +- .../action_dispatch/journey/router/utils.rb | 23 +- .../lib/action_dispatch/journey/routes.rb | 6 +- .../lib/action_dispatch/journey/scanner.rb | 6 +- .../lib/action_dispatch/journey/visitors.rb | 2 + .../lib/action_dispatch/log_subscriber.rb | 2 + .../middleware/actionable_exceptions.rb | 2 + .../action_dispatch/middleware/assume_ssl.rb | 13 +- .../action_dispatch/middleware/callbacks.rb | 4 +- .../lib/action_dispatch/middleware/cookies.rb | 223 +-- .../middleware/debug_exceptions.rb | 12 +- .../action_dispatch/middleware/debug_locks.rb | 28 +- .../action_dispatch/middleware/debug_view.rb | 2 + .../middleware/exception_wrapper.rb | 10 +- .../action_dispatch/middleware/executor.rb | 2 + .../lib/action_dispatch/middleware/flash.rb | 116 +- .../middleware/host_authorization.rb | 30 +- .../middleware/public_exceptions.rb | 14 +- .../action_dispatch/middleware/reloader.rb | 8 +- .../action_dispatch/middleware/remote_ip.rb | 149 +- .../action_dispatch/middleware/request_id.rb | 23 +- .../middleware/server_timing.rb | 6 +- .../middleware/session/abstract_store.rb | 2 + .../middleware/session/cache_store.rb | 21 +- .../middleware/session/cookie_store.rb | 53 +- .../middleware/session/mem_cache_store.rb | 10 +- .../middleware/show_exceptions.rb | 32 +- .../lib/action_dispatch/middleware/ssl.rb | 109 +- .../lib/action_dispatch/middleware/stack.rb | 21 +- .../lib/action_dispatch/middleware/static.rb | 62 +- actionpack/lib/action_dispatch/railtie.rb | 2 + .../lib/action_dispatch/request/session.rb | 44 +- .../lib/action_dispatch/request/utils.rb | 2 + actionpack/lib/action_dispatch/routing.rb | 296 ++-- .../lib/action_dispatch/routing/endpoint.rb | 2 + .../lib/action_dispatch/routing/inspector.rb | 6 +- .../lib/action_dispatch/routing/mapper.rb | 1268 ++++++++--------- .../routing/polymorphic_routes.rb | 131 +- .../action_dispatch/routing/redirection.rb | 69 +- .../lib/action_dispatch/routing/route_set.rb | 82 +- .../action_dispatch/routing/routes_proxy.rb | 10 +- .../lib/action_dispatch/routing/url_for.rb | 239 ++-- .../lib/action_dispatch/system_test_case.rb | 152 +- .../action_dispatch/system_testing/browser.rb | 6 +- .../action_dispatch/system_testing/driver.rb | 2 + .../action_dispatch/system_testing/server.rb | 2 + .../test_helpers/screenshot_helper.rb | 51 +- .../test_helpers/setup_and_teardown.rb | 2 + .../testing/assertion_response.rb | 14 +- .../lib/action_dispatch/testing/assertions.rb | 2 + .../testing/assertions/response.rb | 49 +- .../testing/assertions/routing.rb | 145 +- .../action_dispatch/testing/integration.rb | 457 +++--- .../testing/request_encoder.rb | 2 + .../action_dispatch/testing/test_process.rb | 16 +- .../action_dispatch/testing/test_request.rb | 4 +- .../action_dispatch/testing/test_response.rb | 53 +- actionpack/lib/action_pack.rb | 33 +- actionpack/lib/action_pack/gem_version.rb | 4 +- actionpack/lib/action_pack/version.rb | 4 +- 151 files changed, 5573 insertions(+), 5084 deletions(-) diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 8cb8e4378f..6b3004045b 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "action_pack" require "active_support" require "active_support/rails" diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb index f08912b556..bbfdea52bd 100644 --- a/actionpack/lib/abstract_controller/asset_paths.rb +++ b/actionpack/lib/abstract_controller/asset_paths.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module AbstractController module AssetPaths # :nodoc: extend ActiveSupport::Concern diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 3d3f03156b..8b1a0c99de 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "abstract_controller/error" require "active_support/configurable" require "active_support/descendants_tracker" @@ -26,12 +28,12 @@ def corrections # :nodoc: end end - # = Abstract Controller \Base + # # Abstract Controller Base # - # AbstractController::Base is a low-level API. Nobody should be - # using it directly, and subclasses (like ActionController::Base) are - # expected to provide their own +render+ method, since rendering means - # different things depending on the context. + # AbstractController::Base is a low-level API. Nobody should be using it + # directly, and subclasses (like ActionController::Base) are expected to provide + # their own `render` method, since rendering means different things depending on + # the context. class Base ## # Returns the body of the HTTP response sent by the controller. @@ -52,27 +54,26 @@ class << self attr_reader :abstract alias_method :abstract?, :abstract - # Define a controller as abstract. See internal_methods for more - # details. + # Define a controller as abstract. See internal_methods for more details. def abstract! @abstract = true end def inherited(klass) # :nodoc: - # Define the abstract ivar on subclasses so that we don't get - # uninitialized ivar warnings + # Define the abstract ivar on subclasses so that we don't get uninitialized ivar + # warnings unless klass.instance_variable_defined?(:@abstract) klass.instance_variable_set(:@abstract, false) end super end - # A list of all internal methods for a controller. This finds the first - # abstract superclass of a controller, and gets a list of all public - # instance methods on that abstract class. Public instance methods of - # a controller would normally be considered action methods, so methods - # declared on abstract classes are being removed. - # (ActionController::Metal and ActionController::Base are defined as abstract) + # A list of all internal methods for a controller. This finds the first abstract + # superclass of a controller, and gets a list of all public instance methods on + # that abstract class. Public instance methods of a controller would normally be + # considered action methods, so methods declared on abstract classes are being + # removed. (ActionController::Metal and ActionController::Base are defined as + # abstract) def internal_methods controller = self methods = [] @@ -85,18 +86,18 @@ def internal_methods controller.public_instance_methods(true) - methods end - # A list of method names that should be considered actions. This - # includes all public instance methods on a controller, less - # any internal methods (see internal_methods), adding back in - # any methods that are internal, but still exist on the class - # itself. + # A list of method names that should be considered actions. This includes all + # public instance methods on a controller, less any internal methods (see + # internal_methods), adding back in any methods that are internal, but still + # exist on the class itself. + # + # #### Returns + # * `Set` - A set of all methods that should be considered actions. # - # ==== Returns - # * Set - A set of all methods that should be considered actions. def action_methods @action_methods ||= begin - # All public instance methods of this class, including ancestors - # except for public instance methods of Base and its ancestors. + # All public instance methods of this class, including ancestors except for + # public instance methods of Base and its ancestors. methods = public_instance_methods(true) - internal_methods # Be sure to include shadowed public instance methods of this class. methods.concat(public_instance_methods(false)) @@ -105,23 +106,24 @@ def action_methods end end - # 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. + # 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! @action_methods = nil end # Returns the full controller name, underscored, without the ending Controller. # - # class MyApp::MyPostsController < AbstractController::Base + # class MyApp::MyPostsController < AbstractController::Base # - # end + # end # - # MyApp::MyPostsController.controller_path # => "my_app/my_posts" + # MyApp::MyPostsController.controller_path # => "my_app/my_posts" + # + # #### Returns + # * `String` # - # ==== Returns - # * String def controller_path @controller_path ||= name.delete_suffix("Controller").underscore unless anonymous? end @@ -142,12 +144,13 @@ def eager_load! # :nodoc: # Calls the action going through the entire Action Dispatch stack. # - # The actual method that is called is determined by calling - # #method_for_action. If no method can handle the action, then an - # AbstractController::ActionNotFound error is raised. + # The actual method that is called is determined by calling #method_for_action. + # If no method can handle the action, then an AbstractController::ActionNotFound + # error is raised. + # + # #### Returns + # * `self` # - # ==== Returns - # * self def process(action, ...) @_action_name = action.to_s @@ -170,31 +173,30 @@ def action_methods self.class.action_methods end - # Returns true if a method for the action is available and - # can be dispatched, false otherwise. + # Returns true if a method for the action is available and can be dispatched, + # false otherwise. # - # Notice that action_methods.include?("foo") may return - # false and available_action?("foo") returns true because - # this method considers actions that are also available - # through other means, for example, implicit render ones. + # Notice that `action_methods.include?("foo")` may return false and + # `available_action?("foo")` returns true because this method considers actions + # that are also available through other means, for example, implicit render + # ones. + # + # #### Parameters + # * `action_name` - The name of an action to be tested # - # ==== Parameters - # * action_name - The name of an action to be tested def available_action?(action_name) _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. + # 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 - # support paths, only full URLs. + # 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 support paths, only full URLs. def self.supports_path? true end @@ -204,80 +206,83 @@ def inspect # :nodoc: end private - # Returns true if the name can be considered an action because - # it has a method defined in the controller. + # Returns true if the name can be considered an action because it has a method + # defined in the controller. + # + # #### Parameters + # * `name` - The name of an action to be tested # - # ==== Parameters - # * name - The name of an action to be tested def action_method?(name) self.class.action_methods.include?(name) end - # Call the action. Override this in a subclass to modify the - # behavior around processing an action. This, and not #process, - # is the intended way to override action dispatching. + # Call the action. Override this in a subclass to modify the behavior around + # processing an action. This, and not #process, is the intended way to override + # action dispatching. # - # Notice that the first argument is the method to be dispatched - # which is *not* necessarily the same as the action name. + # Notice that the first argument is the method to be dispatched which is **not** + # necessarily the same as the action name. def process_action(...) send_action(...) end - # Actually call the method associated with the action. Override - # this method if you wish to change how action methods are called, - # not to add additional behavior around it. For example, you would - # override #send_action if you want to inject arguments into the - # method. + # Actually call the method associated with the action. Override this method if + # you wish to change how action methods are called, not to add additional + # behavior around it. For example, you would override #send_action if you want + # to inject arguments into the method. alias send_action send - # If the action name was not found, but a method called "action_missing" - # was found, #method_for_action will return "_handle_action_missing". - # This method calls #action_missing with the current action name. + # If the action name was not found, but a method called "action_missing" was + # found, #method_for_action will return "_handle_action_missing". This method + # calls #action_missing with the current action name. def _handle_action_missing(*args) action_missing(@_action_name, *args) end - # Takes an action name and returns the name of the method that will - # handle the action. + # Takes an action name and returns the name of the method that will handle the + # action. # # It checks if the action name is valid and returns false otherwise. # # See method_for_action for more information. # - # ==== Parameters - # * action_name - An action name to find a method name for + # #### Parameters + # * `action_name` - An action name to find a method name for # - # ==== Returns - # * string - The name of the method that handles the action - # * false - No valid method name could be found. - # Raise +AbstractController::ActionNotFound+. + # + # #### Returns + # * `string` - The name of the method that handles the action + # * false - No valid method name could be found. + # + # Raise `AbstractController::ActionNotFound`. def _find_action_name(action_name) _valid_action_name?(action_name) && method_for_action(action_name) end - # Takes an action name and returns the name of the method that will - # handle the action. In normal cases, this method returns the same - # name as it receives. By default, if #method_for_action receives - # a name that is not an action, it will look for an #action_missing - # method and return "_handle_action_missing" if one is found. + # Takes an action name and returns the name of the method that will handle the + # action. In normal cases, this method returns the same name as it receives. By + # default, if #method_for_action receives a name that is not an action, it will + # look for an #action_missing method and return "_handle_action_missing" if one + # is found. # - # Subclasses may override this method to add additional conditions - # that should be considered an action. For instance, an HTTP controller - # with a template matching the action name is considered to exist. + # Subclasses may override this method to add additional conditions that should + # be considered an action. For instance, an HTTP controller with a template + # matching the action name is considered to exist. # - # If you override this method to handle additional cases, you may - # also provide a method (like +_handle_method_missing+) to handle - # the case. + # If you override this method to handle additional cases, you may also provide a + # method (like `_handle_method_missing`) to handle the case. # - # If none of these conditions are true, and +method_for_action+ - # returns +nil+, an +AbstractController::ActionNotFound+ exception will be raised. + # If none of these conditions are true, and `method_for_action` returns `nil`, + # an `AbstractController::ActionNotFound` exception will be raised. # - # ==== Parameters - # * action_name - An action name to find a method name for + # #### Parameters + # * `action_name` - An action name to find a method name for + # + # + # #### Returns + # * `string` - The name of the method that handles the action + # * `nil` - No method name could be found. # - # ==== Returns - # * string - The name of the method that handles the action - # * nil - No method name could be found. def method_for_action(action_name) if action_method?(action_name) action_name diff --git a/actionpack/lib/abstract_controller/caching.rb b/actionpack/lib/abstract_controller/caching.rb index 3b6f9b1cef..3e5fc6ac3b 100644 --- a/actionpack/lib/abstract_controller/caching.rb +++ b/actionpack/lib/abstract_controller/caching.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module AbstractController module Caching extend ActiveSupport::Concern diff --git a/actionpack/lib/abstract_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb index 10433ab988..8998e204aa 100644 --- a/actionpack/lib/abstract_controller/caching/fragments.rb +++ b/actionpack/lib/abstract_controller/caching/fragments.rb @@ -1,22 +1,23 @@ # frozen_string_literal: true +# :markup: markdown + module AbstractController module Caching - # = Abstract Controller Caching \Fragments + # # Abstract Controller Caching Fragments # - # Fragment caching is used for caching various blocks within - # views without caching the entire action as a whole. This is - # useful when certain elements of an action change frequently or - # depend on complicated state while other parts rarely change or - # can be shared amongst multiple parties. The caching is done using - # the +cache+ helper available in the Action View. See + # Fragment caching is used for caching various blocks within views without + # caching the entire action as a whole. This is useful when certain elements of + # an action change frequently or depend on complicated state while other parts + # rarely change or can be shared amongst multiple parties. The caching is done + # using the `cache` helper available in the Action View. See # ActionView::Helpers::CacheHelper for more information. # - # While it's strongly recommended that you use key-based cache - # expiration (see links in CacheHelper for more information), - # it is also possible to manually expire caches. For example: + # While it's strongly recommended that you use key-based cache expiration (see + # links in CacheHelper for more information), it is also possible to manually + # expire caches. For example: # - # expire_fragment('name_of_cache') + # expire_fragment('name_of_cache') module Fragments extend ActiveSupport::Concern @@ -35,38 +36,35 @@ module Fragments end module ClassMethods - # Allows you to specify controller-wide key prefixes for - # cache fragments. Pass either a constant +value+, or a block - # which computes a value each time a cache key is generated. + # Allows you to specify controller-wide key prefixes for cache fragments. Pass + # either a constant `value`, or a block which computes a value each time a cache + # key is generated. # - # For example, you may want to prefix all fragment cache keys - # with a global version identifier, so you can easily - # invalidate all caches. + # For example, you may want to prefix all fragment cache keys with a global + # version identifier, so you can easily invalidate all caches. # - # class ApplicationController - # fragment_cache_key "v1" - # end - # - # When it's time to invalidate all fragments, simply change - # the string constant. Or, progressively roll out the cache - # invalidation using a computed value: - # - # class ApplicationController - # fragment_cache_key do - # @account.id.odd? ? "v1" : "v2" + # class ApplicationController + # fragment_cache_key "v1" + # end + # + # When it's time to invalidate all fragments, simply change the string constant. + # Or, progressively roll out the cache invalidation using a computed value: + # + # class ApplicationController + # fragment_cache_key do + # @account.id.odd? ? "v1" : "v2" + # end # end - # end def fragment_cache_key(value = nil, &key) self.fragment_cache_keys += [key || -> { value }] end end - # Given a key (as described in +expire_fragment+), returns - # a key array suitable for use in reading, writing, or expiring a - # cached fragment. All keys begin with :views, - # followed by ENV["RAILS_CACHE_ID"] or ENV["RAILS_APP_VERSION"] if set, - # followed by any controller-wide key prefix values, ending - # with the specified +key+ value. + # Given a key (as described in `expire_fragment`), returns a key array suitable + # for use in reading, writing, or expiring a cached fragment. All keys begin + # with `:views`, followed by `ENV["RAILS_CACHE_ID"]` or + # `ENV["RAILS_APP_VERSION"]` if set, followed by any controller-wide key prefix + # values, ending with the specified `key` value. def combined_fragment_cache_key(key) head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) } tail = key.is_a?(Hash) ? url_for(key).split("://").last : key @@ -77,8 +75,8 @@ def combined_fragment_cache_key(key) cache_key end - # Writes +content+ to the location signified by - # +key+ (see +expire_fragment+ for acceptable formats). + # Writes `content` to the location signified by `key` (see `expire_fragment` for + # acceptable formats). def write_fragment(key, content, options = nil) return content unless cache_configured? @@ -90,8 +88,8 @@ def write_fragment(key, content, options = nil) content end - # Reads a cached fragment from the location signified by +key+ - # (see +expire_fragment+ for acceptable formats). + # Reads a cached fragment from the location signified by `key` (see + # `expire_fragment` for acceptable formats). def read_fragment(key, options = nil) return unless cache_configured? @@ -102,8 +100,8 @@ def read_fragment(key, options = nil) end end - # Check if a cached fragment from the location signified by - # +key+ exists (see +expire_fragment+ for acceptable formats). + # Check if a cached fragment from the location signified by `key` exists (see + # `expire_fragment` for acceptable formats). def fragment_exist?(key, options = nil) return unless cache_configured? key = combined_fragment_cache_key(key) @@ -115,22 +113,21 @@ def fragment_exist?(key, options = nil) # Removes fragments from the cache. # - # +key+ can take one of three forms: + # `key` can take one of three forms: # - # * String - This would normally take the form of a path, like - # pages/45/notes. - # * Hash - Treated as an implicit call to +url_for+, like - # { controller: 'pages', action: 'notes', id: 45} - # * Regexp - Will remove any fragment that matches, so - # %r{pages/\d*/notes} might remove all notes. Make sure you - # don't use anchors in the regex (^ or $) because - # the actual filename matched looks like - # ./cache/filename/path.cache. Note: Regexp expiration is - # only supported on caches that can iterate over all keys (unlike - # memcached). + # * String - This would normally take the form of a path, like + # `pages/45/notes`. + # * Hash - Treated as an implicit call to `url_for`, like `{ controller: + # 'pages', action: 'notes', id: 45}` + # * Regexp - Will remove any fragment that matches, so `%r{pages/\d*/notes}` + # might remove all notes. Make sure you don't use anchors in the regex (`^` + # or `$`) because the actual filename matched looks like + # `./cache/filename/path.cache`. Note: Regexp expiration is only supported + # on caches that can iterate over all keys (unlike memcached). # - # +options+ is passed through to the cache store's +delete+ - # method (or delete_matched, for Regexp keys). + # + # `options` is passed through to the cache store's `delete` method (or + # `delete_matched`, for Regexp keys). def expire_fragment(key, options = nil) return unless cache_configured? key = combined_fragment_cache_key(key) unless key.is_a?(Regexp) diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 76477f96f0..cba63b2e38 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -1,29 +1,32 @@ # frozen_string_literal: true +# :markup: markdown + module AbstractController - # = Abstract Controller \Callbacks + # # Abstract Controller Callbacks # - # Abstract Controller provides hooks during the life cycle of a controller action. - # Callbacks allow you to trigger logic during this cycle. Available callbacks are: + # Abstract Controller provides hooks during the life cycle of a controller + # action. Callbacks allow you to trigger logic during this cycle. Available + # callbacks are: # - # * after_action - # * append_after_action - # * append_around_action - # * append_before_action - # * around_action - # * before_action - # * prepend_after_action - # * prepend_around_action - # * prepend_before_action - # * skip_after_action - # * skip_around_action - # * skip_before_action + # * `after_action` + # * `append_after_action` + # * `append_around_action` + # * `append_before_action` + # * `around_action` + # * `before_action` + # * `prepend_after_action` + # * `prepend_around_action` + # * `prepend_before_action` + # * `skip_after_action` + # * `skip_around_action` + # * `skip_before_action` module Callbacks extend ActiveSupport::Concern - # Uses ActiveSupport::Callbacks as the base functionality. For - # more details on the whole callback system, read the documentation - # for ActiveSupport::Callbacks. + # Uses ActiveSupport::Callbacks as the base functionality. For more details on + # the whole callback system, read the documentation for + # ActiveSupport::Callbacks. include ActiveSupport::Callbacks included do @@ -69,25 +72,24 @@ def match?(controller) end module ClassMethods - # If +:only+ or +:except+ are used, convert the options into the - # +:if+ and +:unless+ options of ActiveSupport::Callbacks. + # If `:only` or `:except` are used, convert the options into the `:if` and + # `:unless` options of ActiveSupport::Callbacks. # - # The basic idea is that :only => :index gets converted to - # :if => proc {|c| c.action_name == "index" }. + # The basic idea is that `:only => :index` gets converted to `:if => proc {|c| + # c.action_name == "index" }`. # - # Note that :only has priority over :if in case they - # are used together. + # Note that `:only` has priority over `:if` in case they are used together. # - # only: :index, if: -> { true } # the :if option will be ignored. + # only: :index, if: -> { true } # the :if option will be ignored. # - # Note that :if has priority over :except in case they - # are used together. + # Note that `:if` has priority over `:except` in case they are used together. # - # except: :index, if: -> { true } # the :except option will be ignored. + # except: :index, if: -> { true } # the :except option will be ignored. + # + # #### Options + # * `only` - The callback should be run only for this action. + # * `except` - The callback should be run for all actions except this action. # - # ==== Options - # * only - The callback should be run only for this action. - # * except - The callback should be run for all actions except this action. def _normalize_callback_options(options) _normalize_callback_option(options, :only, :if) _normalize_callback_option(options, :except, :unless) @@ -101,18 +103,20 @@ def _normalize_callback_option(options, from, to) # :nodoc: end end - # Take callback names and an optional callback proc, normalize them, - # then call the block with each callback. This allows us to abstract - # the normalization across several methods that use it. + # Take callback names and an optional callback proc, normalize them, then call + # the block with each callback. This allows us to abstract the normalization + # across several methods that use it. # - # ==== Parameters - # * callbacks - An array of callbacks, with an optional - # options hash as the last parameter. - # * block - A proc that should be added to the callbacks. + # #### Parameters + # * `callbacks` - An array of callbacks, with an optional options hash as the + # last parameter. + # * `block` - A proc that should be added to the callbacks. + # + # + # #### Block Parameters + # * `name` - The callback to be added. + # * `options` - A hash of options to be used when adding the callback. # - # ==== Block Parameters - # * name - The callback to be added. - # * options - A hash of options to be used when adding the callback. def _insert_callbacks(callbacks, block = nil) options = callbacks.extract_options! callbacks.push(block) if block @@ -131,20 +135,21 @@ def _insert_callbacks(callbacks, block = nil) # # Append a callback before actions. See _insert_callbacks for parameter details. # - # If the callback renders or redirects, the action will not run. If there - # are additional callbacks scheduled to run after that callback, they are - # also cancelled. + # If the callback renders or redirects, the action will not run. If there are + # additional callbacks scheduled to run after that callback, they are also + # cancelled. ## # :method: prepend_before_action # # :call-seq: prepend_before_action(names, block) # - # Prepend a callback before actions. See _insert_callbacks for parameter details. + # Prepend a callback before actions. See _insert_callbacks for parameter + # details. # - # If the callback renders or redirects, the action will not run. If there - # are additional callbacks scheduled to run after that callback, they are - # also cancelled. + # If the callback renders or redirects, the action will not run. If there are + # additional callbacks scheduled to run after that callback, they are also + # cancelled. ## # :method: skip_before_action @@ -160,9 +165,9 @@ def _insert_callbacks(callbacks, block = nil) # # Append a callback before actions. See _insert_callbacks for parameter details. # - # If the callback renders or redirects, the action will not run. If there - # are additional callbacks scheduled to run after that callback, they are - # also cancelled. + # If the callback renders or redirects, the action will not run. If there are + # additional callbacks scheduled to run after that callback, they are also + # cancelled. ## # :method: after_action @@ -204,7 +209,8 @@ def _insert_callbacks(callbacks, block = nil) # # :call-seq: prepend_around_action(names, block) # - # Prepend a callback around actions. See _insert_callbacks for parameter details. + # Prepend a callback around actions. See _insert_callbacks for parameter + # details. ## # :method: skip_around_action @@ -219,9 +225,8 @@ def _insert_callbacks(callbacks, block = nil) # :call-seq: append_around_action(names, block) # # Append a callback around actions. See _insert_callbacks for parameter details. - - # set up before_action, prepend_before_action, skip_before_action, etc. - # for each of before, after, and around. + # set up before_action, prepend_before_action, skip_before_action, etc. for each + # of before, after, and around. [:before, :after, :around].each do |callback| define_method "#{callback}_action" do |*names, &blk| _insert_callbacks(names, blk) do |name, options| @@ -235,8 +240,8 @@ def _insert_callbacks(callbacks, block = nil) end end - # Skip a before, after or around callback. See _insert_callbacks - # for details on the allowed parameters. + # Skip a before, after or around callback. See _insert_callbacks for details on + # the allowed parameters. define_method "skip_#{callback}_action" do |*names| _insert_callbacks(names) do |name, options| skip_callback(:process_action, callback, name, options) @@ -249,8 +254,8 @@ def _insert_callbacks(callbacks, block = nil) end private - # Override AbstractController::Base#process_action to run the - # process_action callbacks around the normal behavior. + # Override `AbstractController::Base#process_action` to run the `process_action` + # callbacks around the normal behavior. def process_action(...) run_callbacks(:process_action) do super diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb index b8c156491b..dee0b49708 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/http/mime_type" module AbstractController diff --git a/actionpack/lib/abstract_controller/deprecator.rb b/actionpack/lib/abstract_controller/deprecator.rb index 49686bd44a..989500fa13 100644 --- a/actionpack/lib/abstract_controller/deprecator.rb +++ b/actionpack/lib/abstract_controller/deprecator.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module AbstractController def self.deprecator # :nodoc: @deprecator ||= ActiveSupport::Deprecation.new diff --git a/actionpack/lib/abstract_controller/error.rb b/actionpack/lib/abstract_controller/error.rb index 88d863c719..812241bfdd 100644 --- a/actionpack/lib/abstract_controller/error.rb +++ b/actionpack/lib/abstract_controller/error.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module AbstractController class Error < StandardError # :nodoc: end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index f731772118..92de0f7f32 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/dependencies" require "active_support/core_ext/name_error" @@ -10,8 +12,8 @@ module Helpers included do class_attribute :_helper_methods, default: Array.new - # This is here so that it is always higher in the inheritance chain than - # the definition in lib/action_view/rendering.rb + # This is here so that it is always higher in the inheritance chain than the + # definition in lib/action_view/rendering.rb redefine_singleton_method(:_helpers) do if @_helpers ||= nil @_helpers @@ -60,9 +62,9 @@ def helper_modules_from_paths(paths) extend Resolution module ClassMethods - # When a class is inherited, wrap its helper module in a new module. - # This ensures that the parent class's module can be changed - # independently of the child class's. + # When a class is inherited, wrap its helper module in a new module. This + # ensures that the parent class's module can be changed independently of the + # child class's. def inherited(klass) # Inherited from parent by default klass._helpers = nil @@ -79,49 +81,48 @@ def inherited(klass) # :method: modules_for_helpers # :call-seq: modules_for_helpers(modules_or_helper_prefixes) # - # Given an array of values like the ones accepted by +helper+, this method + # Given an array of values like the ones accepted by `helper`, this method # returns an array with the corresponding modules, in the same order. # - # ActionController::Base.modules_for_helpers(["application", "chart", "rubygems"]) - # # => [ApplicationHelper, ChartHelper, RubygemsHelper] + # ActionController::Base.modules_for_helpers(["application", "chart", "rubygems"]) + # # => [ApplicationHelper, ChartHelper, RubygemsHelper] # #-- # Implemented by Resolution#modules_for_helpers. - ## - # :method: all_helpers_from_path + # :method: # all_helpers_from_path # :call-seq: all_helpers_from_path(path) # # Returns a list of helper names in a given path. # - # ActionController::Base.all_helpers_from_path 'app/helpers' - # # => ["application", "chart", "rubygems"] + # ActionController::Base.all_helpers_from_path 'app/helpers' + # # => ["application", "chart", "rubygems"] # #-- # Implemented by Resolution#all_helpers_from_path. # Declare a controller method as a helper. For example, the following - # makes the +current_user+ and +logged_in?+ controller methods available + # makes the `current_user` and `logged_in?` controller methods available # to the view: - # class ApplicationController < ActionController::Base - # helper_method :current_user, :logged_in? + # class ApplicationController < ActionController::Base + # helper_method :current_user, :logged_in? # - # private - # def current_user - # @current_user ||= User.find_by(id: session[:user]) - # end + # private + # def current_user + # @current_user ||= User.find_by(id: session[:user]) + # end # - # def logged_in? - # current_user != nil - # end - # end + # def logged_in? + # current_user != nil + # end + # end # # In a view: - # <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%> + # <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%> # - # ==== Parameters - # * method[, method] - A name or names of a method on the controller - # to be made available on the view. + # #### Parameters + # * `method[, method]` - A name or names of a method on the controller to be + # made available on the view. def helper_method(*methods) methods.flatten! self._helper_methods += methods @@ -131,7 +132,7 @@ def helper_method(*methods) methods.each do |method| # def current_user(*args, &block) - # controller.send(:'current_user', *args, &block) + # controller.send(:'current_user', *args, &block) # end _helpers_for_modification.class_eval <<~ruby_eval.lines.map(&:strip).join(";"), file, line def #{method}(...) @@ -143,54 +144,54 @@ def #{method}(...) # Includes the given modules in the template class. # - # Modules can be specified in different ways. All of the following calls - # include +FooHelper+: + # Modules can be specified in different ways. All of the following calls include + # `FooHelper`: # - # # Module, recommended. - # helper FooHelper + # # Module, recommended. + # helper FooHelper # - # # String/symbol without the "helper" suffix, camel or snake case. - # helper "Foo" - # helper :Foo - # helper "foo" - # helper :foo + # # String/symbol without the "helper" suffix, camel or snake case. + # helper "Foo" + # helper :Foo + # helper "foo" + # helper :foo # - # The last two assume that "foo".camelize returns "Foo". + # The last two assume that `"foo".camelize` returns "Foo". # - # When strings or symbols are passed, the method finds the actual module - # object using String#constantize. Therefore, if the module has not been - # yet loaded, it has to be autoloadable, which is normally the case. + # When strings or symbols are passed, the method finds the actual module object + # using String#constantize. Therefore, if the module has not been yet loaded, it + # has to be autoloadable, which is normally the case. # - # Namespaces are supported. The following calls include +Foo::BarHelper+: + # Namespaces are supported. The following calls include `Foo::BarHelper`: # - # # Module, recommended. - # helper Foo::BarHelper + # # Module, recommended. + # helper Foo::BarHelper # - # # String/symbol without the "helper" suffix, camel or snake case. - # helper "Foo::Bar" - # helper :"Foo::Bar" - # helper "foo/bar" - # helper :"foo/bar" + # # String/symbol without the "helper" suffix, camel or snake case. + # helper "Foo::Bar" + # helper :"Foo::Bar" + # helper "foo/bar" + # helper :"foo/bar" # - # The last two assume that "foo/bar".camelize returns "Foo::Bar". + # The last two assume that `"foo/bar".camelize` returns "Foo::Bar". # - # The method accepts a block too. If present, the block is evaluated in - # the context of the controller helper module. This simple call makes the - # +wadus+ method available in templates of the enclosing controller: + # The method accepts a block too. If present, the block is evaluated in the + # context of the controller helper module. This simple call makes the `wadus` + # method available in templates of the enclosing controller: # - # helper do - # def wadus - # "wadus" + # helper do + # def wadus + # "wadus" + # end # end - # end # # Furthermore, all the above styles can be mixed together: # - # helper FooHelper, "woo", "bar/baz" do - # def wadus - # "wadus" + # helper FooHelper, "woo", "bar/baz" do + # def wadus + # "wadus" + # end # end - # end # def helper(*args, &block) modules_for_helpers(args).each do |mod| @@ -201,8 +202,8 @@ def helper(*args, &block) _helpers_for_modification.module_eval(&block) if block_given? end - # Clears up all existing helpers in this class, only keeping the helper - # with the same name as this class. + # Clears up all existing helpers in this class, only keeping the helper with the + # same name as this class. def clear_helpers inherited_helper_methods = _helper_methods self._helpers = Module.new @@ -221,8 +222,8 @@ def _helpers_for_modification private def define_helpers_module(klass, helpers = nil) - # In some tests inherited is called explicitly. In that case, just - # return the module from the first time it was defined + # In some tests inherited is called explicitly. In that case, just return the + # module from the first time it was defined return klass.const_get(:HelperMethods) if klass.const_defined?(:HelperMethods, false) mod = Module.new diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb index c7ab9c7737..ba3e06ff6c 100644 --- a/actionpack/lib/abstract_controller/logger.rb +++ b/actionpack/lib/abstract_controller/logger.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/benchmarkable" module AbstractController diff --git a/actionpack/lib/abstract_controller/railties/routes_helpers.rb b/actionpack/lib/abstract_controller/railties/routes_helpers.rb index 01114bcda5..3e909512e4 100644 --- a/actionpack/lib/abstract_controller/railties/routes_helpers.rb +++ b/actionpack/lib/abstract_controller/railties/routes_helpers.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/module/introspection" module AbstractController diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 18dbb53ce0..4dd0361aff 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "abstract_controller/error" require "action_view" require "action_view/view_paths" @@ -19,9 +21,9 @@ module Rendering include ActionView::ViewPaths # Normalizes arguments and options, and then delegates to render_to_body and - # sticks the result in self.response_body. + # sticks the result in `self.response_body`. # - # Supported options depend on the underlying +render_to_body+ implementation. + # Supported options depend on the underlying `render_to_body` implementation. def render(*args, &block) options = _normalize_render(*args, &block) rendered_body = render_to_body(options) @@ -35,11 +37,11 @@ def render(*args, &block) end # Similar to #render, but only returns the rendered template as a string, - # instead of setting +self.response_body+. + # instead of setting `self.response_body`. # - # If a component extends the semantics of +response_body+ (as ActionController - # extends it to be anything that responds to the method each), this method - # needs to be overridden in order to still return a string. + # If a component extends the semantics of `response_body` (as ActionController + # extends it to be anything that responds to the method each), this method needs + # to be overridden in order to still return a string. def render_to_string(*args, &block) options = _normalize_render(*args, &block) render_to_body(options) @@ -49,15 +51,15 @@ def render_to_string(*args, &block) def render_to_body(options = {}) end - # Returns +Content-Type+ of rendered content. + # Returns `Content-Type` of rendered content. def rendered_format Mime[:text] end DEFAULT_PROTECTED_INSTANCE_VARIABLES = %i(@_action_name @_response_body @_formats @_prefixes) - # This method should return a hash with assigns. - # You can overwrite this configuration per controller. + # This method should return a hash with assigns. You can overwrite this + # configuration per controller. def view_assigns variables = instance_variables - _protected_ivars @@ -67,9 +69,8 @@ def view_assigns end private - # Normalize args by converting render "foo" to - # render action: "foo" and render "foo/bar" to - # render file: "foo/bar". + # Normalize args by converting `render "foo"` to `render action: "foo"` and + # `render "foo/bar"` to `render file: "foo/bar"`. def _normalize_args(action = nil, options = {}) # :doc: if action.respond_to?(:permitted?) if action.permitted? diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb index 20b1a80e38..86285c60fa 100644 --- a/actionpack/lib/abstract_controller/translation.rb +++ b/actionpack/lib/abstract_controller/translation.rb @@ -1,17 +1,19 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/html_safe_translation" module AbstractController module Translation - # Delegates to I18n.translate. + # Delegates to `I18n.translate`. # # When the given key starts with a period, it will be scoped by the current - # controller and action. So if you call translate(".foo") from - # PeopleController#index, it will convert the call to - # I18n.translate("people.index.foo"). This makes it less repetitive - # to translate many keys within the same controller / action and gives you a - # simple framework for scoping them consistently. + # controller and action. So if you call `translate(".foo")` from + # `PeopleController#index`, it will convert the call to + # `I18n.translate("people.index.foo")`. This makes it less repetitive to + # translate many keys within the same controller / action and gives you a simple + # framework for scoping them consistently. def translate(key, **options) if key&.start_with?(".") path = controller_path.tr("/", ".") @@ -25,7 +27,7 @@ def translate(key, **options) end alias :t :translate - # Delegates to I18n.localize. + # Delegates to `I18n.localize`. def localize(object, **options) I18n.localize(object, **options) end diff --git a/actionpack/lib/abstract_controller/url_for.rb b/actionpack/lib/abstract_controller/url_for.rb index 76267cd285..c2d2da7099 100644 --- a/actionpack/lib/abstract_controller/url_for.rb +++ b/actionpack/lib/abstract_controller/url_for.rb @@ -1,14 +1,16 @@ # frozen_string_literal: true +# :markup: markdown + module AbstractController - # = URL For + # # URL For # - # Includes +url_for+ into the host class (e.g. an abstract controller or mailer). The class - # has to provide a +RouteSet+ by implementing the _routes methods. Otherwise, an - # exception will be raised. + # Includes `url_for` into the host class (e.g. an abstract controller or + # mailer). The class has to provide a `RouteSet` by implementing the `_routes` + # methods. Otherwise, an exception will be raised. # - # Note that this module is completely decoupled from HTTP - the only requirement is a valid - # _routes implementation. + # Note that this module is completely decoupled from HTTP - the only requirement + # is a valid `_routes` implementation. module UrlFor extend ActiveSupport::Concern include ActionDispatch::Routing::UrlFor diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index ca49e288a8..434f32f08b 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true +# :markup: markdown + require "abstract_controller" require "action_dispatch" require "action_controller/deprecator" require "action_controller/metal/strong_parameters" require "action_controller/metal/exceptions" -# = Action Controller +# # Action Controller # # Action Controller is a module of Action Pack. # diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index f5ef9ed4dc..2c652df6a9 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -1,107 +1,108 @@ # frozen_string_literal: true +# :markup: markdown + require "action_view" require "action_controller" require "action_controller/log_subscriber" module ActionController - # = Action Controller \API + # # Action Controller API # - # API Controller is a lightweight version of ActionController::Base, - # created for applications that don't require all functionalities that a complete - # \Rails controller provides, allowing you to create controllers with just the - # features that you need for API only applications. + # API Controller is a lightweight version of ActionController::Base, created for + # applications that don't require all functionalities that a complete Rails + # controller provides, allowing you to create controllers with just the features + # that you need for API only applications. # - # An API Controller is different from a normal controller in the sense that - # by default it doesn't include a number of features that are usually required - # by browser access only: layouts and templates rendering, - # 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. + # An API Controller is different from a normal controller in the sense that by + # default it doesn't include a number of features that are usually required by + # browser access only: layouts and templates rendering, 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. # - # Normally, +ApplicationController+ is the only controller that inherits from - # +ActionController::API+. All other controllers in turn inherit from - # +ApplicationController+. + # Normally, `ApplicationController` is the only controller that inherits from + # `ActionController::API`. 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 + # class PostsController < ApplicationController + # def index + # posts = Post.all + # render json: posts + # end # end - # end # # Request, response, and parameters objects all work the exact same way as # ActionController::Base. # - # == Renders + # ## Renders # - # The default API Controller stack includes all renderers, which means you - # can use render :json and siblings 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 render or redirect_to in - # all actions, otherwise it will return 204 No Content. + # The default API Controller stack includes all renderers, which means you can + # use `render :json` and siblings 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 `render` or `redirect_to` in all actions, otherwise it will + # return `204 No Content`. # - # def show - # post = Post.find(params[:id]) - # render json: post - # end + # def show + # post = Post.find(params[:id]) + # render json: post + # end # - # == Redirects + # ## Redirects # # Redirects are used to move from one action to another. You can use the - # redirect_to method in your controllers in the same way as in + # `redirect_to` method in your controllers in the same way as in # ActionController::Base. For example: # - # def create - # redirect_to root_url and return if not_authorized? - # # do stuff here - # end + # def create + # redirect_to root_url and return if not_authorized? + # # do stuff here + # end # - # == Adding New Behavior + # ## Adding New Behavior # # In some scenarios you may want to add back some functionality provided by # ActionController::Base that is not present by default in - # +ActionController::API+, for instance MimeResponds. This - # module gives you the respond_to method. Adding it is quite simple, - # you just need to include the module in a specific controller or in - # +ApplicationController+ in case you want it available in your entire - # application: + # `ActionController::API`, for instance `MimeResponds`. This module gives you + # the `respond_to` method. Adding it is quite simple, you just need to include + # the module in a specific controller or in `ApplicationController` in case you + # want it available in your entire application: # - # class ApplicationController < ActionController::API - # include ActionController::MimeResponds - # end + # class ApplicationController < ActionController::API + # include ActionController::MimeResponds + # end # - # class PostsController < ApplicationController - # def index - # posts = Post.all + # class PostsController < ApplicationController + # def index + # posts = Post.all # - # respond_to do |format| - # format.json { render json: posts } - # format.xml { render xml: posts } + # respond_to do |format| + # format.json { render json: posts } + # format.xml { render xml: posts } + # end # end # end - # end # - # Make sure to check the modules included in ActionController::Base - # if you want to use any other functionality that is not provided - # by +ActionController::API+ out of the box. + # Make sure to check the modules included in ActionController::Base if you want + # to use any other functionality that is not provided by `ActionController::API` + # out of the box. class API < Metal abstract! - # Shortcut helper that returns all the ActionController::API modules except - # the ones passed as arguments: + # Shortcut helper that returns all the ActionController::API modules except the + # ones passed as arguments: # - # class MyAPIBaseController < ActionController::Metal - # ActionController::API.without_modules(:UrlFor).each do |left| - # include left + # class MyAPIBaseController < ActionController::Metal + # ActionController::API.without_modules(:UrlFor).each do |left| + # include left + # end # end - # end # - # This gives better control over what you want to exclude and makes it easier - # to create an API controller class, instead of listing the modules required + # This gives better control over what you want to exclude and makes it easier to + # create an API controller class, instead of listing the modules required # manually. def self.without_modules(*modules) modules = modules.map do |m| @@ -127,19 +128,19 @@ def self.without_modules(*modules) DefaultHeaders, Logging, - # Before callbacks should also be executed as early as possible, so - # also include them at the bottom. + # Before callbacks should also be executed as early as possible, so also include + # them at the bottom. AbstractController::Callbacks, # Append rescue at the bottom to wrap as much as possible. Rescue, - # Add instrumentations hooks at the bottom, to ensure they instrument - # all the methods properly. + # Add instrumentations hooks at the bottom, to ensure they instrument all the + # methods properly. Instrumentation, - # Params wrapper should come before instrumentation so they are - # properly showed in logs + # Params wrapper should come before instrumentation so they are properly showed + # in logs ParamsWrapper ] diff --git a/actionpack/lib/action_controller/api/api_rendering.rb b/actionpack/lib/action_controller/api/api_rendering.rb index aca5265313..dcb27315f1 100644 --- a/actionpack/lib/action_controller/api/api_rendering.rb +++ b/actionpack/lib/action_controller/api/api_rendering.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController module ApiRendering extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 565b8f442e..a9fcbe4325 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1,170 +1,205 @@ # frozen_string_literal: true +# :markup: markdown + require "action_view" require "action_controller/log_subscriber" require "action_controller/metal/params_wrapper" module ActionController - # = Action Controller \Base + # # Action Controller Base # - # Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed - # on request and then either it renders a template or redirects to another action. An action is defined as a public method - # on the controller, which will automatically be made accessible to the web-server through \Rails Routes. + # Action Controllers are the core of a web request in Rails. They are made up of + # one or more actions that are executed on request and then either it renders a + # template or redirects to another action. An action is defined as a public + # method on the controller, which will automatically be made accessible to the + # web-server through Rails Routes. # - # By default, only the ApplicationController in a \Rails application inherits from +ActionController::Base+. All other - # controllers inherit from ApplicationController. This gives you one class to configure things such as + # By default, only the ApplicationController in a Rails application inherits + # from `ActionController::Base`. All other controllers inherit from + # ApplicationController. This gives you one class to configure things such as # request forgery protection and filtering of sensitive request parameters. # # A sample controller could look like this: # - # class PostsController < ApplicationController - # def index - # @posts = Post.all + # class PostsController < ApplicationController + # def index + # @posts = Post.all + # end + # + # def create + # @post = Post.create params[:post] + # redirect_to posts_path + # end # end # - # def create - # @post = Post.create params[:post] - # redirect_to posts_path + # Actions, by default, render a template in the `app/views` directory + # corresponding to the name of the controller and action after executing code in + # the action. For example, the `index` action of the PostsController would + # render the template `app/views/posts/index.html.erb` by default after + # populating the `@posts` instance variable. + # + # Unlike index, the create action will not render a template. After performing + # its main purpose (creating a new post), it initiates a redirect instead. This + # redirect works by returning an external `302 Moved` HTTP response that takes + # the user to the index action. + # + # These two methods represent the two basic action archetypes used in Action + # Controllers: Get-and-show and do-and-redirect. Most actions are variations on + # these themes. + # + # ## Requests + # + # For every request, the router determines the value of the `controller` and + # `action` keys. These determine which controller and action are called. The + # remaining request parameters, the session (if one is available), and the full + # request with all the HTTP headers are made available to the action through + # accessor methods. Then the action is performed. + # + # The full request object is available via the request accessor and is primarily + # used to query for HTTP headers: + # + # def server_ip + # location = request.env["REMOTE_ADDR"] + # render plain: "This server hosted at #{location}" # end - # end # - # Actions, by default, render a template in the app/views directory corresponding to the name of the controller and action - # after executing code in the action. For example, the +index+ action of the PostsController would render the - # template app/views/posts/index.html.erb by default after populating the @posts instance variable. + # ## Parameters # - # Unlike index, the create action will not render a template. After performing its main purpose (creating a - # new post), it initiates a redirect instead. This redirect works by returning an external - # 302 Moved HTTP response that takes the user to the index action. + # All request parameters, whether they come from a query string in the URL or + # form data submitted through a POST request are available through the `params` + # method which returns a hash. For example, an action that was performed through + # `/posts?category=All&limit=5` will include `{ "category" => "All", "limit" => + # "5" }` in `params`. # - # These two methods represent the two basic action archetypes used in Action Controllers: Get-and-show and do-and-redirect. - # Most actions are variations on these themes. + # It's also possible to construct multi-dimensional parameter hashes by + # specifying keys using brackets, such as: # - # == Requests + # + # # - # For every request, the router determines the value of the +controller+ and +action+ keys. These determine which controller - # and action are called. The remaining request parameters, the session (if one is available), and the full request with - # all the HTTP headers are made available to the action through accessor methods. Then the action is performed. + # A request coming from a form holding these inputs will include `{ "post" => { + # "name" => "david", "address" => "hyacintvej" } }`. If the address input had + # been named `post[address][street]`, the `params` would have included `{ "post" + # => { "address" => { "street" => "hyacintvej" } } }`. There's no limit to the + # depth of the nesting. # - # The full request object is available via the request accessor and is primarily used to query for HTTP headers: + # ## Sessions # - # def server_ip - # location = request.env["REMOTE_ADDR"] - # render plain: "This server hosted at #{location}" - # end + # Sessions allow you to store objects in between requests. This is useful for + # objects that are not yet ready to be persisted, such as a Signup object + # constructed in a multi-paged process, or objects that don't change much and + # are needed all the time, such as a User object for a system that requires + # login. The session should not be used, however, as a cache for objects where + # it's likely they could be changed unknowingly. It's usually too much work to + # keep it all synchronized -- something databases already excel at. # - # == Parameters + # You can place objects in the session by using the `session` method, which + # accesses a hash: # - # All request parameters, whether they come from a query string in the URL or form data submitted through a POST request are - # available through the params method which returns a hash. For example, an action that was performed through - # /posts?category=All&limit=5 will include { "category" => "All", "limit" => "5" } in params. - # - # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as: - # - # - # - # - # A request coming from a form holding these inputs will include { "post" => { "name" => "david", "address" => "hyacintvej" } }. - # If the address input had been named post[address][street], the params would have included - # { "post" => { "address" => { "street" => "hyacintvej" } } }. There's no limit to the depth of the nesting. - # - # == Sessions - # - # Sessions allow you to store objects in between requests. This is useful for objects that are not yet ready to be persisted, - # such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such - # as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely - # they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at. - # - # You can place objects in the session by using the session method, which accesses a hash: - # - # session[:person] = Person.authenticate(user_name, password) + # session[:person] = Person.authenticate(user_name, password) # # You can retrieve it again through the same hash: # - # "Hello #{session[:person]}" + # "Hello #{session[:person]}" # - # For removing objects from the session, you can either assign a single key to +nil+: + # For removing objects from the session, you can either assign a single key to + # `nil`: # - # # removes :person from session - # session[:person] = nil + # # removes :person from session + # session[:person] = nil # - # or you can remove the entire session with +reset_session+. + # or you can remove the entire session with `reset_session`. # # By default, sessions are stored in an encrypted browser cookie (see - # ActionDispatch::Session::CookieStore). Thus the user will not be able to - # read or edit the session data. However, the user can keep a copy of the - # cookie even after it has expired, so you should avoid storing sensitive - # information in cookie-based sessions. + # ActionDispatch::Session::CookieStore). Thus the user will not be able to read + # or edit the session data. However, the user can keep a copy of the cookie even + # after it has expired, so you should avoid storing sensitive information in + # cookie-based sessions. # - # == Responses + # ## Responses # - # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response - # object is generated automatically through the use of renders and redirects and requires no user intervention. + # Each action results in a response, which holds the headers and document to be + # sent to the user's browser. The actual response object is generated + # automatically through the use of renders and redirects and requires no user + # intervention. # - # == Renders + # ## Renders # - # Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering - # of a template. Included in the Action Pack is the Action View, which enables rendering of ERB templates. It's automatically configured. - # The controller passes objects to the view by assigning instance variables: + # Action Controller sends content to the user by using one of five rendering + # methods. The most versatile and common is the rendering of a template. + # Included in the Action Pack is the Action View, which enables rendering of ERB + # templates. It's automatically configured. The controller passes objects to the + # view by assigning instance variables: # - # def show - # @post = Post.find(params[:id]) - # end + # def show + # @post = Post.find(params[:id]) + # end # # Which are then automatically available to the view: # - # Title: <%= @post.title %> + # Title: <%= @post.title %> # - # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates - # will use the manual rendering methods: + # You don't have to rely on the automated rendering. For example, actions that + # could result in the rendering of different templates will use the manual + # rendering methods: # - # def search - # @results = Search.find(params[:query]) - # case @results.count - # when 0 then render action: "no_results" - # when 1 then render action: "show" - # when 2..10 then render action: "show_many" + # def search + # @results = Search.find(params[:query]) + # case @results.count + # when 0 then render action: "no_results" + # when 1 then render action: "show" + # when 2..10 then render action: "show_many" + # end # end - # end # # Read more about writing ERB and Builder templates in ActionView::Base. # - # == Redirects + # ## Redirects # - # Redirects are used to move from one action to another. For example, after a create action, which stores a blog entry to the - # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're - # going to reuse (and redirect to) a show action that we'll assume has already been created. The code might look like this: + # Redirects are used to move from one action to another. For example, after a + # `create` action, which stores a blog entry to the database, we might like to + # show the user the new entry. Because we're following good DRY principles + # (Don't Repeat Yourself), we're going to reuse (and redirect to) a `show` + # action that we'll assume has already been created. The code might look like + # this: # - # def create - # @entry = Entry.new(params[:entry]) - # if @entry.save - # # The entry was saved correctly, redirect to show - # redirect_to action: 'show', id: @entry.id - # else - # # things didn't go so well, do something else + # def create + # @entry = Entry.new(params[:entry]) + # if @entry.save + # # The entry was saved correctly, redirect to show + # redirect_to action: 'show', id: @entry.id + # else + # # things didn't go so well, do something else + # end # end - # end # - # In this case, after saving our new entry to the database, the user is redirected to the show method, which is then executed. - # Note that this is an external HTTP-level redirection which will cause the browser to make a second request (a GET to the show action), - # and not some internal re-routing which calls both "create" and then "show" within one request. + # In this case, after saving our new entry to the database, the user is + # redirected to the `show` method, which is then executed. Note that this is an + # external HTTP-level redirection which will cause the browser to make a second + # request (a GET to the show action), and not some internal re-routing which + # calls both "create" and then "show" within one request. # - # Learn more about redirect_to and what options you have in ActionController::Redirecting. + # Learn more about `redirect_to` and what options you have in + # ActionController::Redirecting. # - # == Calling multiple redirects or renders + # ## Calling multiple redirects or renders # - # An action may contain only a single render or a single redirect. Attempting to try to do either again will result in a DoubleRenderError: + # An action may contain only a single render or a single redirect. Attempting to + # try to do either again will result in a DoubleRenderError: # - # def do_something - # redirect_to action: "elsewhere" - # render action: "overthere" # raises DoubleRenderError - # end + # def do_something + # redirect_to action: "elsewhere" + # render action: "overthere" # raises DoubleRenderError + # end # - # If you need to redirect on the condition of something, then be sure to add "and return" to halt execution. + # If you need to redirect on the condition of something, then be sure to add + # "and return" to halt execution. # - # def do_something - # redirect_to(action: "elsewhere") and return if monkeys.nil? - # render action: "overthere" # won't be called if monkeys is nil - # end + # def do_something + # redirect_to(action: "elsewhere") and return if monkeys.nil? + # render action: "overthere" # won't be called if monkeys is nil + # end # class Base < Metal abstract! @@ -172,15 +207,15 @@ class Base < Metal # Shortcut helper that returns all the modules included in # ActionController::Base except the ones passed as arguments: # - # class MyBaseController < ActionController::Metal - # ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left| - # include left + # class MyBaseController < ActionController::Metal + # ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left| + # include left + # end # end - # end # - # This gives better control over what you want to exclude and makes it - # easier to create a bare controller class, instead of listing the modules - # required manually. + # This gives better control over what you want to exclude and makes it easier to + # create a bare controller class, instead of listing the modules required + # manually. def self.without_modules(*modules) modules = modules.map do |m| m.is_a?(Symbol) ? ActionController.const_get(m) : m @@ -224,19 +259,19 @@ def self.without_modules(*modules) DefaultHeaders, Logging, - # Before callbacks should also be executed as early as possible, so - # also include them at the bottom. + # Before callbacks should also be executed as early as possible, so also include + # them at the bottom. AbstractController::Callbacks, # Append rescue at the bottom to wrap as much as possible. Rescue, - # Add instrumentations hooks at the bottom, to ensure they instrument - # all the methods properly. + # Add instrumentations hooks at the bottom, to ensure they instrument all the + # methods properly. Instrumentation, - # Params wrapper should come before instrumentation so they are - # properly showed in logs + # Params wrapper should come before instrumentation so they are properly showed + # in logs ParamsWrapper ] diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index fd6869e175..ece325a276 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -1,28 +1,31 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController - # = Action Controller \Caching + # # Action Controller Caching # - # \Caching is a cheap way of speeding up slow applications by keeping the result of - # calculations, renderings, and database calls around for subsequent requests. + # Caching is a cheap way of speeding up slow applications by keeping the result + # of calculations, renderings, and database calls around for subsequent + # requests. # # You can read more about each approach by clicking the modules below. # # Note: To turn off all caching provided by Action Controller, set - # config.action_controller.perform_caching = false + # config.action_controller.perform_caching = false # - # == \Caching stores + # ## Caching stores # - # All the caching stores from ActiveSupport::Cache are available to be used as backends - # for Action Controller caching. + # All the caching stores from ActiveSupport::Cache are available to be used as + # backends for Action Controller caching. # # Configuration examples (FileStore is the default): # - # config.action_controller.cache_store = :memory_store - # config.action_controller.cache_store = :file_store, '/path/to/cache/directory' - # config.action_controller.cache_store = :mem_cache_store, 'localhost' - # config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211') - # config.action_controller.cache_store = MyOwnStore.new('parameter') + # config.action_controller.cache_store = :memory_store + # config.action_controller.cache_store = :file_store, '/path/to/cache/directory' + # config.action_controller.cache_store = :mem_cache_store, 'localhost' + # 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 diff --git a/actionpack/lib/action_controller/deprecator.rb b/actionpack/lib/action_controller/deprecator.rb index e1169d4735..3c0ea2a2fb 100644 --- a/actionpack/lib/action_controller/deprecator.rb +++ b/actionpack/lib/action_controller/deprecator.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController def self.deprecator # :nodoc: AbstractController.deprecator diff --git a/actionpack/lib/action_controller/form_builder.rb b/actionpack/lib/action_controller/form_builder.rb index b9888a58d8..68aec5994d 100644 --- a/actionpack/lib/action_controller/form_builder.rb +++ b/actionpack/lib/action_controller/form_builder.rb @@ -1,31 +1,33 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController - # = Action Controller Form Builder + # # Action Controller Form Builder # - # Override the default form builder for all views rendered by this - # controller and any of its descendants. Accepts a subclass of + # Override the default form builder for all views rendered by this controller + # and any of its descendants. Accepts a subclass of # ActionView::Helpers::FormBuilder. # # For example, given a form builder: # - # class AdminFormBuilder < ActionView::Helpers::FormBuilder - # def special_field(name) + # class AdminFormBuilder < ActionView::Helpers::FormBuilder + # def special_field(name) + # end # end - # end # # The controller specifies a form builder as its default: # - # class AdminAreaController < ApplicationController - # default_form_builder AdminFormBuilder - # end + # class AdminAreaController < ApplicationController + # default_form_builder AdminFormBuilder + # end # - # Then in the view any form using +form_for+ will be an instance of the + # Then in the view any form using `form_for` will be an instance of the # specified form builder: # - # <%= form_for(@instance) do |builder| %> - # <%= builder.special_field(:name) %> - # <% end %> + # <%= form_for(@instance) do |builder| %> + # <%= builder.special_field(:name) %> + # <% end %> module FormBuilder extend ActiveSupport::Concern @@ -34,11 +36,12 @@ module FormBuilder end module ClassMethods - # Set the form builder to be used as the default for all forms - # in the views rendered by this controller and its subclasses. + # Set the form builder to be used as the default for all forms in the views + # rendered by this controller and its subclasses. # - # ==== Parameters - # * builder - Default form builder, an instance of ActionView::Helpers::FormBuilder + # #### Parameters + # * `builder` - Default form builder, an instance of + # ActionView::Helpers::FormBuilder def default_form_builder(builder) self._default_form_builder = builder end diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index cb05c27acf..5325827e56 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController class LogSubscriber < ActiveSupport::LogSubscriber INTERNAL_PARAMS = %w(controller action format _method only_path) diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 8e0ab8b4e5..9f2bb0eccc 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -1,17 +1,19 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/array/extract_options" require "action_dispatch/middleware/stack" module ActionController - # = Action Controller \MiddlewareStack + # # Action Controller MiddlewareStack # - # Extend ActionDispatch middleware stack to make it aware of options - # allowing the following syntax in controllers: + # Extend ActionDispatch middleware stack to make it aware of options allowing + # the following syntax in controllers: # - # class PostsController < ApplicationController - # use AuthenticationMiddleware, except: [:index, :show] - # end + # class PostsController < ApplicationController + # use AuthenticationMiddleware, except: [:index, :show] + # end # class MiddlewareStack < ActionDispatch::MiddlewareStack # :nodoc: class Middleware < ActionDispatch::MiddlewareStack::Middleware # :nodoc: @@ -60,73 +62,71 @@ def build_middleware(klass, args, block) end end - # = Action Controller \Metal + # # Action Controller Metal # - # +ActionController::Metal+ is the simplest possible controller, providing a + # `ActionController::Metal` is the simplest possible controller, providing a # valid Rack interface without the additional niceties provided by # ActionController::Base. # # A sample metal controller might look like this: # - # class HelloController < ActionController::Metal - # def index - # self.response_body = "Hello World!" + # class HelloController < ActionController::Metal + # def index + # self.response_body = "Hello World!" + # end # end - # end # - # And then to route requests to your metal controller, you would add - # something like this to config/routes.rb: + # And then to route requests to your metal controller, you would add something + # like this to `config/routes.rb`: # - # get 'hello', to: HelloController.action(:index) + # get 'hello', to: HelloController.action(:index) # - # The +action+ method returns a valid Rack application for the \Rails - # router to dispatch to. + # The `action` method returns a valid Rack application for the Rails router to + # dispatch to. # - # == \Rendering \Helpers + # ## Rendering Helpers # - # +ActionController::Metal+ by default provides no utilities for rendering + # `ActionController::Metal` by default provides no utilities for rendering # views, partials, or other responses aside from explicitly calling of - # response_body=, content_type=, and status=. To - # add the render helpers you're used to having in a normal controller, you - # can do the following: + # `response_body=`, `content_type=`, and `status=`. To add the render helpers + # you're used to having in a normal controller, you can do the following: # - # class HelloController < ActionController::Metal - # include AbstractController::Rendering - # include ActionView::Layouts - # append_view_path "#{Rails.root}/app/views" + # class HelloController < ActionController::Metal + # include AbstractController::Rendering + # include ActionView::Layouts + # append_view_path "#{Rails.root}/app/views" # - # def index - # render "hello/index" + # def index + # render "hello/index" + # end # end - # end # - # == Redirection \Helpers + # ## Redirection Helpers # # To add redirection helpers to your metal controller, do the following: # - # class HelloController < ActionController::Metal - # include ActionController::Redirecting - # include Rails.application.routes.url_helpers + # class HelloController < ActionController::Metal + # include ActionController::Redirecting + # include Rails.application.routes.url_helpers # - # def index - # redirect_to root_url + # def index + # redirect_to root_url + # end # end - # end # - # == Other \Helpers - # - # You can refer to the modules included in ActionController::Base to see - # other features you can bring into your metal controller. + # ## Other Helpers # + # You can refer to the modules included in ActionController::Base to see other + # features you can bring into your metal controller. class Metal < AbstractController::Base abstract! - # Returns the last part of the controller's name, underscored, without the ending - # Controller. For instance, +PostsController+ returns posts. - # Namespaces are left out, so +Admin::PostsController+ returns posts as well. + # Returns the last part of the controller's name, underscored, without the + # ending `Controller`. For instance, `PostsController` returns `posts`. + # Namespaces are left out, so `Admin::PostsController` returns `posts` as well. # - # ==== Returns - # * string + # #### Returns + # * `string` def self.controller_name @controller_name ||= (name.demodulize.delete_suffix("Controller").underscore unless anonymous?) end @@ -172,7 +172,7 @@ def controller_name ## # The ActionDispatch::Request::Session instance for the current request. # See further details in the - # {Active Controller Session guide}[https://guides.rubyonrails.org/action_controller_overview.html#session]. + # [Active Controller Session guide](https://guides.rubyonrails.org/action_controller_overview.html#session). delegate :session, to: "@_request" ## @@ -201,7 +201,7 @@ def params=(val) alias :response_code :status # :nodoc: - # Basic \url_for that can be overridden for more robust functionality. + # Basic url_for that can be overridden for more robust functionality. def url_for(string) string end @@ -238,7 +238,8 @@ def set_response!(response) # :nodoc: @_response = response end - # Assign the response and mark it as committed. No further processing will occur. + # Assign the response and mark it as committed. No further processing will + # occur. def response=(response) set_response!(response) @@ -271,15 +272,15 @@ def use(...) # The middleware stack used by this controller. # - # By default uses a variation of ActionDispatch::MiddlewareStack which - # allows for the following syntax: + # By default uses a variation of ActionDispatch::MiddlewareStack which allows + # for the following syntax: # - # class PostsController < ApplicationController - # use AuthenticationMiddleware, except: [:index, :show] - # end + # class PostsController < ApplicationController + # use AuthenticationMiddleware, except: [:index, :show] + # end # - # Read more about {Rails middleware - # stack}[https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack] + # Read more about [Rails middleware stack] + # (https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack) # in the guides. def self.middleware middleware_stack @@ -300,8 +301,8 @@ def self.action(name) end end - # Direct dispatch to the controller. Instantiates the controller, then - # executes the action named +name+. + # Direct dispatch to the controller. Instantiates the controller, then executes + # the action named `name`. def self.dispatch(name, req, res) if middleware_stack.any? middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env diff --git a/actionpack/lib/action_controller/metal/allow_browser.rb b/actionpack/lib/action_controller/metal/allow_browser.rb index 7375b426ae..152dda8748 100644 --- a/actionpack/lib/action_controller/metal/allow_browser.rb +++ b/actionpack/lib/action_controller/metal/allow_browser.rb @@ -1,41 +1,48 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController # :nodoc: module AllowBrowser extend ActiveSupport::Concern module ClassMethods - # Specify the browser versions that will be allowed to access all actions (or some, as limited by only: or except:). - # Only browsers matched in the hash or named set passed to versions: will be blocked if they're below the versions specified. - # This means that all other browsers, as well as agents that aren't reporting a user-agent header, will be allowed access. + # Specify the browser versions that will be allowed to access all actions (or + # some, as limited by `only:` or `except:`). Only browsers matched in the hash + # or named set passed to `versions:` will be blocked if they're below the + # versions specified. This means that all other browsers, as well as agents that + # aren't reporting a user-agent header, will be allowed access. # - # A browser that's blocked will by default be served the file in public/426.html with a HTTP status code of "426 Upgrade Required". + # A browser that's blocked will by default be served the file in public/426.html + # with a HTTP status code of "426 Upgrade Required". # - # In addition to specifically named browser versions, you can also pass :modern as the set to restrict support to browsers - # natively supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. - # This includes Safari 17.2+, Chrome 119+, Firefox 121+, Opera 104+. + # In addition to specifically named browser versions, you can also pass + # `:modern` as the set to restrict support to browsers natively supporting webp + # images, web push, badges, import maps, CSS nesting, and CSS :has. This + # includes Safari 17.2+, Chrome 119+, Firefox 121+, Opera 104+. # - # You can use https://caniuse.com to check for browser versions supporting the features you use. + # You can use https://caniuse.com to check for browser versions supporting the + # features you use. # - # You can use +ActiveSupport::Notifications+ to subscribe to events of browsers being blocked using the +browser_block.action_controller+ - # event name. + # You can use `ActiveSupport::Notifications` to subscribe to events of browsers + # being blocked using the `browser_block.action_controller` event name. # # Examples: # - # class ApplicationController < ActionController::Base - # # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting + :has - # allow_browser versions: :modern - # end + # class ApplicationController < ActionController::Base + # # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting + :has + # allow_browser versions: :modern + # end # - # class ApplicationController < ActionController::Base - # # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+. - # allow_browser versions: { safari: 16.4, firefox: 121, ie: false } - # end + # class ApplicationController < ActionController::Base + # # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+. + # allow_browser versions: { safari: 16.4, firefox: 121, ie: false } + # end # - # class MessagesController < ApplicationController - # # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action. - # allow_browser versions: { opera: 104, chrome: 119 }, only: :show - # end + # class MessagesController < ApplicationController + # # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action. + # allow_browser versions: { opera: 104, chrome: 119 }, only: :show + # end def allow_browser(versions:, block: -> { render file: Rails.root.join("public/426.html"), layout: false, status: :upgrade_required }, **options) before_action -> { allow_browser(versions: versions, block: block) }, **options end diff --git a/actionpack/lib/action_controller/metal/basic_implicit_render.rb b/actionpack/lib/action_controller/metal/basic_implicit_render.rb index 15413317b8..08230e93f9 100644 --- a/actionpack/lib/action_controller/metal/basic_implicit_render.rb +++ b/actionpack/lib/action_controller/metal/basic_implicit_render.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController module BasicImplicitRender # :nodoc: def send_action(method, *args) diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 824dd45441..7c4ea8f8b3 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/object/try" require "active_support/core_ext/integer/time" @@ -14,116 +16,118 @@ module ConditionalGet end module ClassMethods - # Allows you to consider additional controller-wide information when generating an ETag. - # For example, if you serve pages tailored depending on who's logged in at the moment, you - # may want to add the current user id to be part of the ETag to prevent unauthorized displaying - # of cached pages. + # Allows you to consider additional controller-wide information when generating + # an ETag. For example, if you serve pages tailored depending on who's logged in + # at the moment, you may want to add the current user id to be part of the ETag + # to prevent unauthorized displaying of cached pages. # - # class InvoicesController < ApplicationController - # etag { current_user&.id } + # class InvoicesController < ApplicationController + # etag { current_user&.id } # - # def show - # # Etag will differ even for the same invoice when it's viewed by a different current_user - # @invoice = Invoice.find(params[:id]) - # fresh_when etag: @invoice + # def show + # # Etag will differ even for the same invoice when it's viewed by a different current_user + # @invoice = Invoice.find(params[:id]) + # fresh_when etag: @invoice + # end # end - # end def etag(&etagger) self.etaggers += [etagger] end end - # Sets the +etag+, +last_modified+, or both on the response, and renders a - # 304 Not Modified response if the request is already fresh. + # Sets the `etag`, `last_modified`, or both on the response, and renders a `304 + # Not Modified` response if the request is already fresh. # - # ==== Options + # #### Options # - # [+:etag+] - # Sets a "weak" ETag validator on the response. See the +:weak_etag+ option. - # [+:weak_etag+] - # Sets a "weak" ETag validator on the response. Requests that specify an - # +If-None-Match+ header may receive a 304 Not Modified response - # if the ETag matches exactly. + # `:etag` + # : Sets a "weak" ETag validator on the response. See the `:weak_etag` option. + # `:weak_etag` + # : Sets a "weak" ETag validator on the response. Requests that specify an + # `If-None-Match` header may receive a `304 Not Modified` response if the + # ETag matches 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. - # [+:strong_etag+] - # Sets a "strong" ETag validator on the response. Requests that specify an - # +If-None-Match+ header may receive a 304 Not Modified response - # if the ETag matches 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. + # `:strong_etag` + # : Sets a "strong" ETag validator on the response. Requests that specify an + # `If-None-Match` header may receive a `304 Not Modified` response if the + # ETag matches exactly. # - # A strong ETag implies exact equality -- the response must match byte for - # byte. This is necessary for serving +Range+ requests within a large - # video or PDF file, for example, or for compatibility with some CDNs that - # don't support weak ETags. - # [+:last_modified+] - # Sets a "weak" last-update validator on the response. Subsequent requests - # that specify an +If-Modified-Since+ header may receive a 304 Not Modified - # response if +last_modified+ <= +If-Modified-Since+. - # [+:public+] - # By default the +Cache-Control+ header is private. Set this option to - # +true+ if you want your application to be cacheable by other devices, - # such as proxy caches. - # [+:cache_control+] - # When given, will overwrite an existing +Cache-Control+ header. For a - # list of +Cache-Control+ directives, see the {article on - # MDN}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control]. - # [+:template+] - # By default, the template digest for the current controller/action is - # included in ETags. If the action renders a different template, you can - # include its digest instead. If the action doesn't render a template at - # all, you can pass template: false to skip any attempt to check - # for a template digest. + # A strong ETag implies exact equality -- the response must match byte for + # byte. This is necessary for serving `Range` requests within a large video + # or PDF file, for example, or for compatibility with some CDNs that don't + # support weak ETags. + # `:last_modified` + # : Sets a "weak" last-update validator on the response. Subsequent requests + # that specify an `If-Modified-Since` header may receive a `304 Not + # Modified` response if `last_modified` <= `If-Modified-Since`. + # `:public` + # : By default the `Cache-Control` header is private. Set this option to + # `true` if you want your application to be cacheable by other devices, such + # as proxy caches. + # `:cache_control` + # : When given, will overwrite an existing `Cache-Control` header. For a list + # of `Cache-Control` directives, see the [article on + # MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Contr + # ol). + # `:template` + # : By default, the template digest for the current controller/action is + # included in ETags. If the action renders a different template, you can + # include its digest instead. If the action doesn't render a template at + # all, you can pass `template: false` to skip any attempt to check for a + # template digest. # - # ==== Examples # - # def show - # @article = Article.find(params[:id]) - # fresh_when(etag: @article, last_modified: @article.updated_at, public: true) - # end + # #### Examples # - # This will send a 304 Not Modified response if the request - # specifies a matching ETag and +If-Modified-Since+ header. Otherwise, it - # will render the +show+ template. + # def show + # @article = Article.find(params[:id]) + # fresh_when(etag: @article, last_modified: @article.updated_at, public: true) + # end + # + # This will send a `304 Not Modified` response if the request specifies a + # matching ETag and `If-Modified-Since` header. Otherwise, it will render the + # `show` template. # # You can also just pass a record: # - # def show - # @article = Article.find(params[:id]) - # fresh_when(@article) - # end + # def show + # @article = Article.find(params[:id]) + # fresh_when(@article) + # end # - # +etag+ will be set to the record, and +last_modified+ will be set to the - # record's +updated_at+. + # `etag` will be set to the record, and `last_modified` will be set to the + # record's `updated_at`. # - # You can also pass an object that responds to +maximum+, such as a - # collection of records: + # You can also pass an object that responds to `maximum`, such as a collection + # of records: # - # def index - # @articles = Article.all - # fresh_when(@articles) - # end + # def index + # @articles = Article.all + # fresh_when(@articles) + # end # - # In this case, +etag+ will be set to the collection, and +last_modified+ - # will be set to maximum(:updated_at) (the timestamp of the most - # recently updated record). + # In this case, `etag` will be set to the collection, and `last_modified` will + # be set to `maximum(:updated_at)` (the timestamp of the most recently updated + # record). # - # When passing a record or a collection, you can still specify other - # options, such as +:public+ and +:cache_control+: + # When passing a record or a collection, you can still specify other options, + # such as `:public` and `:cache_control`: # - # def show - # @article = Article.find(params[:id]) - # fresh_when(@article, public: true, cache_control: { no_cache: true }) - # end + # def show + # @article = Article.find(params[:id]) + # fresh_when(@article, public: true, cache_control: { no_cache: true }) + # end # - # The above will set Cache-Control: public, no-cache in the response. + # The above will set `Cache-Control: public, no-cache` in the response. # # When rendering a different template than the controller/action's default # template, you can indicate which digest to include in the ETag: # - # before_action { fresh_when @article, template: "widgets/show" } + # before_action { fresh_when @article, template: "widgets/show" } # def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil) response.cache_control.delete(:no_store) @@ -145,131 +149,132 @@ def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_m head :not_modified if request.fresh?(response) end - # Sets the +etag+ and/or +last_modified+ on the response and checks them - # against the request. If the request doesn't match the provided options, it - # is considered stale, and the response should be rendered from scratch. - # Otherwise, it is fresh, and a 304 Not Modified is sent. + # Sets the `etag` and/or `last_modified` on the response and checks them against + # the request. If the request doesn't match the provided options, it is + # considered stale, and the response should be rendered from scratch. Otherwise, + # it is fresh, and a `304 Not Modified` is sent. # - # ==== Options + # #### Options # # See #fresh_when for supported options. # - # ==== Examples + # #### Examples # - # def show - # @article = Article.find(params[:id]) + # def show + # @article = Article.find(params[:id]) # - # if stale?(etag: @article, last_modified: @article.updated_at) - # @statistics = @article.really_expensive_call - # respond_to do |format| - # # all the supported formats + # if stale?(etag: @article, last_modified: @article.updated_at) + # @statistics = @article.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end # end # end - # end # # You can also just pass a record: # - # def show - # @article = Article.find(params[:id]) + # def show + # @article = Article.find(params[:id]) # - # if stale?(@article) - # @statistics = @article.really_expensive_call - # respond_to do |format| - # # all the supported formats + # if stale?(@article) + # @statistics = @article.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end # end # end - # end # - # +etag+ will be set to the record, and +last_modified+ will be set to the - # record's +updated_at+. + # `etag` will be set to the record, and `last_modified` will be set to the + # record's `updated_at`. # - # You can also pass an object that responds to +maximum+, such as a - # collection of records: + # You can also pass an object that responds to `maximum`, such as a collection + # of records: # - # def index - # @articles = Article.all + # def index + # @articles = Article.all # - # if stale?(@articles) - # @statistics = @articles.really_expensive_call - # respond_to do |format| - # # all the supported formats + # if stale?(@articles) + # @statistics = @articles.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end # end # end - # end # - # In this case, +etag+ will be set to the collection, and +last_modified+ - # will be set to maximum(:updated_at) (the timestamp of the most - # recently updated record). + # In this case, `etag` will be set to the collection, and `last_modified` will + # be set to `maximum(:updated_at)` (the timestamp of the most recently updated + # record). # - # When passing a record or a collection, you can still specify other - # options, such as +:public+ and +:cache_control+: + # When passing a record or a collection, you can still specify other options, + # such as `:public` and `:cache_control`: # - # def show - # @article = Article.find(params[:id]) + # def show + # @article = Article.find(params[:id]) # - # if stale?(@article, public: true, cache_control: { no_cache: true }) - # @statistics = @articles.really_expensive_call - # respond_to do |format| - # # all the supported formats + # if stale?(@article, public: true, cache_control: { no_cache: true }) + # @statistics = @articles.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end # end # end - # end # - # The above will set Cache-Control: public, no-cache in the response. + # The above will set `Cache-Control: public, no-cache` in the response. # # When rendering a different template than the controller/action's default # template, you can indicate which digest to include in the ETag: # - # def show - # super if stale?(@article, template: "widgets/show") - # end + # def show + # super if stale?(@article, template: "widgets/show") + # end # def stale?(object = nil, **freshness_kwargs) fresh_when(object, **freshness_kwargs) !request.fresh?(response) end - # Sets the +Cache-Control+ header, overwriting existing directives. This - # method will also ensure an HTTP +Date+ header for client compatibility. + # Sets the `Cache-Control` header, overwriting existing directives. This method + # will also ensure an HTTP `Date` header for client compatibility. # - # Defaults to issuing the +private+ directive, so that intermediate caches - # must not cache the response. + # Defaults to issuing the `private` directive, so that intermediate caches must + # not cache the response. # - # ==== Options + # #### Options # - # [+:public+] - # If true, replaces the default +private+ directive with the +public+ - # directive. - # [+:must_revalidate+] - # If true, adds the +must-revalidate+ directive. - # [+:stale_while_revalidate+] - # Sets the value of the +stale-while-revalidate+ directive. - # [+:stale_if_error+] - # Sets the value of the +stale-if-error+ directive. + # `:public` + # : If true, replaces the default `private` directive with the `public` + # directive. + # `:must_revalidate` + # : If true, adds the `must-revalidate` directive. + # `:stale_while_revalidate` + # : Sets the value of the `stale-while-revalidate` directive. + # `:stale_if_error` + # : Sets the value of the `stale-if-error` directive. # - # Any additional key-value pairs are concatenated as directives. For a list - # of supported +Cache-Control+ directives, see the {article on - # MDN}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control]. # - # ==== Examples + # Any additional key-value pairs are concatenated as directives. For a list of + # supported `Cache-Control` directives, see the [article on + # MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). # - # expires_in 10.minutes - # # => Cache-Control: max-age=600, private + # #### Examples # - # expires_in 10.minutes, public: true - # # => Cache-Control: max-age=600, public + # expires_in 10.minutes + # # => Cache-Control: max-age=600, private # - # expires_in 10.minutes, public: true, must_revalidate: true - # # => Cache-Control: max-age=600, public, must-revalidate + # expires_in 10.minutes, public: true + # # => Cache-Control: max-age=600, public # - # expires_in 1.hour, stale_while_revalidate: 60.seconds - # # => Cache-Control: max-age=3600, private, stale-while-revalidate=60 + # expires_in 10.minutes, public: true, must_revalidate: true + # # => Cache-Control: max-age=600, public, must-revalidate # - # expires_in 1.hour, stale_if_error: 5.minutes - # # => Cache-Control: max-age=3600, private, stale-if-error=300 + # expires_in 1.hour, stale_while_revalidate: 60.seconds + # # => Cache-Control: max-age=3600, private, stale-while-revalidate=60 # - # expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true - # # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true + # expires_in 1.hour, stale_if_error: 5.minutes + # # => Cache-Control: max-age=3600, private, stale-if-error=300 + # + # expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true + # # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true # def expires_in(seconds, options = {}) response.cache_control.delete(:no_store) @@ -286,8 +291,8 @@ def expires_in(seconds, options = {}) response.date = Time.now unless response.date? end - # Sets an HTTP 1.1 +Cache-Control+ header of no-cache. This means the - # resource will be marked as stale, so clients must always revalidate. + # Sets an HTTP 1.1 `Cache-Control` header of `no-cache`. This means the resource + # will be marked as stale, so clients must always revalidate. # Intermediate/browser caches may still store the asset. def expires_now response.cache_control.replace(no_cache: true) @@ -295,12 +300,13 @@ def expires_now # Cache or yield the block. The cache is supposed to never expire. # - # You can use this method when you have an HTTP response that never changes, - # and the browser and proxies should cache it indefinitely. + # 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. # - # * +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. def http_cache_forever(public: false) expires_in 100.years, public: public @@ -309,8 +315,8 @@ def http_cache_forever(public: false) public: public) end - # Sets an HTTP 1.1 +Cache-Control+ header of no-store. This means the - # resource may not be stored in any cache. + # Sets an HTTP 1.1 `Cache-Control` header of `no-store`. This means the resource + # may not be stored in any cache. def no_store response.cache_control.replace(no_store: true) end diff --git a/actionpack/lib/action_controller/metal/content_security_policy.rb b/actionpack/lib/action_controller/metal/content_security_policy.rb index 16bfa2f3cd..6af6706c1c 100644 --- a/actionpack/lib/action_controller/metal/content_security_policy.rb +++ b/actionpack/lib/action_controller/metal/content_security_policy.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController # :nodoc: module ContentSecurityPolicy extend ActiveSupport::Concern @@ -13,29 +15,28 @@ module ContentSecurityPolicy end module ClassMethods - # Overrides parts of the globally configured +Content-Security-Policy+ - # header: + # Overrides parts of the globally configured `Content-Security-Policy` header: # - # class PostsController < ApplicationController - # content_security_policy do |policy| - # policy.base_uri "https://www.example.com" + # class PostsController < ApplicationController + # content_security_policy do |policy| + # policy.base_uri "https://www.example.com" + # end # end - # end # - # Options can be passed similar to +before_action+. For example, pass - # only: :index to override the header on the index action only: + # Options can be passed similar to `before_action`. For example, pass `only: + # :index` to override the header on the index action only: # - # class PostsController < ApplicationController - # content_security_policy(only: :index) do |policy| - # policy.default_src :self, :https + # class PostsController < ApplicationController + # content_security_policy(only: :index) do |policy| + # policy.default_src :self, :https + # end # end - # end # - # Pass +false+ to remove the +Content-Security-Policy+ header: + # Pass `false` to remove the `Content-Security-Policy` header: # - # class PostsController < ApplicationController - # content_security_policy false, only: :index - # end + # class PostsController < ApplicationController + # content_security_policy false, only: :index + # end def content_security_policy(enabled = true, **options, &block) before_action(options) do if block_given? @@ -50,18 +51,18 @@ def content_security_policy(enabled = true, **options, &block) end end - # Overrides the globally configured +Content-Security-Policy-Report-Only+ + # Overrides the globally configured `Content-Security-Policy-Report-Only` # header: # - # class PostsController < ApplicationController - # content_security_policy_report_only only: :index - # end + # class PostsController < ApplicationController + # content_security_policy_report_only only: :index + # end # - # Pass +false+ to remove the +Content-Security-Policy-Report-Only+ header: + # Pass `false` to remove the `Content-Security-Policy-Report-Only` header: # - # class PostsController < ApplicationController - # content_security_policy_report_only false, only: :index - # end + # class PostsController < ApplicationController + # content_security_policy_report_only false, only: :index + # end def content_security_policy_report_only(report_only = true, **options) before_action(options) do request.content_security_policy_report_only = report_only diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb index 17b51d215f..a738532c18 100644 --- a/actionpack/lib/action_controller/metal/cookies.rb +++ b/actionpack/lib/action_controller/metal/cookies.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController # :nodoc: module Cookies extend ActiveSupport::Concern @@ -9,8 +11,8 @@ module Cookies end private - # The cookies for the current request. See ActionDispatch::Cookies for - # more information. + # The cookies for the current request. See ActionDispatch::Cookies for more + # information. def cookies # :doc: request.cookie_jar end diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index c4b0337c20..714745ef63 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true +# :markup: markdown + require "action_controller/metal/exceptions" require "action_dispatch/http/content_disposition" module ActionController # :nodoc: - # = Action Controller Data \Streaming + # # Action Controller Data Streaming # # Methods for sending arbitrary data and for streaming files to the browser, # instead of rendering. @@ -17,57 +19,60 @@ module DataStreaming DEFAULT_SEND_FILE_DISPOSITION = "attachment" # :nodoc: private - # Sends the file. This uses a server-appropriate method (such as +X-Sendfile+) - # via the +Rack::Sendfile+ middleware. The header to use is set via - # +config.action_dispatch.x_sendfile_header+. - # Your server can also configure this for you by setting the +X-Sendfile-Type+ header. + # Sends the file. This uses a server-appropriate method (such as `X-Sendfile`) + # via the `Rack::Sendfile` middleware. The header to use is set via + # `config.action_dispatch.x_sendfile_header`. Your server can also configure + # this for you by setting the `X-Sendfile-Type` header. # - # Be careful to sanitize the path parameter if it is coming from a web - # page. send_file(params[:path]) allows a malicious user to - # download any file on your server. + # Be careful to sanitize the path parameter if it is coming from a web page. + # `send_file(params[:path])` allows a malicious user to download any file on + # your server. # # Options: - # * :filename - suggests a filename for the browser to use. - # Defaults to File.basename(path). - # * :type - specifies an HTTP content type. - # You can specify either a string or a symbol for a registered type with Mime::Type.register, for example +:json+. - # If omitted, the type will be inferred from the file extension specified in :filename. - # If no content type is registered for the extension, the default type +application/octet-stream+ will be used. - # * :disposition - specifies whether the file will be shown inline or downloaded. - # Valid values are "inline" and "attachment" (default). - # * :status - specifies the status code to send with the response. Defaults to 200. - # * :url_based_filename - 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 :filename overrides this option). + # * `:filename` - suggests a filename for the browser to use. Defaults to + # `File.basename(path)`. + # * `:type` - specifies an HTTP content type. You can specify either a string + # or a symbol for a registered type with `Mime::Type.register`, for example + # `:json`. If omitted, the type will be inferred from the file extension + # specified in `:filename`. If no content type is registered for the + # extension, the default type `application/octet-stream` will be used. + # * `:disposition` - specifies whether the file will be shown inline or + # downloaded. Valid values are `"inline"` and `"attachment"` (default). + # * `:status` - specifies the status code to send with the response. Defaults + # to 200. + # * `:url_based_filename` - 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 `:filename` overrides this option). # - # The default +Content-Type+ and +Content-Disposition+ headers are - # set to download arbitrary binary files in as many browsers as - # possible. IE versions 4, 5, 5.5, and 6 are all known to have - # a variety of quirks (especially when downloading over SSL). + # + # The default `Content-Type` and `Content-Disposition` headers are set to + # download arbitrary binary files in as many browsers as possible. IE versions + # 4, 5, 5.5, and 6 are all known to have a variety of quirks (especially when + # downloading over SSL). # # Simple download: # - # send_file '/path/to.zip' + # send_file '/path/to.zip' # # Show a JPEG in the browser: # - # send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline' + # send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline' # # Show a 404 page in the browser: # - # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404 + # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404 # - # You can use other Content-* HTTP headers to provide additional - # information to the client. See MDN for a - # {list of HTTP headers}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers]. + # You can use other `Content-*` HTTP headers to provide additional information + # to the client. See MDN for a [list of HTTP + # headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers). # - # Also be aware that the document may be cached by proxies and browsers. - # The +Pragma+ and +Cache-Control+ headers declare how the file may be cached - # by intermediaries. They default to require clients to validate with - # the server before releasing cached responses. See - # https://www.mnot.net/cache_docs/ for an overview of web caching and - # {RFC 9111}[https://www.rfc-editor.org/rfc/rfc9111.html#name-cache-control] - # for the +Cache-Control+ header spec. + # Also be aware that the document may be cached by proxies and browsers. The + # `Pragma` and `Cache-Control` headers declare how the file may be cached by + # intermediaries. They default to require clients to validate with the server + # before releasing cached responses. See https://www.mnot.net/cache_docs/ for an + # overview of web caching and [RFC + # 9111](https://www.rfc-editor.org/rfc/rfc9111.html#name-cache-control) for the + # `Cache-Control` header spec. def send_file(path, options = {}) # :doc: raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path) @@ -79,35 +84,39 @@ def send_file(path, options = {}) # :doc: response.send_file path end - # Sends the given binary data to the browser. This method is similar to - # render plain: data, 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 file name, and other things. + # Sends the given binary data to the browser. This method is similar to `render + # plain: data`, 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 file name, and other + # things. # # Options: - # * :filename - suggests a filename for the browser to use. - # * :type - specifies an HTTP content type. Defaults to +application/octet-stream+. - # You can specify either a string or a symbol for a registered type with Mime::Type.register, for example +:json+. - # If omitted, type will be inferred from the file extension specified in :filename. - # If no content type is registered for the extension, the default type +application/octet-stream+ will be used. - # * :disposition - specifies whether the file will be shown inline or downloaded. - # Valid values are "inline" and "attachment" (default). - # * :status - specifies the status code to send with the response. Defaults to 200. + # * `:filename` - suggests a filename for the browser to use. + # * `:type` - specifies an HTTP content type. Defaults to + # `application/octet-stream`. You can specify either a string or a symbol + # for a registered type with `Mime::Type.register`, for example `:json`. If + # omitted, type will be inferred from the file extension specified in + # `:filename`. If no content type is registered for the extension, the + # default type `application/octet-stream` will be used. + # * `:disposition` - specifies whether the file will be shown inline or + # downloaded. Valid values are `"inline"` and `"attachment"` (default). + # * `:status` - specifies the status code to send with the response. Defaults + # to 200. + # # # Generic data download: # - # send_data buffer + # send_data buffer # # Download a dynamically-generated tarball: # - # send_data generate_tgz('dir'), filename: 'dir.tgz' + # send_data generate_tgz('dir'), filename: 'dir.tgz' # # Display an image Active Record in the browser: # - # send_data image.data, type: image.content_type, disposition: 'inline' + # send_data image.data, type: image.content_type, disposition: 'inline' # - # See +send_file+ for more information on HTTP Content-* headers and caching. + # See `send_file` for more information on HTTP `Content-*` headers and caching. def send_data(data, options = {}) # :doc: send_file_headers! options render options.slice(:status, :content_type).merge(body: data) diff --git a/actionpack/lib/action_controller/metal/default_headers.rb b/actionpack/lib/action_controller/metal/default_headers.rb index c7bee2259f..fef8e78946 100644 --- a/actionpack/lib/action_controller/metal/default_headers.rb +++ b/actionpack/lib/action_controller/metal/default_headers.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController - # = Action Controller Default Headers + # # Action Controller Default Headers # - # Allows configuring default headers that will be automatically merged into - # each response. + # Allows configuring default headers that will be automatically merged into each + # response. module DefaultHeaders extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/metal/etag_with_flash.rb b/actionpack/lib/action_controller/metal/etag_with_flash.rb index 6cda6aa14b..6caf8a4315 100644 --- a/actionpack/lib/action_controller/metal/etag_with_flash.rb +++ b/actionpack/lib/action_controller/metal/etag_with_flash.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController - # = Action Controller Etag With \Flash + # # Action Controller Etag With Flash # # When you're using the flash, it's generally used as a conditional on the view. # This means the content of the view depends on the flash. Which in turn means diff --git a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb index 0a40ac13fa..20715b1fc6 100644 --- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb +++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb @@ -1,24 +1,26 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController - # = Action Controller Etag With Template \Digest + # # Action Controller Etag With Template Digest # - # When our views change, they should bubble up into HTTP cache freshness - # and bust browser caches. So the template digest for the current action - # is automatically included in the ETag. + # When our views change, they should bubble up into HTTP cache freshness and + # bust browser caches. So the template digest for the current action is + # automatically included in the ETag. # # Enabled by default for apps that use Action View. Disable by setting # - # config.action_controller.etag_with_template_digest = false + # config.action_controller.etag_with_template_digest = false # - # Override the template to digest by passing +:template+ to +fresh_when+ - # and +stale?+ calls. For example: + # Override the template to digest by passing `:template` to `fresh_when` and + # `stale?` calls. For example: # - # # We're going to render widgets/show, not posts/show - # fresh_when @post, template: 'widgets/show' + # # We're going to render widgets/show, not posts/show + # fresh_when @post, template: 'widgets/show' # - # # We're not going to render a template, so omit it from the ETag. - # fresh_when @post, template: false + # # We're not going to render a template, so omit it from the ETag. + # fresh_when @post, template: false # module EtagWithTemplateDigest extend ActiveSupport::Concern @@ -40,10 +42,10 @@ def determine_template_etag(options) end end - # Pick the template digest to include in the ETag. If the +:template+ option - # is present, use the named template. If +:template+ is +nil+ or absent, use - # the default controller/action template. If +:template+ is false, omit the - # template digest from the ETag. + # Pick the template digest to include in the ETag. If the `:template` option is + # present, use the named template. If `:template` is `nil` or absent, use the + # default controller/action template. If `:template` is false, omit the template + # digest from the ETag. def pick_template_for_etag(options) unless options[:template] == false options[:template] || lookup_context.find_all(action_name, _prefixes).first&.virtual_path diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index 2c51e5b9e1..35dd1a9138 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController class ActionControllerError < StandardError # :nodoc: end @@ -73,16 +75,16 @@ class UnknownHttpMethod < ActionControllerError # :nodoc: class UnknownFormat < ActionControllerError # :nodoc: end - # Raised when a nested respond_to is triggered and the content types of each - # are incompatible. For example: + # Raised when a nested respond_to is triggered and the content types of each are + # incompatible. For example: # - # respond_to do |outer_type| - # outer_type.js do - # respond_to do |inner_type| - # inner_type.html { render body: "HTML" } - # end - # end - # end + # respond_to do |outer_type| + # outer_type.js do + # respond_to do |inner_type| + # inner_type.html { render body: "HTML" } + # end + # end + # end class RespondToMismatchError < ActionControllerError DEFAULT_MESSAGE = "respond_to was called multiple times and matched with conflicting formats in this action. Please note that you may only call respond_to and match on a single format per action." diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index 22f0d34c25..c6c052dc16 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController # :nodoc: module Flash extend ActiveSupport::Concern @@ -13,19 +15,19 @@ module Flash module ClassMethods # Creates new flash types. You can pass as many types as you want to create - # flash types other than the default alert and notice in - # your controllers and views. For instance: + # flash types other than the default `alert` and `notice` in your controllers + # and views. For instance: # - # # in application_controller.rb - # class ApplicationController < ActionController::Base - # add_flash_types :warning - # end + # # in application_controller.rb + # class ApplicationController < ActionController::Base + # add_flash_types :warning + # end # - # # in your controller - # redirect_to user_path(@user), warning: "Incomplete profile" + # # in your controller + # redirect_to user_path(@user), warning: "Incomplete profile" # - # # in your view - # <%= warning %> + # # in your view + # <%= warning %> # # This method will automatically define a new method for each of the given # names, and it will be available in your views. diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index bb3d24ce97..4509aa135a 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -1,23 +1,25 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController module Head - # Returns a response that has no content (merely headers). The options - # argument is interpreted to be a hash of header names and values. - # This allows you to easily return a response that consists only of - # significant headers: + # Returns a response that has no content (merely headers). The options argument + # is interpreted to be a hash of header names and values. This allows you to + # easily return a response that consists only of significant headers: # - # head :created, location: person_path(@person) + # head :created, location: person_path(@person) # - # head :created, location: @person + # head :created, location: @person # # It can also be used to return exceptional conditions: # - # return head(:method_not_allowed) unless request.post? - # return head(:bad_request) unless valid_request? - # render + # return head(:method_not_allowed) unless request.post? + # return head(:bad_request) unless valid_request? + # render # - # See +Rack::Utils::SYMBOL_TO_STATUS_CODE+ for a full list of valid +status+ symbols. + # See `Rack::Utils::SYMBOL_TO_STATUS_CODE` for a full list of valid `status` + # symbols. def head(status, options = nil) if status.is_a?(Hash) raise ArgumentError, "#{status.inspect} is not a valid value for `status`." diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 9f254950bf..3b660843cb 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -1,59 +1,64 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController - # = Action Controller \Helpers + # # Action Controller Helpers # - # The \Rails framework provides a large number of helpers for working with assets, dates, forms, - # numbers and model objects, to name a few. These helpers are available to all templates - # by default. + # The Rails framework provides a large number of helpers for working with + # assets, dates, forms, numbers and model objects, to name a few. These helpers + # are available to all templates by default. # - # 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 #helpers + # 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 `#helpers` # - # In previous versions of \Rails the controller will include a helper which - # matches the name of the controller, e.g., MyController will automatically - # include MyHelper. You can revert to the old behavior with the following: + # In previous versions of Rails the controller will include a helper which + # matches the name of the controller, e.g., `MyController` will automatically + # include `MyHelper`. You can revert to the old behavior with the following: # - # # config/application.rb - # class Application < Rails::Application - # config.action_controller.include_all_helpers = false - # end - # - # Additional helpers can be specified using the +helper+ class method in ActionController::Base or any - # controller which inherits from it. - # - # The +to_s+ method from the \Time class can be wrapped in a helper method to display a custom message if - # a \Time object is blank: - # - # module FormattedTimeHelper - # def format_time(time, format=:long, blank_message=" ") - # time.blank? ? blank_message : time.to_fs(format) + # # config/application.rb + # class Application < Rails::Application + # config.action_controller.include_all_helpers = false # end - # end # - # FormattedTimeHelper can now be included in a controller, using the +helper+ class method: + # Additional helpers can be specified using the `helper` class method in + # ActionController::Base or any controller which inherits from it. # - # class EventsController < ActionController::Base - # helper FormattedTimeHelper - # def index - # @events = Event.all + # The `to_s` method from the Time class can be wrapped in a helper method to + # display a custom message if a Time object is blank: + # + # module FormattedTimeHelper + # def format_time(time, format=:long, blank_message=" ") + # time.blank? ? blank_message : time.to_fs(format) + # end # end - # end # - # Then, in any view rendered by EventsController, the format_time method can be called: + # FormattedTimeHelper can now be included in a controller, using the `helper` + # class method: # - # <% @events.each do |event| -%> - #

- # <%= format_time(event.time, :short, "N/A") %> | <%= event.name %> - #

- # <% end -%> + # class EventsController < ActionController::Base + # helper FormattedTimeHelper + # def index + # @events = Event.all + # end + # end # - # Finally, assuming we have two event instances, one which has a time and one which does not, - # the output might look like this: + # Then, in any view rendered by `EventsController`, the `format_time` method can + # be called: # - # 23 Aug 11:30 | Carolina Railhawks Soccer Match - # N/A | Carolina Railhawks Training Workshop + # <% @events.each do |event| -%> + #

+ # <%= format_time(event.time, :short, "N/A") %> | <%= event.name %> + #

+ # <% end -%> + # + # Finally, assuming we have two event instances, one which has a time and one + # which does not, the output might look like this: + # + # 23 Aug 11:30 | Carolina Railhawks Soccer Match + # N/A | Carolina Railhawks Training Workshop # module Helpers extend ActiveSupport::Concern @@ -68,23 +73,24 @@ class << self; attr_accessor :helpers_path; end module ClassMethods # Declares helper accessors for controller attributes. For example, the - # following adds new +name+ and name= instance methods to a - # controller and makes them available to the view: - # attr_accessor :name - # helper_attr :name + # following adds new `name` and `name=` instance methods to a controller and + # makes them available to the view: + # attr_accessor :name + # helper_attr :name + # + # #### Parameters + # * `attrs` - Names of attributes to be converted into helpers. # - # ==== Parameters - # * attrs - Names of attributes to be converted into helpers. def helper_attr(*attrs) attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } end # Provides a proxy to access helper methods from outside the view. # - # Note that the proxy is rendered under a different view context. - # This may cause incorrect behavior with capture methods. Consider - # using {helper}[rdoc-ref:AbstractController::Helpers::ClassMethods#helper] - # instead when using +capture+. + # Note that the proxy is rendered under a different view context. This may cause + # incorrect behavior with capture methods. Consider using + # [helper](rdoc-ref:AbstractController::Helpers::ClassMethods#helper) instead + # when using `capture`. def helpers @helper_proxy ||= begin proxy = ActionView::Base.empty @@ -93,21 +99,23 @@ def helpers end end - # Override modules_for_helpers to accept +:all+ as argument, which loads - # all helpers in helpers_path. + # Override modules_for_helpers to accept `:all` as argument, which loads all + # helpers in helpers_path. # - # ==== Parameters - # * args - A list of helpers + # #### Parameters + # * `args` - A list of helpers + # + # + # #### Returns + # * `array` - A normalized list of modules for the list of helpers provided. # - # ==== Returns - # * array - A normalized list of modules for the list of helpers provided. def modules_for_helpers(args) args += all_application_helpers if args.delete(:all) super(args) end private - # Extract helper names from files in app/helpers/**/*_helper.rb + # Extract helper names from files in `app/helpers/***/**_helper.rb` def all_application_helpers all_helpers_from_path(helpers_path) end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 1d1151336e..017e7a3a2f 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "base64" require "active_support/security_utils" require "active_support/core_ext/array/access" @@ -7,62 +9,63 @@ module ActionController # HTTP Basic, Digest, and Token authentication. module HttpAuthentication - # = HTTP \Basic authentication + # # HTTP Basic authentication # - # === Simple \Basic example + # ### Simple Basic example # - # class PostsController < ApplicationController - # http_basic_authenticate_with name: "dhh", password: "secret", except: :index + # class PostsController < ApplicationController + # http_basic_authenticate_with name: "dhh", password: "secret", except: :index # - # def index - # render plain: "Everyone can see me!" - # end - # - # def edit - # render plain: "I'm only accessible if you know the password" - # end - # end - # - # === Advanced \Basic example - # - # Here is a more advanced \Basic example where only Atom feeds and the XML API are protected by HTTP authentication. - # The regular HTML interface is protected by a session approach: - # - # class ApplicationController < ActionController::Base - # before_action :set_account, :authenticate - # - # private - # def set_account - # @account = Account.find_by(url_name: request.subdomains.first) + # def index + # render plain: "Everyone can see me!" # end # - # def authenticate - # case request.format - # when Mime[:xml], Mime[:atom] - # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) } - # @current_user = user + # def edit + # render plain: "I'm only accessible if you know the password" + # end + # end + # + # ### Advanced Basic example + # + # Here is a more advanced Basic example where only Atom feeds and the XML API + # are protected by HTTP authentication. The regular HTML interface is protected + # by a session approach: + # + # class ApplicationController < ActionController::Base + # before_action :set_account, :authenticate + # + # private + # def set_account + # @account = Account.find_by(url_name: request.subdomains.first) + # end + # + # def authenticate + # case request.format + # when Mime[:xml], Mime[:atom] + # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) } + # @current_user = user + # else + # request_http_basic_authentication + # end # else - # request_http_basic_authentication - # end - # else - # if session_authenticated? - # @current_user = @account.users.find(session[:authenticated][:user_id]) - # else - # redirect_to(login_url) and return false + # if session_authenticated? + # @current_user = @account.users.find(session[:authenticated][:user_id]) + # else + # redirect_to(login_url) and return false + # end # end # end - # end - # end + # end # # In your integration tests, you can do something like this: # - # def test_access_granted_from_xml - # authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password) + # def test_access_granted_from_xml + # authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password) # - # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization } + # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization } # - # assert_equal 200, status - # end + # assert_equal 200, status + # end module Basic extend self @@ -70,7 +73,7 @@ module ControllerMethods extend ActiveSupport::Concern module ClassMethods - # Enables HTTP \Basic authentication. + # Enables HTTP Basic authentication. # # See ActionController::HttpAuthentication::Basic for example usage. def http_basic_authenticate_with(name:, password:, realm: nil, **options) @@ -82,8 +85,8 @@ def http_basic_authenticate_with(name:, password:, realm: nil, **options) def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil) authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password| - # This comparison uses & so that it doesn't short circuit and - # uses `secure_compare` so that length information isn't leaked. + # This comparison uses & so that it doesn't short circuit and uses + # `secure_compare` so that length information isn't leaked. ActiveSupport::SecurityUtils.secure_compare(given_name.to_s, name) & ActiveSupport::SecurityUtils.secure_compare(given_password.to_s, password) end @@ -140,67 +143,68 @@ def authentication_request(controller, realm, message) end end - # = HTTP \Digest authentication + # # HTTP Digest authentication # - # === Simple \Digest example + # ### Simple Digest example # - # require "openssl" - # class PostsController < ApplicationController - # REALM = "SuperSecret" - # USERS = {"dhh" => "secret", #plain text password - # "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password + # require "openssl" + # class PostsController < ApplicationController + # REALM = "SuperSecret" + # USERS = {"dhh" => "secret", #plain text password + # "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password # - # before_action :authenticate, except: [:index] + # before_action :authenticate, except: [:index] # - # def index - # render plain: "Everyone can see me!" - # end - # - # def edit - # render plain: "I'm only accessible if you know the password" - # end - # - # private - # def authenticate - # authenticate_or_request_with_http_digest(REALM) do |username| - # USERS[username] - # end + # def index + # render plain: "Everyone can see me!" # end - # end # - # === Notes + # def edit + # render plain: "I'm only accessible if you know the password" + # end # - # The +authenticate_or_request_with_http_digest+ block must return the user's password - # or the ha1 digest hash so the framework can appropriately hash to check the user's - # credentials. Returning +nil+ will cause authentication to fail. + # private + # def authenticate + # authenticate_or_request_with_http_digest(REALM) do |username| + # USERS[username] + # end + # end + # end # - # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If - # the password file or database is compromised, the attacker would be able to use the ha1 hash to - # authenticate as the user at this +realm+, but would not have the user's password to try using at - # other sites. + # ### Notes # - # In rare instances, web servers or front proxies strip authorization headers before - # they reach your application. You can debug this situation by logging all environment - # variables, and check for HTTP_AUTHORIZATION, amongst others. + # The `authenticate_or_request_with_http_digest` block must return the user's + # password or the ha1 digest hash so the framework can appropriately hash to + # check the user's credentials. Returning `nil` will cause authentication to + # fail. + # + # Storing the ha1 hash: MD5(username:realm:password), is better than storing a + # plain password. If the password file or database is compromised, the attacker + # would be able to use the ha1 hash to authenticate as the user at this `realm`, + # but would not have the user's password to try using at other sites. + # + # In rare instances, web servers or front proxies strip authorization headers + # before they reach your application. You can debug this situation by logging + # all environment variables, and check for HTTP_AUTHORIZATION, amongst others. module Digest extend self module ControllerMethods - # Authenticate using an HTTP \Digest, or otherwise render an HTTP header - # requesting the client to send a \Digest. + # Authenticate using an HTTP Digest, or otherwise render an HTTP header + # requesting the client to send a Digest. # # See ActionController::HttpAuthentication::Digest for example usage. def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure) authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message) end - # Authenticate using an HTTP \Digest. Returns true if authentication is + # Authenticate using an HTTP Digest. Returns true if authentication is # successful, false otherwise. def authenticate_with_http_digest(realm = "Application", &password_procedure) HttpAuthentication::Digest.authenticate(request, realm, &password_procedure) end - # Render an HTTP header requesting the client to send a \Digest for + # Render an HTTP header requesting the client to send a Digest for # authentication. def request_http_digest_authentication(realm = "Application", message = nil) HttpAuthentication::Digest.authentication_request(self, realm, message) @@ -212,9 +216,9 @@ def authenticate(request, realm, &password_procedure) request.authorization && validate_digest_response(request, realm, &password_procedure) end - # Returns false unless the request credentials response value matches the expected value. - # First try the password as a ha1 digest password. If this fails, then try it as a plain - # text password. + # Returns false unless the request credentials response value matches the + # expected value. First try the password as a ha1 digest password. If this + # fails, then try it as a plain text password. def validate_digest_response(request, realm, &password_procedure) secret_key = secret_token(request) credentials = decode_credentials_header(request) @@ -237,9 +241,10 @@ def validate_digest_response(request, realm, &password_procedure) end end - # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+ - # Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead - # of a plain-text password. + # Returns the expected response for a request of `http_method` to `uri` with the + # decoded `credentials` and the expected `password` Optional parameter + # `password_is_ha1` is set to `true` by default, since best practice is to store + # ha1 digest instead of a plain-text password. def expected_response(http_method, uri, credentials, password, password_is_ha1 = true) ha1 = password_is_ha1 ? password : ha1(credentials, password) ha2 = OpenSSL::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":")) @@ -288,36 +293,40 @@ def secret_token(request) # Uses an MD5 digest based on time to generate a value to be used only once. # - # A server-specified data string which should be uniquely generated each time a 401 response is made. - # It is recommended that this string be base64 or hexadecimal data. - # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed. + # A server-specified data string which should be uniquely generated each time a + # 401 response is made. It is recommended that this string be base64 or + # hexadecimal data. Specifically, since the string is passed in the header lines + # as a quoted string, the double-quote character is not allowed. # - # The contents of the nonce are implementation dependent. - # The quality of the implementation depends on a good choice. - # A nonce might, for example, be constructed as the base 64 encoding of + # The contents of the nonce are implementation dependent. The quality of the + # implementation depends on a good choice. A nonce might, for example, be + # constructed as the base 64 encoding of # - # time-stamp H(time-stamp ":" ETag ":" private-key) + # time-stamp H(time-stamp ":" ETag ":" private-key) # - # where time-stamp is a server-generated time or other non-repeating value, - # ETag is the value of the HTTP ETag header associated with the requested entity, - # and private-key is data known only to the server. - # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and - # reject the request if it did not match the nonce from that header or - # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity. - # The inclusion of the ETag prevents a replay request for an updated version of the resource. - # (Note: including the IP address of the client in the nonce would appear to offer the server the ability - # to limit the reuse of the nonce to the same client that originally got it. - # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm. - # Also, IP address spoofing is not that hard.) + # where time-stamp is a server-generated time or other non-repeating value, ETag + # is the value of the HTTP ETag header associated with the requested entity, and + # private-key is data known only to the server. With a nonce of this form a + # server would recalculate the hash portion after receiving the client + # authentication header and reject the request if it did not match the nonce + # from that header or if the time-stamp value is not recent enough. In this way + # the server can limit the time of the nonce's validity. The inclusion of the + # ETag prevents a replay request for an updated version of the resource. (Note: + # including the IP address of the client in the nonce would appear to offer the + # server the ability to limit the reuse of the nonce to the same client that + # originally got it. However, that would break proxy farms, where requests from + # a single user often go through different proxies in the farm. Also, IP address + # spoofing is not that hard.) # - # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to - # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for - # POST, PUT, or PATCH requests, and a time-stamp for GET requests. For more details on the issues involved see Section 4 - # of this document. + # An implementation might choose not to accept a previously used nonce or a + # previously used digest, in order to protect against a replay attack. Or, an + # implementation might choose to use one-time nonces or digests for POST, PUT, + # or PATCH requests, and a time-stamp for GET requests. For more details on the + # issues involved see Section 4 of this document. # - # The nonce is opaque to the client. Composed of Time, and hash of Time with secret - # key from the \Rails session secret generated upon creation of project. Ensures - # the time cannot be modified by client. + # The nonce is opaque to the client. Composed of Time, and hash of Time with + # secret key from the Rails session secret generated upon creation of project. + # Ensures the time cannot be modified by client. def nonce(secret_key, time = Time.now) t = time.to_i hashed = [t, secret_key] @@ -325,11 +334,10 @@ def nonce(secret_key, time = Time.now) ::Base64.strict_encode64("#{t}:#{digest}") end - # Might want a shorter timeout depending on whether the request - # 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 the user again for their - # username and password. + # Might want a shorter timeout depending on whether the request 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 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? t = ::Base64.decode64(value).split(":").first.to_i @@ -342,80 +350,78 @@ def opaque(secret_key) end end - # = HTTP \Token authentication + # # HTTP Token authentication # - # === Simple \Token example + # ### Simple Token example # - # class PostsController < ApplicationController - # TOKEN = "secret" + # class PostsController < ApplicationController + # TOKEN = "secret" # - # before_action :authenticate, except: [ :index ] + # before_action :authenticate, except: [ :index ] # - # def index - # render plain: "Everyone can see me!" - # end - # - # def edit - # render plain: "I'm only accessible if you know the password" - # end - # - # private - # def authenticate - # authenticate_or_request_with_http_token do |token, options| - # # Compare the tokens in a time-constant manner, to mitigate - # # timing attacks. - # ActiveSupport::SecurityUtils.secure_compare(token, TOKEN) - # end - # end - # end - # - # - # Here is a more advanced Token example where only Atom feeds and the XML API are protected by HTTP token authentication. - # The regular HTML interface is protected by a session approach: - # - # class ApplicationController < ActionController::Base - # before_action :set_account, :authenticate - # - # private - # def set_account - # @account = Account.find_by(url_name: request.subdomains.first) + # def index + # render plain: "Everyone can see me!" # end # - # def authenticate - # case request.format - # when Mime[:xml], Mime[:atom] - # if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) } - # @current_user = user - # else - # request_http_token_authentication - # end - # else - # if session_authenticated? - # @current_user = @account.users.find(session[:authenticated][:user_id]) - # else - # redirect_to(login_url) and return false + # def edit + # render plain: "I'm only accessible if you know the password" + # end + # + # private + # def authenticate + # authenticate_or_request_with_http_token do |token, options| + # # Compare the tokens in a time-constant manner, to mitigate + # # timing attacks. + # ActiveSupport::SecurityUtils.secure_compare(token, TOKEN) # end # end - # end - # end + # end # + # Here is a more advanced Token example where only Atom feeds and the XML API + # are protected by HTTP token authentication. The regular HTML interface is + # protected by a session approach: + # + # class ApplicationController < ActionController::Base + # before_action :set_account, :authenticate + # + # private + # def set_account + # @account = Account.find_by(url_name: request.subdomains.first) + # end + # + # def authenticate + # case request.format + # when Mime[:xml], Mime[:atom] + # if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) } + # @current_user = user + # else + # request_http_token_authentication + # end + # else + # if session_authenticated? + # @current_user = @account.users.find(session[:authenticated][:user_id]) + # else + # redirect_to(login_url) and return false + # end + # end + # end + # end # # In your integration tests, you can do something like this: # - # def test_access_granted_from_xml - # authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token) + # def test_access_granted_from_xml + # authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token) # - # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization } + # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization } # - # assert_equal 200, status - # end + # assert_equal 200, status + # end # - # - # On shared hosts, Apache sometimes doesn't pass authentication headers to - # FCGI instances. If your environment matches this description and you cannot + # On shared hosts, Apache sometimes doesn't pass authentication headers to FCGI + # instances. If your environment matches this description and you cannot # authenticate, try this rule in your Apache setup: # - # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] + # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] module Token TOKEN_KEY = "token=" TOKEN_REGEX = /^(Token|Bearer)\s+/ @@ -423,19 +429,18 @@ module Token extend self module ControllerMethods - # Authenticate using an HTTP Bearer token, or otherwise render an HTTP - # header requesting the client to send a Bearer token. For the authentication - # to be considered successful, +login_procedure+ should return a non-nil - # value. Typically, the authenticated user is returned. + # Authenticate using an HTTP Bearer token, or otherwise render an HTTP header + # requesting the client to send a Bearer token. For the authentication to be + # considered successful, `login_procedure` should return a non-nil value. + # Typically, the authenticated user is returned. # # See ActionController::HttpAuthentication::Token for example usage. def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure) authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message) end - # Authenticate using an HTTP Bearer token. - # Returns the return value of +login_procedure+ if a - # token is found. Returns +nil+ if no token is found. + # Authenticate using an HTTP Bearer token. Returns the return value of + # `login_procedure` if a token is found. Returns `nil` if no token is found. # # See ActionController::HttpAuthentication::Token for example usage. def authenticate_with_http_token(&login_procedure) @@ -449,19 +454,20 @@ def request_http_token_authentication(realm = "Application", message = nil) end end - # If token Authorization header is present, call the login - # procedure with the present token and options. + # If token Authorization header is present, call the login procedure with the + # present token and options. # - # Returns the return value of +login_procedure+ if a - # token is found. Returns +nil+ if no token is found. + # Returns the return value of `login_procedure` if a token is found. Returns + # `nil` if no token is found. # - # ==== Parameters + # #### Parameters # - # * +controller+ - ActionController::Base instance for the current request. - # * +login_procedure+ - Proc to call if a token is present. The Proc - # should take two arguments: + # * `controller` - ActionController::Base instance for the current request. + # * `login_procedure` - Proc to call if a token is present. The Proc should + # take two arguments: + # + # authenticate(controller) { |token, options| ... } # - # authenticate(controller) { |token, options| ... } # def authenticate(controller, &login_procedure) token, options = token_and_options(controller.request) @@ -470,21 +476,21 @@ def authenticate(controller, &login_procedure) end end - # Parses the token and options out of the token Authorization header. - # The value for the Authorization header is expected to have the prefix - # "Token" or "Bearer". If the header looks like this: + # Parses the token and options out of the token Authorization header. The value + # for the Authorization header is expected to have the prefix `"Token"` or + # `"Bearer"`. If the header looks like this: # - # Authorization: Token token="abc", nonce="def" + # Authorization: Token token="abc", nonce="def" # - # Then the returned token is "abc", and the options are - # {nonce: "def"}. + # Then the returned token is `"abc"`, and the options are `{nonce: "def"}`. # - # Returns an +Array+ of [String, Hash] if a token is present. - # Returns +nil+ if no token is found. + # Returns an `Array` of `[String, Hash]` if a token is present. Returns `nil` if + # no token is found. # - # ==== Parameters + # #### Parameters + # + # * `request` - ActionDispatch::Request instance with the current headers. # - # * +request+ - ActionDispatch::Request instance with the current headers. def token_and_options(request) authorization_request = request.authorization.to_s if authorization_request[TOKEN_REGEX] @@ -497,12 +503,12 @@ def token_params_from(auth) rewrite_param_values params_array_from raw_params auth end - # Takes +raw_params+ and turns it into an array of parameters. + # Takes `raw_params` and turns it into an array of parameters. def params_array_from(raw_params) raw_params.map { |param| param.split %r/=(.+)?/ } end - # This removes the " characters wrapping the value. + # This removes the `"` characters wrapping the value. def rewrite_param_values(array_params) array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" } end @@ -510,9 +516,9 @@ def rewrite_param_values(array_params) WHITESPACED_AUTHN_PAIR_DELIMITERS = /\s*#{AUTHN_PAIR_DELIMITERS}\s*/ private_constant :WHITESPACED_AUTHN_PAIR_DELIMITERS - # This method takes an authorization body and splits up the key-value - # pairs by the standardized :, ;, or \t - # delimiters defined in +AUTHN_PAIR_DELIMITERS+. + # This method takes an authorization body and splits up the key-value pairs by + # the standardized `:`, `;`, or `\t` delimiters defined in + # `AUTHN_PAIR_DELIMITERS`. def raw_params(auth) _raw_params = auth.sub(TOKEN_REGEX, "").split(WHITESPACED_AUTHN_PAIR_DELIMITERS) _raw_params.reject!(&:empty?) @@ -528,10 +534,11 @@ def raw_params(auth) # # Returns String. # - # ==== Parameters + # #### Parameters + # + # * `token` - String token. + # * `options` - Optional Hash of the options. # - # * +token+ - String token. - # * +options+ - Optional Hash of the options. def encode_credentials(token, options = {}) values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value| "#{key}=#{value.to_s.inspect}" @@ -543,10 +550,11 @@ def encode_credentials(token, options = {}) # # Returns nothing. # - # ==== Parameters + # #### Parameters + # + # * `controller` - ActionController::Base instance for the outgoing response. + # * `realm` - String realm to use in the header. # - # * +controller+ - ActionController::Base instance for the outgoing response. - # * +realm+ - String realm to use in the header. def authentication_request(controller, realm, message = nil) message ||= "HTTP Token: Access denied.\n" controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}") diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index eed7348efb..fe2590e74e 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -1,33 +1,35 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController - # = Action Controller Implicit Render + # # Action Controller Implicit Render # - # Handles implicit rendering for a controller action that does not - # explicitly respond with +render+, +respond_to+, +redirect+, or +head+. + # 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 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: + # 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). + # 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 + # templates for other formats, variants, etc., then we trust that you meant to + # provide a template for this response, too, and we raise # ActionController::UnknownFormat 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 + # browser (technically, a non-XHR GET request for an HTML response) where you + # reasonably expect to have rendered a template, then we raise # ActionController::MissingExactTemplate 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. + # load, then we implicitly respond with `204 No Content`. module ImplicitRender # :stopdoc: include BasicImplicitRender diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 43ebc120c5..4e3a0c8528 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -1,14 +1,17 @@ # frozen_string_literal: true +# :markup: markdown + require "benchmark" require "abstract_controller/logger" module ActionController - # = Action Controller \Instrumentation + # # Action Controller Instrumentation # - # Adds instrumentation to several ends in ActionController::Base. It also provides - # some hooks related with process_action. This allows an ORM like Active Record - # and/or DataMapper to plug in ActionController and show related information. + # Adds instrumentation to several ends in ActionController::Base. It also + # provides some hooks related with process_action. This allows an ORM like + # Active Record and/or DataMapper to plug in ActionController and show related + # information. # # Check ActiveRecord::Railties::ControllerRuntime for an example. module Instrumentation @@ -91,23 +94,23 @@ def halted_callback_hook(filter, _) # 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 - # end + # def cleanup_view_runtime + # super - time_taken_in_something_expensive + # end def cleanup_view_runtime # :doc: yield end - # Every time after an action is processed, this method is invoked - # with the payload, so you can add more information. + # Every time after an action is processed, this method is invoked with the + # payload, so you can add more information. def append_info_to_payload(payload) # :doc: payload[:view_runtime] = view_runtime end module ClassMethods - # A hook which allows other frameworks to log what happened during - # controller process action. This method should return an array - # with the messages to be added. + # A hook which allows other frameworks to log what happened during controller + # process action. This method should return an array with the messages to be + # added. def log_process_action(payload) # :nodoc: messages, view_runtime = [], payload[:view_runtime] messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 1fa77029fa..a84eb86188 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -1,56 +1,58 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/http/response" require "delegate" require "active_support/json" module ActionController - # = Action Controller \Live + # # Action Controller Live # - # 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. + # 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 - # include ActionController::Live + # class MyController < ActionController::Base + # include ActionController::Live # - # def stream - # response.headers['Content-Type'] = 'text/event-stream' - # 100.times { - # response.stream.write "hello world\n" - # sleep 1 - # } - # ensure - # response.stream.close + # def stream + # response.headers['Content-Type'] = 'text/event-stream' + # 100.times { + # response.stream.write "hello world\n" + # sleep 1 + # } + # ensure + # response.stream.close + # end # end - # end # - # 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 - # or close on your stream. + # 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 or + # close on your stream. # - # You *must* call close on your stream when you're finished, otherwise the + # You **must** call close on your stream when you're finished, otherwise the # socket may be left open forever. # # The final caveat is that your actions are executed in a separate thread than - # the main thread. Make sure your actions are thread safe, and this shouldn't - # be a problem (don't share state across threads, etc). + # the main thread. Make sure your actions are thread safe, and this shouldn't be + # a problem (don't share state across threads, etc). # - # Note that \Rails includes +Rack::ETag+ by default, which will buffer your + # Note that Rails includes `Rack::ETag` by default, which will buffer your # response. As a result, streaming responses may not work properly with Rack - # 2.2.x, and you may need to implement workarounds in your application. - # You can either set the +ETag+ or +Last-Modified+ response headers or remove - # +Rack::ETag+ from the middleware stack to address this issue. + # 2.2.x, and you may need to implement workarounds in your application. You can + # either set the `ETag` or `Last-Modified` response headers or remove + # `Rack::ETag` from the middleware stack to address this issue. # - # Here's an example of how you can set the +Last-Modified+ header if your Rack + # Here's an example of how you can set the `Last-Modified` header if your Rack # version is 2.2.x: # - # def stream - # response.headers["Content-Type"] = "text/event-stream" - # response.headers["Last-Modified"] = Time.now.httpdate # Add this line if your Rack version is 2.2.x - # ... - # end + # def stream + # response.headers["Content-Type"] = "text/event-stream" + # response.headers["Last-Modified"] = Time.now.httpdate # Add this line if your Rack version is 2.2.x + # ... + # end module Live extend ActiveSupport::Concern @@ -66,44 +68,44 @@ def make_response!(request) end end - # = Action Controller \Live Server Sent Events + # # Action Controller Live Server Sent Events # - # This class provides the ability to write an SSE (Server Sent Event) - # to an IO stream. The class is initialized with a stream and can be used - # to either write a JSON string or an object which can be converted to JSON. + # This class provides the ability to write an SSE (Server Sent Event) to an IO + # stream. The class is initialized with a stream and can be used to either write + # a JSON string or an object which can be converted to JSON. # # Writing an object will convert it into standard SSE format with whatever # options you have configured. You may choose to set the following options: # - # 1) Event. If specified, an event with this name will be dispatched on - # the browser. - # 2) Retry. The reconnection time in milliseconds used when attempting - # to send the event. - # 3) Id. If the connection dies while sending an SSE to the browser, then - # the server will receive a +Last-Event-ID+ header with value equal to +id+. + # 1) Event. If specified, an event with this name will be dispatched on + # the browser. + # 2) Retry. The reconnection time in milliseconds used when attempting + # to send the event. + # 3) Id. If the connection dies while sending an SSE to the browser, then + # the server will receive a +Last-Event-ID+ header with value equal to +id+. # - # After setting an option in the constructor of the SSE object, all future - # SSEs sent across the stream will use those options unless overridden. + # After setting an option in the constructor of the SSE object, all future SSEs + # sent across the stream will use those options unless overridden. # # Example Usage: # - # class MyController < ActionController::Base - # include ActionController::Live + # class MyController < ActionController::Base + # include ActionController::Live # - # def index - # response.headers['Content-Type'] = 'text/event-stream' - # sse = SSE.new(response.stream, retry: 300, event: "event-name") - # sse.write({ name: 'John'}) - # sse.write({ name: 'John'}, id: 10) - # sse.write({ name: 'John'}, id: 10, event: "other-event") - # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500) - # ensure - # sse.close + # def index + # response.headers['Content-Type'] = 'text/event-stream' + # sse = SSE.new(response.stream, retry: 300, event: "event-name") + # sse.write({ name: 'John'}) + # sse.write({ name: 'John'}, id: 10) + # sse.write({ name: 'John'}, id: 10, event: "other-event") + # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500) + # ensure + # sse.close + # end # end - # end # - # Note: SSEs are not currently supported by IE. However, they are supported - # by Chrome, Firefox, Opera, and Safari. + # Note: SSEs are not currently supported by IE. However, they are supported by + # Chrome, Firefox, Opera, and Safari. class SSE PERMITTED_OPTIONS = %w( retry event id ) @@ -153,10 +155,9 @@ class << self # Ignore that the client has disconnected. # - # If this value is `true`, calling `write` after the client - # disconnects will result in the written content being silently - # discarded. If this value is `false` (the default), a - # ClientDisconnected exception will be raised. + # If this value is `true`, calling `write` after the client disconnects will + # result in the written content being silently discarded. If this value is + # `false` (the default), a ClientDisconnected exception will be raised. attr_accessor :ignore_disconnect def initialize(response) @@ -167,9 +168,10 @@ def initialize(response) @ignore_disconnect = false end - # ActionDispatch::Response delegates #to_ary to the internal ActionDispatch::Response::Buffer, - # defining #to_ary is an indicator that the response body can be buffered and/or cached by - # Rack middlewares, this is not the case for Live responses so we undefine it for this Buffer subclass. + # ActionDispatch::Response delegates #to_ary to the internal + # ActionDispatch::Response::Buffer, defining #to_ary is an indicator that the + # response body can be buffered and/or cached by Rack middlewares, this is not + # the case for Live responses so we undefine it for this Buffer subclass. undef_method :to_ary def write(string) @@ -184,21 +186,20 @@ def write(string) @buf.clear unless @ignore_disconnect - # Raise ClientDisconnected, which is a RuntimeError (not an - # IOError), because that's more appropriate for something beyond - # the developer's control. + # Raise ClientDisconnected, which is a RuntimeError (not an IOError), because + # that's more appropriate for something beyond the developer's control. raise ClientDisconnected, "client disconnected" end end end - # Same as +write+ but automatically include a newline at the end of the string. + # Same as `write` but automatically include a newline at the end of the string. def writeln(string) write string.end_with?("\n") ? string : "#{string}\n" end - # Write a 'close' event to the buffer; the producer/writing thread - # uses this to notify us that it's finished supplying content. + # Write a 'close' event to the buffer; the producer/writing thread uses this to + # notify us that it's finished supplying content. # # See also #abort. def close @@ -209,9 +210,8 @@ def close end end - # Inform the producer/writing thread that the client has - # disconnected; the reading thread is no longer interested in - # anything that's being written. + # Inform the producer/writing thread that the client has disconnected; the + # reading thread is no longer interested in anything that's being written. # # See also #close. def abort @@ -223,8 +223,8 @@ def abort # Is the client still connected and waiting for content? # - # The result of calling `write` when this is `false` is determined - # by `ignore_disconnect`. + # The result of calling `write` when this is `false` is determined by + # `ignore_disconnect`. def connected? !@aborted end @@ -275,15 +275,15 @@ def process(name) locals = t1.keys.map { |key| [key, t1[key]] } error = nil - # 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. + # 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. 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. :'( + # 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 } ActiveSupport::IsolatedExecutionState.share_with(t1) @@ -321,27 +321,30 @@ def response_body=(body) response.close if response end - # Sends a stream to the browser, which is helpful when you're generating exports or other running data where you - # don't want the entire file buffered in memory first. Similar to send_data, but where the data is generated live. + # Sends a stream to the browser, which is helpful when you're generating exports + # or other running data where you don't want the entire file buffered in memory + # first. Similar to send_data, but where the data is generated live. # # Options: - # * :filename - suggests a filename for the browser to use. - # * :type - specifies an HTTP content type. - # You can specify either a string or a symbol for a registered type with Mime::Type.register, for example :json. - # If omitted, type will be inferred from the file extension specified in :filename. - # If no content type is registered for the extension, the default type 'application/octet-stream' will be used. - # * :disposition - specifies whether the file will be shown inline or downloaded. - # Valid values are 'inline' and 'attachment' (default). + # * `:filename` - suggests a filename for the browser to use. + # * `:type` - specifies an HTTP content type. You can specify either a string + # or a symbol for a registered type with `Mime::Type.register`, for example + # :json. If omitted, type will be inferred from the file extension specified + # in `:filename`. If no content type is registered for the extension, the + # default type 'application/octet-stream' will be used. + # * `:disposition` - specifies whether the file will be shown inline or + # downloaded. Valid values are 'inline' and 'attachment' (default). + # # # Example of generating a csv export: # - # send_stream(filename: "subscribers.csv") do |stream| - # stream.write "email_address,updated_at\n" + # send_stream(filename: "subscribers.csv") do |stream| + # stream.write "email_address,updated_at\n" # - # @subscribers.find_each do |subscriber| - # stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n" - # end - # end + # @subscribers.find_each do |subscriber| + # stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n" + # end + # end def send_stream(filename:, disposition: "attachment", type: nil) payload = { filename: filename, disposition: disposition, type: type } ActiveSupport::Notifications.instrument("send_stream.action_controller", payload) do @@ -360,10 +363,10 @@ def send_stream(filename:, disposition: "attachment", type: nil) end private - # 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! + # 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 diff --git a/actionpack/lib/action_controller/metal/logging.rb b/actionpack/lib/action_controller/metal/logging.rb index 589856e506..c00f16141c 100644 --- a/actionpack/lib/action_controller/metal/logging.rb +++ b/actionpack/lib/action_controller/metal/logging.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController module Logging extend ActiveSupport::Concern @@ -7,10 +9,10 @@ module Logging module ClassMethods # Set a different log level per request. # - # # Use the debug log level if a particular cookie is set. - # class ApplicationController < ActionController::Base - # log_at :debug, if: -> { cookies[:debug] } - # end + # # Use the debug log level if a particular cookie is set. + # class ApplicationController < ActionController::Base + # log_at :debug, if: -> { cookies[:debug] } + # end # def log_at(level, **options) around_action ->(_, action) { logger.log_at(level, &action) }, **options diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index b676b68249..c03b17700f 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -1,203 +1,213 @@ # frozen_string_literal: true +# :markup: markdown + require "abstract_controller/collector" module ActionController # :nodoc: module MimeResponds - # Without web-service support, an action which collects the data for displaying a list of people - # might look something like this: + # Without web-service support, an action which collects the data for displaying + # a list of people might look something like this: # - # def index - # @people = Person.all - # end + # def index + # @people = Person.all + # end # - # That action implicitly responds to all formats, but formats can also be explicitly enumerated: + # That action implicitly responds to all formats, but formats can also be + # explicitly enumerated: # - # def index - # @people = Person.all - # respond_to :html, :js - # end + # def index + # @people = Person.all + # respond_to :html, :js + # end # # Here's the same action, with web-service support baked in: # - # def index - # @people = Person.all + # def index + # @people = Person.all # - # respond_to do |format| - # format.html - # format.js - # format.xml { render xml: @people } + # respond_to do |format| + # format.html + # format.js + # format.xml { render xml: @people } + # end # end - # end # - # What that says is, "if the client wants HTML or JS in response to this action, just respond as we - # would have before, but if the client wants XML, return them the list of people in XML format." - # (\Rails determines the desired response format from the HTTP Accept header submitted by the client.) + # What that says is, "if the client wants HTML or JS in response to this action, + # just respond as we would have before, but if the client wants XML, return them + # the list of people in XML format." (Rails determines the desired response + # format from the HTTP Accept header submitted by the client.) # - # Supposing you have an action that adds a new person, optionally creating their company - # (by name) if it does not already exist, without web-services, it might look like this: + # Supposing you have an action that adds a new person, optionally creating their + # company (by name) if it does not already exist, without web-services, it might + # look like this: # - # def create - # @company = Company.find_or_create_by(name: params[:company][:name]) - # @person = @company.people.create(params[:person]) + # def create + # @company = Company.find_or_create_by(name: params[:company][:name]) + # @person = @company.people.create(params[:person]) # - # redirect_to(person_list_url) - # end + # redirect_to(person_list_url) + # end # # Here's the same action, with web-service support baked in: # - # def create - # company = params[:person].delete(:company) - # @company = Company.find_or_create_by(name: company[:name]) - # @person = @company.people.create(params[:person]) + # def create + # company = params[:person].delete(:company) + # @company = Company.find_or_create_by(name: company[:name]) + # @person = @company.people.create(params[:person]) # - # respond_to do |format| - # format.html { redirect_to(person_list_url) } - # format.js - # format.xml { render xml: @person.to_xml(include: @company) } + # respond_to do |format| + # format.html { redirect_to(person_list_url) } + # format.js + # format.xml { render xml: @person.to_xml(include: @company) } + # end # end - # end # - # If the client wants HTML, we just redirect them back to the person list. If they want JavaScript, - # then it is an Ajax request and we render the JavaScript template associated with this action. - # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also - # include the person's company in the rendered XML, so you get something like this: + # If the client wants HTML, we just redirect them back to the person list. If + # they want JavaScript, then it is an Ajax request and we render the JavaScript + # template associated with this action. Lastly, if the client wants XML, we + # render the created person as XML, but with a twist: we also include the + # person's company in the rendered XML, so you get something like this: # - # - # ... - # ... - # + # # ... - # ... # ... - # - # + # + # ... + # ... + # ... + # + # # # Note, however, the extra bit at the top of that action: # - # company = params[:person].delete(:company) - # @company = Company.find_or_create_by(name: company[:name]) + # company = params[:person].delete(:company) + # @company = Company.find_or_create_by(name: company[:name]) # - # This is because the incoming XML document (if a web-service request is in process) can only contain a - # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded): + # This is because the incoming XML document (if a web-service request is in + # process) can only contain a single root-node. So, we have to rearrange things + # so that the request looks like this (url-encoded): # - # person[name]=...&person[company][name]=...&... + # person[name]=...&person[company][name]=...&... # # And, like this (xml-encoded): # - # - # ... - # + # # ... - # - # + # + # ... + # + # # - # In other words, we make the request so that it operates on a single entity's person. Then, in the action, - # we extract the company data from the request, find or create the company, and then create the new person - # with the remaining data. + # In other words, we make the request so that it operates on a single entity's + # person. Then, in the action, we extract the company data from the request, + # find or create the company, and then create the new person with the remaining + # data. # - # Note that you can define your own XML parameter parser which would allow you to describe multiple entities - # in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow - # and accept \Rails' defaults, life will be much easier. + # Note that you can define your own XML parameter parser which would allow you + # to describe multiple entities in a single request (i.e., by wrapping them all + # in a single root node), but if you just go with the flow and accept Rails' + # defaults, life will be much easier. # - # If you need to use a MIME type which isn't supported by default, you can register your own handlers in - # +config/initializers/mime_types.rb+ as follows. + # If you need to use a MIME type which isn't supported by default, you can + # register your own handlers in `config/initializers/mime_types.rb` as follows. # - # Mime::Type.register "image/jpeg", :jpg + # Mime::Type.register "image/jpeg", :jpg # - # +respond_to+ also allows you to specify a common block for different formats by using +any+: + # `respond_to` also allows you to specify a common block for different formats + # by using `any`: # - # def index - # @people = Person.all + # def index + # @people = Person.all # - # respond_to do |format| - # format.html - # format.any(:xml, :json) { render request.format.to_sym => @people } + # respond_to do |format| + # format.html + # format.any(:xml, :json) { render request.format.to_sym => @people } + # end # end - # end # # In the example above, if the format is xml, it will render: # - # render xml: @people + # render xml: @people # # Or if the format is json: # - # render json: @people + # render json: @people # - # +any+ can also be used with no arguments, in which case it will be used for any format requested by - # the user: + # `any` can also be used with no arguments, in which case it will be used for + # any format requested by the user: # - # respond_to do |format| - # format.html - # format.any { redirect_to support_path } - # end + # respond_to do |format| + # format.html + # format.any { redirect_to support_path } + # end # # Formats can have different variants. # - # The request variant is a specialization of the request format, like :tablet, - # :phone, or :desktop. + # The request variant is a specialization of the request format, like `:tablet`, + # `:phone`, or `:desktop`. # - # We often want to render different html/json/xml templates for phones, - # tablets, and desktop browsers. Variants make it easy. + # We often want to render different html/json/xml templates for phones, tablets, + # and desktop browsers. Variants make it easy. # - # You can set the variant in a +before_action+: + # You can set the variant in a `before_action`: # - # request.variant = :tablet if /iPad/.match?(request.user_agent) + # request.variant = :tablet if /iPad/.match?(request.user_agent) # # Respond to variants in the action just like you respond to formats: # - # respond_to do |format| - # format.html do |variant| - # variant.tablet # renders app/views/projects/show.html+tablet.erb - # variant.phone { extra_setup; render ... } - # variant.none { special_setup } # executed only if there is no variant set + # respond_to do |format| + # format.html do |variant| + # variant.tablet # renders app/views/projects/show.html+tablet.erb + # variant.phone { extra_setup; render ... } + # variant.none { special_setup } # executed only if there is no variant set + # end # end - # end # # Provide separate templates for each format and variant: # - # app/views/projects/show.html.erb - # app/views/projects/show.html+tablet.erb - # app/views/projects/show.html+phone.erb + # app/views/projects/show.html.erb + # app/views/projects/show.html+tablet.erb + # app/views/projects/show.html+phone.erb # - # When you're not sharing any code within the format, you can simplify defining variants - # using the inline syntax: + # When you're not sharing any code within the format, you can simplify defining + # variants using the inline syntax: # - # respond_to do |format| - # format.js { render "trash" } - # format.html.phone { redirect_to progress_path } - # format.html.none { render "trash" } - # end + # respond_to do |format| + # format.js { render "trash" } + # format.html.phone { redirect_to progress_path } + # format.html.none { render "trash" } + # end # - # Variants also support common +any+/+all+ block that formats have. + # Variants also support common `any`/`all` block that formats have. # # It works for both inline: # - # respond_to do |format| - # format.html.any { render html: "any" } - # format.html.phone { render html: "phone" } - # end + # respond_to do |format| + # format.html.any { render html: "any" } + # format.html.phone { render html: "phone" } + # end # # and block syntax: # - # respond_to do |format| - # format.html do |variant| - # variant.any(:tablet, :phablet){ render html: "any" } - # variant.phone { render html: "phone" } + # respond_to do |format| + # format.html do |variant| + # variant.any(:tablet, :phablet){ render html: "any" } + # variant.phone { render html: "phone" } + # end # end - # end # # You can also set an array of variants: # - # request.variant = [:tablet, :phone] + # request.variant = [:tablet, :phone] # - # This will work similarly to formats and MIME types negotiation. If there - # is no +:tablet+ variant declared, the +:phone+ variant will be used: + # This will work similarly to formats and MIME types negotiation. If there is no + # `:tablet` variant declared, the `:phone` variant will be used: # - # respond_to do |format| - # format.html.none - # format.html.phone # this gets rendered - # end + # respond_to do |format| + # format.html.none + # format.html.phone # this gets rendered + # end def respond_to(*mimes) raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? @@ -217,27 +227,26 @@ def respond_to(*mimes) end end - # A container for responses available from the current controller for - # requests for different mime-types sent to a particular action. + # A container for responses available from the current controller for requests + # for different mime-types sent to a particular action. # - # The public controller methods +respond_to+ may be called with a block - # that is used to define responses to different mime-types, e.g. - # for +respond_to+ : + # The public controller methods `respond_to` may be called with a block that is + # used to define responses to different mime-types, e.g. for `respond_to` : # - # respond_to do |format| - # format.html - # format.xml { render xml: @people } - # end + # respond_to do |format| + # format.html + # format.xml { render xml: @people } + # end # - # In this usage, the argument passed to the block (+format+ above) is an - # instance of the ActionController::MimeResponds::Collector class. This - # object serves as a container in which available responses can be stored by - # calling any of the dynamically generated, mime-type-specific methods such - # as +html+, +xml+ etc on the Collector. Each response is represented by a - # corresponding block if present. + # In this usage, the argument passed to the block (`format` above) is an + # instance of the ActionController::MimeResponds::Collector class. This object + # serves as a container in which available responses can be stored by calling + # any of the dynamically generated, mime-type-specific methods such as `html`, + # `xml` etc on the Collector. Each response is represented by a corresponding + # block if present. # - # A subsequent call to #negotiate_format(request) will enable the Collector - # to determine which specific mime-type it should respond with for the current + # A subsequent call to #negotiate_format(request) will enable the Collector to + # determine which specific mime-type it should respond with for the current # request, with this response then being accessible by calling #response. class Collector include AbstractController::Collector diff --git a/actionpack/lib/action_controller/metal/parameter_encoding.rb b/actionpack/lib/action_controller/metal/parameter_encoding.rb index 802c0c81e7..cf570cd99c 100644 --- a/actionpack/lib/action_controller/metal/parameter_encoding.rb +++ b/actionpack/lib/action_controller/metal/parameter_encoding.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController # Specify binary encoding for parameters for a given action. module ParameterEncoding @@ -21,59 +23,59 @@ def action_encoding_template(action) # :nodoc: end end - # Specify that a given action's parameters should all be encoded as - # ASCII-8BIT (it "skips" the encoding default of UTF-8). + # Specify that a given action's parameters should all be encoded as ASCII-8BIT + # (it "skips" the encoding default of UTF-8). # # For example, a controller would use it like this: # - # class RepositoryController < ActionController::Base - # skip_parameter_encoding :show + # class RepositoryController < ActionController::Base + # skip_parameter_encoding :show # - # def show - # @repo = Repository.find_by_filesystem_path params[:file_path] + # def show + # @repo = Repository.find_by_filesystem_path params[:file_path] # - # # `repo_name` is guaranteed to be UTF-8, but was ASCII-8BIT, so - # # tag it as such - # @repo_name = params[:repo_name].force_encoding 'UTF-8' + # # `repo_name` is guaranteed to be UTF-8, but was ASCII-8BIT, so + # # tag it as such + # @repo_name = params[:repo_name].force_encoding 'UTF-8' + # end + # + # def index + # @repositories = Repository.all + # end # end # - # def index - # @repositories = Repository.all - # end - # end - # # The show action in the above controller would have all parameter values - # encoded as ASCII-8BIT. This is useful in the case where an application - # must handle data but encoding of the data is unknown, like file system data. + # encoded as ASCII-8BIT. This is useful in the case where an application must + # handle data but encoding of the data is unknown, like file system data. def skip_parameter_encoding(action) @_parameter_encodings[action.to_s] = Hash.new { Encoding::ASCII_8BIT } end - # Specify the encoding for a parameter on an action. - # If not specified the default is UTF-8. + # Specify the encoding for a parameter on an action. If not specified the + # default is UTF-8. # # You can specify a binary (ASCII_8BIT) parameter with: # - # class RepositoryController < ActionController::Base - # # This specifies that file_path is not UTF-8 and is instead ASCII_8BIT - # param_encoding :show, :file_path, Encoding::ASCII_8BIT + # class RepositoryController < ActionController::Base + # # This specifies that file_path is not UTF-8 and is instead ASCII_8BIT + # param_encoding :show, :file_path, Encoding::ASCII_8BIT # - # def show - # @repo = Repository.find_by_filesystem_path params[:file_path] + # def show + # @repo = Repository.find_by_filesystem_path params[:file_path] # - # # params[:repo_name] remains UTF-8 encoded - # @repo_name = params[:repo_name] + # # params[:repo_name] remains UTF-8 encoded + # @repo_name = params[:repo_name] + # end + # + # def index + # @repositories = Repository.all + # end # end # - # def index - # @repositories = Repository.all - # end - # end - # - # The file_path parameter on the show action would be encoded as ASCII-8BIT, - # but all other arguments will remain UTF-8 encoded. - # This is useful in the case where an application must handle data - # but encoding of the data is unknown, like file system data. + # The file_path parameter on the show action would be encoded as ASCII-8BIT, but + # all other arguments will remain UTF-8 encoded. This is useful in the case + # where an application must handle data but encoding of the data is unknown, + # like file system data. def param_encoding(action, param, encoding) @_parameter_encodings[action.to_s][param.to_s] = encoding end diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index e28d4195b3..12bdcd1687 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/hash/slice" require "active_support/core_ext/hash/except" require "active_support/core_ext/module/anonymous" require "action_dispatch/http/mime_type" module ActionController - # = Action Controller Params Wrapper + # # Action Controller Params Wrapper # # Wraps the parameters hash into a nested hash. This will allow clients to # submit requests without having to specify any root elements. @@ -24,8 +26,8 @@ module ActionController # wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form] # end # - # If you enable +ParamsWrapper+ for +:json+ format, instead of having to - # send JSON parameters like this: + # If you enable `ParamsWrapper` for `:json` format, instead of having to send + # JSON parameters like this: # # {"user": {"name": "Konata"}} # @@ -34,45 +36,44 @@ module ActionController # {"name": "Konata"} # # And it will be wrapped into a nested hash with the key name matching the - # controller's name. For example, if you're posting to +UsersController+, - # your new +params+ hash will look like this: + # controller's name. For example, if you're posting to `UsersController`, your + # new `params` hash will look like this: # # {"name" => "Konata", "user" => {"name" => "Konata"}} # - # You can also specify the key in which the parameters should be wrapped to, - # and also the list of attributes it should wrap by using either +:include+ or - # +:exclude+ options like this: + # You can also specify the key in which the parameters should be wrapped to, and + # also the list of attributes it should wrap by using either `:include` or + # `:exclude` options like this: # # class UsersController < ApplicationController # wrap_parameters :person, include: [:username, :password] # end # - # On Active Record models with no +:include+ or +:exclude+ option set, - # it will only wrap the parameters returned by the class method - # attribute_names. + # On Active Record models with no `:include` or `:exclude` option set, it will + # only wrap the parameters returned by the class method `attribute_names`. # - # If you're going to pass the parameters to an +ActiveModel+ object (such as - # User.new(params[:user])), you might consider passing the model class to - # the method instead. The +ParamsWrapper+ will actually try to determine the - # list of attribute names from the model and only wrap those attributes: + # If you're going to pass the parameters to an `ActiveModel` object (such as + # `User.new(params[:user])`), you might consider passing the model class to the + # method instead. The `ParamsWrapper` will actually try to determine the list of + # attribute names from the model and only wrap those attributes: # # class UsersController < ApplicationController # wrap_parameters Person # end # - # You still could pass +:include+ and +:exclude+ to set the list of attributes + # You still could pass `:include` and `:exclude` to set the list of attributes # you want to wrap. # # By default, if you don't specify the key in which the parameters would be - # wrapped to, +ParamsWrapper+ will actually try to determine if there's - # a model related to it or not. This controller, for example: + # wrapped to, `ParamsWrapper` will actually try to determine if there's a model + # related to it or not. This controller, for example: # # class Admin::UsersController < ApplicationController # end # - # will try to check if +Admin::User+ or +User+ model exists, and use it to - # determine the wrapper key respectively. If both models don't exist, - # it will then fall back to use +user+ as the key. + # will try to check if `Admin::User` or `User` model exists, and use it to + # determine the wrapper key respectively. If both models don't exist, it will + # then fall back to use `user` as the key. # # To disable this functionality for a controller: # @@ -154,13 +155,13 @@ def name end private - # Determine the wrapper model from the controller's name. By convention, - # this could be done by trying to find the defined model that has the - # same singular name as the controller. For example, +UsersController+ - # will try to find if the +User+ model exists. + # Determine the wrapper model from the controller's name. By convention, this + # could be done by trying to find the defined model that has the same singular + # name as the controller. For example, `UsersController` will try to find if the + # `User` model exists. # - # This method also does namespace lookup. Foo::Bar::UsersController will - # try to find Foo::Bar::User, Foo::User and finally User. + # This method also does namespace lookup. Foo::Bar::UsersController will try to + # find Foo::Bar::User, Foo::User and finally User. def _default_wrap_model return nil if klass.anonymous? model_name = klass.name.delete_suffix("Controller").classify @@ -189,33 +190,34 @@ def _set_wrapper_options(options) self._wrapper_options = Options.from_hash(options) end - # Sets the name of the wrapper key, or the model which +ParamsWrapper+ - # would use to determine the attribute names from. + # Sets the name of the wrapper key, or the model which `ParamsWrapper` would use + # to determine the attribute names from. # - # ==== Examples - # wrap_parameters format: :xml - # # enables the parameter wrapper for XML format + # #### Examples + # wrap_parameters format: :xml + # # enables the parameter wrapper for XML format # - # wrap_parameters :person - # # wraps parameters into +params[:person]+ hash + # wrap_parameters :person + # # wraps parameters into +params[:person]+ hash # - # wrap_parameters Person - # # wraps parameters by determining the wrapper key from Person class - # # (+person+, in this case) and the list of attribute names + # wrap_parameters Person + # # wraps parameters by determining the wrapper key from Person class + # # (+person+, in this case) and the list of attribute names # - # wrap_parameters include: [:username, :title] - # # wraps only +:username+ and +:title+ attributes from parameters. + # wrap_parameters include: [:username, :title] + # # wraps only +:username+ and +:title+ attributes from parameters. # - # wrap_parameters false - # # disables parameters wrapping for this controller altogether. + # wrap_parameters false + # # disables parameters wrapping for this controller altogether. + # + # #### Options + # * `:format` - The list of formats in which the parameters wrapper will be + # enabled. + # * `:include` - The list of attribute names which parameters wrapper will + # wrap into a nested hash. + # * `:exclude` - The list of attribute names which parameters wrapper will + # exclude from a nested hash. # - # ==== Options - # * :format - The list of formats in which the parameters wrapper - # will be enabled. - # * :include - The list of attribute names which parameters wrapper - # will wrap into a nested hash. - # * :exclude - The list of attribute names which parameters wrapper - # will exclude from a nested hash. def wrap_parameters(name_or_model_or_options, options = {}) model = nil @@ -237,9 +239,8 @@ def wrap_parameters(name_or_model_or_options, options = {}) self._wrapper_options = opts end - # Sets the default wrapper key or model which will be used to determine - # wrapper key and attribute names. Called automatically when the - # module is inherited. + # Sets the default wrapper key or model which will be used to determine wrapper + # key and attribute names. Called automatically when the module is inherited. def inherited(klass) if klass._wrapper_options.format.any? params = klass._wrapper_options.dup @@ -251,8 +252,8 @@ def inherited(klass) end private - # Performs parameters wrapping upon the request. Called automatically - # by the metal call stack. + # Performs parameters wrapping upon the request. Called automatically by the + # metal call stack. def process_action(*) _perform_parameter_wrapping if _wrapper_enabled? super diff --git a/actionpack/lib/action_controller/metal/permissions_policy.rb b/actionpack/lib/action_controller/metal/permissions_policy.rb index b2fcf28fba..9e1e4a454d 100644 --- a/actionpack/lib/action_controller/metal/permissions_policy.rb +++ b/actionpack/lib/action_controller/metal/permissions_policy.rb @@ -1,27 +1,28 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController # :nodoc: module PermissionsPolicy extend ActiveSupport::Concern module ClassMethods - # Overrides parts of the globally configured +Feature-Policy+ - # header: + # Overrides parts of the globally configured `Feature-Policy` header: # - # class PagesController < ApplicationController - # permissions_policy do |policy| - # policy.geolocation "https://example.com" + # class PagesController < ApplicationController + # permissions_policy do |policy| + # policy.geolocation "https://example.com" + # end # end - # end # - # Options can be passed similar to +before_action+. For example, pass - # only: :index to override the header on the index action only: + # Options can be passed similar to `before_action`. For example, pass `only: + # :index` to override the header on the index action only: # - # class PagesController < ApplicationController - # permissions_policy(only: :index) do |policy| - # policy.camera :self + # class PagesController < ApplicationController + # permissions_policy(only: :index) do |policy| + # policy.camera :self + # end # end - # end # def permissions_policy(**options, &block) before_action(options) do diff --git a/actionpack/lib/action_controller/metal/rate_limiting.rb b/actionpack/lib/action_controller/metal/rate_limiting.rb index b6afa341f0..b2f9f218b8 100644 --- a/actionpack/lib/action_controller/metal/rate_limiting.rb +++ b/actionpack/lib/action_controller/metal/rate_limiting.rb @@ -1,39 +1,49 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController # :nodoc: module RateLimiting extend ActiveSupport::Concern module ClassMethods - # Applies a rate limit to all actions or those specified by the normal before_action filters with only: and except:. + # Applies a rate limit to all actions or those specified by the normal + # `before_action` filters with `only:` and `except:`. # - # The maximum number of requests allowed is specified to: and constrained to the window of time given by within:. + # The maximum number of requests allowed is specified `to:` and constrained to + # the window of time given by `within:`. # - # Rate limits are by default unique to the ip address making the request, but you can provide your own identity function by passing a callable - # in the by: parameter. It's evaluated within the context of the controller processing the request. + # Rate limits are by default unique to the ip address making the request, but + # you can provide your own identity function by passing a callable in the `by:` + # parameter. It's evaluated within the context of the controller processing the + # request. # - # Requests that exceed the rate limit are refused with a 429 Too Many Requests response. You can specialize this by passing a callable - # in the with: parameter. It's evaluated within the context of the controller processing the request. + # Requests that exceed the rate limit are refused with a `429 Too Many Requests` + # response. You can specialize this by passing a callable in the `with:` + # parameter. It's evaluated within the context of the controller processing the + # request. # - # Rate limiting relies on a backing ActiveSupport::Cache store and defaults to config.action_controller.cache_store, which - # itself defaults to the global config.cache_store. If you don't want to store rate limits in the same datastore as your general caches, - # you can pass a custom store in the store parameter. + # Rate limiting relies on a backing `ActiveSupport::Cache` store and defaults to + # `config.action_controller.cache_store`, which itself defaults to the global + # `config.cache_store`. If you don't want to store rate limits in the same + # datastore as your general caches, you can pass a custom store in the `store` + # parameter. # # Examples: # - # class SessionsController < ApplicationController - # rate_limit to: 10, within: 3.minutes, only: :create - # end + # class SessionsController < ApplicationController + # rate_limit to: 10, within: 3.minutes, only: :create + # end # - # class SignupsController < ApplicationController - # rate_limit to: 1000, within: 10.seconds, - # by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups on domain!" }, only: :new - # end + # class SignupsController < ApplicationController + # rate_limit to: 1000, within: 10.seconds, + # by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups on domain!" }, only: :new + # end # - # class APIController < ApplicationController - # RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"]) - # rate_limit to: 10, within: 3.minutes, store: RATE_LIMIT_STORE - # end + # class APIController < ApplicationController + # RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"]) + # rate_limit to: 10, within: 3.minutes, store: RATE_LIMIT_STORE + # end def rate_limit(to:, within:, by: -> { request.remote_ip }, with: -> { head :too_many_requests }, store: cache_store, **options) before_action -> { rate_limiting(to: to, within: within, by: by, with: with, store: store) }, **options end diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index af0cc89a3b..952aca5bb0 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController module Redirecting extend ActiveSupport::Concern @@ -15,72 +17,89 @@ class UnsafeRedirectError < StandardError; end mattr_accessor :raise_on_open_redirects, default: false end - # Redirects the browser to the target specified in +options+. This parameter can be any one of: + # Redirects the browser to the target specified in `options`. This parameter can + # be any one of: # - # * Hash - The URL will be generated by calling url_for with the +options+. - # * Record - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. - # * String starting with protocol:// (like http://) or a protocol relative reference (like //) - Is passed straight through as the target for redirection. - # * String not containing a protocol - The current protocol and host is prepended to the string. - # * Proc - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+. + # * `Hash` - The URL will be generated by calling url_for with the `options`. + # * `Record` - The URL will be generated by calling url_for with the + # `options`, which will reference a named URL for that record. + # * `String` starting with `protocol://` (like `http://`) or a protocol + # relative reference (like `//`) - Is passed straight through as the target + # for redirection. + # * `String` not containing a protocol - The current protocol and host is + # prepended to the string. + # * `Proc` - A block that will be executed in the controller's context. Should + # return any option accepted by `redirect_to`. # - # === Examples # - # redirect_to action: "show", id: 5 - # redirect_to @post - # redirect_to "http://www.rubyonrails.org" - # redirect_to "/images/screenshot.jpg" - # redirect_to posts_url - # redirect_to proc { edit_post_url(@post) } + # ### Examples # - # The redirection happens as a 302 Found header unless otherwise specified using the :status option: + # redirect_to action: "show", id: 5 + # redirect_to @post + # redirect_to "http://www.rubyonrails.org" + # redirect_to "/images/screenshot.jpg" + # redirect_to posts_url + # redirect_to proc { edit_post_url(@post) } # - # redirect_to post_url(@post), status: :found - # redirect_to action: 'atom', status: :moved_permanently - # redirect_to post_url(@post), status: 301 - # redirect_to action: 'atom', status: 302 + # The redirection happens as a `302 Found` header unless otherwise specified + # using the `:status` option: # - # The status code can either be a standard {HTTP Status code}[https://www.iana.org/assignments/http-status-codes] as an - # integer, or a symbol representing the downcased, underscored and symbolized description. - # Note that the status code must be a 3xx HTTP code, or redirection will not occur. + # redirect_to post_url(@post), status: :found + # redirect_to action: 'atom', status: :moved_permanently + # redirect_to post_url(@post), status: 301 + # redirect_to action: 'atom', status: 302 + # + # The status code can either be a standard [HTTP Status + # code](https://www.iana.org/assignments/http-status-codes) as an integer, or a + # symbol representing the downcased, underscored and symbolized description. + # Note that the status code must be a 3xx HTTP code, or redirection will not + # occur. # # If you are using XHR requests other than GET or POST and redirecting after the # request then some browsers will follow the redirect using the original request # method. This may lead to undesirable behavior such as a double DELETE. To work - # around this you can return a 303 See Other status code which will be + # around this you can return a `303 See Other` status code which will be # followed using a GET request. # - # redirect_to posts_url, status: :see_other - # redirect_to action: 'index', status: 303 + # redirect_to posts_url, status: :see_other + # redirect_to action: 'index', status: 303 # - # It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names - # +alert+ and +notice+ as well as a general purpose +flash+ bucket. + # It is also possible to assign a flash message as part of the redirection. + # There are two special accessors for the commonly used flash names `alert` and + # `notice` as well as a general purpose `flash` bucket. # - # redirect_to post_url(@post), alert: "Watch it, mister!" - # redirect_to post_url(@post), status: :found, notice: "Pay attention to the road" - # redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id } - # redirect_to({ action: 'atom' }, alert: "Something serious happened") + # redirect_to post_url(@post), alert: "Watch it, mister!" + # redirect_to post_url(@post), status: :found, notice: "Pay attention to the road" + # redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id } + # redirect_to({ action: 'atom' }, alert: "Something serious happened") # - # Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function. - # To terminate the execution of the function immediately after the +redirect_to+, use return. + # Statements after `redirect_to` in our controller get executed, so + # `redirect_to` doesn't stop the execution of the function. To terminate the + # execution of the function immediately after the `redirect_to`, use return. # - # redirect_to post_url(@post) and return + # redirect_to post_url(@post) and return # - # === Open Redirect protection + # ### Open Redirect protection # - # By default, \Rails protects against redirecting to external hosts for your app's safety, so called open redirects. - # Note: this was a new default in \Rails 7.0, after upgrading opt-in by uncommenting the line with +raise_on_open_redirects+ in config/initializers/new_framework_defaults_7_0.rb + # By default, Rails protects against redirecting to external hosts for your + # app's safety, so called open redirects. Note: this was a new default in Rails + # 7.0, after upgrading opt-in by uncommenting the line with + # `raise_on_open_redirects` in + # `config/initializers/new_framework_defaults_7_0.rb` # # Here #redirect_to automatically validates the potentially-unsafe URL: # - # redirect_to params[:redirect_url] + # redirect_to params[:redirect_url] # # Raises UnsafeRedirectError in the case of an unsafe redirect. # - # To allow any external redirects pass allow_other_host: true, though using a user-provided param in that case is unsafe. + # To allow any external redirects pass `allow_other_host: true`, though using a + # user-provided param in that case is unsafe. # - # redirect_to "https://rubyonrails.org", allow_other_host: true + # redirect_to "https://rubyonrails.org", allow_other_host: true # - # See #url_from for more information on what an internal and safe URL is, or how to fall back to an alternate redirect URL in the unsafe case. + # See #url_from for more information on what an internal and safe URL is, or how + # to fall back to an alternate redirect URL in the unsafe case. def redirect_to(options = {}, response_options = {}) raise ActionControllerError.new("Cannot redirect to nil!") unless options raise AbstractController::DoubleRenderError if response_body @@ -96,50 +115,53 @@ def redirect_to(options = {}, response_options = {}) self.response_body = "" end - # Soft deprecated alias for #redirect_back_or_to where the +fallback_location+ location is supplied as a keyword argument instead - # of the first positional argument. + # Soft deprecated alias for #redirect_back_or_to where the `fallback_location` + # location is supplied as a keyword argument instead of the first positional + # argument. def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args) redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args end - # Redirects the browser to the page that issued the request (the referrer) - # if possible, otherwise redirects to the provided default fallback - # location. + # Redirects the browser to the page that issued the request (the referrer) if + # possible, otherwise redirects to the provided default fallback location. # - # The referrer information is pulled from the HTTP +Referer+ (sic) header on - # the request. This is an optional header and its presence on the request is - # subject to browser security settings and user preferences. If the request - # is missing this header, the fallback_location will be used. + # The referrer information is pulled from the HTTP `Referer` (sic) header on the + # request. This is an optional header and its presence on the request is subject + # to browser security settings and user preferences. If the request is missing + # this header, the `fallback_location` will be used. # - # redirect_back_or_to({ action: "show", id: 5 }) - # redirect_back_or_to @post - # redirect_back_or_to "http://www.rubyonrails.org" - # redirect_back_or_to "/images/screenshot.jpg" - # redirect_back_or_to posts_url - # redirect_back_or_to proc { edit_post_url(@post) } - # redirect_back_or_to '/', allow_other_host: false + # redirect_back_or_to({ action: "show", id: 5 }) + # redirect_back_or_to @post + # redirect_back_or_to "http://www.rubyonrails.org" + # redirect_back_or_to "/images/screenshot.jpg" + # redirect_back_or_to posts_url + # redirect_back_or_to proc { edit_post_url(@post) } + # redirect_back_or_to '/', allow_other_host: false # - # ==== Options - # * :allow_other_host - Allow or disallow redirection to the host that is different to the current host, defaults to true. + # #### Options + # * `:allow_other_host` - Allow or disallow redirection to the host that is + # different to the current host, defaults to true. # - # All other options that can be passed to #redirect_to are accepted as - # options, and the behavior is identical. + # + # All other options that can be passed to #redirect_to are accepted as options, + # and the behavior is identical. def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options) if request.referer && (allow_other_host || _url_host_allowed?(request.referer)) redirect_to request.referer, allow_other_host: allow_other_host, **options else - # The method level `allow_other_host` doesn't apply in the fallback case, omit and let the `redirect_to` handling take over. + # The method level `allow_other_host` doesn't apply in the fallback case, omit + # and let the `redirect_to` handling take over. redirect_to fallback_location, **options end end def _compute_redirect_to_location(request, options) # :nodoc: case options - # The scheme name consist of a letter followed by any combination of - # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") - # characters; and is terminated by a colon (":"). - # See https://tools.ietf.org/html/rfc3986#section-3.1 - # The protocol relative scheme starts with a double slash "//". + # The scheme name consist of a letter followed by any combination of letters, + # digits, and the plus ("+"), period ("."), or hyphen ("-") characters; and is + # terminated by a colon (":"). See + # https://tools.ietf.org/html/rfc3986#section-3.1 The protocol relative scheme + # starts with a double slash "//". when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i options.to_str when String @@ -153,25 +175,30 @@ def _compute_redirect_to_location(request, options) # :nodoc: module_function :_compute_redirect_to_location public :_compute_redirect_to_location - # Verifies the passed +location+ is an internal URL that's safe to redirect to and returns it, or nil if not. - # Useful to wrap a params provided redirect URL and fall back to an alternate URL to redirect to: + # Verifies the passed `location` is an internal URL that's safe to redirect to + # and returns it, or nil if not. Useful to wrap a params provided redirect URL + # and fall back to an alternate URL to redirect to: # - # redirect_to url_from(params[:redirect_url]) || root_url + # redirect_to url_from(params[:redirect_url]) || root_url # - # The +location+ is considered internal, and safe, if it's on the same host as request.host: + # The `location` is considered internal, and safe, if it's on the same host as + # `request.host`: # - # # If request.host is example.com: - # url_from("https://example.com/profile") # => "https://example.com/profile" - # url_from("http://example.com/profile") # => "http://example.com/profile" - # url_from("http://evil.com/profile") # => nil + # # If request.host is example.com: + # url_from("https://example.com/profile") # => "https://example.com/profile" + # url_from("http://example.com/profile") # => "http://example.com/profile" + # url_from("http://evil.com/profile") # => nil # # Subdomains are considered part of the host: # - # # If request.host is on https://example.com or https://app.example.com, you'd get: - # url_from("https://dev.example.com/profile") # => nil + # # If request.host is on https://example.com or https://app.example.com, you'd get: + # url_from("https://dev.example.com/profile") # => nil # - # NOTE: there's a similarity with {url_for}[rdoc-ref:ActionDispatch::Routing::UrlFor#url_for], which generates an internal URL from various options from within the app, e.g. url_for(@post). - # However, #url_from is meant to take an external parameter to verify as in url_from(params[:redirect_url]). + # NOTE: there's a similarity with + # [url_for](rdoc-ref:ActionDispatch::Routing::UrlFor#url_for), which generates + # an internal URL from various options from within the app, e.g. + # `url_for(@post)`. However, #url_from is meant to take an external parameter to + # verify as in `url_from(params[:redirect_url])`. def url_from(location) location = location.presence location if location && _url_host_allowed?(location) @@ -212,9 +239,8 @@ def _url_host_allowed?(url) end def _ensure_url_is_http_header_safe(url) - # Attempt to comply with the set of valid token characters - # defined for an HTTP header value in - # https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 + # Attempt to comply with the set of valid token characters defined for an HTTP + # header value in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 if url.match?(ILLEGAL_HEADER_VALUE_REGEX) msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \ "Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6" diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 3ea0216e2b..86999f5181 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "set" module ActionController @@ -13,7 +15,7 @@ def self.remove_renderer(key) Renderers.remove(key) end - # See Responder#api_behavior + # See `Responder#api_behavior` class MissingRenderer < LoadError def initialize(format) super "No renderer defined for format: #{format}" @@ -24,7 +26,7 @@ module Renderers extend ActiveSupport::Concern # A Set containing renderer names that correspond to available renderer procs. - # Default values are :json, :js, :xml. + # Default values are `:json`, `:js`, `:xml`. RENDERERS = Set.new included do @@ -42,35 +44,34 @@ module All end end - # Adds a new renderer to call within controller actions. - # A renderer is invoked by passing its name as an option to - # AbstractController::Rendering#render. To create a renderer - # pass it a name and a block. The block takes two arguments, the first - # is the value paired with its key and the second is the remaining - # hash of options passed to +render+. + # Adds a new renderer to call within controller actions. A renderer is invoked + # by passing its name as an option to AbstractController::Rendering#render. To + # create a renderer pass it a name and a block. The block takes two arguments, + # the first is the value paired with its key and the second is the remaining + # hash of options passed to `render`. # # Create a csv renderer: # - # ActionController::Renderers.add :csv do |obj, options| - # filename = options[:filename] || 'data' - # str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s - # send_data str, type: Mime[:csv], - # disposition: "attachment; filename=#{filename}.csv" - # end + # ActionController::Renderers.add :csv do |obj, options| + # filename = options[:filename] || 'data' + # str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s + # send_data str, type: Mime[:csv], + # disposition: "attachment; filename=#{filename}.csv" + # end # - # Note that we used Mime[:csv] for the csv mime type as it comes with \Rails. + # Note that we used [Mime](:csv) for the csv mime type as it comes with Rails. # For a custom renderer, you'll need to register a mime type with - # Mime::Type.register. + # `Mime::Type.register`. # # To use the csv renderer in a controller action: # - # def show - # @csvable = Csvable.find(params[:id]) - # respond_to do |format| - # format.html - # format.csv { render csv: @csvable, filename: @csvable.name } + # def show + # @csvable = Csvable.find(params[:id]) + # respond_to do |format| + # format.html + # format.csv { render csv: @csvable, filename: @csvable.name } + # end # end - # end def self.add(key, &block) define_method(_render_with_renderer_method_name(key), &block) RENDERERS << key.to_sym @@ -80,7 +81,7 @@ def self.add(key, &block) # # To remove a csv renderer: # - # ActionController::Renderers.remove(:csv) + # ActionController::Renderers.remove(:csv) def self.remove(key) RENDERERS.delete(key.to_sym) method_name = _render_with_renderer_method_name(key) @@ -92,39 +93,39 @@ def self._render_with_renderer_method_name(key) end module ClassMethods - # Adds, by name, a renderer or renderers to the +_renderers+ available - # to call within controller actions. + # Adds, by name, a renderer or renderers to the `_renderers` available to call + # within controller actions. # # It is useful when rendering from an ActionController::Metal controller or # otherwise to add an available renderer proc to a specific controller. # - # Both ActionController::Base and ActionController::API - # include ActionController::Renderers::All, making all renderers - # available in the controller. See Renderers::RENDERERS and Renderers.add. + # Both ActionController::Base and ActionController::API include + # ActionController::Renderers::All, making all renderers available in the + # controller. See Renderers::RENDERERS and Renderers.add. # - # Since ActionController::Metal controllers cannot render, the controller - # must include AbstractController::Rendering, ActionController::Rendering, - # and ActionController::Renderers, and have at least one renderer. + # Since ActionController::Metal controllers cannot render, the controller must + # include AbstractController::Rendering, ActionController::Rendering, and + # ActionController::Renderers, and have at least one renderer. # - # Rather than including ActionController::Renderers::All and including all renderers, - # you may specify which renderers to include by passing the renderer name or names to - # +use_renderers+. For example, a controller that includes only the :json renderer - # (+_render_with_renderer_json+) might look like: + # Rather than including ActionController::Renderers::All and including all + # renderers, you may specify which renderers to include by passing the renderer + # name or names to `use_renderers`. For example, a controller that includes only + # the `:json` renderer (`_render_with_renderer_json`) might look like: # - # class MetalRenderingController < ActionController::Metal - # include AbstractController::Rendering - # include ActionController::Rendering - # include ActionController::Renderers + # class MetalRenderingController < ActionController::Metal + # include AbstractController::Rendering + # include ActionController::Rendering + # include ActionController::Renderers # - # use_renderers :json + # use_renderers :json # - # def show - # render json: record + # def show + # render json: record + # end # end - # end # - # You must specify a +use_renderer+, else the +controller.renderer+ and - # +controller._renderers+ will be nil, and the action will fail. + # You must specify a `use_renderer`, else the `controller.renderer` and + # `controller._renderers` will be `nil`, and the action will fail. def use_renderers(*args) renderers = _renderers + args self._renderers = renderers.freeze @@ -132,11 +133,11 @@ def use_renderers(*args) alias use_renderer use_renderers end - # Called by +render+ in AbstractController::Rendering - # which sets the return value as the +response_body+. + # Called by `render` in AbstractController::Rendering which sets the return + # value as the `response_body`. # - # If no renderer is found, +super+ returns control to - # ActionView::Rendering.render_to_body, if present. + # If no renderer is found, `super` returns control to + # `ActionView::Rendering.render_to_body`, if present. def render_to_body(options) _render_to_body_with_renderer(options) || super end diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 1d5639d92b..a411c7e116 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController module Rendering extend ActiveSupport::Concern @@ -10,8 +12,8 @@ module ClassMethods # Documentation at ActionController::Renderer#render delegate :render, to: :renderer - # Returns a renderer instance (inherited from ActionController::Renderer) - # for the controller. + # Returns a renderer instance (inherited from ActionController::Renderer) for + # the controller. attr_reader :renderer def setup_renderer! # :nodoc: @@ -24,137 +26,139 @@ def inherited(klass) end end - # Renders a template and assigns the result to +self.response_body+. + # Renders a template and assigns the result to `self.response_body`. # - # If no rendering mode option is specified, the template will be derived - # from the first argument. + # If no rendering mode option is specified, the template will be derived from + # the first argument. # - # render "posts/show" - # # => renders app/views/posts/show.html.erb + # render "posts/show" + # # => renders app/views/posts/show.html.erb # - # # In a PostsController action... - # render :show - # # => renders app/views/posts/show.html.erb + # # In a PostsController action... + # render :show + # # => renders app/views/posts/show.html.erb # - # If the first argument responds to +render_in+, the template will be - # rendered by calling +render_in+ with the current view context. + # If the first argument responds to `render_in`, the template will be rendered + # by calling `render_in` with the current view context. # - # class Greeting - # def render_in(view_context) - # view_context.render html: "

Hello, World

" + # class Greeting + # def render_in(view_context) + # view_context.render html: "

Hello, World

" + # end + # + # def format + # :html + # end # end # - # def format - # :html - # end - # end + # render(Greeting.new) + # # => "

Hello, World

" # - # render(Greeting.new) - # # => "

Hello, World

" + # render(renderable: Greeting.new) + # # => "

Hello, World

" # - # render(renderable: Greeting.new) - # # => "

Hello, World

" + # #### Rendering Mode # - # ==== \Rendering Mode + # `:partial` + # : See ActionView::PartialRenderer for details. # - # [+:partial+] - # See ActionView::PartialRenderer for details. + # render partial: "posts/form", locals: { post: Post.new } + # # => renders app/views/posts/_form.html.erb # - # render partial: "posts/form", locals: { post: Post.new } - # # => renders app/views/posts/_form.html.erb + # `:file` + # : Renders the contents of a file. This option should **not** be used with + # unsanitized user input. # - # [+:file+] - # Renders the contents of a file. This option should not be used - # with unsanitized user input. + # render file: "/path/to/some/file" + # # => renders /path/to/some/file # - # render file: "/path/to/some/file" - # # => renders /path/to/some/file + # `:inline` + # : Renders an ERB template string. # - # [+:inline+] - # Renders an ERB template string. + # @name = "World" + # render inline: "

Hello, <%= @name %>!

" + # # => renders "

Hello, World!

" # - # @name = "World" - # render inline: "

Hello, <%= @name %>!

" - # # => renders "

Hello, World!

" + # `:body` + # : Renders the provided text, and sets the content type as `text/plain`. # - # [+:body+] - # Renders the provided text, and sets the content type as +text/plain+. + # render body: "Hello, World!" + # # => renders "Hello, World!" # - # render body: "Hello, World!" - # # => renders "Hello, World!" + # `:plain` + # : Renders the provided text, and sets the content type as `text/plain`. # - # [+:plain+] - # Renders the provided text, and sets the content type as +text/plain+. + # render plain: "Hello, World!" + # # => renders "Hello, World!" # - # render plain: "Hello, World!" - # # => renders "Hello, World!" + # `:html` + # : Renders the provided HTML string, and sets the content type as + # `text/html`. If the string is not `html_safe?`, performs HTML escaping on + # the string before rendering. # - # [+:html+] - # Renders the provided HTML string, and sets the content type as +text/html+. - # If the string is not +html_safe?+, performs HTML escaping on the string - # before rendering. + # render html: "

Hello, World!

".html_safe + # # => renders "

Hello, World!

" # - # render html: "

Hello, World!

".html_safe - # # => renders "

Hello, World!

" + # render html: "

Hello, World!

" + # # => renders "<h1>Hello, World!</h1>" # - # render html: "

Hello, World!

" - # # => renders "<h1>Hello, World!</h1>" + # `:json` + # : Renders the provided object as JSON, and sets the content type as + # `application/json`. If the object is not a string, it will be converted to + # JSON by calling `to_json`. # - # [+:json+] - # Renders the provided object as JSON, and sets the content type as - # +application/json+. If the object is not a string, it will be converted - # to JSON by calling +to_json+. + # render json: { hello: "world" } + # # => renders "{\"hello\":\"world\"}" # - # render json: { hello: "world" } - # # => renders "{\"hello\":\"world\"}" + # `:renderable` + # : Renders the provided object by calling `render_in` with the current view + # context. The response format is determined by calling `format` on the + # renderable if it responds to `format`, falling back to `text/html` by + # default. # - # [+:renderable+] - # Renders the provided object by calling +render_in+ with the current view - # context. The response format is determined by calling +format+ on the - # renderable if it responds to +format+, falling back to +text/html+ by default. + # render renderable: Greeting.new + # # => renders "

Hello, World

" # - # render renderable: Greeting.new - # # => renders "

Hello, World

" # # By default, when a rendering mode is specified, no layout template is # rendered. # - # ==== Options + # #### Options # - # [+:assigns+] - # Hash of instance variable assignments for the template. + # `:assigns` + # : Hash of instance variable assignments for the template. # - # render inline: "

Hello, <%= @name %>!

", assigns: { name: "World" } - # # => renders "

Hello, World!

" + # render inline: "

Hello, <%= @name %>!

", assigns: { name: "World" } + # # => renders "

Hello, World!

" # - # [+:locals+] - # Hash of local variable assignments for the template. + # `:locals` + # : Hash of local variable assignments for the template. # - # render inline: "

Hello, <%= name %>!

", locals: { name: "World" } - # # => renders "

Hello, World!

" + # render inline: "

Hello, <%= name %>!

", locals: { name: "World" } + # # => renders "

Hello, World!

" # - # [+:layout+] - # The layout template to render. Can also be +false+ or +true+ to disable - # or (re)enable the default layout template. + # `:layout` + # : The layout template to render. Can also be `false` or `true` to disable or + # (re)enable the default layout template. # - # render "posts/show", layout: "holiday" - # # => renders app/views/posts/show.html.erb with the app/views/layouts/holiday.html.erb layout + # render "posts/show", layout: "holiday" + # # => renders app/views/posts/show.html.erb with the app/views/layouts/holiday.html.erb layout # - # render "posts/show", layout: false - # # => renders app/views/posts/show.html.erb with no layout + # render "posts/show", layout: false + # # => renders app/views/posts/show.html.erb with no layout # - # render inline: "

Hello, World!

", layout: true - # # => renders "

Hello, World!

" with the default layout + # render inline: "

Hello, World!

", layout: true + # # => renders "

Hello, World!

" with the default layout # - # [+:status+] - # The HTTP status code to send with the response. Can be specified as a - # number or as the status name in Symbol form. Defaults to 200. + # `:status` + # : The HTTP status code to send with the response. Can be specified as a + # number or as the status name in Symbol form. Defaults to 200. # - # render "posts/new", status: 422 - # # => renders app/views/posts/new.html.erb with HTTP status code 422 + # render "posts/new", status: 422 + # # => renders app/views/posts/new.html.erb with HTTP status code 422 # - # render "posts/new", status: :unprocessable_entity - # # => renders app/views/posts/new.html.erb with HTTP status code 422 + # render "posts/new", status: :unprocessable_entity + # # => renders app/views/posts/new.html.erb with HTTP status code 422 # #-- # Check for double render errors and set the content_type after rendering. @@ -164,7 +168,7 @@ def render(*args) end # Similar to #render, but only returns the rendered template as a string, - # instead of setting +self.response_body+. + # instead of setting `self.response_body`. #-- # Override render_to_string because body can now be set to a Rack body. def render_to_string(*) diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index d72aec8227..215b1bfb1b 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "rack/session/abstract/id" require "action_controller/metal/exceptions" require "active_support/security_utils" @@ -11,51 +13,53 @@ class InvalidAuthenticityToken < ActionControllerError # :nodoc: class InvalidCrossOriginRequest < ActionControllerError # :nodoc: end - # = Action Controller Request Forgery Protection + # # Action Controller Request Forgery Protection # - # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks - # by including a token in the rendered HTML for your application. This token is - # stored as a random string in the session, to which an attacker does not have - # access. When a request reaches your application, \Rails verifies the received - # token with the token in the session. All requests are checked except GET requests - # as these should be idempotent. Keep in mind that all session-oriented requests - # are CSRF protected by default, including JavaScript and HTML requests. + # Controller actions are protected from Cross-Site Request Forgery (CSRF) + # attacks by including a token in the rendered HTML for your application. This + # token is stored as a random string in the session, to which an attacker does + # not have access. When a request reaches your application, Rails verifies the + # received token with the token in the session. All requests are checked except + # GET requests as these should be idempotent. Keep in mind that all + # session-oriented requests are CSRF protected by default, including JavaScript + # and HTML requests. # # Since HTML and JavaScript requests are typically made from the browser, we - # need to ensure to verify request authenticity for the web browser. We can - # use session-oriented authentication for these types of requests, by using - # the protect_from_forgery method in our controllers. + # need to ensure to verify request authenticity for the web browser. We can use + # session-oriented authentication for these types of requests, by using the + # `protect_from_forgery` method in our controllers. # # GET requests are not protected since they don't have side effects like writing # to the database and don't leak sensitive information. JavaScript requests are - # an exception: a third-party site can use a + # "> # - # The first two characters (">) are required in case the exception - # happens while rendering attributes for a given tag. You can check the real - # cause for the exception in your logger. + # The first two characters (`">`) are required in case the exception happens + # while rendering attributes for a given tag. You can check the real cause for + # the exception in your logger. # - # == Web server support + # ## Web server support # - # Not all web servers support streaming out-of-the-box. You need to check - # the instructions for each of them. + # Not all web servers support streaming out-of-the-box. You need to check the + # instructions for each of them. # - # ==== Unicorn + # #### Unicorn # - # Unicorn supports streaming but it needs to be configured. For this, you - # need to create a config file as follow: + # Unicorn supports streaming but it needs to be configured. For this, you need + # to create a config file as follow: # - # # unicorn.config.rb - # listen 3000, tcp_nopush: false + # # unicorn.config.rb + # listen 3000, tcp_nopush: false # # And use it on initialization: # - # unicorn_rails --config-file unicorn.config.rb + # unicorn_rails --config-file unicorn.config.rb # - # You may also want to configure other parameters like :tcp_nodelay. + # You may also want to configure other parameters like `:tcp_nodelay`. # # For more information, please check the - # {documentation}[https://bogomips.org/unicorn/Unicorn/Configurator.html#method-i-listen]. + # [documentation](https://bogomips.org/unicorn/Unicorn/Configurator.html#method- + # i-listen). # - # If you are using Unicorn with NGINX, you may need to tweak NGINX. - # \Streaming should work out of the box on Rainbows. + # If you are using Unicorn with NGINX, you may need to tweak NGINX. Streaming + # should work out of the box on Rainbows. # - # ==== Passenger + # #### Passenger # # Phusion Passenger with NGINX, offers two streaming mechanisms out of the box. # - # 1. NGINX response buffering mechanism which is dependent on the value of - # +passenger_buffer_response+ option (default is "off"). - # 2. Passenger buffering system which is always 'on' irrespective of the value - # of +passenger_buffer_response+. + # 1. NGINX response buffering mechanism which is dependent on the value of + # `passenger_buffer_response` option (default is "off"). + # 2. Passenger buffering system which is always 'on' irrespective of the value + # of `passenger_buffer_response`. # - # When +passenger_buffer_response+ is turned "on", then streaming would be - # done at the NGINX level which waits until the application is done sending - # the response back to the client. # - # For more information, please check the - # {documentation}[https://www.phusionpassenger.com/docs/references/config_reference/nginx/#passenger_buffer_response]. + # When `passenger_buffer_response` is turned "on", then streaming would be done + # at the NGINX level which waits until the application is done sending the + # response back to the client. # + # For more information, please check the [documentation] + # (https://www.phusionpassenger.com/docs/references/config_reference/nginx/#passenger_buffer_response). module Streaming class Body # :nodoc: TERM = "\r\n" @@ -213,8 +215,8 @@ def initialize(body) @body = body end - # For each element yielded by the response body, yield - # the element in chunked encoding. + # For each element yielded by the response body, yield the element in chunked + # encoding. def each(&block) term = TERM @body.each do |chunk| @@ -248,7 +250,7 @@ def _process_options(options) end end - # Call render_body if we are streaming instead of usual +render+. + # Call render_body if we are streaming instead of usual `render`. def _render_template(options) if options.delete(:stream) Body.new view_renderer.render_body(view_context, options) diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 6afb4bdb2d..323138ae02 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/hash/indifferent_access" require "active_support/core_ext/array/wrap" require "active_support/core_ext/string/filters" @@ -14,11 +16,11 @@ module ActionController # Raised when a required parameter is missing. # - # params = ActionController::Parameters.new(a: {}) - # params.fetch(:b) - # # => ActionController::ParameterMissing: param is missing or the value is empty: b - # params.require(:a) - # # => ActionController::ParameterMissing: param is missing or the value is empty: a + # params = ActionController::Parameters.new(a: {}) + # params.fetch(:b) + # # => ActionController::ParameterMissing: param is missing or the value is empty: b + # params.require(:a) + # # => ActionController::ParameterMissing: param is missing or the value is empty: a class ParameterMissing < KeyError attr_reader :param, :keys # :nodoc: @@ -38,12 +40,12 @@ def corrections # :nodoc: end # Raised when a supplied parameter is not expected and - # ActionController::Parameters.action_on_unpermitted_parameters - # is set to :raise. + # ActionController::Parameters.action_on_unpermitted_parameters is set to + # `:raise`. # - # params = ActionController::Parameters.new(a: "123", b: "456") - # params.permit(:c) - # # => ActionController::UnpermittedParameters: found unpermitted parameters: :a, :b + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => ActionController::UnpermittedParameters: found unpermitted parameters: :a, :b class UnpermittedParameters < IndexError attr_reader :params # :nodoc: @@ -53,12 +55,12 @@ def initialize(params) # :nodoc: end end - # Raised when a Parameters instance is not marked as permitted and - # an operation to transform it to hash is called. + # Raised when a Parameters instance is not marked as permitted and an operation + # to transform it to hash is called. # - # params = ActionController::Parameters.new(a: "123", b: "456") - # params.to_h - # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.to_h + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash class UnfilteredParameters < ArgumentError def initialize # :nodoc: super("unable to convert unpermitted parameters to hash") @@ -67,12 +69,12 @@ def initialize # :nodoc: # Raised when initializing Parameters with keys that aren't strings or symbols. # - # ActionController::Parameters.new(123 => 456) - # # => ActionController::InvalidParameterKey: all keys must be Strings or Symbols, got: Integer + # ActionController::Parameters.new(123 => 456) + # # => ActionController::InvalidParameterKey: all keys must be Strings or Symbols, got: Integer class InvalidParameterKey < ArgumentError end - # = Action Controller \Parameters + # # Action Controller Parameters # # Allows you to choose which attributes should be permitted for mass updating # and thus prevent accidentally exposing that which shouldn't be exposed. @@ -80,63 +82,68 @@ class InvalidParameterKey < ArgumentError # used to mark parameters as required. The latter is used to set the parameter # as permitted and limit which attributes should be allowed for mass updating. # - # params = ActionController::Parameters.new({ - # person: { - # name: "Francesco", - # age: 22, - # role: "admin" - # } - # }) + # params = ActionController::Parameters.new({ + # person: { + # name: "Francesco", + # age: 22, + # role: "admin" + # } + # }) # - # permitted = params.require(:person).permit(:name, :age) - # permitted # => #"Francesco", "age"=>22} permitted: true> - # permitted.permitted? # => true + # permitted = params.require(:person).permit(:name, :age) + # permitted # => #"Francesco", "age"=>22} permitted: true> + # permitted.permitted? # => true # - # Person.first.update!(permitted) - # # => # + # Person.first.update!(permitted) + # # => # # # It provides two options that controls the top-level behavior of new instances: # - # * +permit_all_parameters+ - If it's +true+, all the parameters will be - # permitted by default. The default is +false+. - # * +action_on_unpermitted_parameters+ - Controls behavior when parameters that are not explicitly - # permitted are found. The default value is :log in test and development environments, - # +false+ otherwise. The values can be: - # * +false+ to take no action. - # * :log to emit an ActiveSupport::Notifications.instrument event on the - # unpermitted_parameters.action_controller topic and log at the DEBUG level. - # * :raise to raise an ActionController::UnpermittedParameters exception. + # * `permit_all_parameters` - If it's `true`, all the parameters will be + # permitted by default. The default is `false`. + # * `action_on_unpermitted_parameters` - Controls behavior when parameters + # that are not explicitly permitted are found. The default value is `:log` + # in test and development environments, `false` otherwise. The values can + # be: + # * `false` to take no action. + # * `:log` to emit an `ActiveSupport::Notifications.instrument` event on + # the `unpermitted_parameters.action_controller` topic and log at the + # DEBUG level. + # * `:raise` to raise an ActionController::UnpermittedParameters + # exception. + # + # # # Examples: # - # params = ActionController::Parameters.new - # params.permitted? # => false + # params = ActionController::Parameters.new + # params.permitted? # => false # - # ActionController::Parameters.permit_all_parameters = true + # ActionController::Parameters.permit_all_parameters = true # - # params = ActionController::Parameters.new - # params.permitted? # => true + # params = ActionController::Parameters.new + # params.permitted? # => true # - # params = ActionController::Parameters.new(a: "123", b: "456") - # params.permit(:c) - # # => # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => # # - # ActionController::Parameters.action_on_unpermitted_parameters = :raise + # ActionController::Parameters.action_on_unpermitted_parameters = :raise # - # params = ActionController::Parameters.new(a: "123", b: "456") - # params.permit(:c) - # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b # # Please note that these options *are not thread-safe*. In a multi-threaded # environment they should only be set once at boot-time and never mutated at # runtime. # - # You can fetch values of +ActionController::Parameters+ using either - # :key or "key". + # You can fetch values of `ActionController::Parameters` using either `:key` or + # `"key"`. # - # params = ActionController::Parameters.new(key: "value") - # params[:key] # => "value" - # params["key"] # => "value" + # params = ActionController::Parameters.new(key: "value") + # params[:key] # => "value" + # params["key"] # => "value" class Parameters include ActiveSupport::DeepMergeable @@ -148,12 +155,13 @@ class Parameters # :method: deep_merge # # :call-seq: - # deep_merge(other_hash, &block) + # deep_merge(other_hash, &block) # - # Returns a new +ActionController::Parameters+ instance with +self+ and +other_hash+ merged recursively. + # Returns a new `ActionController::Parameters` instance with `self` and + # `other_hash` merged recursively. # - # Like with Hash#merge in the standard library, a block can be provided - # to merge values. + # Like with `Hash#merge` in the standard library, a block can be provided to + # merge values. # #-- # Implemented by ActiveSupport::DeepMergeable#deep_merge. @@ -162,9 +170,9 @@ class Parameters # :method: deep_merge! # # :call-seq: - # deep_merge!(other_hash, &block) + # deep_merge!(other_hash, &block) # - # Same as +#deep_merge+, but modifies +self+. + # Same as `#deep_merge`, but modifies `self`. # #-- # Implemented by ActiveSupport::DeepMergeable#deep_merge!. @@ -173,7 +181,7 @@ class Parameters # :method: as_json # # :call-seq: - # as_json(options=nil) + # as_json(options=nil) # # Returns a hash that can be used as the JSON representation for the parameters. @@ -181,16 +189,16 @@ class Parameters # :method: each_key # # :call-seq: - # each_key(&block) + # each_key(&block) # - # Calls block once for each key in the parameters, passing the key. - # If no block is given, an enumerator is returned instead. + # Calls block once for each key in the parameters, passing the key. If no block + # is given, an enumerator is returned instead. ## # :method: empty? # # :call-seq: - # empty?() + # empty?() # # Returns true if the parameters have no key/value pairs. @@ -198,7 +206,7 @@ class Parameters # :method: exclude? # # :call-seq: - # exclude?(key) + # exclude?(key) # # Returns true if the given key is not present in the parameters. @@ -206,7 +214,7 @@ class Parameters # :method: include? # # :call-seq: - # include?(key) + # include?(key) # # Returns true if the given key is present in the parameters. @@ -214,7 +222,7 @@ class Parameters # :method: keys # # :call-seq: - # keys() + # keys() # # Returns a new array of the keys of the parameters. @@ -222,7 +230,7 @@ class Parameters # :method: to_s # # :call-seq: - # to_s() + # to_s() # # Returns the content of the parameters as a string. @@ -233,13 +241,12 @@ class Parameters alias_method :key?, :include? alias_method :member?, :include? - # By default, never raise an UnpermittedParameters exception if these - # params are present. The default includes both 'controller' and 'action' - # because they are added by Rails and should be of no concern. One way - # to change these is to specify +always_permitted_parameters+ in your - # config. For instance: + # By default, never raise an UnpermittedParameters exception if these params are + # present. The default includes both 'controller' and 'action' because they are + # added by Rails and should be of no concern. One way to change these is to + # specify `always_permitted_parameters` in your config. For instance: # - # config.action_controller.always_permitted_parameters = %w( controller action format ) + # config.action_controller.always_permitted_parameters = %w( controller action format ) cattr_accessor :always_permitted_parameters, default: %w( controller action ) class << self @@ -262,22 +269,22 @@ def nested_attribute?(key, value) # :nodoc: end end - # Returns a new +ActionController::Parameters+ instance. - # Also, sets the +permitted+ attribute to the default value of - # ActionController::Parameters.permit_all_parameters. + # Returns a new `ActionController::Parameters` instance. Also, sets the + # `permitted` attribute to the default value of + # `ActionController::Parameters.permit_all_parameters`. # - # class Person < ActiveRecord::Base - # end + # class Person < ActiveRecord::Base + # end # - # params = ActionController::Parameters.new(name: "Francesco") - # params.permitted? # => false - # Person.new(params) # => ActiveModel::ForbiddenAttributesError + # params = ActionController::Parameters.new(name: "Francesco") + # params.permitted? # => false + # Person.new(params) # => ActiveModel::ForbiddenAttributesError # - # ActionController::Parameters.permit_all_parameters = true + # ActionController::Parameters.permit_all_parameters = true # - # params = ActionController::Parameters.new(name: "Francesco") - # params.permitted? # => true - # Person.new(params) # => # + # params = ActionController::Parameters.new(name: "Francesco") + # params.permitted? # => true + # Person.new(params) # => # def initialize(parameters = {}, logging_context = {}) parameters.each_key do |key| unless key.is_a?(String) || key.is_a?(Symbol) @@ -290,7 +297,7 @@ def initialize(parameters = {}, logging_context = {}) @permitted = self.class.permit_all_parameters end - # Returns true if another +Parameters+ object contains the same content and + # Returns true if another `Parameters` object contains the same content and # permitted flag. def ==(other) if other.respond_to?(:permitted?) @@ -310,18 +317,18 @@ def hash [self.class, @parameters, @permitted].hash end - # Returns a safe ActiveSupport::HashWithIndifferentAccess - # representation of the parameters with all unpermitted keys removed. + # Returns a safe ActiveSupport::HashWithIndifferentAccess representation of the + # parameters with all unpermitted keys removed. # - # params = ActionController::Parameters.new({ - # name: "Senjougahara Hitagi", - # oddity: "Heavy stone crab" - # }) - # params.to_h - # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + # params = ActionController::Parameters.new({ + # name: "Senjougahara Hitagi", + # oddity: "Heavy stone crab" + # }) + # params.to_h + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash # - # safe_params = params.permit(:name) - # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"} + # safe_params = params.permit(:name) + # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"} def to_h(&block) if permitted? convert_parameters_to_hashes(@parameters, :to_h, &block) @@ -330,18 +337,18 @@ def to_h(&block) end end - # Returns a safe Hash representation of the parameters - # with all unpermitted keys removed. + # Returns a safe `Hash` representation of the parameters with all unpermitted + # keys removed. # - # params = ActionController::Parameters.new({ - # name: "Senjougahara Hitagi", - # oddity: "Heavy stone crab" - # }) - # params.to_hash - # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + # params = ActionController::Parameters.new({ + # name: "Senjougahara Hitagi", + # oddity: "Heavy stone crab" + # }) + # params.to_hash + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash # - # safe_params = params.permit(:name) - # safe_params.to_hash # => {"name"=>"Senjougahara Hitagi"} + # safe_params = params.permit(:name) + # safe_params.to_hash # => {"name"=>"Senjougahara Hitagi"} def to_hash to_h.to_hash end @@ -349,29 +356,29 @@ def to_hash # Returns a string representation of the receiver suitable for use as a URL # query string: # - # params = ActionController::Parameters.new({ - # name: "David", - # nationality: "Danish" - # }) - # params.to_query - # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + # params = ActionController::Parameters.new({ + # name: "David", + # nationality: "Danish" + # }) + # params.to_query + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash # - # safe_params = params.permit(:name, :nationality) - # safe_params.to_query - # # => "name=David&nationality=Danish" + # safe_params = params.permit(:name, :nationality) + # safe_params.to_query + # # => "name=David&nationality=Danish" # # An optional namespace can be passed to enclose key names: # - # params = ActionController::Parameters.new({ - # name: "David", - # nationality: "Danish" - # }) - # safe_params = params.permit(:name, :nationality) - # safe_params.to_query("user") - # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" + # params = ActionController::Parameters.new({ + # name: "David", + # nationality: "Danish" + # }) + # safe_params = params.permit(:name, :nationality) + # safe_params.to_query("user") + # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" # - # The string pairs "key=value" that conform the query string - # are sorted lexicographically in ascending order. + # The string pairs `"key=value"` that conform the query string are sorted + # lexicographically in ascending order. def to_query(*args) to_h.to_query(*args) end @@ -380,19 +387,19 @@ def to_query(*args) # Returns an unsafe, unfiltered ActiveSupport::HashWithIndifferentAccess # representation of the parameters. # - # params = ActionController::Parameters.new({ - # name: "Senjougahara Hitagi", - # oddity: "Heavy stone crab" - # }) - # params.to_unsafe_h - # # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"} + # 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 in - # the same way as Hash#each_pair. + # Convert all hashes in values into parameters, then yield each pair in the same + # way as `Hash#each_pair`. def each_pair(&block) return to_enum(__callee__) unless block_given? @parameters.each_pair do |key, value| @@ -403,8 +410,8 @@ def each_pair(&block) end alias_method :each, :each_pair - # Convert all hashes in values into parameters, then yield each value in - # the same way as Hash#each_value. + # Convert all hashes in values into parameters, then yield each value in the + # same way as `Hash#each_value`. def each_value(&block) return to_enum(:each_value) unless block_given? @parameters.each_pair do |key, value| @@ -420,38 +427,38 @@ def values end # Attribute that keeps track of converted arrays, if any, to avoid double - # looping in the common use case permit + mass-assignment. Defined in a - # method to instantiate it only if needed. + # looping in the common use case permit + mass-assignment. Defined in a method + # to instantiate it only if needed. # - # \Testing membership still loops, but it's going to be faster than our own - # loop that converts values. Also, we are not going to build a new array - # object per fetch. + # Testing membership still loops, but it's going to be faster than our own loop + # that converts values. Also, we are not going to build a new array object per + # fetch. def converted_arrays @converted_arrays ||= Set.new end - # Returns +true+ if the parameter is permitted, +false+ otherwise. + # Returns `true` if the parameter is permitted, `false` otherwise. # - # params = ActionController::Parameters.new - # params.permitted? # => false - # params.permit! - # params.permitted? # => true + # params = ActionController::Parameters.new + # params.permitted? # => false + # params.permit! + # params.permitted? # => true def permitted? @permitted end - # Sets the +permitted+ attribute to +true+. This can be used to pass - # mass assignment. Returns +self+. + # Sets the `permitted` attribute to `true`. This can be used to pass mass + # assignment. Returns `self`. # - # class Person < ActiveRecord::Base - # end + # class Person < ActiveRecord::Base + # end # - # params = ActionController::Parameters.new(name: "Francesco") - # params.permitted? # => false - # Person.new(params) # => ActiveModel::ForbiddenAttributesError - # params.permit! - # params.permitted? # => true - # Person.new(params) # => # + # params = ActionController::Parameters.new(name: "Francesco") + # params.permitted? # => false + # Person.new(params) # => ActiveModel::ForbiddenAttributesError + # params.permit! + # params.permitted? # => true + # Person.new(params) # => # def permit! each_pair do |key, value| Array.wrap(value).flatten.each do |v| @@ -465,52 +472,51 @@ def permit! # This method accepts both a single key and an array of keys. # - # When passed a single key, if it exists and its associated value is - # either present or the singleton +false+, returns said value: + # When passed a single key, if it exists and its associated value is either + # present or the singleton `false`, returns said value: # - # ActionController::Parameters.new(person: { name: "Francesco" }).require(:person) - # # => #"Francesco"} permitted: false> + # ActionController::Parameters.new(person: { name: "Francesco" }).require(:person) + # # => #"Francesco"} permitted: false> # # Otherwise raises ActionController::ParameterMissing: # - # ActionController::Parameters.new.require(:person) - # # ActionController::ParameterMissing: param is missing or the value is empty: person + # ActionController::Parameters.new.require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty: person # - # ActionController::Parameters.new(person: nil).require(:person) - # # ActionController::ParameterMissing: param is missing or the value is empty: person + # ActionController::Parameters.new(person: nil).require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty: person # - # ActionController::Parameters.new(person: "\t").require(:person) - # # ActionController::ParameterMissing: param is missing or the value is empty: person + # ActionController::Parameters.new(person: "\t").require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty: person # - # ActionController::Parameters.new(person: {}).require(:person) - # # ActionController::ParameterMissing: param is missing or the value is empty: person + # ActionController::Parameters.new(person: {}).require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty: person # - # When given an array of keys, the method tries to require each one of them - # in order. If it succeeds, an array with the respective return values is - # returned: + # When given an array of keys, the method tries to require each one of them in + # order. If it succeeds, an array with the respective return values is returned: # - # params = ActionController::Parameters.new(user: { ... }, profile: { ... }) - # user_params, profile_params = params.require([:user, :profile]) + # params = ActionController::Parameters.new(user: { ... }, profile: { ... }) + # user_params, profile_params = params.require([:user, :profile]) # # Otherwise, the method re-raises the first exception found: # - # params = ActionController::Parameters.new(user: {}, profile: {}) - # user_params, profile_params = params.require([:user, :profile]) - # # ActionController::ParameterMissing: param is missing or the value is empty: user + # params = ActionController::Parameters.new(user: {}, profile: {}) + # user_params, profile_params = params.require([:user, :profile]) + # # ActionController::ParameterMissing: param is missing or the value is empty: user # # Technically this method can be used to fetch terminal values: # - # # CAREFUL - # params = ActionController::Parameters.new(person: { name: "Finn" }) - # name = params.require(:person).require(:name) # CAREFUL + # # CAREFUL + # params = ActionController::Parameters.new(person: { name: "Finn" }) + # name = params.require(:person).require(:name) # CAREFUL # # but take into account that at some point those ones have to be permitted: # - # def person_params - # params.require(:person).permit(:name).tap do |person_params| - # person_params.require(:name) # SAFER + # def person_params + # params.require(:person).permit(:name).tap do |person_params| + # person_params.require(:name) # SAFER + # end # end - # end # # for example. def require(key) @@ -525,119 +531,120 @@ def require(key) alias :required :require - # Returns a new +ActionController::Parameters+ instance that - # includes only the given +filters+ and sets the +permitted+ attribute - # for the object to +true+. This is useful for limiting which attributes - # should be allowed for mass updating. + # Returns a new `ActionController::Parameters` instance that includes only the + # given `filters` and sets the `permitted` attribute for the object to `true`. + # This is useful for limiting which attributes should be allowed for mass + # updating. # - # params = ActionController::Parameters.new(user: { name: "Francesco", age: 22, role: "admin" }) - # permitted = params.require(:user).permit(:name, :age) - # permitted.permitted? # => true - # permitted.has_key?(:name) # => true - # permitted.has_key?(:age) # => true - # permitted.has_key?(:role) # => false + # params = ActionController::Parameters.new(user: { name: "Francesco", age: 22, role: "admin" }) + # permitted = params.require(:user).permit(:name, :age) + # permitted.permitted? # => true + # permitted.has_key?(:name) # => true + # permitted.has_key?(:age) # => true + # permitted.has_key?(:role) # => false # # Only permitted scalars pass the filter. For example, given # - # params.permit(:name) + # params.permit(:name) # - # +:name+ passes if it is a key of +params+ whose associated value is of type - # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+, - # +Date+, +Time+, +DateTime+, +StringIO+, +IO+, - # ActionDispatch::Http::UploadedFile or +Rack::Test::UploadedFile+. - # Otherwise, the key +:name+ is filtered out. + # `:name` passes if it is a key of `params` whose associated value is of type + # `String`, `Symbol`, `NilClass`, `Numeric`, `TrueClass`, `FalseClass`, `Date`, + # `Time`, `DateTime`, `StringIO`, `IO`, ActionDispatch::Http::UploadedFile or + # `Rack::Test::UploadedFile`. Otherwise, the key `:name` is filtered out. # - # You may declare that the parameter should be an array of permitted scalars - # by mapping it to an empty array: + # You may declare that the parameter should be an array of permitted scalars by + # mapping it to an empty array: # - # params = ActionController::Parameters.new(tags: ["rails", "parameters"]) - # params.permit(tags: []) + # params = ActionController::Parameters.new(tags: ["rails", "parameters"]) + # params.permit(tags: []) # - # Sometimes it is not possible or convenient to declare the valid keys of - # a hash parameter or its internal structure. Just map to an empty hash: + # Sometimes it is not possible or convenient to declare the valid keys of a hash + # parameter or its internal structure. Just map to an empty hash: # - # params.permit(preferences: {}) + # params.permit(preferences: {}) # - # Be careful because this opens the door to arbitrary input. In this - # case, +permit+ ensures values in the returned structure are permitted - # scalars and filters out anything else. + # Be careful because this opens the door to arbitrary input. In this case, + # `permit` ensures values in the returned structure are permitted scalars and + # filters out anything else. # - # You can also use +permit+ on nested parameters, like: + # You can also use `permit` on nested parameters, like: # - # params = ActionController::Parameters.new({ - # person: { - # name: "Francesco", - # age: 22, - # pets: [{ - # name: "Purplish", - # category: "dogs" - # }] - # } - # }) - # - # permitted = params.permit(person: [ :name, { pets: :name } ]) - # permitted.permitted? # => true - # permitted[:person][:name] # => "Francesco" - # permitted[:person][:age] # => nil - # permitted[:person][:pets][0][:name] # => "Purplish" - # permitted[:person][:pets][0][:category] # => nil - # - # Note that if you use +permit+ in a key that points to a hash, - # it won't allow all the hash. You also need to specify which - # attributes inside the hash should be permitted. - # - # params = ActionController::Parameters.new({ - # person: { - # contact: { - # email: "none@test.com", - # phone: "555-1234" + # params = ActionController::Parameters.new({ + # person: { + # name: "Francesco", + # age: 22, + # pets: [{ + # name: "Purplish", + # category: "dogs" + # }] # } - # } - # }) + # }) # - # params.require(:person).permit(:contact) - # # => # + # permitted = params.permit(person: [ :name, { pets: :name } ]) + # permitted.permitted? # => true + # permitted[:person][:name] # => "Francesco" + # permitted[:person][:age] # => nil + # permitted[:person][:pets][0][:name] # => "Purplish" + # permitted[:person][:pets][0][:category] # => nil # - # params.require(:person).permit(contact: :phone) - # # => ##"555-1234"} permitted: true>} permitted: true> + # Note that if you use `permit` in a key that points to a hash, it won't allow + # all the hash. You also need to specify which attributes inside the hash should + # be permitted. # - # params.require(:person).permit(contact: [ :email, :phone ]) - # # => ##"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true> + # params = ActionController::Parameters.new({ + # person: { + # contact: { + # email: "none@test.com", + # phone: "555-1234" + # } + # } + # }) # - # If your parameters specify multiple parameters indexed by a number, - # you can permit each set of parameters under the numeric key to be the same using the same syntax as permitting a single item. + # params.require(:person).permit(:contact) + # # => # # - # params = ActionController::Parameters.new({ - # person: { - # '0': { - # email: "none@test.com", - # phone: "555-1234" - # }, - # '1': { - # email: "nothing@test.com", - # phone: "555-6789" - # }, - # } - # }) - # params.permit(person: [:email]).to_h - # # => {"person"=>{"0"=>{"email"=>"none@test.com"}, "1"=>{"email"=>"nothing@test.com"}}} + # params.require(:person).permit(contact: :phone) + # # => ##"555-1234"} permitted: true>} permitted: true> # - # If you want to specify what keys you want from each numeric key, you can instead specify each one individually + # params.require(:person).permit(contact: [ :email, :phone ]) + # # => ##"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true> # - # params = ActionController::Parameters.new({ - # person: { - # '0': { - # email: "none@test.com", - # phone: "555-1234" - # }, - # '1': { - # email: "nothing@test.com", - # phone: "555-6789" - # }, - # } - # }) - # params.permit(person: { '0': [:email], '1': [:phone]}).to_h - # # => {"person"=>{"0"=>{"email"=>"none@test.com"}, "1"=>{"phone"=>"555-6789"}}} + # If your parameters specify multiple parameters indexed by a number, you can + # permit each set of parameters under the numeric key to be the same using the + # same syntax as permitting a single item. + # + # params = ActionController::Parameters.new({ + # person: { + # '0': { + # email: "none@test.com", + # phone: "555-1234" + # }, + # '1': { + # email: "nothing@test.com", + # phone: "555-6789" + # }, + # } + # }) + # params.permit(person: [:email]).to_h + # # => {"person"=>{"0"=>{"email"=>"none@test.com"}, "1"=>{"email"=>"nothing@test.com"}}} + # + # If you want to specify what keys you want from each numeric key, you can + # instead specify each one individually + # + # params = ActionController::Parameters.new({ + # person: { + # '0': { + # email: "none@test.com", + # phone: "555-1234" + # }, + # '1': { + # email: "nothing@test.com", + # phone: "555-6789" + # }, + # } + # }) + # params.permit(person: { '0': [:email], '1': [:phone]}).to_h + # # => {"person"=>{"0"=>{"email"=>"none@test.com"}, "1"=>{"phone"=>"555-6789"}}} def permit(*filters) params = self.class.new @@ -655,35 +662,34 @@ def permit(*filters) params.permit! end - # Returns a parameter for the given +key+. If not found, - # returns +nil+. + # Returns a parameter for the given `key`. If not found, returns `nil`. # - # params = ActionController::Parameters.new(person: { name: "Francesco" }) - # params[:person] # => #"Francesco"} permitted: false> - # params[:none] # => nil + # params = ActionController::Parameters.new(person: { name: "Francesco" }) + # params[:person] # => #"Francesco"} permitted: false> + # params[:none] # => nil def [](key) convert_hashes_to_parameters(key, @parameters[key]) end - # Assigns a value to a given +key+. The given key may still get filtered out + # Assigns a value to a given `key`. The given key may still get filtered out # when #permit is called. def []=(key, value) @parameters[key] = value end - # Returns a parameter for the given +key+. If the +key+ - # can't be found, there are several options: With no other arguments, - # it will raise an ActionController::ParameterMissing error; - # if a second argument is given, then that is returned (converted to an - # instance of +ActionController::Parameters+ if possible); if a block - # is given, then that will be run and its result returned. + # Returns a parameter for the given `key`. If the `key` can't be found, there + # are several options: With no other arguments, it will raise an + # ActionController::ParameterMissing error; if a second argument is given, then + # that is returned (converted to an instance of `ActionController::Parameters` + # if possible); if a block is given, then that will be run and its result + # returned. # - # params = ActionController::Parameters.new(person: { name: "Francesco" }) - # params.fetch(:person) # => #"Francesco"} permitted: false> - # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none - # params.fetch(:none, {}) # => # - # params.fetch(:none, "Francesco") # => "Francesco" - # params.fetch(:none) { "Francesco" } # => "Francesco" + # params = ActionController::Parameters.new(person: { name: "Francesco" }) + # params.fetch(:person) # => #"Francesco"} permitted: false> + # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none + # params.fetch(:none, {}) # => # + # params.fetch(:none, "Francesco") # => "Francesco" + # params.fetch(:none) { "Francesco" } # => "Francesco" def fetch(key, *args) convert_value_to_parameters( @parameters.fetch(key) { @@ -696,44 +702,43 @@ def fetch(key, *args) ) end - # Extracts the nested parameter from the given +keys+ by calling +dig+ - # at each step. Returns +nil+ if any intermediate step is +nil+. + # 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 + # 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 + # params2 = ActionController::Parameters.new(foo: [10, 11, 12]) + # params2.dig(:foo, 1) # => 11 def dig(*keys) convert_hashes_to_parameters(keys.first, @parameters[keys.first]) @parameters.dig(*keys) end - # Returns a new +ActionController::Parameters+ instance that - # includes only the given +keys+. If the given +keys+ - # don't exist, returns an empty hash. + # Returns a new `ActionController::Parameters` instance that includes only the + # given `keys`. If the given `keys` don't exist, returns an empty hash. # - # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) - # params.slice(:a, :b) # => #1, "b"=>2} permitted: false> - # params.slice(:d) # => # + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.slice(:a, :b) # => #1, "b"=>2} permitted: false> + # params.slice(:d) # => # def slice(*keys) new_instance_with_inherited_permitted_status(@parameters.slice(*keys)) end - # Returns the current +ActionController::Parameters+ instance which - # contains only the given +keys+. + # Returns the current `ActionController::Parameters` instance which contains + # only the given `keys`. def slice!(*keys) @parameters.slice!(*keys) self end - # Returns a new +ActionController::Parameters+ instance that - # filters out the given +keys+. + # Returns a new `ActionController::Parameters` instance that filters out the + # given `keys`. # - # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) - # params.except(:a, :b) # => #3} permitted: false> - # params.except(:d) # => #1, "b"=>2, "c"=>3} permitted: false> + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.except(:a, :b) # => #3} permitted: false> + # params.except(:d) # => #1, "b"=>2, "c"=>3} permitted: false> def except(*keys) new_instance_with_inherited_permitted_status(@parameters.except(*keys)) end @@ -741,19 +746,19 @@ def except(*keys) # Removes and returns the key/value pairs matching the given keys. # - # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) - # params.extract!(:a, :b) # => #1, "b"=>2} permitted: false> - # params # => #3} permitted: false> + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.extract!(:a, :b) # => #1, "b"=>2} permitted: false> + # params # => #3} permitted: false> def extract!(*keys) new_instance_with_inherited_permitted_status(@parameters.extract!(*keys)) end - # Returns a new +ActionController::Parameters+ instance with the results of - # running +block+ once for every value. The keys are unchanged. + # Returns a new `ActionController::Parameters` instance with the results of + # running `block` once for every value. The keys are unchanged. # - # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) - # params.transform_values { |x| x * 2 } - # # => #2, "b"=>4, "c"=>6} permitted: false> + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.transform_values { |x| x * 2 } + # # => #2, "b"=>4, "c"=>6} permitted: false> def transform_values return to_enum(:transform_values) unless block_given? new_instance_with_inherited_permitted_status( @@ -762,15 +767,15 @@ def transform_values end # Performs values transformation and returns the altered - # +ActionController::Parameters+ instance. + # `ActionController::Parameters` instance. def transform_values! return to_enum(:transform_values!) unless block_given? @parameters.transform_values! { |v| yield convert_value_to_parameters(v) } self end - # Returns a new +ActionController::Parameters+ instance with the - # results of running +block+ once for every key. The values are unchanged. + # Returns a new `ActionController::Parameters` instance with the results of + # running `block` once for every key. The values are unchanged. def transform_keys(&block) return to_enum(:transform_keys) unless block_given? new_instance_with_inherited_permitted_status( @@ -779,53 +784,53 @@ def transform_keys(&block) end # Performs keys transformation and returns the altered - # +ActionController::Parameters+ instance. + # `ActionController::Parameters` instance. def transform_keys!(&block) return to_enum(:transform_keys!) unless block_given? @parameters.transform_keys!(&block) self end - # Returns a new +ActionController::Parameters+ instance with the - # results of running +block+ once for every key. This includes the keys - # from the root hash and from all nested hashes and arrays. The values are unchanged. + # Returns a new `ActionController::Parameters` instance with the results of + # running `block` once for every key. This includes the keys from the root hash + # and from all nested hashes and arrays. The values are unchanged. def deep_transform_keys(&block) new_instance_with_inherited_permitted_status( @parameters.deep_transform_keys(&block) ) end - # Returns the same +ActionController::Parameters+ instance with - # changed keys. This includes the keys from the root hash and from all - # nested hashes and arrays. The values are unchanged. + # Returns the same `ActionController::Parameters` instance with changed keys. + # This includes the keys from the root hash and from all nested hashes and + # arrays. The values are unchanged. def deep_transform_keys!(&block) @parameters.deep_transform_keys!(&block) self end - # Deletes a key-value pair from +Parameters+ and returns the value. If - # +key+ is not found, returns +nil+ (or, with optional code block, yields - # +key+ and returns the result). This method is similar to #extract!, which - # returns the corresponding +ActionController::Parameters+ object. + # Deletes a key-value pair from `Parameters` and returns the value. If `key` is + # not found, returns `nil` (or, with optional code block, yields `key` and + # returns the result). This method is similar to #extract!, which returns the + # corresponding `ActionController::Parameters` object. def delete(key, &block) convert_value_to_parameters(@parameters.delete(key, &block)) end - # Returns a new +ActionController::Parameters+ instance with only - # items that the block evaluates to true. + # Returns a new `ActionController::Parameters` instance with only items that the + # block evaluates to true. def select(&block) new_instance_with_inherited_permitted_status(@parameters.select(&block)) end - # Equivalent to Hash#keep_if, but returns +nil+ if no changes were made. + # Equivalent to Hash#keep_if, but returns `nil` if no changes were made. def select!(&block) @parameters.select!(&block) self end alias_method :keep_if, :select! - # Returns a new +ActionController::Parameters+ instance with items - # that the block evaluates to true removed. + # Returns a new `ActionController::Parameters` instance with items that the + # block evaluates to true removed. def reject(&block) new_instance_with_inherited_permitted_status(@parameters.reject(&block)) end @@ -837,24 +842,26 @@ def reject!(&block) end alias_method :delete_if, :reject! - # Returns a new +ActionController::Parameters+ instance with +nil+ values removed. + # Returns a new `ActionController::Parameters` instance with `nil` values + # removed. def compact new_instance_with_inherited_permitted_status(@parameters.compact) end - # Removes all +nil+ values in place and returns +self+, or +nil+ if no changes were made. + # Removes all `nil` values in place and returns `self`, or `nil` if no changes + # were made. def compact! self if @parameters.compact! end - # Returns a new +ActionController::Parameters+ instance without the blank values. - # Uses Object#blank? for determining if a value is blank. + # Returns a new `ActionController::Parameters` instance without the blank + # values. Uses Object#blank? for determining if a value is blank. def compact_blank reject { |_k, v| v.blank? } end - # Removes all blank values in place and returns self. - # Uses Object#blank? for determining if a value is blank. + # Removes all blank values in place and returns self. Uses Object#blank? for + # determining if a value is blank. def compact_blank! reject! { |_k, v| v.blank? } end @@ -866,14 +873,14 @@ def has_value?(value) alias value? has_value? - # Returns values that were assigned to the given +keys+. Note that all the - # +Hash+ objects will be converted to +ActionController::Parameters+. + # Returns values that were assigned to the given `keys`. Note that all the + # `Hash` objects will be converted to `ActionController::Parameters`. def values_at(*keys) convert_value_to_parameters(@parameters.values_at(*keys)) end - # Returns a new +ActionController::Parameters+ instance with all keys from - # +other_hash+ merged into current hash. + # Returns a new `ActionController::Parameters` instance with all keys from + # `other_hash` merged into current hash. def merge(other_hash) new_instance_with_inherited_permitted_status( @parameters.merge(other_hash.to_h) @@ -883,8 +890,8 @@ def merge(other_hash) ## # :call-seq: merge!(other_hash) # - # Returns the current +ActionController::Parameters+ instance with - # +other_hash+ merged into current hash. + # Returns the current `ActionController::Parameters` instance with `other_hash` + # merged into current hash. def merge!(other_hash, &block) @parameters.merge!(other_hash.to_h, &block) self @@ -894,8 +901,8 @@ def deep_merge?(other_hash) # :nodoc: other_hash.is_a?(ActiveSupport::DeepMergeable) end - # Returns a new +ActionController::Parameters+ instance with all keys - # from current hash merged into +other_hash+. + # Returns a new `ActionController::Parameters` instance with all keys from + # current hash merged into `other_hash`. def reverse_merge(other_hash) new_instance_with_inherited_permitted_status( other_hash.to_h.merge(@parameters) @@ -903,17 +910,17 @@ def reverse_merge(other_hash) end alias_method :with_defaults, :reverse_merge - # Returns the current +ActionController::Parameters+ instance with - # current hash merged into +other_hash+. + # Returns the current `ActionController::Parameters` instance with current hash + # merged into `other_hash`. def reverse_merge!(other_hash) @parameters.merge!(other_hash.to_h) { |key, left, right| left } self end alias_method :with_defaults!, :reverse_merge! - # This is required by ActiveModel attribute assignment, so that user can - # pass +Parameters+ to a mass assignment methods in a model. It should not - # matter as we are using +HashWithIndifferentAccess+ internally. + # This is required by ActiveModel attribute assignment, so that user can pass + # `Parameters` to a mass assignment methods in a model. It should not matter as + # we are using `HashWithIndifferentAccess` internally. def stringify_keys # :nodoc: dup end @@ -938,13 +945,13 @@ def init_with(coder) # :nodoc: @parameters = coder.map.with_indifferent_access @permitted = false when "!ruby/hash-with-ivars:ActionController::Parameters" - # YAML 2.0.9's Hash subclass format where keys and values - # were stored under an elements hash and `permitted` within an ivars hash. + # YAML 2.0.9's Hash subclass format where keys and values were stored under an + # elements hash and `permitted` within an ivars hash. @parameters = coder.map["elements"].with_indifferent_access @permitted = coder.map["ivars"][:@permitted] when "!ruby/object:ActionController::Parameters" - # YAML's Object format. Only needed because of the format - # backwards compatibility above, otherwise equivalent to YAML's initialization. + # YAML's Object format. Only needed because of the format backwards + # compatibility above, otherwise equivalent to YAML's initialization. @parameters, @permitted = coder.map["parameters"], coder.map["permitted"] end end @@ -953,25 +960,26 @@ def encode_with(coder) # :nodoc: coder.map = { "parameters" => @parameters, "permitted" => @permitted } end - # Returns a duplicate +ActionController::Parameters+ instance with the same permitted parameters. + # Returns a duplicate `ActionController::Parameters` instance with the same + # permitted parameters. def deep_dup self.class.new(@parameters.deep_dup, @logging_context).tap do |duplicate| duplicate.permitted = @permitted end end - # Returns parameter value for the given +key+ separated by +delimiter+. + # Returns parameter value for the given `key` separated by `delimiter`. # - # params = ActionController::Parameters.new(id: "1_123", tags: "ruby,rails") - # params.extract_value(:id) # => ["1", "123"] - # params.extract_value(:tags, delimiter: ",") # => ["ruby", "rails"] - # params.extract_value(:non_existent_key) # => nil + # params = ActionController::Parameters.new(id: "1_123", tags: "ruby,rails") + # params.extract_value(:id) # => ["1", "123"] + # params.extract_value(:tags, delimiter: ",") # => ["ruby", "rails"] + # params.extract_value(:non_existent_key) # => nil # - # Note that if the given +key+'s value contains blank elements, then - # the returned array will include empty strings. + # Note that if the given `key`'s value contains blank elements, then the + # returned array will include empty strings. # - # params = ActionController::Parameters.new(tags: "ruby,rails,,web") - # params.extract_value(:tags) # => ["ruby", "rails", "", "web"] + # params = ActionController::Parameters.new(tags: "ruby,rails,,web") + # params.extract_value(:tags) # => ["ruby", "rails", "", "web"] def extract_value(key, delimiter: "_") @parameters[key]&.split(delimiter, -1) end @@ -1073,15 +1081,14 @@ def unpermitted_keys(params) # # --- Filtering ---------------------------------------------------------- # - - # This is a list of permitted scalar types that includes the ones - # supported in XML and JSON requests. + # This is a list of permitted scalar types that includes the ones supported in + # XML and JSON requests. # - # This list is in particular used to filter ordinary requests, \String goes - # as first element to quickly short-circuit the common case. + # This list is in particular used to filter ordinary requests, String goes as + # first element to quickly short-circuit the common case. # - # If you modify this collection please update the one in the #permit doc - # as well. + # If you modify this collection please update the one in the #permit doc as + # well. PERMITTED_SCALAR_TYPES = [ String, Symbol, @@ -1106,12 +1113,12 @@ def permitted_scalar?(value) # # For example: # - # puts self.keys #=> ["zipcode(90210i)"] - # params = {} + # puts self.keys #=> ["zipcode(90210i)"] + # params = {} # - # permitted_scalar_filter(params, "zipcode") + # permitted_scalar_filter(params, "zipcode") # - # puts params.keys # => ["zipcode"] + # puts params.keys # => ["zipcode"] def permitted_scalar_filter(params, permitted_key) permitted_key = permitted_key.to_s @@ -1206,77 +1213,76 @@ def initialize_copy(source) end end - # = Strong \Parameters + # # Strong Parameters # - # It provides an interface for protecting attributes from end-user - # assignment. This makes Action Controller parameters forbidden - # to be used in Active Model mass assignment until they have been explicitly - # enumerated. + # It provides an interface for protecting attributes from end-user assignment. + # This makes Action Controller parameters forbidden to be used in Active Model + # mass assignment until they have been explicitly enumerated. # # In addition, parameters can be marked as required and flow through a - # predefined raise/rescue flow to end up as a 400 Bad Request with no - # effort. + # predefined raise/rescue flow to end up as a `400 Bad Request` with no effort. # - # class PeopleController < ActionController::Base - # # Using "Person.create(params[:person])" would raise an - # # ActiveModel::ForbiddenAttributesError exception because it'd - # # be using mass assignment without an explicit permit step. - # # This is the recommended form: - # def create - # Person.create(person_params) - # end - # - # # This will pass with flying colors as long as there's a person key in the - # # parameters, otherwise it'll raise an ActionController::ParameterMissing - # # exception, which will get caught by ActionController::Base and turned - # # into a 400 Bad Request reply. - # def update - # redirect_to current_account.people.find(params[:id]).tap { |person| - # person.update!(person_params) - # } - # end - # - # private - # # Using a private method to encapsulate the permissible parameters is - # # a good pattern since you'll be able to reuse the same permit - # # list between create and update. Also, you can specialize this method - # # with per-user checking of permissible attributes. - # def person_params - # params.require(:person).permit(:name, :age) + # class PeopleController < ActionController::Base + # # Using "Person.create(params[:person])" would raise an + # # ActiveModel::ForbiddenAttributesError exception because it'd + # # be using mass assignment without an explicit permit step. + # # This is the recommended form: + # def create + # Person.create(person_params) # end - # end # - # In order to use accepts_nested_attributes_for with Strong \Parameters, you - # will need to specify which nested attributes should be permitted. You might want - # to allow +:id+ and +:_destroy+, see ActiveRecord::NestedAttributes for more information. + # # This will pass with flying colors as long as there's a person key in the + # # parameters, otherwise it'll raise an ActionController::ParameterMissing + # # exception, which will get caught by ActionController::Base and turned + # # into a 400 Bad Request reply. + # def update + # redirect_to current_account.people.find(params[:id]).tap { |person| + # person.update!(person_params) + # } + # end # - # class Person - # has_many :pets - # accepts_nested_attributes_for :pets - # end - # - # class PeopleController < ActionController::Base - # def create - # Person.create(person_params) + # private + # # Using a private method to encapsulate the permissible parameters is + # # a good pattern since you'll be able to reuse the same permit + # # list between create and update. Also, you can specialize this method + # # with per-user checking of permissible attributes. + # def person_params + # params.require(:person).permit(:name, :age) + # end # end # - # ... + # In order to use `accepts_nested_attributes_for` with Strong Parameters, you + # will need to specify which nested attributes should be permitted. You might + # want to allow `:id` and `:_destroy`, see ActiveRecord::NestedAttributes for + # more information. # - # private + # class Person + # has_many :pets + # accepts_nested_attributes_for :pets + # end # - # def person_params - # # It's mandatory to specify the nested attributes that should be permitted. - # # 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: [ :id, :name, :category ]) + # class PeopleController < ActionController::Base + # def create + # Person.create(person_params) # end - # end # - # See ActionController::Parameters.require and ActionController::Parameters.permit - # for more information. + # ... + # + # private + # + # def person_params + # # It's mandatory to specify the nested attributes that should be permitted. + # # 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: [ :id, :name, :category ]) + # end + # end + # + # See ActionController::Parameters.require and + # ActionController::Parameters.permit for more information. module StrongParameters - # Returns a new ActionController::Parameters object that - # has been instantiated with the request.parameters. + # Returns a new ActionController::Parameters object that has been instantiated + # with the `request.parameters`. def params @_params ||= begin context = { @@ -1289,9 +1295,9 @@ def params end end - # Assigns the given +value+ to the +params+ hash. If +value+ - # is a Hash, this will create an ActionController::Parameters - # object that has been instantiated with the given +value+ hash. + # Assigns the given `value` to the `params` hash. If `value` is a Hash, this + # will create an ActionController::Parameters object that has been instantiated + # with the given `value` hash. def params=(value) @_params = value.is_a?(Hash) ? Parameters.new(value) : value end diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index 0a4d6c3ca0..125299f751 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController module Testing # Behavior specific to functional tests diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index a9b2770571..c240399bc3 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -1,27 +1,29 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController - # = Action Controller \UrlFor + # # Action Controller UrlFor # - # Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing - # the _routes method. Otherwise, an exception will be raised. + # Includes `url_for` into the host class. The class has to provide a `RouteSet` + # by implementing the `_routes` method. Otherwise, an exception will be raised. # - # In addition to AbstractController::UrlFor, this module accesses the HTTP layer to define - # URL options like the +host+. In order to do so, this module requires the host class - # to implement +env+ which needs to be Rack-compatible, and +request+ which - # returns an ActionDispatch::Request instance. + # In addition to AbstractController::UrlFor, this module accesses the HTTP layer + # to define URL options like the `host`. In order to do so, this module requires + # the host class to implement `env` which needs to be Rack-compatible, and + # `request` which returns an ActionDispatch::Request instance. # - # class RootUrl - # include ActionController::UrlFor - # include Rails.application.routes.url_helpers + # class RootUrl + # include ActionController::UrlFor + # include Rails.application.routes.url_helpers # - # delegate :env, :request, to: :controller + # delegate :env, :request, to: :controller # - # def initialize(controller) - # @controller = controller - # @url = root_path # named route from the application. + # def initialize(controller) + # @controller = controller + # @url = root_path # named route from the application. + # end # end - # end module UrlFor extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 72ff7ef2fe..5ee9ae0db9 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "rails" require "action_controller" require "action_dispatch/railtie" diff --git a/actionpack/lib/action_controller/railties/helpers.rb b/actionpack/lib/action_controller/railties/helpers.rb index 75938108d6..2ae7c87773 100644 --- a/actionpack/lib/action_controller/railties/helpers.rb +++ b/actionpack/lib/action_controller/railties/helpers.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController module Railties module Helpers diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb index c15ac5ad7f..068d023bc9 100644 --- a/actionpack/lib/action_controller/renderer.rb +++ b/actionpack/lib/action_controller/renderer.rb @@ -1,25 +1,28 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController - # = Action Controller \Renderer + # # Action Controller Renderer # # ActionController::Renderer allows you to render arbitrary templates without # being inside a controller action. # - # You can get a renderer instance by calling +renderer+ on a controller class: + # You can get a renderer instance by calling `renderer` on a controller class: # - # ApplicationController.renderer - # PostsController.renderer + # ApplicationController.renderer + # PostsController.renderer # # and render a template by calling the #render method: # - # ApplicationController.renderer.render template: "posts/show", assigns: { post: Post.first } - # PostsController.renderer.render :show, assigns: { post: Post.first } + # ApplicationController.renderer.render template: "posts/show", assigns: { post: Post.first } + # PostsController.renderer.render :show, assigns: { post: Post.first } # - # As a shortcut, you can also call +render+ directly on the controller class itself: + # As a shortcut, you can also call `render` directly on the controller class + # itself: # - # ApplicationController.render template: "posts/show", assigns: { post: Post.first } - # PostsController.render :show, assigns: { post: Post.first } + # ApplicationController.render template: "posts/show", assigns: { post: Post.first } + # PostsController.render :show, assigns: { post: Post.first } # class Renderer attr_reader :controller @@ -64,45 +67,47 @@ def self.for(controller, env = nil, defaults = DEFAULTS) # Creates a new renderer using the same controller, but with a new Rack env. # - # ApplicationController.renderer.new(method: "post") + # ApplicationController.renderer.new(method: "post") # def new(env = nil) self.class.new controller, env, @defaults end - # Creates a new renderer using the same controller, but with the given - # defaults merged on top of the previous defaults. + # Creates a new renderer using the same controller, but with the given defaults + # merged on top of the previous defaults. def with_defaults(defaults) self.class.new controller, @env, @defaults.merge(defaults) end # Initializes a new Renderer. # - # ==== Parameters + # #### Parameters # - # * +controller+ - The controller class to instantiate for rendering. - # * +env+ - The Rack env to use for mocking a request when rendering. - # Entries can be typical Rack env keys and values, or they can be any of - # the following, which will be converted appropriately: - # * +:http_host+ - The HTTP host for the incoming request. Converts to - # Rack's +HTTP_HOST+. - # * +:https+ - Boolean indicating whether the incoming request uses HTTPS. - # Converts to Rack's +HTTPS+. - # * +:method+ - The HTTP method for the incoming request, case-insensitive. - # Converts to Rack's +REQUEST_METHOD+. - # * +:script_name+ - The portion of the incoming request's URL path that - # corresponds to the application. Converts to Rack's +SCRIPT_NAME+. - # * +:input+ - The input stream. Converts to Rack's +rack.input+. - # * +defaults+ - Default values for the Rack env. Entries are specified in - # the same format as +env+. +env+ will be merged on top of these values. - # +defaults+ will be retained when calling #new on a renderer instance. + # * `controller` - The controller class to instantiate for rendering. + # * `env` - The Rack env to use for mocking a request when rendering. Entries + # can be typical Rack env keys and values, or they can be any of the + # following, which will be converted appropriately: + # * `:http_host` - The HTTP host for the incoming request. Converts to + # Rack's `HTTP_HOST`. + # * `:https` - Boolean indicating whether the incoming request uses HTTPS. + # Converts to Rack's `HTTPS`. + # * `:method` - The HTTP method for the incoming request, + # case-insensitive. Converts to Rack's `REQUEST_METHOD`. + # * `:script_name` - The portion of the incoming request's URL path that + # corresponds to the application. Converts to Rack's `SCRIPT_NAME`. + # * `:input` - The input stream. Converts to Rack's `rack.input`. # - # If no +http_host+ is specified, the env HTTP host will be derived from the - # routes' +default_url_options+. In this case, the +https+ boolean and the - # +script_name+ will also be derived from +default_url_options+ if they were - # not specified. Additionally, the +https+ boolean will fall back to - # +Rails.application.config.force_ssl+ if +default_url_options+ does not - # specify a +protocol+. + # * `defaults` - Default values for the Rack env. Entries are specified in the + # same format as `env`. `env` will be merged on top of these values. + # `defaults` will be retained when calling #new on a renderer instance. + # + # + # If no `http_host` is specified, the env HTTP host will be derived from the + # routes' `default_url_options`. In this case, the `https` boolean and the + # `script_name` will also be derived from `default_url_options` if they were not + # specified. Additionally, the `https` boolean will fall back to + # `Rails.application.config.force_ssl` if `default_url_options` does not specify + # a `protocol`. def initialize(controller, env, defaults) @controller = controller @defaults = defaults @@ -119,7 +124,8 @@ def defaults @defaults end - # Renders a template to a string, just like ActionController::Rendering#render_to_string. + # Renders a template to a string, just like + # ActionController::Rendering#render_to_string. def render(*args) request = ActionDispatch::Request.new(env_for_request) request.routes = controller._routes diff --git a/actionpack/lib/action_controller/template_assertions.rb b/actionpack/lib/action_controller/template_assertions.rb index 87cd5d18da..c3bf41faee 100644 --- a/actionpack/lib/action_controller/template_assertions.rb +++ b/actionpack/lib/action_controller/template_assertions.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionController module TemplateAssertions # :nodoc: def assert_template(options = {}, message = nil) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index fa5c33bdc4..1275300904 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "rack/session/abstract/id" require "active_support/core_ext/hash/conversions" require "active_support/core_ext/object/to_query" @@ -16,10 +18,10 @@ class Metal 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. + # 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. silence_redefinition_of_method :new_controller_thread def new_controller_thread # :nodoc: yield @@ -29,8 +31,8 @@ def new_controller_thread # :nodoc: Buffer.queue_size = nil end - # ActionController::TestCase will be deprecated and moved to a gem in the future. - # Please use ActionDispatch::IntegrationTest going forward. + # ActionController::TestCase will be deprecated and moved to a gem in the + # future. Please use ActionDispatch::IntegrationTest going forward. class TestRequest < ActionDispatch::TestRequest # :nodoc: DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup DEFAULT_ENV.delete "PATH_INFO" @@ -232,116 +234,125 @@ def load! end end - # = Action Controller Test Case + # # Action Controller Test Case # - # Superclass for ActionController functional tests. Functional tests allow you to - # test a single controller action per test method. + # Superclass for ActionController functional tests. Functional tests allow you + # to test a single controller action per test method. # - # == Use integration style controller tests over functional style controller tests. + # ## Use integration style controller tests over functional style controller tests. # - # \Rails discourages the use of functional tests in favor of integration tests + # Rails discourages the use of functional tests in favor of integration tests # (use ActionDispatch::IntegrationTest). # - # New \Rails applications no longer generate functional style controller tests and they should - # only be used for backward compatibility. Integration style controller tests perform actual - # requests, whereas functional style controller tests merely simulate a request. Besides, - # integration tests are as fast as functional tests and provide lot of helpers such as +as+, - # +parsed_body+ for effective testing of controller actions including even API endpoints. + # New Rails applications no longer generate functional style controller tests + # and they should only be used for backward compatibility. Integration style + # controller tests perform actual requests, whereas functional style controller + # tests merely simulate a request. Besides, integration tests are as fast as + # functional tests and provide lot of helpers such as `as`, `parsed_body` for + # effective testing of controller actions including even API endpoints. # - # == Basic example + # ## Basic example # # Functional tests are written as follows: - # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+, or +head+ method to simulate - # an HTTP request. - # 2. Then, one asserts whether the current state is as expected. "State" can be anything: - # the controller's HTTP response, the database contents, etc. + # 1. First, one uses the `get`, `post`, `patch`, `put`, `delete`, or `head` + # method to simulate an HTTP request. + # 2. Then, one asserts whether the current state is as expected. "State" can be + # anything: the controller's HTTP response, the database contents, etc. + # # # For example: # - # class BooksControllerTest < ActionController::TestCase - # def test_create - # # Simulate a POST response with the given HTTP parameters. - # post(:create, params: { book: { title: "Love Hina" }}) + # class BooksControllerTest < ActionController::TestCase + # def test_create + # # Simulate a POST response with the given HTTP parameters. + # post(:create, params: { book: { title: "Love Hina" }}) # - # # Asserts that the controller tried to redirect us to - # # the created book's URI. - # assert_response :found + # # Asserts that the controller tried to redirect us to + # # the created book's URI. + # assert_response :found # - # # Asserts that the controller really put the book in the database. - # assert_not_nil Book.find_by(title: "Love Hina") + # # Asserts that the controller really put the book in the database. + # assert_not_nil Book.find_by(title: "Love Hina") + # end # end - # end # # You can also send a real document in the simulated HTTP request. # - # def test_create - # json = {book: { title: "Love Hina" }}.to_json - # post :create, body: json - # end + # def test_create + # json = {book: { title: "Love Hina" }}.to_json + # post :create, body: json + # end # - # == Special instance variables + # ## Special instance variables # - # ActionController::TestCase will also automatically provide the following instance - # variables for use in the tests: + # ActionController::TestCase will also automatically provide the following + # instance variables for use in the tests: # - # @controller:: - # The controller instance that will be tested. - # @request:: - # An ActionController::TestRequest, representing the current HTTP - # request. You can modify this object before sending the HTTP request. For example, - # you might want to set some session properties before sending a GET request. - # @response:: - # An ActionDispatch::TestResponse object, representing the response - # of the last HTTP response. In the above example, @response becomes valid - # after calling +post+. If the various assert methods are not sufficient, then you - # may use this object to inspect the HTTP response in detail. + # **@controller** + # : The controller instance that will be tested. + # **@request** + # : An ActionController::TestRequest, representing the current HTTP request. + # You can modify this object before sending the HTTP request. For example, + # you might want to set some session properties before sending a GET + # request. + # **@response** + # : An ActionDispatch::TestResponse object, representing the response of the + # last HTTP response. In the above example, `@response` becomes valid after + # calling `post`. If the various assert methods are not sufficient, then you + # may use this object to inspect the HTTP response in detail. # - # == Controller is automatically inferred + # + # ## Controller is automatically inferred # # ActionController::TestCase will automatically infer the controller under test # from the test class name. If the controller cannot be inferred from the test - # class name, you can explicitly set it with +tests+. + # class name, you can explicitly set it with `tests`. # - # class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase - # tests WidgetController - # end + # class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase + # tests WidgetController + # end # - # == \Testing controller internals + # ## Testing controller internals # - # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions - # can be used against. These collections are: + # In addition to these specific assertions, you also have easy access to various + # collections that the regular test/unit assertions can be used against. These + # collections are: + # + # * session: Objects being saved in the session. + # * flash: The flash objects currently in the session. + # * cookies: Cookies being sent to the user on this request. # - # * session: Objects being saved in the session. - # * flash: The flash objects currently in the session. - # * cookies: \Cookies being sent to the user on this request. # # These collections can be used just like any other hash: # - # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave" - # assert flash.empty? # makes sure that there's nothing in the flash + # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave" + # assert flash.empty? # makes sure that there's nothing in the flash # - # On top of the collections, you have the complete URL that a given action redirected to available in redirect_to_url. + # On top of the collections, you have the complete URL that a given action + # redirected to available in `redirect_to_url`. # - # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another - # action call which can then be asserted against. + # For redirects within the same controller, you can even call follow_redirect + # and the redirect will be followed, triggering another action call which can + # then be asserted against. # - # == Manipulating session and cookie variables + # ## Manipulating session and cookie variables # - # Sometimes you need to set up the session and cookie variables for a test. - # To do this just assign a value to the session or cookie collection: + # Sometimes you need to set up the session and cookie variables for a test. To + # do this just assign a value to the session or cookie collection: # - # session[:key] = "value" - # cookies[:key] = "value" + # session[:key] = "value" + # cookies[:key] = "value" # # To clear the cookies for a test just clear the cookie collection: # - # cookies.clear + # cookies.clear # - # == \Testing named routes + # ## Testing named routes # - # If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case. + # If you're using named routes, they can be easily tested using the original + # named routes' methods straight in the test case. # - # assert_redirected_to page_url(title: 'foo') + # assert_redirected_to page_url(title: 'foo') class TestCase < ActiveSupport::TestCase singleton_class.attr_accessor :executor_around_each_request @@ -354,12 +365,12 @@ module Behavior attr_reader :response, :request module ClassMethods - # Sets the controller class name. Useful if the name can't be inferred from test class. - # Normalizes +controller_class+ before using. + # Sets the controller class name. Useful if the name can't be inferred from test + # class. Normalizes `controller_class` before using. # - # tests WidgetController - # tests :widget - # tests 'widget' + # tests WidgetController + # tests :widget + # tests 'widget' def tests(controller_class) case controller_class when String, Symbol @@ -392,21 +403,24 @@ def determine_default_controller_class(name) # Simulate a GET request with the given parameters. # - # - +action+: The controller action to call. - # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+. - # - +body+: The request body with a string that is appropriately encoded - # (application/x-www-form-urlencoded or multipart/form-data). - # - +session+: A hash of parameters to store in the session. This may be +nil+. - # - +flash+: A hash of parameters to store in the flash. This may be +nil+. + # * `action`: The controller action to call. + # * `params`: The hash with HTTP parameters that you want to pass. This may be + # `nil`. + # * `body`: The request body with a string that is appropriately encoded + # (`application/x-www-form-urlencoded` or `multipart/form-data`). + # * `session`: A hash of parameters to store in the session. This may be + # `nil`. + # * `flash`: A hash of parameters to store in the flash. This may be `nil`. # - # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with - # +post+, +patch+, +put+, +delete+, and +head+. - # Example sending parameters, session, and setting a flash message: # - # get :show, - # params: { id: 7 }, - # session: { user_id: 1 }, - # flash: { notice: 'This is flash message' } + # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with `post`, + # `patch`, `put`, `delete`, and `head`. Example sending parameters, session, and + # setting a flash message: + # + # get :show, + # params: { id: 7 }, + # session: { user_id: 1 }, + # flash: { notice: 'This is flash message' } # # Note that the request method is not verified. The different methods are # available to make the tests more expressive. @@ -417,67 +431,71 @@ def get(action, **args) end # Simulate a POST request with the given parameters and set/volley the response. - # See +get+ for more details. + # See `get` for more details. def post(action, **args) process(action, method: "POST", **args) end - # Simulate a PATCH request with the given parameters and set/volley the response. - # See +get+ for more details. + # Simulate a PATCH request with the given parameters and set/volley the + # response. See `get` for more details. def patch(action, **args) process(action, method: "PATCH", **args) end # Simulate a PUT request with the given parameters and set/volley the response. - # See +get+ for more details. + # See `get` for more details. def put(action, **args) process(action, method: "PUT", **args) end - # Simulate a DELETE request with the given parameters and set/volley the response. - # See +get+ for more details. + # Simulate a DELETE request with the given parameters and set/volley the + # response. See `get` for more details. def delete(action, **args) process(action, method: "DELETE", **args) end # Simulate a HEAD request with the given parameters and set/volley the response. - # See +get+ for more details. + # See `get` for more details. def head(action, **args) process(action, method: "HEAD", **args) end - # Simulate an HTTP request to +action+ by specifying request method, - # parameters and set/volley the response. + # Simulate an HTTP request to `action` by specifying request method, parameters + # and set/volley the response. # - # - +action+: The controller action to call. - # - +method+: Request method used to send the HTTP request. Possible values - # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol. - # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+. - # - +body+: The request body with a string that is appropriately encoded - # (application/x-www-form-urlencoded or multipart/form-data). - # - +session+: A hash of parameters to store in the session. This may be +nil+. - # - +flash+: A hash of parameters to store in the flash. This may be +nil+. - # - +format+: Request format. Defaults to +nil+. Can be string or symbol. - # - +as+: Content type. Defaults to +nil+. Must be a symbol that corresponds - # to a mime type. + # * `action`: The controller action to call. + # * `method`: Request method used to send the HTTP request. Possible values + # are `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, `HEAD`. Defaults to `GET`. + # Can be a symbol. + # * `params`: The hash with HTTP parameters that you want to pass. This may be + # `nil`. + # * `body`: The request body with a string that is appropriately encoded + # (`application/x-www-form-urlencoded` or `multipart/form-data`). + # * `session`: A hash of parameters to store in the session. This may be + # `nil`. + # * `flash`: A hash of parameters to store in the flash. This may be `nil`. + # * `format`: Request format. Defaults to `nil`. Can be string or symbol. + # * `as`: Content type. Defaults to `nil`. Must be a symbol that corresponds + # to a mime type. # - # Example calling +create+ action and sending two params: # - # process :create, - # method: 'POST', - # params: { - # user: { name: 'Gaurish Sharma', email: 'user@example.com' } - # }, - # session: { user_id: 1 }, - # flash: { notice: 'This is flash message' } + # Example calling `create` action and sending two params: # - # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, and +HEAD+ requests - # prefer using #get, #post, #patch, #put, #delete and #head methods - # respectively which will make tests more expressive. + # process :create, + # method: 'POST', + # params: { + # user: { name: 'Gaurish Sharma', email: 'user@example.com' } + # }, + # session: { user_id: 1 }, + # flash: { notice: 'This is flash message' } + # + # To simulate `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, and `HEAD` requests + # prefer using #get, #post, #patch, #put, #delete and #head methods respectively + # which will make tests more expressive. # # It's not recommended to make more than one request in the same test. Instance # variables that are set in one request will not persist to the next request, - # but it's not guaranteed that all \Rails internal state will be reset. Prefer + # but it's not guaranteed that all Rails internal state will be reset. Prefer # ActionDispatch::IntegrationTest for making multiple requests in the same test. # # Note that the request method is not verified. @@ -654,8 +672,8 @@ def document_root_element end def check_required_ivars - # Check for required instance variables so we can give an - # understandable error message. + # Check for required instance variables so we can give an understandable error + # message. [:@routes, :@controller, :@request, :@response].each do |iv_name| if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil? raise "#{iv_name} is nil: make sure you set it in your test's setup method." diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 307ab0f713..4d870cb2bb 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -3,26 +3,27 @@ #-- # Copyright (c) David Heinemeier Hansson # -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: +# 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 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. +# 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. #++ +# :markup: markdown + require "active_support" require "active_support/rails" require "active_support/core_ext/module/attribute_accessors" @@ -35,14 +36,14 @@ module Rack # :nodoc: autoload :Test, "rack/test" end -# = Action Dispatch +# # Action Dispatch # # Action Dispatch is a module of Action Pack. # -# Action Dispatch parses information about the web request, handles -# routing as defined by the user, and does advanced processing related to HTTP -# such as MIME-type negotiation, decoding parameters in POST, PATCH, or PUT -# bodies, handling HTTP caching logic, cookies and sessions. +# Action Dispatch parses information about the web request, handles routing as +# defined by the user, and does advanced processing related to HTTP such as +# MIME-type negotiation, decoding parameters in POST, PATCH, or PUT bodies, +# handling HTTP caching logic, cookies and sessions. module ActionDispatch extend ActiveSupport::Autoload diff --git a/actionpack/lib/action_dispatch/constants.rb b/actionpack/lib/action_dispatch/constants.rb index 31ed436c4b..c1b53150e1 100644 --- a/actionpack/lib/action_dispatch/constants.rb +++ b/actionpack/lib/action_dispatch/constants.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "rack/version" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/deprecator.rb b/actionpack/lib/action_dispatch/deprecator.rb index bb846afc75..453ad29463 100644 --- a/actionpack/lib/action_dispatch/deprecator.rb +++ b/actionpack/lib/action_dispatch/deprecator.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch def self.deprecator # :nodoc: @deprecator ||= ActiveSupport::Deprecation.new diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 5dd80d6301..c08ddf6487 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Http module Cache @@ -32,8 +34,8 @@ def etag_matches?(etag) end end - # Check response freshness (+Last-Modified+ and ETag) against request - # +If-Modified-Since+ and +If-None-Match+ conditions. If both headers are + # Check response freshness (`Last-Modified` and ETag) against request + # `If-Modified-Since` and `If-None-Match` conditions. If both headers are # supplied, both must match, or the request is not considered fresh. def fresh?(response) last_modified = if_modified_since @@ -79,25 +81,24 @@ def date=(utc_time) set_header DATE, utc_time.httpdate end - # 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. + # 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. + # 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. + # 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. + # 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 @@ -112,12 +113,13 @@ def strong_etag=(strong_validators) def etag?; etag; end - # True if an ETag is set, and it's a weak validator (preceded with W/). + # True if an ETag is set, and it's a weak validator (preceded with `W/`). def weak_etag? etag? && etag.start_with?('W/"') end - # True if an ETag is set, and it isn't a weak validator (not preceded with W/). + # True if an ETag is set, and it isn't a weak validator (not preceded with + # `W/`). def strong_etag? etag? && !weak_etag? end @@ -171,10 +173,9 @@ def prepare_cache_control! MUST_REVALIDATE = "must-revalidate" def handle_conditional_get! - # Normally default cache control setting is handled by ETag - # middleware. But, if an etag is already set, the middleware - # defaults to `no-cache` unless a default `Cache-Control` value is - # previously set. So, set a default one here. + # Normally default cache control setting is handled by ETag middleware. But, if + # an etag is already set, the middleware defaults to `no-cache` unless a default + # `Cache-Control` value is previously set. So, set a default one here. if (etag? || last_modified?) && !self._cache_control self._cache_control = DEFAULT_CACHE_CONTROL end @@ -186,8 +187,8 @@ def merge_and_normalize_cache_control!(cache_control) return if control.empty? && cache_control.empty? # Let middleware handle default behavior if cache_control.any? - # Any caching directive coming from a controller overrides - # no-cache/no-store in the default Cache-Control header. + # Any caching directive coming from a controller overrides no-cache/no-store in + # the default Cache-Control header. control.delete(:no_cache) control.delete(:no_store) diff --git a/actionpack/lib/action_dispatch/http/content_disposition.rb b/actionpack/lib/action_dispatch/http/content_disposition.rb index 89b5bb2fa6..da5ce42b55 100644 --- a/actionpack/lib/action_dispatch/http/content_disposition.rb +++ b/actionpack/lib/action_dispatch/http/content_disposition.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Http class ContentDisposition # :nodoc: diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb index c0ea12becc..02a001ff76 100644 --- a/actionpack/lib/action_dispatch/http/content_security_policy.rb +++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb @@ -1,28 +1,31 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/object/deep_dup" require "active_support/core_ext/array/wrap" module ActionDispatch # :nodoc: - # = Action Dispatch Content Security Policy + # # Action Dispatch Content Security Policy # - # Configures the HTTP - # {Content-Security-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy] - # response header to help protect against XSS and injection attacks. + # Configures the HTTP [Content-Security-Policy] + # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) + # response header to help protect against XSS and + # injection attacks. # # Example global policy: # - # Rails.application.config.content_security_policy do |policy| - # policy.default_src :self, :https - # policy.font_src :self, :https, :data - # policy.img_src :self, :https, :data - # policy.object_src :none - # policy.script_src :self, :https - # policy.style_src :self, :https + # Rails.application.config.content_security_policy do |policy| + # policy.default_src :self, :https + # policy.font_src :self, :https, :data + # policy.img_src :self, :https, :data + # policy.object_src :none + # policy.script_src :self, :https + # policy.style_src :self, :https # - # # Specify URI for violation reports - # policy.report_uri "/csp-violation-report-endpoint" - # end + # # Specify URI for violation reports + # policy.report_uri "/csp-violation-report-endpoint" + # end class ContentSecurityPolicy class Middleware def initialize(app) @@ -32,8 +35,8 @@ def initialize(app) def call(env) status, headers, _ = response = @app.call(env) - # Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new - # CSP headers might not match nonces in the cached HTML. + # Returning CSP headers with a 304 Not Modified is harmful, since nonces in the + # new CSP headers might not match nonces in the cached HTML. return response if status == 304 return response if policy_present?(headers) @@ -190,14 +193,14 @@ def initialize_copy(other) end end - # Specify whether to prevent the user agent from loading any assets over - # HTTP when the page uses HTTPS: + # Specify whether to prevent the user agent from loading any assets over HTTP + # when the page uses HTTPS: # - # policy.block_all_mixed_content + # policy.block_all_mixed_content # - # Pass +false+ to allow it again: + # Pass `false` to allow it again: # - # policy.block_all_mixed_content false + # policy.block_all_mixed_content false # def block_all_mixed_content(enabled = true) if enabled @@ -209,11 +212,11 @@ def block_all_mixed_content(enabled = true) # Restricts the set of plugins that can be embedded: # - # policy.plugin_types "application/x-shockwave-flash" + # policy.plugin_types "application/x-shockwave-flash" # # Leave empty to allow all plugins: # - # policy.plugin_types + # policy.plugin_types # def plugin_types(*types) if types.first @@ -223,23 +226,25 @@ def plugin_types(*types) end end - # Enable the {report-uri}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri] - # directive. Violation reports will be sent to the specified URI: + # Enable the [report-uri] + # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri) + # directive. Violation reports will be sent to the + # specified URI: # - # policy.report_uri "/csp-violation-report-endpoint" + # policy.report_uri "/csp-violation-report-endpoint" # def report_uri(uri) @directives["report-uri"] = [uri] end - # Specify asset types for which {Subresource Integrity}[https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity] - # is required: + # Specify asset types for which [Subresource Integrity] + # (https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required: # - # policy.require_sri_for :script, :style + # policy.require_sri_for :script, :style # # Leave empty to not require Subresource Integrity: # - # policy.require_sri_for + # policy.require_sri_for # def require_sri_for(*types) if types.first @@ -249,18 +254,19 @@ def require_sri_for(*types) end end - # Specify whether a {sandbox}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox] + # Specify whether a [sandbox] + # (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox) # should be enabled for the requested resource: # - # policy.sandbox + # policy.sandbox # # Values can be passed as arguments: # - # policy.sandbox "allow-scripts", "allow-modals" + # policy.sandbox "allow-scripts", "allow-modals" # - # Pass +false+ to disable the sandbox: + # Pass `false` to disable the sandbox: # - # policy.sandbox false + # policy.sandbox false # def sandbox(*values) if values.empty? @@ -274,11 +280,11 @@ def sandbox(*values) # Specify whether user agents should treat any assets over HTTP as HTTPS: # - # policy.upgrade_insecure_requests + # policy.upgrade_insecure_requests # - # Pass +false+ to disable it: + # Pass `false` to disable it: # - # policy.upgrade_insecure_requests false + # policy.upgrade_insecure_requests false # def upgrade_insecure_requests(enabled = true) if enabled diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 54ba0ec7b8..f954f2a55c 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -1,18 +1,21 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/parameter_filter" module ActionDispatch module Http - # = Action Dispatch HTTP Filter Parameters + # # Action Dispatch HTTP Filter Parameters # # Allows you to specify sensitive query string and POST parameters to filter # from the request log. # - # # Replaces values with "[FILTERED]" for keys that match /foo|bar/i. - # env["action_dispatch.parameter_filter"] = [:foo, "bar"] + # # Replaces values with "[FILTERED]" for keys that match /foo|bar/i. + # env["action_dispatch.parameter_filter"] = [:foo, "bar"] # - # For more information about filter behavior, see ActiveSupport::ParameterFilter. + # For more information about filter behavior, see + # ActiveSupport::ParameterFilter. module FilterParameters ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc: NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc: @@ -43,7 +46,8 @@ def filtered_path @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}" end - # Returns the +ActiveSupport::ParameterFilter+ object used to filter in this request. + # Returns the `ActiveSupport::ParameterFilter` object used to filter in this + # request. def parameter_filter @parameter_filter ||= if has_header?("action_dispatch.parameter_filter") parameter_filter_for get_header("action_dispatch.parameter_filter") diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb index 3bd1f5109d..a664527515 100644 --- a/actionpack/lib/action_dispatch/http/filter_redirect.rb +++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Http module FilterRedirect diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index d9bd5da2b1..d703acf304 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -1,28 +1,30 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Http - # = Action Dispatch HTTP \Headers + # # Action Dispatch HTTP Headers # # Provides access to the request's HTTP headers from the environment. # - # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" } - # headers = ActionDispatch::Http::Headers.from_hash(env) - # headers["Content-Type"] # => "text/plain" - # headers["User-Agent"] # => "curl/7.43.0" + # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" } + # headers = ActionDispatch::Http::Headers.from_hash(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 + # # GET / HTTP/1.1 + # # ... + # # User-Agent: curl/7.43.0 + # # X_Custom_Header: token # - # headers["X_Custom_Header"] # => nil - # headers["X-Custom-Header"] # => "token" + # headers["X_Custom_Header"] # => nil + # headers["X-Custom-Header"] # => "token" class Headers CGI_VARIABLES = Set.new(%W[ AUTH_TYPE @@ -67,7 +69,7 @@ def []=(key, value) @req.set_header env_name(key), value end - # Add a value to a multivalued header like +Vary+ or +Accept-Encoding+. + # Add a value to a multivalued header like `Vary` or `Accept-Encoding`. def add(key, value) @req.add_header env_name(key), value end @@ -81,11 +83,10 @@ def key?(key) # Returns the value for the given key mapped to @env. # - # If the key is not found and an optional code block is not provided, - # raises a KeyError exception. + # If the key is not found and an optional code block is not provided, raises a + # `KeyError` exception. # - # If the code block is provided, then it will be run and - # its result returned. + # If the code block is provided, then it will be run and its result returned. def fetch(key, default = DEFAULT) @req.fetch_header(env_name(key)) do return default unless default == DEFAULT @@ -99,16 +100,15 @@ def each(&block) end # Returns a new Http::Headers instance containing the contents of - # headers_or_env and the original instance. + # `headers_or_env` and the original instance. def merge(headers_or_env) headers = @req.dup.headers headers.merge!(headers_or_env) headers end - # Adds the contents of headers_or_env to original instance - # entries; duplicate keys are overwritten with the values from - # headers_or_env. + # Adds the contents of `headers_or_env` to original instance entries; duplicate + # keys are overwritten with the values from `headers_or_env`. def merge!(headers_or_env) headers_or_env.each do |key, value| @req.set_header env_name(key), value @@ -118,8 +118,8 @@ def merge!(headers_or_env) def env; @req.env.dup; end private - # Converts an HTTP header name to an environment variable name if it is - # not contained within the headers hash. + # Converts an HTTP header name to an environment variable name if it is not + # contained within the headers hash. def env_name(key) key = key.to_s if HTTP_HEADER.match?(key) diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index fc41daaf67..ce44efb965 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/module/attribute_accessors" module ActionDispatch @@ -18,7 +20,7 @@ class InvalidType < ::Mime::Type::InvalidMimeType; end mattr_accessor :ignore_accept_header, default: false end - # The MIME type of the HTTP request, such as Mime[:xml]. + # The MIME type of the HTTP request, such as [Mime](:xml). def content_mime_type fetch_header("action_dispatch.request.content_type") do |k| v = if get_header("CONTENT_TYPE") =~ /^([^,;]*)/ @@ -52,11 +54,11 @@ def accepts end end - # Returns the MIME type for the \format used in the request. + # Returns the MIME type for the format used in the request. # - # GET /posts/5.xml | request.format => Mime[:xml] - # GET /posts/5.xhtml | request.format => Mime[:html] - # GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first + # GET /posts/5.xml | request.format => Mime[:xml] + # GET /posts/5.xhtml | request.format => Mime[:html] + # GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first # def format(_view_path = nil) formats.first || Mime::NullType.instance @@ -84,7 +86,7 @@ def formats end end - # Sets the \variant for template. + # Sets the variant for template. def variant=(variant) variant = Array(variant) @@ -99,36 +101,37 @@ def variant @variant ||= ActiveSupport::ArrayInquirer.new end - # Sets the \format by string extension, which can be used to force custom formats + # Sets the format by string extension, which can be used to force custom formats # that are not controlled by the extension. # - # class ApplicationController < ActionController::Base - # before_action :adjust_format_for_iphone + # class ApplicationController < ActionController::Base + # before_action :adjust_format_for_iphone # - # private - # def adjust_format_for_iphone - # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/] - # end - # end + # private + # def adjust_format_for_iphone + # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/] + # end + # end def format=(extension) parameters[:format] = extension.to_s set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])] end - # Sets the \formats by string extensions. This differs from #format= by allowing you - # to set multiple, ordered formats, which is useful when you want to have a fallback. + # Sets the formats by string extensions. This differs from #format= by allowing + # you to set multiple, ordered formats, which is useful when you want to have a + # fallback. # - # In this example, the +:iphone+ format will be used if it's available, otherwise it'll fall back - # to the +:html+ format. + # In this example, the `:iphone` format will be used if it's available, + # otherwise it'll fall back to the `:html` format. # - # class ApplicationController < ActionController::Base - # before_action :adjust_format_for_iphone_with_html_fallback + # class ApplicationController < ActionController::Base + # before_action :adjust_format_for_iphone_with_html_fallback # - # private - # def adjust_format_for_iphone_with_html_fallback - # request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/] - # end - # end + # private + # def adjust_format_for_iphone_with_html_fallback + # request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/] + # end + # end def formats=(extensions) parameters[:format] = extensions.first.to_s set_header "action_dispatch.request.formats", extensions.collect { |extension| @@ -154,8 +157,8 @@ def should_apply_vary_header? end private - # We use normal content negotiation unless you include */* in your list, - # in which case we assume you're a browser and send HTML. + # We use normal content negotiation unless you include **/** in your list, in + # which case we assume you're a browser and send HTML. BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ def params_readable? diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 6e3ef04981..7946b84ebd 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "singleton" module Mime @@ -65,19 +67,20 @@ def fetch(type, &block) end end - # Encapsulates the notion of a MIME type. Can be used at render time, for example, with: + # Encapsulates the notion of a MIME type. Can be used at render time, for + # example, with: # - # class PostsController < ActionController::Base - # def show - # @post = Post.find(params[:id]) + # class PostsController < ActionController::Base + # def show + # @post = Post.find(params[:id]) # - # respond_to do |format| - # format.html - # format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") } - # format.xml { render xml: @post } + # respond_to do |format| + # format.html + # format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") } + # format.xml { render xml: @post } + # end # end # end - # end class Type attr_reader :symbol @@ -170,8 +173,9 @@ def lookup_by_extension(extension) EXTENSION_LOOKUP[extension.to_s] end - # Registers an alias that's not used on MIME type lookup, but can be referenced directly. Especially useful for - # rendering different HTML versions depending on the user agent, like an iPhone. + # Registers an alias that's not used on MIME type lookup, but can be referenced + # directly. Especially useful for rendering different HTML versions depending on + # the user agent, like an iPhone. def register_alias(string, symbol, extension_synonyms = []) register(string, symbol, [], extension_synonyms, true) end @@ -221,11 +225,11 @@ def parse_trailing_star(accept_header) parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP end - # For an input of 'text', returns [Mime[:json], Mime[:xml], Mime[:ics], - # Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]. + # For an input of `'text'`, returns `[Mime[:json], Mime[:xml], Mime[:ics], + # Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]`. # - # For an input of 'application', returns [Mime[:html], Mime[:js], - # Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]. + # For an input of `'application'`, returns `[Mime[:html], Mime[:js], Mime[:xml], + # Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]`. def parse_data_with_trailing_star(type) Mime::SET.select { |m| m.match?(type) } end @@ -234,7 +238,7 @@ def parse_data_with_trailing_star(type) # # To unregister a MIME type: # - # Mime::Type.unregister(:mobile) + # Mime::Type.unregister(:mobile) def unregister(symbol) symbol = symbol.downcase if mime = Mime[symbol] @@ -350,9 +354,9 @@ def all?; true; end def html?; true; end end - # ALL isn't a real MIME type, so we don't register it for lookup with the - # other concrete types. It's a wildcard match that we use for +respond_to+ - # negotiation internals. + # ALL isn't a real MIME type, so we don't register it for lookup with the other + # concrete types. It's a wildcard match that we use for `respond_to` negotiation + # internals. ALL = AllType.instance class NullType diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb index fd58256e23..426154fe57 100644 --- a/actionpack/lib/action_dispatch/http/mime_types.rb +++ b/actionpack/lib/action_dispatch/http/mime_types.rb @@ -3,6 +3,8 @@ # Build list of Mime types for HTTP responses # https://www.iana.org/assignments/media-types/ +# :markup: markdown + Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml ) Mime::Type.register "text/plain", :text, [], %w(txt) Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript ) diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index eb3c381064..1c702569bd 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Http module Parameters @@ -14,8 +16,8 @@ module Parameters } } - # Raised when raw data from the request cannot be parsed by the parser - # defined for request's content MIME type. + # Raised when raw data from the request cannot be parsed by the parser defined + # for request's content MIME type. class ParseError < StandardError def initialize(message = $!.message) super(message) @@ -34,8 +36,8 @@ class << self module ClassMethods # Configure the parameter parser for a given MIME type. # - # It accepts a hash where the key is the symbol of the MIME type - # and the value is a proc. + # It accepts a hash where the key is the symbol of the MIME type and the value + # is a proc. # # original_parsers = ActionDispatch::Request.parameter_parsers # xml_parser = -> (raw_post) { Hash.from_xml(raw_post) || {} } @@ -46,7 +48,7 @@ def parameter_parsers=(parsers) end end - # Returns both GET and POST \parameters in a single hash. + # Returns both GET and POST parameters in a single hash. def parameters params = get_header("action_dispatch.request.parameters") return params if params @@ -66,8 +68,8 @@ def path_parameters=(parameters) # :nodoc: delete_header("action_dispatch.request.parameters") parameters = Request::Utils.set_binary_encoding(self, parameters, parameters[:controller], parameters[:action]) - # If any of the path parameters has an invalid encoding then - # raise since it's likely to trigger errors further on. + # If any of the path parameters has an invalid encoding then raise since it's + # likely to trigger errors further on. Request::Utils.check_param_encoding(parameters) set_header PARAMETERS_KEY, parameters @@ -75,10 +77,10 @@ def path_parameters=(parameters) # :nodoc: raise ActionController::BadRequest.new("Invalid path parameters: #{e.message}") end - # Returns a hash with the \parameters used to form the \path of the request. + # Returns a hash with the parameters used to form the path of the request. # Returned hash keys are symbols: # - # { action: "my_action", controller: "my_controller" } + # { action: "my_action", controller: "my_controller" } def path_parameters get_header(PARAMETERS_KEY) || set_header(PARAMETERS_KEY, {}) end diff --git a/actionpack/lib/action_dispatch/http/permissions_policy.rb b/actionpack/lib/action_dispatch/http/permissions_policy.rb index 261564fd0a..01b7051511 100644 --- a/actionpack/lib/action_dispatch/http/permissions_policy.rb +++ b/actionpack/lib/action_dispatch/http/permissions_policy.rb @@ -1,31 +1,33 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/object/deep_dup" module ActionDispatch # :nodoc: - # = Action Dispatch \PermissionsPolicy + # # Action Dispatch PermissionsPolicy # # Configures the HTTP - # {Feature-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy] - # response header to specify which browser features the current document and - # its iframes can use. + # [Feature-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy) + # response header to specify which browser features the current + # document and its iframes can use. # # Example global policy: # - # Rails.application.config.permissions_policy do |policy| - # policy.camera :none - # policy.gyroscope :none - # policy.microphone :none - # policy.usb :none - # policy.fullscreen :self - # policy.payment :self, "https://secure.example.com" - # end + # Rails.application.config.permissions_policy do |policy| + # policy.camera :none + # policy.gyroscope :none + # policy.microphone :none + # policy.usb :none + # policy.fullscreen :self + # policy.payment :self, "https://secure.example.com" + # end # - # The Feature-Policy header has been renamed to Permissions-Policy. - # The Permissions-Policy requires a different implementation and isn't - # yet supported by all browsers. To avoid having to rename this - # middleware in the future we use the new name for the middleware but - # keep the old header name and implementation for now. + # The Feature-Policy header has been renamed to Permissions-Policy. The + # Permissions-Policy requires a different implementation and isn't yet supported + # by all browsers. To avoid having to rename this middleware in the future we + # use the new name for the middleware but keep the old header name and + # implementation for now. class PermissionsPolicy class Middleware def initialize(app) diff --git a/actionpack/lib/action_dispatch/http/rack_cache.rb b/actionpack/lib/action_dispatch/http/rack_cache.rb index 335e6a1fa2..1429e74b7c 100644 --- a/actionpack/lib/action_dispatch/http/rack_cache.rb +++ b/actionpack/lib/action_dispatch/http/rack_cache.rb @@ -2,6 +2,8 @@ # :enddoc: +# :markup: markdown + require "rack/cache" require "rack/cache/context" require "active_support/cache" diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 64e06045ce..e310a7ce74 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "stringio" require "active_support/inflector" @@ -102,26 +104,26 @@ def controller_class_for(name) # Returns true if the request has a header matching the given key parameter. # - # request.key? :ip_spoofing_check # => true + # request.key? :ip_spoofing_check # => true def key?(key) has_header? key end - # HTTP methods from {RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1}[https://www.ietf.org/rfc/rfc2616.txt] + # HTTP methods from [RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1](https://www.ietf.org/rfc/rfc2616.txt) RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT) - # HTTP methods from {RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV}[https://www.ietf.org/rfc/rfc2518.txt] + # HTTP methods from [RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV](https://www.ietf.org/rfc/rfc2518.txt) RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK) - # HTTP methods from {RFC 3253: Versioning Extensions to WebDAV}[https://www.ietf.org/rfc/rfc3253.txt] + # HTTP methods from [RFC 3253: Versioning Extensions to WebDAV](https://www.ietf.org/rfc/rfc3253.txt) RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY) - # HTTP methods from {RFC 3648: WebDAV Ordered Collections Protocol}[https://www.ietf.org/rfc/rfc3648.txt] + # HTTP methods from [RFC 3648: WebDAV Ordered Collections Protocol](https://www.ietf.org/rfc/rfc3648.txt) RFC3648 = %w(ORDERPATCH) - # HTTP methods from {RFC 3744: WebDAV Access Control Protocol}[https://www.ietf.org/rfc/rfc3744.txt] + # HTTP methods from [RFC 3744: WebDAV Access Control Protocol](https://www.ietf.org/rfc/rfc3744.txt) RFC3744 = %w(ACL) - # HTTP methods from {RFC 5323: WebDAV SEARCH}[https://www.ietf.org/rfc/rfc5323.txt] + # HTTP methods from [RFC 5323: WebDAV SEARCH](https://www.ietf.org/rfc/rfc5323.txt) RFC5323 = %w(SEARCH) - # HTTP methods from {RFC 4791: Calendaring Extensions to WebDAV}[https://www.ietf.org/rfc/rfc4791.txt] + # HTTP methods from [RFC 4791: Calendaring Extensions to WebDAV](https://www.ietf.org/rfc/rfc4791.txt) RFC4791 = %w(MKCALENDAR) - # HTTP methods from {RFC 5789: PATCH Method for HTTP}[https://www.ietf.org/rfc/rfc5789.txt] + # HTTP methods from [RFC 5789: PATCH Method for HTTP](https://www.ietf.org/rfc/rfc5789.txt) RFC5789 = %w(PATCH) HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789 @@ -135,20 +137,19 @@ def key?(key) alias raw_request_method request_method # :nodoc: - # Returns the HTTP \method that the application should see. - # In the case where the \method was overridden by a middleware - # (for instance, if a HEAD request was converted to a GET, - # or if a _method parameter was used to determine the \method - # the application should use), this \method returns the overridden - # value, not the original. + # Returns the HTTP method that the application should see. In the case where the + # method was overridden by a middleware (for instance, if a HEAD request was + # converted to a GET, or if a _method parameter was used to determine the method + # the application should use), this method returns the overridden value, not the + # original. def request_method @request_method ||= check_method(super) end - # Returns the URI pattern of the matched route for the request, - # using the same format as bin/rails routes: + # Returns the URI pattern of the matched route for the request, using the same + # format as `bin/rails routes`: # - # request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)" + # request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)" def route_uri_pattern get_header("action_dispatch.route_uri_pattern") end @@ -196,12 +197,11 @@ def request_method_symbol HTTP_METHOD_LOOKUP[request_method] end - # Returns the original value of the environment's REQUEST_METHOD, - # even if it was overridden by middleware. See #request_method for - # more information. + # Returns the original value of the environment's REQUEST_METHOD, even if it was + # overridden by middleware. See #request_method for more information. # - # For debugging purposes, when called with arguments this method will - # fall back to Object#method + # For debugging purposes, when called with arguments this method will fall back + # to Object#method def method(*args) if args.empty? @method ||= check_method( @@ -221,60 +221,61 @@ def method_symbol # Provides access to the request's HTTP headers, for example: # - # request.headers["Content-Type"] # => "text/plain" + # request.headers["Content-Type"] # => "text/plain" def headers @headers ||= Http::Headers.new(self) end - # Early Hints is an HTTP/2 status code that indicates hints to help a client start - # making preparations for processing the final response. + # Early Hints is an HTTP/2 status code that indicates hints to help a client + # start making preparations for processing the final response. # - # If the env contains +rack.early_hints+ then the server accepts HTTP2 push for Link headers. + # If the env contains `rack.early_hints` then the server accepts HTTP2 push for + # Link headers. # - # The +send_early_hints+ method accepts a hash of links as follows: + # The `send_early_hints` method accepts a hash of links as follows: # - # send_early_hints("Link" => "; rel=preload; as=style\n; rel=preload") + # send_early_hints("Link" => "; rel=preload; as=style\n; rel=preload") # - # If you are using +javascript_include_tag+ or +stylesheet_link_tag+ the - # Early Hints headers are included by default if supported. + # If you are using `javascript_include_tag` or `stylesheet_link_tag` the Early + # Hints headers are included by default if supported. def send_early_hints(links) env["rack.early_hints"]&.call(links) end - # Returns a +String+ with the last requested path including their params. + # Returns a `String` with the last requested path including their params. # - # # get '/foo' - # request.original_fullpath # => '/foo' + # # get '/foo' + # request.original_fullpath # => '/foo' # - # # get '/foo?bar' - # request.original_fullpath # => '/foo?bar' + # # get '/foo?bar' + # request.original_fullpath # => '/foo?bar' def original_fullpath @original_fullpath ||= (get_header("ORIGINAL_FULLPATH") || fullpath) end - # Returns the +String+ full path including params of the last URL requested. + # Returns the `String` full path including params of the last URL requested. # - # # get "/articles" - # request.fullpath # => "/articles" + # # get "/articles" + # request.fullpath # => "/articles" # - # # get "/articles?page=2" - # request.fullpath # => "/articles?page=2" + # # get "/articles?page=2" + # request.fullpath # => "/articles?page=2" def fullpath @fullpath ||= super end - # Returns the original request URL as a +String+. + # Returns the original request URL as a `String`. # - # # get "/articles?page=2" - # request.original_url # => "http://www.example.com/articles?page=2" + # # get "/articles?page=2" + # request.original_url # => "http://www.example.com/articles?page=2" def original_url base_url + original_fullpath end - # The +String+ MIME type of the request. + # The `String` MIME type of the request. # - # # get "/articles" - # request.media_type # => "application/x-www-form-urlencoded" + # # get "/articles" + # request.media_type # => "application/x-www-form-urlencoded" def media_type content_mime_type&.to_s end @@ -285,7 +286,7 @@ def content_length super.to_i end - # Returns true if the +X-Requested-With+ header contains "XMLHttpRequest" + # Returns true if the `X-Requested-With` header contains "XMLHttpRequest" # (case-insensitive), which may need to be manually added depending on the # choice of JavaScript libraries and frameworks. def xml_http_request? @@ -293,13 +294,13 @@ def xml_http_request? end alias :xhr? :xml_http_request? - # Returns the IP address of client as a +String+. + # Returns the IP address of client as a `String`. def ip @ip ||= super end - # Returns the IP address of client as a +String+, - # usually set by the RemoteIp middleware. + # Returns the IP address of client as a `String`, usually set by the RemoteIp + # middleware. def remote_ip @remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s end @@ -311,12 +312,14 @@ def remote_ip=(remote_ip) ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id" # :nodoc: - # Returns the unique request id, which is based on either the +X-Request-Id+ header that can - # be generated by a firewall, load balancer, or web server, or by the RequestId middleware - # (which sets the +action_dispatch.request_id+ environment variable). + # Returns the unique request id, which is based on either the `X-Request-Id` + # header that can be generated by a firewall, load balancer, or web server, or + # by the RequestId middleware (which sets the `action_dispatch.request_id` + # environment variable). # - # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging. - # This relies on the Rack variable set by the ActionDispatch::RequestId middleware. + # This unique ID is useful for tracing a request from end-to-end as part of + # logging or debugging. This relies on the Rack variable set by the + # ActionDispatch::RequestId middleware. def request_id get_header ACTION_DISPATCH_REQUEST_ID end @@ -332,8 +335,8 @@ def server_software (get_header("SERVER_SOFTWARE") && /^([a-zA-Z]+)/ =~ get_header("SERVER_SOFTWARE")) ? $1.downcase : nil end - # Read the request \body. This is useful for web services that need to - # work with raw requests directly. + # Read the request body. This is useful for web services that need to work with + # raw requests directly. def raw_post unless has_header? "RAW_POST_DATA" set_header("RAW_POST_DATA", read_body_stream) @@ -353,14 +356,13 @@ def body end end - # Determine whether the request body contains form-data by checking - # the request +Content-Type+ for one of the media-types: - # +application/x-www-form-urlencoded+ or +multipart/form-data+. The - # list of form-data media types can be modified through the - # +FORM_DATA_MEDIA_TYPES+ array. + # Determine whether the request body contains form-data by checking the request + # `Content-Type` for one of the media-types: `application/x-www-form-urlencoded` + # or `multipart/form-data`. The list of form-data media types can be modified + # through the `FORM_DATA_MEDIA_TYPES` array. # - # A request body is not assumed to contain form-data when no - # +Content-Type+ header is provided and the request_method is POST. + # A request body is not assumed to contain form-data when no `Content-Type` + # header is provided and the request_method is POST. def form_data? FORM_DATA_MEDIA_TYPES.include?(media_type) end @@ -413,8 +415,8 @@ def POST end alias :request_parameters :POST - # Returns the authorization header regardless of whether it was specified directly or through one of the - # proxy alternatives. + # Returns the authorization header regardless of whether it was specified + # directly or through one of the proxy alternatives. def authorization get_header("HTTP_AUTHORIZATION") || get_header("X-HTTP_AUTHORIZATION") || diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 1da774857a..e74363f6aa 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,38 +1,40 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/module/attribute_accessors" require "action_dispatch/http/filter_redirect" require "action_dispatch/http/cache" require "monitor" module ActionDispatch # :nodoc: - # = Action Dispatch \Response + # # Action Dispatch Response # # Represents an HTTP response generated by a controller action. Use it to # retrieve the current state of the response, or customize the response. It can - # either represent a real HTTP response (i.e. one that is meant to be sent - # back to the web browser) or a TestResponse (i.e. one that is generated - # from integration tests). + # either represent a real HTTP response (i.e. one that is meant to be sent back + # to the web browser) or a TestResponse (i.e. one that is generated from + # integration tests). # - # The \Response object for the current request is exposed on controllers as - # ActionController::Metal#response. ActionController::Metal also provides a - # few additional methods that delegate to attributes of the \Response such as + # The Response object for the current request is exposed on controllers as + # ActionController::Metal#response. ActionController::Metal also provides a few + # additional methods that delegate to attributes of the Response such as # ActionController::Metal#headers. # - # Integration tests will likely also want to inspect responses in - # more detail. Methods such as Integration::RequestHelpers#get - # and Integration::RequestHelpers#post return instances of - # TestResponse (which inherits from \Response) for this purpose. + # Integration tests will likely also want to inspect responses in more detail. + # Methods such as Integration::RequestHelpers#get and + # Integration::RequestHelpers#post return instances of TestResponse (which + # inherits from Response) for this purpose. # # For example, the following demo integration test prints the body of the # controller response to the console: # - # class DemoControllerTest < ActionDispatch::IntegrationTest - # def test_print_root_path_to_console - # get('/') - # puts response.body - # end - # end + # class DemoControllerTest < ActionDispatch::IntegrationTest + # def test_print_root_path_to_console + # get('/') + # puts response.body + # end + # end class Response begin # For `Rack::Headers` (Rack 3+): @@ -55,17 +57,17 @@ class Response # The headers for the response. # - # header["Content-Type"] # => "text/plain" - # header["Content-Type"] = "application/json" - # header["Content-Type"] # => "application/json" + # header["Content-Type"] # => "text/plain" + # header["Content-Type"] = "application/json" + # header["Content-Type"] # => "application/json" # - # Also aliased as +headers+. + # Also aliased as `headers`. # - # headers["Content-Type"] # => "text/plain" - # headers["Content-Type"] = "application/json" - # headers["Content-Type"] # => "application/json" + # headers["Content-Type"] # => "text/plain" + # headers["Content-Type"] = "application/json" + # headers["Content-Type"] # => "application/json" # - # Also aliased as +header+ for compatibility. + # Also aliased as `header` for compatibility. attr_reader :headers alias_method :header, :headers @@ -234,13 +236,13 @@ def status=(status) @status = Rack::Utils.status_code(status) end - # Sets the HTTP response's content MIME type. For example, in the controller - # you could write this: + # Sets the HTTP response's content MIME type. For example, in the controller you + # could write this: # - # response.content_type = "text/plain" + # response.content_type = "text/plain" # - # If a character set has been defined for this response (see charset=) then - # the character set information will also be included in the content type + # If a character set has been defined for this response (see charset=) then the + # character set information will also be included in the content type # information. def content_type=(content_type) return unless content_type @@ -267,11 +269,11 @@ def sending_file=(v) end end - # Sets the HTTP character set. In case of +nil+ parameter - # it sets the charset to +default_charset+. + # Sets the HTTP character set. In case of `nil` parameter it sets the charset to + # `default_charset`. # - # response.charset = 'utf-16' # => 'utf-16' - # response.charset = nil # => 'utf-8' + # response.charset = 'utf-16' # => 'utf-16' + # response.charset = nil # => 'utf-8' def charset=(charset) content_type = parsed_content_type_header.mime_type if false == charset @@ -281,8 +283,8 @@ def charset=(charset) end end - # The charset of the response. HTML wants to know the encoding of the - # content you're giving them, so we need to send that along. + # The charset of the response. HTML wants to know the encoding of the content + # you're giving them, so we need to send that along. def charset header_info = parsed_content_type_header header_info.charset || self.class.default_charset @@ -293,26 +295,26 @@ def response_code @status end - # Returns a string to ensure compatibility with +Net::HTTPResponse+. + # Returns a string to ensure compatibility with `Net::HTTPResponse`. def code @status.to_s end # Returns the corresponding message for the current HTTP status code: # - # response.status = 200 - # response.message # => "OK" + # response.status = 200 + # response.message # => "OK" # - # response.status = 404 - # response.message # => "Not Found" + # response.status = 404 + # response.message # => "Not Found" # def message Rack::Utils::HTTP_STATUS_CODES[@status] end alias_method :status_message, :message - # Returns the content of the response as a string. This contains the contents - # of any calls to render. + # Returns the content of the response as a string. This contains the contents of + # any calls to `render`. def body @stream.body end @@ -332,9 +334,9 @@ def body=(body) end end - # Avoid having to pass an open file handle as the response body. - # Rack::Sendfile will usually intercept the response and uses - # the path directly, so there is no reason to open the file. + # Avoid having to pass an open file handle as the response body. Rack::Sendfile + # will usually intercept the response and uses the path directly, so there is no + # reason to open the file. class FileBody # :nodoc: attr_reader :to_path @@ -356,7 +358,7 @@ def each end end - # Send the file stored at +path+ as the response body. + # Send the file stored at `path` as the response body. def send_file(path) commit! @stream = FileBody.new(path) @@ -383,17 +385,16 @@ def abort if stream.respond_to?(:abort) stream.abort elsif stream.respond_to?(:close) - # `stream.close` should really be reserved for a close from the - # other direction, but we must fall back to it for - # compatibility. + # `stream.close` should really be reserved for a close from the other direction, + # but we must fall back to it for compatibility. stream.close end end - # Turns the Response into a Rack-compatible array of the status, headers, - # and body. Allows explicit splatting: + # Turns the Response into a Rack-compatible array of the status, headers, and + # body. Allows explicit splatting: # - # status, headers, body = *response + # status, headers, body = *response def to_a commit! rack_response @status, @headers.to_hash @@ -402,7 +403,7 @@ def to_a # Returns the response cookies, converted to a Hash of (name => value) pairs # - # assert_equal 'AuthorOfNewPage', r.cookies['author'] + # assert_equal 'AuthorOfNewPage', r.cookies['author'] def cookies cookies = {} if header = get_header(SET_COOKIE) @@ -456,11 +457,10 @@ def before_committed end def before_sending - # Normally we've already committed by now, but it's possible - # (e.g., if the controller action tries to read back its own - # response) to get here before that. In that case, we must force - # an "early" commit: we're about to freeze the headers, so this is - # our last chance. + # Normally we've already committed by now, but it's possible (e.g., if the + # controller action tries to read back its own response) to get here before + # that. In that case, we must force an "early" commit: we're about to freeze the + # headers, so this is our last chance. commit! unless committed? @request.commit_cookie_jar! unless committed? @@ -488,8 +488,8 @@ def initialize(response) end def close - # Rack "close" maps to Response#abort, and *not* Response#close - # (which is used when the controller's finished writing) + # Rack "close" maps to Response#abort, and **not** Response#close (which is used + # when the controller's finished writing) @response.abort end diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 60d4d72c08..9b14b54a90 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -1,17 +1,19 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Http - # = Action Dispatch HTTP \UploadedFile + # # Action Dispatch HTTP UploadedFile # # Models uploaded files. # - # The actual file is accessible via the +tempfile+ accessor, though some - # of its interface is available directly for convenience. + # The actual file is accessible via the `tempfile` accessor, though some of its + # interface is available directly for convenience. # - # Uploaded files are temporary files whose lifespan is one request. When - # the object is finalized Ruby unlinks the file, so there is no need to - # clean them with a separate maintenance task. + # Uploaded files are temporary files whose lifespan is one request. When the + # object is finalized Ruby unlinks the file, so there is no need to clean them + # with a separate maintenance task. class UploadedFile # The basename of the file in the client. attr_accessor :original_filename @@ -19,8 +21,8 @@ class UploadedFile # A string with the MIME type of the file. attr_accessor :content_type - # A +Tempfile+ object with the actual uploaded file. Note that some of - # its interface is available directly. + # A `Tempfile` object with the actual uploaded file. Note that some of its + # interface is available directly. attr_accessor :tempfile # A string with the headers of the multipart request. @@ -57,42 +59,42 @@ def initialize(hash) # :nodoc: end end - # Shortcut for +tempfile.read+. + # Shortcut for `tempfile.read`. def read(length = nil, buffer = nil) @tempfile.read(length, buffer) end - # Shortcut for +tempfile.open+. + # Shortcut for `tempfile.open`. def open @tempfile.open end - # Shortcut for +tempfile.close+. + # Shortcut for `tempfile.close`. def close(unlink_now = false) @tempfile.close(unlink_now) end - # Shortcut for +tempfile.path+. + # Shortcut for `tempfile.path`. def path @tempfile.path end - # Shortcut for +tempfile.to_path+. + # Shortcut for `tempfile.to_path`. def to_path @tempfile.to_path end - # Shortcut for +tempfile.rewind+. + # Shortcut for `tempfile.rewind`. def rewind @tempfile.rewind end - # Shortcut for +tempfile.size+. + # Shortcut for `tempfile.size`. def size @tempfile.size end - # Shortcut for +tempfile.eof?+. + # Shortcut for `tempfile.eof?`. def eof? @tempfile.eof? end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 9733ecab71..669c34f9fc 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/module/attribute_accessors" module ActionDispatch @@ -15,20 +17,20 @@ module URL class << self # Returns the domain part of a host given the domain level. # - # # Top-level domain example - # extract_domain('www.example.com', 1) # => "example.com" - # # Second-level domain example - # extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk" + # # Top-level domain example + # extract_domain('www.example.com', 1) # => "example.com" + # # Second-level domain example + # extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk" def extract_domain(host, tld_length) extract_domain_from(host, tld_length) if named_host?(host) end # Returns the subdomains of a host as an Array given the domain level. # - # # Top-level domain example - # extract_subdomains('www.example.com', 1) # => ["www"] - # # Second-level domain example - # extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"] + # # Top-level domain example + # extract_subdomains('www.example.com', 1) # => ["www"] + # # Second-level domain example + # extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"] def extract_subdomains(host, tld_length) if named_host?(host) extract_subdomains_from(host, tld_length) @@ -39,10 +41,10 @@ def extract_subdomains(host, tld_length) # Returns the subdomains of a host as a String given the domain level. # - # # Top-level domain example - # extract_subdomain('www.example.com', 1) # => "www" - # # Second-level domain example - # extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www" + # # Top-level domain example + # extract_subdomain('www.example.com', 1) # => "www" + # # Second-level domain example + # extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www" def extract_subdomain(host, tld_length) extract_subdomains(host, tld_length).join(".") end @@ -184,33 +186,33 @@ def initialize # Returns the complete URL used for this request. # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' - # req.url # => "http://example.com" + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.url # => "http://example.com" def url protocol + host_with_port + fullpath end # Returns 'https://' if this is an SSL request and 'http://' otherwise. # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' - # req.protocol # => "http://" + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.protocol # => "http://" # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on' - # req.protocol # => "https://" + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on' + # req.protocol # => "https://" def protocol @protocol ||= ssl? ? "https://" : "http://" end - # Returns the \host and port for this request, such as "example.com:8080". + # Returns the host and port for this request, such as "example.com:8080". # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' - # req.raw_host_with_port # => "example.com" + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.raw_host_with_port # => "example.com" # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' - # req.raw_host_with_port # => "example.com:80" + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.raw_host_with_port # => "example.com:80" # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' - # req.raw_host_with_port # => "example.com:8080" + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.raw_host_with_port # => "example.com:8080" def raw_host_with_port if forwarded = x_forwarded_host.presence forwarded.split(/,\s?/).last @@ -221,35 +223,35 @@ def raw_host_with_port # Returns the host for this request, such as "example.com". # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' - # req.host # => "example.com" + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.host # => "example.com" def host raw_host_with_port.sub(/:\d+$/, "") end - # Returns a \host:\port string for this request, such as "example.com" or - # "example.com:8080". Port is only included if it is not a default port - # (80 or 443) + # Returns a host:port string for this request, such as "example.com" or + # "example.com:8080". Port is only included if it is not a default port (80 or + # 443) # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' - # req.host_with_port # => "example.com" + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.host_with_port # => "example.com" # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' - # req.host_with_port # => "example.com" + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.host_with_port # => "example.com" # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' - # req.host_with_port # => "example.com:8080" + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.host_with_port # => "example.com:8080" def host_with_port "#{host}#{port_string}" end # Returns the port number of this request as an integer. # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' - # req.port # => 80 + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.port # => 80 # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' - # req.port # => 8080 + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.port # => 8080 def port @port ||= if raw_host_with_port =~ /:(\d+)$/ $1.to_i @@ -258,10 +260,10 @@ def port end end - # Returns the standard \port number for this request's protocol. + # Returns the standard port number for this request's protocol. # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' - # req.standard_port # => 80 + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.standard_port # => 80 def standard_port if "https://" == protocol 443 @@ -272,68 +274,68 @@ def standard_port # Returns whether this request is using the standard port # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' - # req.standard_port? # => true + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.standard_port? # => true # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' - # req.standard_port? # => false + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.standard_port? # => false def standard_port? port == standard_port end - # Returns a number \port suffix like 8080 if the \port number of this request - # is not the default HTTP \port 80 or HTTPS \port 443. + # Returns a number port suffix like 8080 if the port number of this request is + # not the default HTTP port 80 or HTTPS port 443. # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' - # req.optional_port # => nil + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.optional_port # => nil # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' - # req.optional_port # => 8080 + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.optional_port # => 8080 def optional_port standard_port? ? nil : port end - # Returns a string \port suffix, including colon, like ":8080" if the \port - # number of this request is not the default HTTP \port 80 or HTTPS \port 443. + # Returns a string port suffix, including colon, like ":8080" if the port number + # of this request is not the default HTTP port 80 or HTTPS port 443. # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' - # req.port_string # => "" + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.port_string # => "" # - # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' - # req.port_string # => ":8080" + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.port_string # => ":8080" def port_string standard_port? ? "" : ":#{port}" end # Returns the requested port, such as 8080, based on SERVER_PORT # - # req = ActionDispatch::Request.new 'SERVER_PORT' => '80' - # req.server_port # => 80 + # req = ActionDispatch::Request.new 'SERVER_PORT' => '80' + # req.server_port # => 80 # - # req = ActionDispatch::Request.new 'SERVER_PORT' => '8080' - # req.server_port # => 8080 + # req = ActionDispatch::Request.new 'SERVER_PORT' => '8080' + # req.server_port # => 8080 def server_port get_header("SERVER_PORT").to_i end - # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify - # a different tld_length, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk". + # Returns the domain part of a host, such as "rubyonrails.org" in + # "www.rubyonrails.org". You can specify a different `tld_length`, such as 2 to + # catch rubyonrails.co.uk in "www.rubyonrails.co.uk". def domain(tld_length = @@tld_length) ActionDispatch::Http::URL.extract_domain(host, tld_length) end - # Returns all the \subdomains as an array, so ["dev", "www"] would be - # returned for "dev.www.rubyonrails.org". You can specify a different tld_length, - # such as 2 to catch ["www"] instead of ["www", "rubyonrails"] - # in "www.rubyonrails.co.uk". + # Returns all the subdomains as an array, so `["dev", "www"]` would be returned + # for "dev.www.rubyonrails.org". You can specify a different `tld_length`, such + # as 2 to catch `["www"]` instead of `["www", "rubyonrails"]` in + # "www.rubyonrails.co.uk". def subdomains(tld_length = @@tld_length) ActionDispatch::Http::URL.extract_subdomains(host, tld_length) end - # Returns all the \subdomains as a string, so "dev.www" would be - # returned for "dev.www.rubyonrails.org". You can specify a different tld_length, - # such as 2 to catch "www" instead of "www.rubyonrails" - # in "www.rubyonrails.co.uk". + # Returns all the subdomains as a string, so `"dev.www"` would be returned for + # "dev.www.rubyonrails.org". You can specify a different `tld_length`, such as 2 + # to catch `"www"` instead of `"www.rubyonrails"` in "www.rubyonrails.co.uk". def subdomain(tld_length = @@tld_length) ActionDispatch::Http::URL.extract_subdomain(host, tld_length) end diff --git a/actionpack/lib/action_dispatch/journey.rb b/actionpack/lib/action_dispatch/journey.rb index 18f966a640..336469f8cc 100644 --- a/actionpack/lib/action_dispatch/journey.rb +++ b/actionpack/lib/action_dispatch/journey.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/journey/router" require "action_dispatch/journey/gtg/builder" require "action_dispatch/journey/gtg/simulator" diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index b27f482f6c..a28a10ca88 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true +# :markup: markdown + require "action_controller/metal/exceptions" module ActionDispatch # :stopdoc: module Journey # The Formatter class is used for formatting URLs. For example, parameters - # passed to +url_for+ in Rails will eventually call Formatter#generate. + # passed to `url_for` in Rails will eventually call Formatter#generate. class Formatter attr_reader :routes @@ -66,16 +68,16 @@ def generate(name, options, path_parameters) match_route(name, constraints) do |route| parameterized_parts = extract_parameterized_parts(route, options, path_parameters) - # Skip this route unless a name has been provided or it is a - # standard Rails route since we can't determine whether an options - # hash passed to url_for matches a Rack application or a redirect. + # Skip this route unless a name has been provided or it is a standard Rails + # route since we can't determine whether an options hash passed to url_for + # matches a Rack application or a redirect. next unless name || route.dispatcher? missing_keys = missing_keys(route, parameterized_parts) next if missing_keys && !missing_keys.empty? params = options.delete_if do |key, _| - # top-level params' normal behavior of generating query_params - # should be preserved even if the same key is also a bind_param + # top-level params' normal behavior of generating query_params should be + # preserved even if the same key is also a bind_param parameterized_parts.key?(key) || route.defaults.key?(key) || (path_params.key?(key) && !original_options.key?(key)) end diff --git a/actionpack/lib/action_dispatch/journey/gtg/builder.rb b/actionpack/lib/action_dispatch/journey/gtg/builder.rb index e83cda55a9..dbcefa71e1 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/builder.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/builder.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/journey/gtg/transition_table" module ActionDispatch @@ -66,9 +68,8 @@ def nullable?(node) when Nodes::Group true when Nodes::Star - # the default star regex is /(.+)/ which is NOT nullable - # but since different constraints can be provided we must - # actually check if this is the case or not. + # the default star regex is /(.+)/ which is NOT nullable but since different + # constraints can be provided we must actually check if this is the case or not. node.regexp.match?("") when Nodes::Or node.children.any? { |c| nullable?(c) } diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb index 67aa399c88..0738ac528f 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "strscan" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index 4e7140afaa..b2071ef9a9 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/journey/nfa/dot" module ActionDispatch @@ -55,8 +57,8 @@ def move(t, full_string, start_index, end_index) t.each { |s, previous_start| if previous_start.nil? - # In the simple case of a "default" param regex do this fast-path - # and add all next states. + # In the simple case of a "default" param regex do this fast-path and add all + # next states. if token_matches_default_component && states = @stdparam_states[s] states.each { |re, v| next_states << [v, nil].freeze if !v.nil? } end @@ -67,10 +69,10 @@ def move(t, full_string, start_index, end_index) end end - # For regexes that aren't the "default" style, they may potentially - # not be terminated by the first "token" [./?], so we need to continue - # to attempt to match this regexp as well as any successful paths that - # continue out of it. both paths could be valid. + # For regexes that aren't the "default" style, they may potentially not be + # terminated by the first "token" [./?], so we need to continue to attempt to + # match this regexp as well as any successful paths that continue out of it. + # both paths could be valid. if states = @regexp_states[s] slice_start = if previous_start.nil? start_index @@ -86,8 +88,8 @@ def move(t, full_string, start_index, end_index) next_states << [v, nil].freeze if !v.nil? && re.match?(curr_slice) } - # and regardless, we must continue accepting tokens and retrying this regexp. - # we need to remember where we started as well so we can take bigger slices. + # and regardless, we must continue accepting tokens and retrying this regexp. we + # need to remember where we started as well so we can take bigger slices. next_states << [s, slice_start].freeze end } diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb index 48b56c8570..130cb1ccb0 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/dot.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Journey # :nodoc: module NFA # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb index d2c3aef52e..283431135f 100644 --- a/actionpack/lib/action_dispatch/journey/nodes/node.rb +++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/journey/visitors" module ActionDispatch @@ -21,9 +23,8 @@ def initialize(tree, formatted) end def requirements=(requirements) - # inject any regexp requirements for `star` nodes so they can be - # determined nullable, which requires knowing if the regex accepts an - # empty string. + # inject any regexp requirements for `star` nodes so they can be determined + # nullable, which requires knowing if the regex accepts an empty string. (symbols + stars).each do |node| re = requirements[node.to_sym] node.regexp = re if re @@ -51,8 +52,8 @@ def visit_tree(formatted) stars << node if formatted != false - # Add a constraint for wildcard route to make it non-greedy and - # match the optional format part of the route by default. + # Add a constraint for wildcard route to make it non-greedy and match the + # optional format part of the route by default. wildcard_options[node.name.to_sym] ||= /.+?/m end end diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb index 6f49680914..02152d2320 100644 --- a/actionpack/lib/action_dispatch/journey/parser.rb +++ b/actionpack/lib/action_dispatch/journey/parser.rb @@ -1,8 +1,9 @@ # # DO NOT MODIFY!!!! -# This file is automatically generated by Racc 1.4.16 -# from Racc grammar file "". -# +# This file is automatically generated by Racc 1.4.16 from +# Racc grammar file "". + +# :markup: markdown require 'racc/parser.rb' diff --git a/actionpack/lib/action_dispatch/journey/parser_extras.rb b/actionpack/lib/action_dispatch/journey/parser_extras.rb index 18ec6c9b9b..09c760fb3a 100644 --- a/actionpack/lib/action_dispatch/journey/parser_extras.rb +++ b/actionpack/lib/action_dispatch/journey/parser_extras.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/journey/scanner" require "action_dispatch/journey/nodes/node" diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb index d156adc108..291a9d21b0 100644 --- a/actionpack/lib/action_dispatch/journey/path/pattern.rb +++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Journey # :nodoc: module Path # :nodoc: @@ -32,7 +34,8 @@ def eager_load! end def requirements_anchored? - # each required param must not be surrounded by a literal, otherwise it isn't simple to chunk-match the url piecemeal + # each required param must not be surrounded by a literal, otherwise it isn't + # simple to chunk-match the url piecemeal terminals = ast.terminals terminals.each_with_index { |s, index| diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 340574cd29..aed783f97f 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch # :stopdoc: module Journey @@ -52,7 +54,7 @@ def self.verb_matcher(verb) ## # +path+ is a path constraint. - # +constraints+ is a hash of constraints to be applied to this route. + # `constraints` is a hash of constraints to be applied to this route. def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false, source_location: nil) @name = name @app = app @@ -82,14 +84,14 @@ def eager_load! nil end - # Needed for `bin/rails routes`. Picks up succinctly defined requirements - # for a route, for example route + # Needed for `bin/rails routes`. Picks up succinctly defined requirements for a + # route, for example route # - # get 'photo/:id', :controller => 'photos', :action => 'show', - # :id => /[A-Z]\d{5}/ + # get 'photo/:id', :controller => 'photos', :action => 'show', + # :id => /[A-Z]\d{5}/ # - # will have {:controller=>"photos", :action=>"show", :id=>/[A-Z]\d{5}/} - # as requirements. + # will have {:controller=>"photos", :action=>"show", :[id=>/](A-Z){5}/} as + # requirements. def requirements @defaults.merge(path.requirements).delete_if { |_, v| /.+?/m == v diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 77bf9157af..a4476dc939 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/journey/router/utils" require "action_dispatch/journey/routes" require "action_dispatch/journey/formatter" @@ -22,8 +24,8 @@ def initialize(routes) end def eager_load! - # Eagerly trigger the simulator's initialization so - # it doesn't happen during a request cycle. + # Eagerly trigger the simulator's initialization so it doesn't happen during a + # request cycle. simulator nil end diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb index 3bcbaf3513..168119dad4 100644 --- a/actionpack/lib/action_dispatch/journey/router/utils.rb +++ b/actionpack/lib/action_dispatch/journey/router/utils.rb @@ -1,19 +1,21 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Journey # :nodoc: class Router # :nodoc: class Utils # :nodoc: # Normalizes URI path. # - # Strips off trailing slash and ensures there is a leading slash. - # Also converts downcase URL encoded string to uppercase. + # Strips off trailing slash and ensures there is a leading slash. Also converts + # downcase URL encoded string to uppercase. # - # normalize_path("/foo") # => "/foo" - # normalize_path("/foo/") # => "/foo" - # normalize_path("foo") # => "/foo" - # normalize_path("") # => "/" - # normalize_path("/%ab") # => "/%AB" + # normalize_path("/foo") # => "/foo" + # normalize_path("/foo/") # => "/foo" + # normalize_path("foo") # => "/foo" + # normalize_path("") # => "/" + # normalize_path("/%ab") # => "/%AB" def self.normalize_path(path) path ||= "" encoding = path.encoding @@ -28,8 +30,7 @@ def self.normalize_path(path) path.force_encoding(encoding) end - # URI path and fragment escaping - # https://tools.ietf.org/html/rfc3986 + # URI path and fragment escaping https://tools.ietf.org/html/rfc3986 class UriEncoder # :nodoc: ENCODE = "%%%02X" US_ASCII = Encoding::US_ASCII @@ -93,8 +94,8 @@ def self.escape_fragment(fragment) # Replaces any escaped sequences with their unescaped representations. # - # uri = "/topics?title=Ruby%20on%20Rails" - # unescape_uri(uri) #=> "/topics?title=Ruby on Rails" + # uri = "/topics?title=Ruby%20on%20Rails" + # unescape_uri(uri) #=> "/topics?title=Ruby on Rails" def self.unescape_uri(uri) ENCODER.unescape_uri(uri) end diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb index 3a9689f156..8f086430bc 100644 --- a/actionpack/lib/action_dispatch/journey/routes.rb +++ b/actionpack/lib/action_dispatch/journey/routes.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Journey # :nodoc: - # The Routing table. Contains all routes for a system. Routes can be - # added to the table by calling Routes#add_route. + # The Routing table. Contains all routes for a system. Routes can be added to + # the table by calling Routes#add_route. class Routes # :nodoc: include Enumerable diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb index eb6fd17aa7..8556562970 100644 --- a/actionpack/lib/action_dispatch/journey/scanner.rb +++ b/actionpack/lib/action_dispatch/journey/scanner.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "strscan" module ActionDispatch @@ -33,8 +35,8 @@ def next_token end private - # takes advantage of String @- deduping capabilities in Ruby 2.5 upwards - # see: https://bugs.ruby-lang.org/issues/13077 + # takes advantage of String @- deduping capabilities in Ruby 2.5 upwards see: + # https://bugs.ruby-lang.org/issues/13077 def dedup_scan(regex) r = @ss.scan(regex) r ? -r : nil diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index 18b81be8ac..a5e33a5665 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch # :stopdoc: module Journey diff --git a/actionpack/lib/action_dispatch/log_subscriber.rb b/actionpack/lib/action_dispatch/log_subscriber.rb index 5fd0e532dc..c9d5d4ba7f 100644 --- a/actionpack/lib/action_dispatch/log_subscriber.rb +++ b/actionpack/lib/action_dispatch/log_subscriber.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch class LogSubscriber < ActiveSupport::LogSubscriber def redirect(event) diff --git a/actionpack/lib/action_dispatch/middleware/actionable_exceptions.rb b/actionpack/lib/action_dispatch/middleware/actionable_exceptions.rb index af5d5313f1..03dabc7337 100644 --- a/actionpack/lib/action_dispatch/middleware/actionable_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/actionable_exceptions.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "uri" require "active_support/actionable_error" diff --git a/actionpack/lib/action_dispatch/middleware/assume_ssl.rb b/actionpack/lib/action_dispatch/middleware/assume_ssl.rb index 0c7cc0f630..3569cc1f91 100644 --- a/actionpack/lib/action_dispatch/middleware/assume_ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/assume_ssl.rb @@ -1,12 +1,15 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch - # = Action Dispatch \AssumeSSL + # # Action Dispatch AssumeSSL # - # When proxying through a load balancer that terminates SSL, the forwarded request will appear - # as though it's HTTP instead of HTTPS to the application. This makes redirects and cookie - # security target HTTP instead of HTTPS. This middleware makes the server assume that the - # proxy already terminated SSL, and that the request really is HTTPS. + # When proxying through a load balancer that terminates SSL, the forwarded + # request will appear as though it's HTTP instead of HTTPS to the application. + # This makes redirects and cookie security target HTTP instead of HTTPS. This + # middleware makes the server assume that the proxy already terminated SSL, and + # that the request really is HTTPS. class AssumeSSL def initialize(app) @app = app diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 4d58f37941..e4d2b08352 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch - # = Action Dispatch \Callbacks + # # Action Dispatch Callbacks # # Provides callbacks to be executed before and after dispatching the request. class Callbacks diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 2595ef45bb..b06c072bb6 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/hash/keys" require "active_support/key_generator" require "active_support/message_verifier" @@ -94,97 +96,100 @@ def use_cookies_with_metadata # Read and write data to cookies through ActionController::Cookies#cookies. # - # When reading cookie data, the data is read from the HTTP request header, Cookie. - # When writing cookie data, the data is sent out in the HTTP response header, +Set-Cookie+. + # When reading cookie data, the data is read from the HTTP request header, + # Cookie. When writing cookie data, the data is sent out in the HTTP response + # header, `Set-Cookie`. # # Examples of writing: # - # # Sets a simple session cookie. - # # This cookie will be deleted when the user's browser is closed. - # cookies[:user_name] = "david" + # # Sets a simple session cookie. + # # This cookie will be deleted when the user's browser is closed. + # cookies[:user_name] = "david" # - # # Cookie values are String-based. Other data types need to be serialized. - # cookies[:lat_lon] = JSON.generate([47.68, -122.37]) + # # Cookie values are String-based. Other data types need to be serialized. + # cookies[:lat_lon] = JSON.generate([47.68, -122.37]) # - # # Sets a cookie that expires in 1 hour. - # cookies[:login] = { value: "XJ-122", expires: 1.hour } + # # Sets a cookie that expires in 1 hour. + # cookies[:login] = { value: "XJ-122", expires: 1.hour } # - # # Sets a cookie that expires at a specific time. - # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) } + # # Sets a cookie that expires at a specific time. + # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) } # - # # Sets a signed cookie, which prevents users from tampering with its value. - # # It can be read using the signed method `cookies.signed[:name]` - # cookies.signed[:user_id] = current_user.id + # # Sets a signed cookie, which prevents users from tampering with its value. + # # It can be read using the signed method `cookies.signed[:name]` + # cookies.signed[:user_id] = current_user.id # - # # Sets an encrypted cookie value before sending it to the client which - # # prevent users from reading and tampering with its value. - # # It can be read using the encrypted method `cookies.encrypted[:name]` - # cookies.encrypted[:discount] = 45 + # # Sets an encrypted cookie value before sending it to the client which + # # prevent users from reading and tampering with its value. + # # It can be read using the encrypted method `cookies.encrypted[:name]` + # cookies.encrypted[:discount] = 45 # - # # Sets a "permanent" cookie (which expires in 20 years from now). - # cookies.permanent[:login] = "XJ-122" + # # Sets a "permanent" cookie (which expires in 20 years from now). + # cookies.permanent[:login] = "XJ-122" # - # # You can also chain these methods: - # cookies.signed.permanent[:login] = "XJ-122" + # # You can also chain these methods: + # cookies.signed.permanent[:login] = "XJ-122" # # Examples of reading: # - # cookies[:user_name] # => "david" - # cookies.size # => 2 - # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37] - # cookies.signed[:login] # => "XJ-122" - # cookies.encrypted[:discount] # => 45 + # cookies[:user_name] # => "david" + # cookies.size # => 2 + # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37] + # cookies.signed[:login] # => "XJ-122" + # cookies.encrypted[:discount] # => 45 # # Example for deleting: # - # cookies.delete :user_name + # cookies.delete :user_name # - # Please note that if you specify a +:domain+ when setting a cookie, you must also specify the domain when deleting the cookie: + # Please note that if you specify a `:domain` when setting a cookie, you must + # also specify the domain when deleting the cookie: # - # cookies[:name] = { - # value: 'a yummy cookie', - # expires: 1.year, - # domain: 'domain.com' - # } + # cookies[:name] = { + # value: 'a yummy cookie', + # expires: 1.year, + # domain: 'domain.com' + # } # - # cookies.delete(:name, domain: 'domain.com') + # cookies.delete(:name, domain: 'domain.com') # # The option symbols for setting cookies are: # - # * :value - The cookie's value. - # * :path - The path for which this cookie applies. Defaults to the root - # of the application. - # * :domain - The domain for which this cookie applies so you can - # restrict to the domain level. If you use a schema like www.example.com - # and want to share session with user.example.com set :domain - # to :all. To support multiple domains, provide an array, and - # the first domain matching request.host will be used. Make - # sure to specify the :domain option with :all or - # Array again when deleting cookies. For more flexibility you - # can set the domain on a per-request basis by specifying :domain - # with a proc. + # * `:value` - The cookie's value. + # * `:path` - The path for which this cookie applies. Defaults to the root of + # the application. + # * `:domain` - The domain for which this cookie applies so you can restrict + # to the domain level. If you use a schema like www.example.com and want to + # share session with user.example.com set `:domain` to `:all`. To support + # multiple domains, provide an array, and the first domain matching + # `request.host` will be used. Make sure to specify the `:domain` option + # with `:all` or `Array` again when deleting cookies. For more flexibility + # you can set the domain on a per-request basis by specifying `:domain` with + # a proc. # - # domain: nil # Does not set cookie domain. (default) - # domain: :all # Allow the cookie for the top most level - # # domain and subdomains. - # domain: %w(.example.com .example.org) # Allow the cookie - # # for concrete domain names. - # domain: proc { Tenant.current.cookie_domain } # Set cookie domain dynamically - # domain: proc { |req| ".sub.#{req.host}" } # Set cookie domain dynamically based on request + # domain: nil # Does not set cookie domain. (default) + # domain: :all # Allow the cookie for the top most level + # # domain and subdomains. + # domain: %w(.example.com .example.org) # Allow the cookie + # # for concrete domain names. + # domain: proc { Tenant.current.cookie_domain } # Set cookie domain dynamically + # domain: proc { |req| ".sub.#{req.host}" } # Set cookie domain dynamically based on request # + # * `:tld_length` - When using `:domain => :all`, this option can be used to + # explicitly set the TLD length when using a short (<= 3 character) domain + # that is being interpreted as part of a TLD. For example, to share cookies + # between user1.lvh.me and user2.lvh.me, set `:tld_length` to 2. + # * `:expires` - The time at which this cookie expires, as a Time or + # ActiveSupport::Duration object. + # * `:secure` - Whether this cookie is only transmitted to HTTPS servers. + # Default is `false`. + # * `:httponly` - Whether this cookie is accessible via scripting or only + # HTTP. Defaults to `false`. + # * `:same_site` - The value of the `SameSite` cookie attribute, which + # determines how this cookie should be restricted in cross-site contexts. + # Possible values are `nil`, `:none`, `:lax`, and `:strict`. Defaults to + # `:lax`. # - # * :tld_length - When using :domain => :all, this option can be used to explicitly - # set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD. - # For example, to share cookies between user1.lvh.me and user2.lvh.me, set :tld_length to 2. - # * :expires - The time at which this cookie expires, as a \Time or ActiveSupport::Duration object. - # * :secure - Whether this cookie is only transmitted to HTTPS servers. - # Default is +false+. - # * :httponly - Whether this cookie is accessible via scripting or - # only HTTP. Defaults to +false+. - # * :same_site - The value of the +SameSite+ cookie attribute, which - # determines how this cookie should be restricted in cross-site contexts. - # Possible values are +nil+, +:none+, +:lax+, and +:strict+. Defaults to - # +:lax+. class Cookies HTTP_HEADER = "Set-Cookie" GENERATOR_KEY = "action_dispatch.key_generator" @@ -208,59 +213,69 @@ class Cookies # Raised when storing more than 4K of session data. CookieOverflow = Class.new StandardError - # Include in a cookie jar to allow chaining, e.g. +cookies.permanent.signed+. + # Include in a cookie jar to allow chaining, e.g. `cookies.permanent.signed`. module ChainedCookieJars - # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example: + # Returns a jar that'll automatically set the assigned cookies to have an + # expiration date 20 years from now. Example: # - # cookies.permanent[:prefers_open_id] = true - # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + # cookies.permanent[:prefers_open_id] = true + # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT # - # This jar is only meant for writing. You'll read permanent cookies through the regular accessor. + # This jar is only meant for writing. You'll read permanent cookies through the + # regular accessor. # - # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples: + # This jar allows chaining with the signed jar as well, so you can set + # permanent, signed cookies. Examples: # - # cookies.permanent.signed[:remember_me] = current_user.id - # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + # cookies.permanent.signed[:remember_me] = current_user.id + # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT def permanent @permanent ||= PermanentCookieJar.new(self) end - # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from - # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed - # cookie was tampered with by the user (or a 3rd party), +nil+ will be returned. + # Returns a jar that'll automatically generate a signed representation of cookie + # value and verify it when reading from the cookie again. This is useful for + # creating cookies with values that the user is not supposed to change. If a + # signed cookie was tampered with by the user (or a 3rd party), `nil` will be + # returned. # - # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+. + # This jar requires that you set a suitable secret for the verification on your + # app's `secret_key_base`. # # Example: # - # cookies.signed[:discount] = 45 - # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/ + # cookies.signed[:discount] = 45 + # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/ # - # cookies.signed[:discount] # => 45 + # cookies.signed[:discount] # => 45 def signed @signed ||= SignedKeyRotatingCookieJar.new(self) end - # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read. - # If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned. + # Returns a jar that'll automatically encrypt cookie values before sending them + # to the client and will decrypt them for read. If the cookie was tampered with + # by the user (or a 3rd party), `nil` will be returned. # - # If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+ - # are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded. + # If `config.action_dispatch.encrypted_cookie_salt` and + # `config.action_dispatch.encrypted_signed_cookie_salt` are both set, legacy + # cookies encrypted with HMAC AES-256-CBC will be transparently upgraded. # - # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+. + # This jar requires that you set a suitable secret for the verification on your + # app's `secret_key_base`. # # Example: # - # cookies.encrypted[:discount] = 45 - # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/ + # cookies.encrypted[:discount] = 45 + # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/ # - # cookies.encrypted[:discount] # => 45 + # cookies.encrypted[:discount] # => 45 def encrypted @encrypted ||= EncryptedKeyRotatingCookieJar.new(self) end - # Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set. - # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores. + # Returns the `signed` or `encrypted` jar, preferring `encrypted` if + # `secret_key_base` is set. Used by ActionDispatch::Session::CookieStore to + # avoid the need to introduce new cookie stores. def signed_or_encrypted @signed_or_encrypted ||= if request.secret_key_base.present? @@ -324,7 +339,7 @@ def each(&block) @cookies.each(&block) end - # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. + # Returns the value of the cookie by `name`, or `nil` if no such cookie exists. def [](name) @cookies[name.to_s] end @@ -357,8 +372,8 @@ def to_header @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; " end - # Sets the cookie named +name+. The second argument may be the cookie's - # value or a hash of options as documented above. + # Sets the cookie named `name`. The second argument may be the cookie's value or + # a hash of options as documented above. def []=(name, options) if options.is_a?(Hash) options.symbolize_keys! @@ -379,11 +394,11 @@ def []=(name, options) value end - # Removes the cookie on the client machine by setting the value to an empty string - # and the expiration date in the past. Like []=, you can pass in - # an options hash to delete cookies with extra data such as a :path. + # Removes the cookie on the client machine by setting the value to an empty + # string and the expiration date in the past. Like `[]=`, you can pass in an + # options hash to delete cookies with extra data such as a `:path`. # - # Returns the value of the cookie, or +nil+ if the cookie does not exist. + # Returns the value of the cookie, or `nil` if the cookie does not exist. def delete(name, options = {}) return unless @cookies.has_key? name.to_s @@ -395,16 +410,16 @@ def delete(name, options = {}) value end - # Whether the given cookie is to be deleted by this CookieJar. - # Like []=, you can pass in an options hash to test if a - # deletion applies to a specific :path, :domain etc. + # Whether the given cookie is to be deleted by this CookieJar. Like `[]=`, you + # can pass in an options hash to test if a deletion applies to a specific + # `:path`, `:domain` etc. def deleted?(name, options = {}) options.symbolize_keys! handle_options(options) @delete_cookies[name.to_s] == options end - # Removes all cookies on the client machine by calling delete for each cookie. + # Removes all cookies on the client machine by calling `delete` for each cookie. def clear(options = {}) @cookies.each_key { |k| delete(k, options) } end @@ -447,8 +462,8 @@ def handle_options(options) cookie_domain = "" dot_splitted_host = request.host.split(".", -1) - # Case where request.host is not an IP address or it's an invalid domain - # (ip confirms to the domain structure we expect so we explicitly check for ip) + # Case where request.host is not an IP address or it's an invalid domain (ip + # confirms to the domain structure we expect so we explicitly check for ip) if request.host.match?(/^[\d.]+$/) || dot_splitted_host.include?("") || dot_splitted_host.length == 1 options[:domain] = nil return diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 3a5beaa02b..9422c830dc 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -1,15 +1,17 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/middleware/exception_wrapper" require "action_dispatch/routing/inspector" require "action_view" module ActionDispatch - # = Action Dispatch \DebugExceptions + # # Action Dispatch DebugExceptions # - # This middleware is responsible for logging exceptions and - # showing a debugging page in case the request is local. + # This middleware is responsible for logging exceptions and showing a debugging + # page in case the request is local. class DebugExceptions cattr_reader :interceptors, instance_accessor: false, default: [] @@ -115,8 +117,8 @@ def create_template(request, wrapper) DebugView.new( request: request, exception_wrapper: wrapper, - # Everything should use the wrapper, but we need to pass - # `exception` for legacy code. + # Everything should use the wrapper, but we need to pass `exception` for legacy + # code. exception: wrapper.exception, traces: wrapper.traces, show_source_idx: wrapper.source_to_show_id, diff --git a/actionpack/lib/action_dispatch/middleware/debug_locks.rb b/actionpack/lib/action_dispatch/middleware/debug_locks.rb index 831e5e248c..314d7bd28c 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_locks.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_locks.rb @@ -1,19 +1,21 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch - # = Action Dispatch \DebugLocks + # # Action Dispatch DebugLocks # # This middleware can be used to diagnose deadlocks in the autoload interlock. # # To use it, insert it near the top of the middleware stack, using - # config/application.rb: + # `config/application.rb`: # # config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks # - # After restarting the application and re-triggering the deadlock condition, - # the route /rails/locks will show a summary of all threads currently - # known to the interlock, which lock level they are holding or awaiting, and - # their current backtrace. + # After restarting the application and re-triggering the deadlock condition, the + # route `/rails/locks` will show a summary of all threads currently known to the + # interlock, which lock level they are holding or awaiting, and their current + # backtrace. # # Generally a deadlock will be caused by the interlock conflicting with some # other external lock or blocking I/O call. These cannot be automatically @@ -46,14 +48,14 @@ def call(env) private def render_details(req) threads = ActiveSupport::Dependencies.interlock.raw_state do |raw_threads| - # The Interlock itself comes to a complete halt as long as this block - # is executing. That gives us a more consistent picture of everything, - # but creates a pretty strong Observer Effect. + # The Interlock itself comes to a complete halt as long as this block is + # executing. That gives us a more consistent picture of everything, but creates + # a pretty strong Observer Effect. # - # Most directly, that means we need to do as little as possible in - # this block. More widely, it means this middleware should remain a - # strictly diagnostic tool (to be used when something has gone wrong), - # and not for any sort of general monitoring. + # Most directly, that means we need to do as little as possible in this block. + # More widely, it means this middleware should remain a strictly diagnostic tool + # (to be used when something has gone wrong), and not for any sort of general + # monitoring. raw_threads.each.with_index do |(thread, info), idx| info[:index] = idx diff --git a/actionpack/lib/action_dispatch/middleware/debug_view.rb b/actionpack/lib/action_dispatch/middleware/debug_view.rb index ed6772343c..9f4e446816 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_view.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_view.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "pp" require "action_view" diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index 025f6c8808..714896b713 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/module/attribute_accessors" require "active_support/syntax_error_proxy" require "active_support/core_ext/thread/backtrace/location" @@ -175,9 +177,8 @@ def self.status_code_for_exception(class_name) end def show?(request) - # We're treating `nil` as "unset", and we want the default setting to be - # `:all`. This logic should be extracted to `env_config` and calculated - # once. + # We're treating `nil` as "unset", and we want the default setting to be `:all`. + # This logic should be extracted to `env_config` and calculated once. config = request.get_header("action_dispatch.show_exceptions") case config @@ -201,7 +202,8 @@ def source_extracts end def error_highlight_available? - # ErrorHighlight.spot with backtrace_location keyword is available since error_highlight 0.4.0 + # ErrorHighlight.spot with backtrace_location keyword is available since + # error_highlight 0.4.0 defined?(ErrorHighlight) && Gem::Version.new(ErrorHighlight::VERSION) >= Gem::Version.new("0.4.0") end diff --git a/actionpack/lib/action_dispatch/middleware/executor.rb b/actionpack/lib/action_dispatch/middleware/executor.rb index 3307ef394d..e0c4b898fe 100644 --- a/actionpack/lib/action_dispatch/middleware/executor.rb +++ b/actionpack/lib/action_dispatch/middleware/executor.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "rack/body_proxy" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index f64a2ae572..6b64295709 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -1,41 +1,48 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/hash/keys" module ActionDispatch - # = Action Dispatch \Flash + # # Action Dispatch Flash # - # The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed - # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create - # action that sets flash[:notice] = "Post successfully created" before redirecting to a display action that can - # then expose the flash to its template. Actually, that exposure is automatically done. + # The flash provides a way to pass temporary primitive-types (String, Array, + # Hash) between actions. Anything you place in the flash will be exposed to the + # very next action and then cleared out. This is a great way of doing notices + # and alerts, such as a create action that sets `flash[:notice] = "Post + # successfully created"` before redirecting to a display action that can then + # expose the flash to its template. Actually, that exposure is automatically + # done. # - # class PostsController < ActionController::Base - # def create - # # save post - # flash[:notice] = "Post successfully created" - # redirect_to @post + # class PostsController < ActionController::Base + # def create + # # save post + # flash[:notice] = "Post successfully created" + # redirect_to @post + # end + # + # def show + # # doesn't need to assign the flash notice to the template, that's done automatically + # end # end # - # def show - # # doesn't need to assign the flash notice to the template, that's done automatically - # end - # end + # Then in `show.html.erb`: # - # Then in +show.html.erb+: + # <% if flash[:notice] %> + #
<%= flash[:notice] %>
+ # <% end %> # - # <% if flash[:notice] %> - #
<%= flash[:notice] %>
- # <% end %> + # Since the `notice` and `alert` keys are a common idiom, convenience accessors + # are available: # - # Since the +notice+ and +alert+ keys are a common idiom, convenience accessors are available: + # flash.alert = "You must be logged in" + # flash.notice = "Post successfully created" # - # flash.alert = "You must be logged in" - # flash.notice = "Post successfully created" - # - # This example places a string in the flash. And of course, you can put as many as you like at a time too. If you want to pass - # non-primitive types, you will have to handle that in your application. Example: To show messages with links, you will have to - # use sanitize helper. + # This example places a string in the flash. And of course, you can put as many + # as you like at a time too. If you want to pass non-primitive types, you will + # have to handle that in your application. Example: To show messages with links, + # you will have to use sanitize helper. # # Just remember: They'll be gone by the time the next action has been performed. # @@ -98,12 +105,12 @@ def [](k) @flash[k.to_s] end - # Convenience accessor for flash.now[:alert]=. + # Convenience accessor for `flash.now[:alert]=`. def alert=(message) self[:alert] = message end - # Convenience accessor for flash.now[:notice]=. + # Convenience accessor for `flash.now[:notice]=`. def notice=(message) self[:notice] = message end @@ -131,8 +138,8 @@ def self.from_session_value(value) # :nodoc: end end - # Builds a hash containing the flashes to keep for the next request. - # If there are none to keep, returns +nil+. + # Builds a hash containing the flashes to keep for the next request. If there + # are none to keep, returns `nil`. def to_session_value # :nodoc: flashes_to_keep = @flashes.except(*@discard) return nil if flashes_to_keep.empty? @@ -177,8 +184,8 @@ def key?(name) @flashes.key? name.to_s end - # Immediately deletes the single flash entry. Use this method when you - # want remove the message within the current action. See also #discard. + # Immediately deletes the single flash entry. Use this method when you want + # remove the message within the current action. See also #discard. def delete(key) key = key.to_s @discard.delete key @@ -211,45 +218,49 @@ def replace(h) # :nodoc: self end - # Sets a flash that will not be available to the next action, only to the current. + # Sets a flash that will not be available to the next action, only to the + # current. # # flash.now[:message] = "Hello current action" # - # This method enables you to use the flash as a central messaging system in your app. - # When you need to pass an object to the next action, you use the standard flash assign ([]=). - # When you need to pass an object to the current action, you use now, and your object will - # vanish when the current action is done. + # This method enables you to use the flash as a central messaging system in your + # app. When you need to pass an object to the next action, you use the standard + # flash assign (`[]=`). When you need to pass an object to the current action, + # you use `now`, and your object will vanish when the current action is done. # - # Entries set via now are accessed the same way as standard entries: flash['my-key']. + # Entries set via `now` are accessed the same way as standard entries: + # `flash['my-key']`. # # Also, brings two convenience accessors: # - # flash.now.alert = "Beware now!" - # # Equivalent to flash.now[:alert] = "Beware now!" + # flash.now.alert = "Beware now!" + # # Equivalent to flash.now[:alert] = "Beware now!" # - # flash.now.notice = "Good luck now!" - # # Equivalent to flash.now[:notice] = "Good luck now!" + # flash.now.notice = "Good luck now!" + # # Equivalent to flash.now[:notice] = "Good luck now!" def now @now ||= FlashNow.new(self) end - # Keeps either the entire current flash or a specific flash entry available for the next action: + # Keeps either the entire current flash or a specific flash entry available for + # the next action: # - # flash.keep # keeps the entire flash - # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded + # flash.keep # keeps the entire flash + # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded def keep(k = nil) k = k.to_s if k @discard.subtract Array(k || keys) k ? self[k] : self end - # Marks the entire flash or a single flash entry to be discarded by the end of the current action: + # Marks the entire flash or a single flash entry to be discarded by the end of + # the current action: # # flash.discard # discard the entire flash at the end of the current action # flash.discard(:warning) # discard only the "warning" entry at the end of the current action # - # Use this method when you want to display the message in the current - # action but not in the next one. See also #delete. + # Use this method when you want to display the message in the current action but + # not in the next one. See also #delete. def discard(k = nil) k = k.to_s if k @discard.merge Array(k || keys) @@ -258,28 +269,29 @@ def discard(k = nil) # Mark for removal entries that were kept, and delete unkept ones. # - # This method is called automatically by filters, so you generally don't need to care about it. + # This method is called automatically by filters, so you generally don't need to + # care about it. def sweep # :nodoc: @discard.each { |k| @flashes.delete k } @discard.replace @flashes.keys end - # Convenience accessor for flash[:alert]. + # Convenience accessor for `flash[:alert]`. def alert self[:alert] end - # Convenience accessor for flash[:alert]=. + # Convenience accessor for `flash[:alert]=`. def alert=(message) self[:alert] = message end - # Convenience accessor for flash[:notice]. + # Convenience accessor for `flash[:notice]`. def notice self[:notice] end - # Convenience accessor for flash[:notice]=. + # Convenience accessor for `flash[:notice]=`. def notice=(message) self[:notice] = message end diff --git a/actionpack/lib/action_dispatch/middleware/host_authorization.rb b/actionpack/lib/action_dispatch/middleware/host_authorization.rb index 8004e2018c..c0a3f79e57 100644 --- a/actionpack/lib/action_dispatch/middleware/host_authorization.rb +++ b/actionpack/lib/action_dispatch/middleware/host_authorization.rb @@ -1,22 +1,24 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch - # = Action Dispatch \HostAuthorization + # # Action Dispatch HostAuthorization # - # This middleware guards from DNS rebinding attacks by explicitly permitting - # the hosts a request can be sent to, and is passed the options set in - # +config.host_authorization+. + # This middleware guards from DNS rebinding attacks by explicitly permitting the + # hosts a request can be sent to, and is passed the options set in + # `config.host_authorization`. # - # Requests can opt-out of Host Authorization with +exclude+: + # Requests can opt-out of Host Authorization with `exclude`: # - # config.host_authorization = { exclude: ->(request) { request.path =~ /healthcheck/ } } + # config.host_authorization = { exclude: ->(request) { request.path =~ /healthcheck/ } } # - # When a request comes to an unauthorized host, the +response_app+ - # application will be executed and rendered. If no +response_app+ is given, a - # default one will run. - # The default response app logs blocked host info with level 'error' and - # responds with 403 Forbidden. The body of the response contains debug info - # if +config.consider_all_requests_local+ is set to true, otherwise the body is empty. + # When a request comes to an unauthorized host, the `response_app` application + # will be executed and rendered. If no `response_app` is given, a default one + # will run. The default response app logs blocked host info with level 'error' + # and responds with `403 Forbidden`. The body of the response contains debug + # info if `config.consider_all_requests_local` is set to true, otherwise the + # body is empty. class HostAuthorization ALLOWED_HOSTS_IN_DEVELOPMENT = [".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")] PORT_REGEX = /(?::\d+)/ # :nodoc: @@ -45,8 +47,8 @@ def allows?(host) begin allowed === extract_hostname(host) rescue - # IPAddr#=== raises an error if you give it a hostname instead of - # IP. Treat similar errors as blocked access. + # IPAddr#=== raises an error if you give it a hostname instead of IP. Treat + # similar errors as blocked access. false end else diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb index 1c7cedb93a..621d82f1e1 100644 --- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb @@ -1,15 +1,17 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch - # = Action Dispatch \PublicExceptions + # # Action Dispatch PublicExceptions # # When called, this middleware renders an error page. By default if an HTML - # response is expected it will render static error pages from the /public + # response is expected it will render static error pages from the `/public` # directory. For example when this middleware receives a 500 response it will - # render the template found in /public/500.html. - # If an internationalized locale is set, this middleware will attempt to render - # the template in /public/500..html. If an internationalized template - # is not found it will fall back on /public/500.html. + # render the template found in `/public/500.html`. If an internationalized + # locale is set, this middleware will attempt to render the template in + # `/public/500..html`. If an internationalized template is not found it + # will fall back on `/public/500.html`. # # When a request with a content type other than HTML is made, this middleware # will attempt to convert error information into the appropriate response type. diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index 0cf5d55f29..f83c4b102a 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -1,14 +1,16 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch - # = Action Dispatch \Reloader + # # Action Dispatch Reloader # # ActionDispatch::Reloader wraps the request with callbacks provided by # ActiveSupport::Reloader, intended to assist with code reloading during # development. # - # ActionDispatch::Reloader is included in the middleware stack only if - # reloading is enabled, which it is by the default in +development+ mode. + # ActionDispatch::Reloader is included in the middleware stack only if reloading + # is enabled, which it is by the default in `development` mode. class Reloader < Executor end end diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index bc06ba62e5..e65fe382e6 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -1,37 +1,41 @@ # frozen_string_literal: true +# :markup: markdown + require "ipaddr" module ActionDispatch - # = Action Dispatch \RemoteIp + # # Action Dispatch RemoteIp # - # This middleware calculates the IP address of the remote client that is - # making the request. It does this by checking various headers that could - # contain the address, and then picking the last-set address that is not - # on the list of trusted IPs. This follows the precedent set by e.g. - # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453]. - # A more detailed explanation of the algorithm is given at GetIp#calculate_ip. + # This middleware calculates the IP address of the remote client that is making + # the request. It does this by checking various headers that could contain the + # address, and then picking the last-set address that is not on the list of + # trusted IPs. This follows the precedent set by e.g. [the Tomcat + # server](https://issues.apache.org/bugzilla/show_bug.cgi?id=50453). A more + # detailed explanation of the algorithm is given at GetIp#calculate_ip. # - # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2] - # requires. Some Rack servers simply drop preceding headers, and only report - # the value that was {given in the last header}[https://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers]. - # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn) - # then you should test your Rack server to make sure your data is good. + # Some Rack servers concatenate repeated headers, like [HTTP RFC + # 2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2) requires. + # Some Rack servers simply drop preceding headers, and only report the value + # that was [given in the last + # header](https://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-server + # s). If you are behind multiple proxy servers (like NGINX to HAProxy to + # Unicorn) then you should test your Rack server to make sure your data is good. # - # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING. - # This middleware assumes that there is at least one proxy sitting around - # and setting headers with the client's remote IP address. If you don't use - # a proxy, because you are hosted on e.g. Heroku without SSL, any client can - # claim to have any IP address by setting the +X-Forwarded-For+ header. If you - # care about that, then you need to explicitly drop or ignore those headers - # sometime before this middleware runs. Alternatively, remove this middleware - # to avoid inadvertently relying on it. + # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING. This + # middleware assumes that there is at least one proxy sitting around and setting + # headers with the client's remote IP address. If you don't use a proxy, because + # you are hosted on e.g. Heroku without SSL, any client can claim to have any IP + # address by setting the `X-Forwarded-For` header. If you care about that, then + # you need to explicitly drop or ignore those headers sometime before this + # middleware runs. Alternatively, remove this middleware to avoid inadvertently + # relying on it. class RemoteIp class IpSpoofAttackError < StandardError; end - # The default trusted IPs list simply includes IP addresses that are - # guaranteed by the IP specification to be private addresses. Those will - # not be the ultimate client IP in production, and so are discarded. See + # The default trusted IPs list simply includes IP addresses that are guaranteed + # by the IP specification to be private addresses. Those will not be the + # ultimate client IP in production, and so are discarded. See # https://en.wikipedia.org/wiki/Private_network for details. TRUSTED_PROXIES = [ "127.0.0.0/8", # localhost IPv4 range, per RFC-3330 @@ -44,20 +48,20 @@ class IpSpoofAttackError < StandardError; end attr_reader :check_ip, :proxies - # Create a new +RemoteIp+ middleware instance. + # Create a new `RemoteIp` middleware instance. # - # The +ip_spoofing_check+ option is on by default. When on, an exception - # is raised if it looks like the client is trying to lie about its own IP - # address. It makes sense to turn off this check on sites aimed at non-IP - # clients (like WAP devices), or behind proxies that set headers in an - # incorrect or confusing way (like AWS ELB). + # The `ip_spoofing_check` option is on by default. When on, an exception is + # raised if it looks like the client is trying to lie about its own IP address. + # It makes sense to turn off this check on sites aimed at non-IP clients (like + # WAP devices), or behind proxies that set headers in an incorrect or confusing + # way (like AWS ELB). # - # The +custom_proxies+ argument can take an enumerable which will be used - # instead of +TRUSTED_PROXIES+. Any proxy setup will put the value you - # want in the middle (or at the beginning) of the +X-Forwarded-For+ list, - # with your proxy servers after it. If your proxies aren't removed, pass - # them in via the +custom_proxies+ parameter. That way, the middleware will - # ignore those IP addresses, and return the one that you want. + # The `custom_proxies` argument can take an enumerable which will be used + # instead of `TRUSTED_PROXIES`. Any proxy setup will put the value you want in + # the middle (or at the beginning) of the `X-Forwarded-For` list, with your + # proxy servers after it. If your proxies aren't removed, pass them in via the + # `custom_proxies` parameter. That way, the middleware will ignore those IP + # addresses, and return the one that you want. def initialize(app, ip_spoofing_check = true, custom_proxies = nil) @app = app @check_ip = ip_spoofing_check @@ -82,19 +86,19 @@ def initialize(app, ip_spoofing_check = true, custom_proxies = nil) end end - # Since the IP address may not be needed, we store the object here - # without calculating the IP to keep from slowing down the majority of - # requests. For those requests that do need to know the IP, the - # GetIp#calculate_ip method will calculate the memoized client IP address. + # Since the IP address may not be needed, we store the object here without + # calculating the IP to keep from slowing down the majority of requests. For + # those requests that do need to know the IP, the GetIp#calculate_ip method will + # calculate the memoized client IP address. def call(env) req = ActionDispatch::Request.new env req.remote_ip = GetIp.new(req, check_ip, proxies) @app.call(req.env) end - # The GetIp class exists as a way to defer processing of the request data - # into an actual IP address. If the ActionDispatch::Request#remote_ip method - # is called, this class will calculate the value and then memoize it. + # The GetIp class exists as a way to defer processing of the request data into + # an actual IP address. If the ActionDispatch::Request#remote_ip method is + # called, this class will calculate the value and then memoize it. class GetIp def initialize(req, check_ip, proxies) @req = req @@ -102,24 +106,25 @@ def initialize(req, check_ip, proxies) @proxies = proxies end - # Sort through the various IP address headers, looking for the IP most - # likely to be the address of the actual remote client making this - # request. + # Sort through the various IP address headers, looking for the IP most likely to + # be the address of the actual remote client making this request. # - # REMOTE_ADDR will be correct if the request is made directly against the - # Ruby process, on e.g. Heroku. When the request is proxied by another - # server like HAProxy or NGINX, the IP address that made the original - # request will be put in an +X-Forwarded-For+ header. If there are multiple - # proxies, that header may contain a list of IPs. Other proxy services - # set the +Client-Ip+ header instead, so we check that too. + # REMOTE_ADDR will be correct if the request is made directly against the Ruby + # process, on e.g. Heroku. When the request is proxied by another server like + # HAProxy or NGINX, the IP address that made the original request will be put in + # an `X-Forwarded-For` header. If there are multiple proxies, that header may + # contain a list of IPs. Other proxy services set the `Client-Ip` header + # instead, so we check that too. # - # As discussed in {this post about Rails IP Spoofing}[https://web.archive.org/web/20170626095448/https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/], - # while the first IP in the list is likely to be the "originating" IP, - # it could also have been set by the client maliciously. + # As discussed in [this post about Rails IP + # Spoofing](https://web.archive.org/web/20170626095448/https://blog.gingerlime.c + # om/2012/rails-ip-spoofing-vulnerabilities-and-protection/), while the first IP + # in the list is likely to be the "originating" IP, it could also have been set + # by the client maliciously. # - # In order to find the first address that is (probably) accurate, we - # take the list of IPs, remove known and trusted proxies, and then take - # the last address left, which was presumably set by one of those proxies. + # In order to find the first address that is (probably) accurate, we take the + # list of IPs, remove known and trusted proxies, and then take the last address + # left, which was presumably set by one of those proxies. def calculate_ip # Set by the Rack web server, this is a single value. remote_addr = ips_from(@req.remote_addr).last @@ -128,19 +133,19 @@ def calculate_ip client_ips = ips_from(@req.client_ip).reverse! forwarded_ips = ips_from(@req.x_forwarded_for).reverse! - # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set. - # If they are both set, it means that either: + # `Client-Ip` and `X-Forwarded-For` should not, generally, both be set. If they + # are both set, it means that either: # # 1) This request passed through two proxies with incompatible IP header - # conventions. - # 2) The client passed one of +Client-Ip+ or +X-Forwarded-For+ - # (whichever the proxy servers weren't using) themselves. + # conventions. # - # Either way, there is no way for us to determine which header is the - # right one after the fact. Since we have no idea, if we are concerned - # about IP spoofing we need to give up and explode. (If you're not - # concerned about IP spoofing you can turn the +ip_spoofing_check+ - # option off.) + # 2) The client passed one of `Client-Ip` or `X-Forwarded-For` + # (whichever the proxy servers weren't using) themselves. + # + # Either way, there is no way for us to determine which header is the right one + # after the fact. Since we have no idea, if we are concerned about IP spoofing + # we need to give up and explode. (If you're not concerned about IP spoofing you + # can turn the `ip_spoofing_check` option off.) should_check_ip = @check_ip && client_ips.last && forwarded_ips.last if should_check_ip && !forwarded_ips.include?(client_ips.last) # We don't know which came from the proxy, and which from the user @@ -151,14 +156,14 @@ def calculate_ip # We assume these things about the IP headers: # - # - X-Forwarded-For will be a list of IPs, one per proxy, or blank - # - Client-Ip is propagated from the outermost proxy, or is blank - # - REMOTE_ADDR will be the IP that made the request to Rack + # - X-Forwarded-For will be a list of IPs, one per proxy, or blank + # - Client-Ip is propagated from the outermost proxy, or is blank + # - REMOTE_ADDR will be the IP that made the request to Rack ips = forwarded_ips + client_ips ips.compact! - # If every single IP option is in the trusted list, return the IP - # that's furthest away + # If every single IP option is in the trusted list, return the IP that's + # furthest away filter_proxies(ips + [remote_addr]).first || ips.last || remote_addr end diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index 42db8a5988..22719790ee 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -1,21 +1,26 @@ # frozen_string_literal: true +# :markup: markdown + require "securerandom" require "active_support/core_ext/string/access" module ActionDispatch - # = Action Dispatch \RequestId + # # Action Dispatch RequestId # - # Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible - # through ActionDispatch::Request#request_id or the alias ActionDispatch::Request#uuid) and sends - # the same id to the client via the +X-Request-Id+ header. + # Makes a unique request id available to the `action_dispatch.request_id` env + # variable (which is then accessible through ActionDispatch::Request#request_id + # or the alias ActionDispatch::Request#uuid) and sends the same id to the client + # via the `X-Request-Id` header. # - # The unique request id is either based on the +X-Request-Id+ header in the request, which would typically be generated - # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the - # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only. + # The unique request id is either based on the `X-Request-Id` header in the + # request, which would typically be generated by a firewall, load balancer, or + # the web server, or, if this header is not available, a random uuid. If the + # header is accepted from the outside world, we sanitize it to a max of 255 + # chars and alphanumeric and dashes only. # - # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files - # from multiple pieces of the stack. + # The unique request id can be used to trace a request end-to-end and would + # typically end up being part of log files from multiple pieces of the stack. class RequestId def initialize(app, header:) @app = app diff --git a/actionpack/lib/action_dispatch/middleware/server_timing.rb b/actionpack/lib/action_dispatch/middleware/server_timing.rb index c80f9df99b..2ab4390875 100644 --- a/actionpack/lib/action_dispatch/middleware/server_timing.rb +++ b/actionpack/lib/action_dispatch/middleware/server_timing.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/notifications" module ActionDispatch @@ -29,8 +31,8 @@ def collect_events def ensure_subscribed @mutex.synchronize do - # Subscribe to all events, except those beginning with "!" - # Ideally we would be more selective of what is being measured + # Subscribe to all events, except those beginning with "!" Ideally we would be + # more selective of what is being measured @subscriber ||= ActiveSupport::Notifications.subscribe(/\A[^!]/, self) end end diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index c76bc49e72..39614fadca 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "rack/utils" require "rack/request" require "rack/session/abstract/id" diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb index d76c3a6b70..ecf94da9a5 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb @@ -1,19 +1,24 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/middleware/session/abstract_store" module ActionDispatch module Session - # = Action Dispatch Session \CacheStore + # # Action Dispatch Session CacheStore # - # A session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful - # if you don't store critical data in your sessions and you don't need them to live for extended periods - # of time. + # A session store that uses an ActiveSupport::Cache::Store to store the + # sessions. This store is most useful if you don't store critical data in your + # sessions and you don't need them to live for extended periods of time. + # + # #### Options + # * `cache` - The cache to use. If it is not specified, `Rails.cache` + # will be used. + # * `expire_after` - The length of time a session will be stored before + # automatically expiring. By default, the `:expires_in` option of the cache + # is used. # - # ==== Options - # * cache - The cache to use. If it is not specified, Rails.cache will be used. - # * expire_after - The length of time a session will be stored before automatically expiring. - # By default, the :expires_in option of the cache is used. class CacheStore < AbstractSecureStore def initialize(app, options = {}) @cache = options[:cache] || Rails.cache diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 3c7aaaea54..c6ee12684b 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -1,53 +1,54 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/hash/keys" require "action_dispatch/middleware/session/abstract_store" require "rack/session/cookie" module ActionDispatch module Session - # = Action Dispatch Session \CookieStore + # # Action Dispatch Session CookieStore # - # This cookie-based session store is the \Rails default. It is - # dramatically faster than the alternatives. + # This cookie-based session store is the Rails default. It is dramatically + # faster than the alternatives. # # Sessions typically contain at most a user ID and flash message; both fit - # within the 4096 bytes cookie size limit. A +CookieOverflow+ exception is raised if - # you attempt to store more than 4096 bytes of data. + # within the 4096 bytes cookie size limit. A `CookieOverflow` exception is + # raised if you attempt to store more than 4096 bytes of data. # - # The cookie jar used for storage is automatically configured to be the - # best possible option given your application's configuration. + # The cookie jar used for storage is automatically configured to be the best + # possible option given your application's configuration. # - # Your cookies will be encrypted using your application's +secret_key_base+. This - # 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. + # Your cookies will be encrypted using your application's `secret_key_base`. + # This 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. # # Configure your session store in an initializer: # - # Rails.application.config.session_store :cookie_store, key: '_your_app_session' + # Rails.application.config.session_store :cookie_store, key: '_your_app_session' # - # In the development and test environments your application's +secret_key_base+ is - # generated by \Rails and stored in a temporary file in tmp/local_secret.txt. - # In all other environments, it is stored encrypted in the - # config/credentials.yml.enc file. + # In the development and test environments your application's `secret_key_base` + # is generated by Rails and stored in a temporary file in + # `tmp/local_secret.txt`. In all other environments, it is stored encrypted in + # the `config/credentials.yml.enc` file. # - # If your application was not updated to \Rails 5.2 defaults, the +secret_key_base+ - # will be found in the old config/secrets.yml file. + # If your application was not updated to Rails 5.2 defaults, the + # `secret_key_base` will be found in the old `config/secrets.yml` file. # - # Note that changing your +secret_key_base+ will invalidate all existing session. - # Additionally, you should take care to make sure you are not relying on the - # ability to decode signed cookies generated by your app in external + # Note that changing your `secret_key_base` will invalidate all existing + # session. Additionally, you should take care to make sure you are not relying + # on the ability to decode signed cookies generated by your app in external # applications or JavaScript before changing it. # - # Because CookieStore extends +Rack::Session::Abstract::Persisted+, many of the - # options described there can be used to customize the session cookie that - # is generated. For example: + # Because CookieStore extends `Rack::Session::Abstract::Persisted`, many of the + # options described there can be used to customize the session cookie that is + # generated. For example: # - # Rails.application.config.session_store :cookie_store, expire_after: 14.days + # Rails.application.config.session_store :cookie_store, expire_after: 14.days # # would set the session cookie to expire automatically 14 days after creation. - # Other useful options include :key, :secure, - # :httponly, and :same_site. + # Other useful options include `:key`, `:secure`, `:httponly`, and `:same_site`. class CookieStore < AbstractSecureStore class SessionId < DelegateClass(Rack::Session::SessionId) attr_reader :cookie_value diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb index cc29e23829..2c86799568 100644 --- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/middleware/session/abstract_store" begin require "rack/session/dalli" @@ -10,12 +12,14 @@ module ActionDispatch module Session - # = Action Dispatch Session \MemCacheStore + # # Action Dispatch Session MemCacheStore # # A session store that uses MemCache to implement storage. # - # ==== Options - # * expire_after - The length of time a session will be stored before automatically expiring. + # #### Options + # * `expire_after` - The length of time a session will be stored before + # automatically expiring. + # class MemCacheStore < Rack::Session::Dalli include Compatibility include StaleSessionCheck diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 07d9a98885..b1a19d793f 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -1,26 +1,27 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/middleware/exception_wrapper" module ActionDispatch - # = Action Dispatch \ShowExceptions + # # Action Dispatch ShowExceptions # - # This middleware rescues any exception returned by the application - # and calls an exceptions app that will wrap it in a format for the end user. + # This middleware rescues any exception returned by the application and calls an + # exceptions app that will wrap it in a format for the end user. # # The exceptions app should be passed as a parameter on initialization of - # +ShowExceptions+. Every time there is an exception, +ShowExceptions+ will - # store the exception in env["action_dispatch.exception"], rewrite - # the +PATH_INFO+ to the exception status code, and call the Rack app. + # `ShowExceptions`. Every time there is an exception, `ShowExceptions` will + # store the exception in `env["action_dispatch.exception"]`, rewrite the + # `PATH_INFO` to the exception status code, and call the Rack app. # - # In \Rails applications, the exceptions app can be configured with - # +config.exceptions_app+, which defaults to ActionDispatch::PublicExceptions. + # In Rails applications, the exceptions app can be configured with + # `config.exceptions_app`, which defaults to ActionDispatch::PublicExceptions. # - # If the application returns a response with the X-Cascade header - # set to "pass", this middleware will send an empty response as a - # result with the correct status code. If any exception happens inside the - # exceptions app, this middleware catches the exceptions and returns a - # failsafe response. + # If the application returns a response with the `X-Cascade` header set to + # `"pass"`, this middleware will send an empty response as a result with the + # correct status code. If any exception happens inside the exceptions app, this + # middleware catches the exceptions and returns a failsafe response. class ShowExceptions def initialize(app, exceptions_app) @app = app @@ -62,9 +63,8 @@ def render_exception(request, wrapper) end def fallback_to_html_format_if_invalid_mime_type(request) - # If the MIME type for the request is invalid then the - # @exceptions_app may not be able to handle it. To make it - # easier to handle, we switch to HTML. + # If the MIME type for the request is invalid then the @exceptions_app may not + # be able to handle it. To make it easier to handle, we switch to HTML. request.formats rescue ActionDispatch::Http::MimeNegotiation::InvalidType request.set_header "HTTP_ACCEPT", "text/html" diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 71c5218410..08dc151375 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -1,57 +1,60 @@ # frozen_string_literal: true -module ActionDispatch - # = Action Dispatch \SSL - # - # This middleware is added to the stack when config.force_ssl = true, and is passed - # the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP - # requests: - # - # 1. TLS redirect: Permanently redirects +http://+ requests to +https://+ - # with the same URL host, path, etc. Enabled by default. Set +config.ssl_options+ - # to modify the destination URL - # (e.g. redirect: { host: "secure.widgets.com", port: 8080 }), or set - # redirect: false to disable this feature. - # - # Requests can opt-out of redirection with +exclude+: - # - # config.ssl_options = { redirect: { exclude: -> request { /healthcheck/.match?(request.path) } } } - # - # Cookies will not be flagged as secure for excluded requests. - # - # 2. Secure cookies: Sets the +secure+ flag on cookies to tell browsers they - # must not be sent along with +http://+ requests. Enabled by default. Set - # +config.ssl_options+ with secure_cookies: false to disable this feature. - # - # 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember - # this site as TLS-only and automatically redirect non-TLS requests. - # Enabled by default. Configure +config.ssl_options+ with hsts: false to disable. - # - # Set +config.ssl_options+ with hsts: { ... } to configure HSTS: - # - # * +expires+: How long, in seconds, these settings will stick. The minimum - # required to qualify for browser preload lists is 1 year. Defaults to - # 2 years (recommended). - # - # * +subdomains+: Set to +true+ to tell the browser to apply these settings - # to all subdomains. This protects your cookies from interception by a - # vulnerable site on a subdomain. Defaults to +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 - # gap, browser vendors include a baked-in list of HSTS-enabled sites. - # Go to https://hstspreload.org to submit your site for inclusion. - # Defaults to +false+. - # - # To turn off HSTS, omitting the header is not enough. Browsers will remember the - # 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 }. - class SSL - # :stopdoc: +# :markup: markdown - # Default to 2 years as recommended on hstspreload.org. +module ActionDispatch + # # Action Dispatch SSL + # + # This middleware is added to the stack when `config.force_ssl = true`, and is + # passed the options set in `config.ssl_options`. It does three jobs to enforce + # secure HTTP requests: + # + # 1. **TLS redirect**: Permanently redirects `http://` requests to `https://` + # with the same URL host, path, etc. Enabled by default. Set + # `config.ssl_options` to modify the destination URL (e.g. `redirect: { + # host: "secure.widgets.com", port: 8080 }`), or set `redirect: false` to + # disable this feature. + # + # Requests can opt-out of redirection with `exclude`: + # + # config.ssl_options = { redirect: { exclude: -> request { /healthcheck/.match?(request.path) } } } + # + # Cookies will not be flagged as secure for excluded requests. + # + # 2. **Secure cookies**: Sets the `secure` flag on cookies to tell browsers + # they must not be sent along with `http://` requests. Enabled by default. + # Set `config.ssl_options` with `secure_cookies: false` to disable this + # feature. + # + # 3. **HTTP Strict Transport Security (HSTS)**: Tells the browser to remember + # this site as TLS-only and automatically redirect non-TLS requests. Enabled + # by default. Configure `config.ssl_options` with `hsts: false` to disable. + # + # Set `config.ssl_options` with `hsts: { ... }` to configure HSTS: + # + # * `expires`: How long, in seconds, these settings will stick. The + # minimum required to qualify for browser preload lists is 1 year. + # Defaults to 2 years (recommended). + # + # * `subdomains`: Set to `true` to tell the browser to apply these + # settings to all subdomains. This protects your cookies from + # interception by a vulnerable site on a subdomain. Defaults to `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 gap, browser vendors include a baked-in list of HSTS-enabled + # sites. Go to https://hstspreload.org to submit your site for + # inclusion. Defaults to `false`. + # + # + # To turn off HSTS, omitting the header is not enough. Browsers will + # remember the 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 }`. + # + class SSL + # :stopdoc: Default to 2 years as recommended on hstspreload.org. HSTS_EXPIRES_IN = 63072000 PERMANENT_REDIRECT_REQUEST_METHODS = %w[GET HEAD] # :nodoc: @@ -93,8 +96,8 @@ def set_hsts_header!(headers) def normalize_hsts_options(options) case options - # Explicitly disabling HSTS clears the existing setting from browsers - # by setting expiry to 0. + # Explicitly disabling HSTS clears the existing setting from browsers by setting + # expiry to 0. when false self.class.default_hsts_options.merge(expires: 0) # Default to enabled, with default options. diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 6a0a2dddb8..fa4a470a3a 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -1,13 +1,15 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/inflector/methods" require "active_support/dependencies" module ActionDispatch - # = Action Dispatch \MiddlewareStack + # # Action Dispatch MiddlewareStack # - # Read more about {Rails middleware - # stack}[https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack] + # Read more about [Rails middleware + # stack](https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack) # in the guides. class MiddlewareStack class Middleware @@ -47,9 +49,8 @@ def build_instrumented(app) end end - # This class is used to instrument the execution of a single middleware. - # It proxies the +call+ method transparently and instruments the method - # call. + # This class is used to instrument the execution of a single middleware. It + # proxies the `call` method transparently and instruments the method call. class InstrumentationProxy EVENT_NAME = "process_middleware.action_dispatch" @@ -125,16 +126,16 @@ def swap(target, *args, &block) # Deletes a middleware from the middleware stack. # - # Returns the array of middlewares not including the deleted item, or - # returns nil if the target is not found. + # Returns the array of middlewares not including the deleted item, or returns + # nil if the target is not found. def delete(target) middlewares.reject! { |m| m.name == target.name } end # Deletes a middleware from the middleware stack. # - # Returns the array of middlewares not including the deleted item, or - # raises +RuntimeError+ if the target is not found. + # Returns the array of middlewares not including the deleted item, or raises + # `RuntimeError` if the target is not found. def delete!(target) delete(target) || (raise "No such middleware to remove: #{target.inspect}") end diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 42bc127af9..d2bf29c1e8 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -1,18 +1,20 @@ # frozen_string_literal: true +# :markup: markdown + require "rack/utils" module ActionDispatch - # = Action Dispatch \Static + # # Action Dispatch Static # - # This middleware serves static files from disk, if available. - # If no file is found, it hands off to the main app. + # This middleware serves static files from disk, if available. If no file is + # found, it hands off to the main app. # - # In \Rails apps, this middleware is configured to serve assets from - # the +public/+ directory. + # In Rails apps, this middleware is configured to serve assets from the + # `public/` directory. # - # Only GET and HEAD requests are served. POST and other HTTP methods - # are handed off to the main app. + # Only GET and HEAD requests are served. POST and other HTTP methods are handed + # off to the main app. # # Only files in the root directory are served; path traversal is denied. class Static @@ -26,24 +28,24 @@ def call(env) end end - # = Action Dispatch \FileHandler + # # Action Dispatch FileHandler # - # This endpoint serves static files from disk using +Rack::Files+. + # This endpoint serves static files from disk using `Rack::Files`. # - # URL paths are matched with static files according to expected - # conventions: +path+, +path+.html, +path+/index.html. + # URL paths are matched with static files according to expected conventions: + # `path`, `path`.html, `path`/index.html. # - # Precompressed versions of these files are checked first. Brotli (.br) - # and gzip (.gz) files are supported. If +path+.br exists, this - # endpoint returns that file with a content-encoding: br header. + # Precompressed versions of these files are checked first. Brotli (.br) and gzip + # (.gz) files are supported. If `path`.br exists, this endpoint returns that + # file with a `content-encoding: br` header. # - # If no matching file is found, this endpoint responds 404 Not Found. + # If no matching file is found, this endpoint responds `404 Not Found`. # - # Pass the +root+ directory to search for matching files, an optional - # index: "index" to change the default +path+/index.html, and optional - # additional response headers. + # Pass the `root` directory to search for matching files, an optional `index: + # "index"` to change the default `path`/index.html, and optional additional + # response headers. class FileHandler - # +Accept-Encoding+ value -> file extension + # `Accept-Encoding` value -> file extension PRECOMPRESSED = { "br" => ".br", "gzip" => ".gz", @@ -91,11 +93,11 @@ def serve(request, filepath, content_headers) # Match a URI path to a static file to be served. # - # Used by the +Static+ class to negotiate a servable file in the - # +public/+ directory (see Static#call). + # Used by the `Static` class to negotiate a servable file in the `public/` + # directory (see Static#call). # - # Checks for +path+, +path+.html, and +path+/index.html files, - # in that order, including .br and .gzip compressed extensions. + # Checks for `path`, `path`.html, and `path`/index.html files, in that order, + # including .br and .gzip compressed extensions. # # If a matching file is found, the path and necessary response headers # (Content-Type, Content-Encoding) are returned. @@ -120,11 +122,11 @@ def try_files(filepath, content_type, accept_encoding:) def try_precompressed_files(filepath, headers, accept_encoding:) each_precompressed_filepath(filepath) do |content_encoding, precompressed_filepath| if file_readable? precompressed_filepath - # Identity encoding is default, so we skip Accept-Encoding - # negotiation and needn't set Content-Encoding. + # Identity encoding is default, so we skip Accept-Encoding negotiation and + # needn't set Content-Encoding. # - # Vary header is expected when we've found other available - # encodings that Accept-Encoding ruled out. + # Vary header is expected when we've found other available encodings that + # Accept-Encoding ruled out. if content_encoding == "identity" return precompressed_filepath, headers else @@ -164,9 +166,9 @@ def each_candidate_filepath(path_info) content_type = ::Rack::Mime.mime_type(ext, nil) yield path, content_type || "text/plain" - # Tack on .html and /index.html only for paths that don't have - # an explicit, resolvable file extension. No need to check - # for foo.js.html and foo.js/index.html. + # Tack on .html and /index.html only for paths that don't have an explicit, + # resolvable file extension. No need to check for foo.js.html and + # foo.js/index.html. unless content_type default_ext = ::ActionController::Base.default_static_extension if ext != default_ext diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index ac9b89181e..0a8c914d5b 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch" require "action_dispatch/log_subscriber" require "active_support/messages/rotation_configuration" diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index fa4502f08c..bd8232c5e0 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "rack/session/abstract/id" module ActionDispatch @@ -107,8 +109,8 @@ def destroy end end - # Returns value of the key stored in the session or - # +nil+ if the given key is not found in the session. + # Returns value of the key stored in the session or `nil` if the given key is + # not found in the session. def [](key) load_for_read! key = key.to_s @@ -120,8 +122,8 @@ def [](key) end end - # Returns the nested value specified by the sequence of keys, returning - # +nil+ if any intermediate step is +nil+. + # Returns the nested value specified by the sequence of keys, returning `nil` if + # any intermediate step is `nil`. def dig(*keys) load_for_read! keys = keys.map.with_index { |key, i| i.zero? ? key.to_s : key } @@ -169,14 +171,14 @@ def to_hash # Updates the session with given Hash. # - # session.to_hash - # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2"} + # session.to_hash + # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2"} # - # session.update({ "foo" => "bar" }) - # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"} + # session.update({ "foo" => "bar" }) + # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"} # - # session.to_hash - # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"} + # session.to_hash + # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"} def update(hash) unless hash.respond_to?(:to_hash) raise TypeError, "no implicit conversion of #{hash.class.name} into Hash" @@ -193,20 +195,20 @@ def delete(key) @delegate.delete key.to_s end - # Returns value of the given key from the session, or raises +KeyError+ - # if can't find the given key and no default value is set. - # Returns default value if specified. + # Returns value of the given key from the session, or raises `KeyError` if can't + # find the given key and no default value is set. Returns default value if + # specified. # - # session.fetch(:foo) - # # => KeyError: key not found: "foo" + # session.fetch(:foo) + # # => KeyError: key not found: "foo" # - # session.fetch(:foo, :bar) - # # => :bar + # session.fetch(:foo, :bar) + # # => :bar # - # session.fetch(:foo) do - # :bar - # end - # # => :bar + # session.fetch(:foo) do + # :bar + # end + # # => :bar def fetch(key, default = Unspecified, &block) load_for_read! if default == Unspecified diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb index 15e80018f0..f9f5f107fb 100644 --- a/actionpack/lib/action_dispatch/request/utils.rb +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/hash/indifferent_access" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index c489fe1520..e1990a2d58 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -1,248 +1,250 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch # The routing module provides URL rewriting in native Ruby. It's a way to # redirect incoming requests to controllers and actions. This replaces - # mod_rewrite rules. Best of all, Rails' \Routing works with any web server. - # Routes are defined in config/routes.rb. + # mod_rewrite rules. Best of all, Rails' Routing works with any web server. + # Routes are defined in `config/routes.rb`. # # Think of creating routes as drawing a map for your requests. The map tells # them where to go based on some predefined pattern: # - # Rails.application.routes.draw do - # Pattern 1 tells some request to go to one place - # Pattern 2 tell them to go to another - # ... - # end + # Rails.application.routes.draw do + # Pattern 1 tells some request to go to one place + # Pattern 2 tell them to go to another + # ... + # end # # The following symbols are special: # - # :controller maps to your controller name - # :action maps to an action with your controllers + # :controller maps to your controller name + # :action maps to an action with your controllers # - # Other names simply map to a parameter as in the case of :id. + # Other names simply map to a parameter as in the case of `:id`. # - # == Resources + # ## Resources # - # Resource routing allows you to quickly declare all of the common routes - # for a given resourceful controller. Instead of declaring separate routes - # for your +index+, +show+, +new+, +edit+, +create+, +update+, and +destroy+ - # actions, a resourceful route declares them in a single line of code: + # Resource routing allows you to quickly declare all of the common routes for a + # given resourceful controller. Instead of declaring separate routes for your + # `index`, `show`, `new`, `edit`, `create`, `update`, and `destroy` actions, a + # resourceful route declares them in a single line of code: # - # resources :photos + # resources :photos # - # Sometimes, you have a resource that clients always look up without - # referencing an ID. A common example, /profile always shows the profile of - # the currently logged in user. In this case, you can use a singular resource - # to map /profile (rather than /profile/:id) to the show action. + # Sometimes, you have a resource that clients always look up without referencing + # an ID. A common example, /profile always shows the profile of the currently + # logged in user. In this case, you can use a singular resource to map /profile + # (rather than /profile/:id) to the show action. # - # resource :profile + # resource :profile # - # It's common to have resources that are logically children of other - # resources: + # It's common to have resources that are logically children of other resources: # - # resources :magazines do - # resources :ads - # end + # resources :magazines do + # resources :ads + # end # # You may wish to organize groups of controllers under a namespace. Most - # commonly, you might group a number of administrative controllers under - # an +admin+ namespace. You would place these controllers under the - # app/controllers/admin directory, and you can group them together - # in your router: + # commonly, you might group a number of administrative controllers under an + # `admin` namespace. You would place these controllers under the + # `app/controllers/admin` directory, and you can group them together in your + # router: # - # namespace "admin" do - # resources :posts, :comments - # end + # namespace "admin" do + # resources :posts, :comments + # end # # Alternatively, you can add prefixes to your path without using a separate - # directory by using +scope+. +scope+ takes additional options which - # apply to all enclosed routes. + # directory by using `scope`. `scope` takes additional options which apply to + # all enclosed routes. # - # scope path: "/cpanel", as: 'admin' do - # resources :posts, :comments - # end + # scope path: "/cpanel", as: 'admin' do + # resources :posts, :comments + # end # # For more, see Routing::Mapper::Resources#resources, # Routing::Mapper::Scoping#namespace, and Routing::Mapper::Scoping#scope. # - # == Non-resourceful routes + # ## Non-resourceful routes # - # For routes that don't fit the resources mold, you can use the HTTP helper - # methods get, post, patch, put and delete. + # For routes that don't fit the `resources` mold, you can use the HTTP helper + # methods `get`, `post`, `patch`, `put` and `delete`. # - # get 'post/:id', to: 'posts#show' - # post 'post/:id', to: 'posts#create_comment' + # get 'post/:id', to: 'posts#show' + # post 'post/:id', to: 'posts#create_comment' # - # Now, if you POST to /posts/:id, it will route to the create_comment action. A GET on the same - # URL will route to the show action. + # Now, if you POST to `/posts/:id`, it will route to the `create_comment` + # action. A GET on the same URL will route to the `show` action. # - # If your route needs to respond to more than one HTTP method (or all methods) then using the - # :via option on match is preferable. + # If your route needs to respond to more than one HTTP method (or all methods) + # then using the `:via` option on `match` is preferable. # - # match 'post/:id', to: 'posts#show', via: [:get, :post] + # match 'post/:id', to: 'posts#show', via: [:get, :post] # - # == Named routes + # ## Named routes # - # Routes can be named by passing an :as option, - # allowing for easy reference within your source as +name_of_route_url+ - # for the full URL and +name_of_route_path+ for the URI path. + # Routes can be named by passing an `:as` option, allowing for easy reference + # within your source as `name_of_route_url` for the full URL and + # `name_of_route_path` for the URI path. # # Example: # - # # In config/routes.rb - # get '/login', to: 'accounts#login', as: 'login' + # # In config/routes.rb + # get '/login', to: 'accounts#login', as: 'login' # - # # With render, redirect_to, tests, etc. - # redirect_to login_url + # # With render, redirect_to, tests, etc. + # redirect_to login_url # # Arguments can be passed as well. # - # redirect_to show_item_path(id: 25) + # redirect_to show_item_path(id: 25) # - # Use root as a shorthand to name a route for the root path "/". + # Use `root` as a shorthand to name a route for the root path "/". # - # # In config/routes.rb - # root to: 'blogs#index' + # # In config/routes.rb + # root to: 'blogs#index' # - # # would recognize http://www.example.com/ as - # params = { controller: 'blogs', action: 'index' } + # # would recognize http://www.example.com/ as + # params = { controller: 'blogs', action: 'index' } # - # # and provide these named routes - # root_url # => 'http://www.example.com/' - # root_path # => '/' + # # and provide these named routes + # root_url # => 'http://www.example.com/' + # root_path # => '/' # - # Note: when using +controller+, the route is simply named after the - # method you call on the block parameter rather than map. + # Note: when using `controller`, the route is simply named after the method you + # call on the block parameter rather than map. # - # # In config/routes.rb - # controller :blog do - # get 'blog/show' => :list - # get 'blog/delete' => :delete - # get 'blog/edit' => :edit - # end + # # In config/routes.rb + # controller :blog do + # get 'blog/show' => :list + # get 'blog/delete' => :delete + # get 'blog/edit' => :edit + # end # - # # provides named routes for show, delete, and edit - # link_to @article.title, blog_show_path(id: @article.id) + # # provides named routes for show, delete, and edit + # link_to @article.title, blog_show_path(id: @article.id) # - # == Pretty URLs + # ## Pretty URLs # # Routes can generate pretty URLs. For example: # - # get '/articles/:year/:month/:day', to: 'articles#find_by_id', constraints: { - # year: /\d{4}/, - # month: /\d{1,2}/, - # day: /\d{1,2}/ - # } + # get '/articles/:year/:month/:day', to: 'articles#find_by_id', constraints: { + # year: /\d{4}/, + # month: /\d{1,2}/, + # day: /\d{1,2}/ + # } # # Using the route above, the URL "http://localhost:3000/articles/2005/11/06" # maps to # - # params = {year: '2005', month: '11', day: '06'} + # params = {year: '2005', month: '11', day: '06'} # - # == Regular Expressions and parameters + # ## Regular Expressions and parameters # You can specify a regular expression to define a format for a parameter. # - # controller 'geocode' do - # get 'geocode/:postalcode', to: :show, constraints: { - # postalcode: /\d{5}(-\d{4})?/ - # } - # end + # controller 'geocode' do + # get 'geocode/:postalcode', to: :show, constraints: { + # postalcode: /\d{5}(-\d{4})?/ + # } + # end # # Constraints can include the 'ignorecase' and 'extended syntax' regular # expression modifiers: # - # controller 'geocode' do - # get 'geocode/:postalcode', to: :show, constraints: { - # postalcode: /hx\d\d\s\d[a-z]{2}/i - # } - # end + # controller 'geocode' do + # get 'geocode/:postalcode', to: :show, constraints: { + # postalcode: /hx\d\d\s\d[a-z]{2}/i + # } + # end # - # controller 'geocode' do - # get 'geocode/:postalcode', to: :show, constraints: { - # postalcode: /# Postalcode format - # \d{5} #Prefix - # (-\d{4})? #Suffix - # /x - # } - # end + # controller 'geocode' do + # get 'geocode/:postalcode', to: :show, constraints: { + # postalcode: /# Postalcode format + # \d{5} #Prefix + # (-\d{4})? #Suffix + # /x + # } + # end # - # Using the multiline modifier will raise an +ArgumentError+. - # Encoding regular expression modifiers are silently ignored. The - # match will always use the default encoding or ASCII. + # Using the multiline modifier will raise an `ArgumentError`. Encoding regular + # expression modifiers are silently ignored. The match will always use the + # default encoding or ASCII. # - # == External redirects + # ## External redirects # - # You can redirect any path to another path using the redirect helper in your router: + # You can redirect any path to another path using the redirect helper in your + # router: # - # get "/stories", to: redirect("/posts") + # get "/stories", to: redirect("/posts") # - # == Unicode character routes + # ## Unicode character routes # # You can specify unicode character routes in your router: # - # get "こんにちは", to: "welcome#index" + # get "こんにちは", to: "welcome#index" # - # == Routing to Rack Applications + # ## Routing to Rack Applications # - # Instead of a String, like posts#index, which corresponds to the - # index action in the PostsController, you can specify any Rack application - # as the endpoint for a matcher: + # Instead of a String, like `posts#index`, which corresponds to the index action + # in the PostsController, you can specify any Rack application as the endpoint + # for a matcher: # - # get "/application.js", to: Sprockets + # get "/application.js", to: Sprockets # - # == Reloading routes + # ## Reloading routes # # You can reload routes if you feel you must: # - # Rails.application.reload_routes! + # Rails.application.reload_routes! # - # 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 reload!. + # 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 `reload!`. # - # == Testing Routes + # ## Testing Routes # # The two main methods for testing your routes: # - # === +assert_routing+ + # ### `assert_routing` # - # def test_movie_route_properly_splits - # opts = {controller: "plugin", action: "checkout", id: "2"} - # assert_routing "plugin/checkout/2", opts - # end + # def test_movie_route_properly_splits + # opts = {controller: "plugin", action: "checkout", id: "2"} + # assert_routing "plugin/checkout/2", opts + # end # - # +assert_routing+ lets you test whether or not the route properly resolves into options. + # `assert_routing` lets you test whether or not the route properly resolves into + # options. # - # === +assert_recognizes+ + # ### `assert_recognizes` # - # def test_route_has_options - # opts = {controller: "plugin", action: "show", id: "12"} - # assert_recognizes opts, "/plugins/show/12" - # end + # def test_route_has_options + # opts = {controller: "plugin", action: "show", id: "12"} + # assert_recognizes opts, "/plugins/show/12" + # end # - # Note the subtle difference between the two: +assert_routing+ tests that - # a URL fits options while +assert_recognizes+ tests that a URL - # breaks into parameters properly. + # Note the subtle difference between the two: `assert_routing` tests that a URL + # fits options while `assert_recognizes` tests that a URL breaks into parameters + # properly. # - # In tests you can simply pass the URL or named route to +get+ or +post+. + # In tests you can simply pass the URL or named route to `get` or `post`. # - # def send_to_jail - # get '/jail' - # assert_response :success - # end + # def send_to_jail + # get '/jail' + # assert_response :success + # end # - # def goes_to_login - # get login_url - # #... - # end + # def goes_to_login + # get login_url + # #... + # end # - # == View a list of all your routes + # ## View a list of all your routes # - # $ bin/rails routes + # $ bin/rails routes # - # Target a specific controller with -c, or grep routes - # using -g. Useful in conjunction with --expanded - # which displays routes vertically. + # Target a specific controller with `-c`, or grep routes using `-g`. Useful in + # conjunction with `--expanded` which displays routes vertically. module Routing extend ActiveSupport::Autoload diff --git a/actionpack/lib/action_dispatch/routing/endpoint.rb b/actionpack/lib/action_dispatch/routing/endpoint.rb index 28bb20d688..0202e66a8a 100644 --- a/actionpack/lib/action_dispatch/routing/endpoint.rb +++ b/actionpack/lib/action_dispatch/routing/endpoint.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Routing class Endpoint # :nodoc: diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 66c26f3193..58c2ed2384 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "delegate" require "io/console/size" @@ -66,8 +68,8 @@ def engine? ## # This class is just used for displaying route information when someone - # executes `bin/rails routes` or looks at the RoutingError page. - # People should not use this class. + # executes `bin/rails routes` or looks at the RoutingError page. People should + # not use this class. class RoutesInspector # :nodoc: def initialize(routes) @engines = {} diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 79034bd329..724bf72e5c 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/hash/slice" require "active_support/core_ext/enumerable" require "active_support/core_ext/array/extract_options" @@ -31,10 +33,10 @@ class Constraints < Routing::Endpoint # :nodoc: CALL = ->(app, req) { app.call req.env } def initialize(app, constraints, strategy) - # Unwrap Constraints objects. I don't actually think it's possible - # to pass a Constraints object to this constructor, but there were - # multiple places that kept testing children of this object. I - # *think* they were just being defensive, but I have no idea. + # Unwrap Constraints objects. I don't actually think it's possible to pass a + # Constraints object to this constructor, but there were multiple places that + # kept testing children of this object. I **think** they were just being + # defensive, but I have no idea. if app.is_a?(self.class) constraints += app.constraints app = app.app @@ -219,7 +221,7 @@ def normalize_options!(options, path_params, modyoule) # Add a default constraint for :controller path segments that matches namespaced # controllers with default routes like :controller/:action/:id(.:format), e.g: # GET /admin/products/show/1 - # => { controller: 'admin/products', action: 'show', id: '1' } + # # > { controller: 'admin/products', action: 'show', id: '1' } options[:controller] ||= /.+?/ end @@ -401,22 +403,20 @@ def route_source_location end end - # Invokes Journey::Router::Utils.normalize_path, then ensures that - # /(:locale) becomes (/:locale). Except for root cases, where the - # former is the correct one. + # Invokes Journey::Router::Utils.normalize_path, then ensures that /(:locale) + # becomes (/:locale). Except for root cases, where the former is the correct + # one. def self.normalize_path(path) path = Journey::Router::Utils.normalize_path(path) # the path for a root URL at this point can be something like # "/(/:locale)(/:platform)/(:browser)", and we would want - # "/(:locale)(/:platform)(/:browser)" - - # reverse "/(", "/((" etc to "(/", "((/" etc + # "/(:locale)(/:platform)(/:browser)" reverse "/(", "/((" etc to "(/", "((/" etc path.gsub!(%r{/(\(+)/?}, '\1/') - # if a path is all optional segments, change the leading "(/" back to - # "/(" so it evaluates to "/" when interpreted with no options. - # Unless, however, at least one secondary segment consists of a static - # part, ex. "(/:locale)(/pages/:page)" + # if a path is all optional segments, change the leading "(/" back to "/(" so it + # evaluates to "/" when interpreted with no options. Unless, however, at least + # one secondary segment consists of a static part, ex. + # "(/:locale)(/pages/:page)" path.sub!(%r{^(\(+)/}, '/\1') if %r{^(\(+[^)]+\))(\(+/:[^)]+\))*$}.match?(path) path end @@ -428,206 +428,202 @@ def self.normalize_name(name) module Base # Matches a URL pattern to one or more routes. # - # You should not use the +match+ method in your router - # without specifying an HTTP method. + # You should not use the `match` method in your router without specifying an + # HTTP method. # # If you want to expose your action to both GET and POST, use: # - # # sets :controller, :action, and :id in params - # match ':controller/:action/:id', via: [:get, :post] + # # sets :controller, :action, and :id in params + # match ':controller/:action/:id', via: [:get, :post] # - # Note that +:controller+, +:action+, and +:id+ are interpreted as URL - # query parameters and thus available through +params+ in an action. + # Note that `:controller`, `:action`, and `:id` are interpreted as URL query + # parameters and thus available through `params` in an action. # - # If you want to expose your action to GET, use +get+ in the router: + # If you want to expose your action to GET, use `get` in the router: # # Instead of: # - # match ":controller/:action/:id" + # match ":controller/:action/:id" # # Do: # - # get ":controller/:action/:id" + # get ":controller/:action/:id" # - # Two of these symbols are special, +:controller+ maps to the controller - # and +:action+ to the controller's action. A pattern can also map - # wildcard segments (globs) to params: + # Two of these symbols are special, `:controller` maps to the controller and + # `:action` to the controller's action. A pattern can also map wildcard segments + # (globs) to params: # - # get 'songs/*category/:title', to: 'songs#show' + # get 'songs/*category/:title', to: 'songs#show' # - # # 'songs/rock/classic/stairway-to-heaven' sets - # # params[:category] = 'rock/classic' - # # params[:title] = 'stairway-to-heaven' + # # 'songs/rock/classic/stairway-to-heaven' sets + # # params[:category] = 'rock/classic' + # # params[:title] = 'stairway-to-heaven' # - # To match a wildcard parameter, it must have a name assigned to it. - # Without a variable name to attach the glob parameter to, the route - # can't be parsed. + # To match a wildcard parameter, it must have a name assigned to it. Without a + # variable name to attach the glob parameter to, the route can't be parsed. # - # When a pattern points to an internal route, the route's +:action+ and - # +:controller+ should be set in options or hash shorthand. Examples: + # When a pattern points to an internal route, the route's `:action` and + # `:controller` should be set in options or hash shorthand. Examples: # - # match 'photos/:id' => 'photos#show', via: :get - # match 'photos/:id', to: 'photos#show', via: :get - # match 'photos/:id', controller: 'photos', action: 'show', via: :get + # match 'photos/:id' => 'photos#show', via: :get + # match 'photos/:id', to: 'photos#show', via: :get + # match 'photos/:id', controller: 'photos', action: 'show', via: :get # - # A pattern can also point to a +Rack+ endpoint i.e. anything that - # responds to +call+: + # A pattern can also point to a `Rack` endpoint i.e. anything that responds to + # `call`: # - # match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get - # match 'photos/:id', to: PhotoRackApp, via: :get - # # Yes, controller actions are just rack endpoints - # match 'photos/:id', to: PhotosController.action(:show), via: :get + # match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get + # match 'photos/:id', to: PhotoRackApp, via: :get + # # Yes, controller actions are just rack endpoints + # match 'photos/:id', to: PhotosController.action(:show), via: :get # # Because requesting various HTTP verbs with a single action has security - # implications, you must either specify the actions in - # the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers] - # instead +match+ + # implications, you must either specify the actions in the via options or use + # one of the [HttpHelpers](rdoc-ref:HttpHelpers) instead `match` # - # === Options + # ### Options # # Any options not seen here are passed on as params with the URL. # - # [:controller] - # The route's controller. + # :controller + # : The route's controller. # - # [:action] - # The route's action. + # :action + # : The route's action. # - # [:param] - # Overrides the default resource identifier +:id+ (name of the - # dynamic segment used to generate the routes). - # You can access that segment from your controller using - # params[<:param>]. - # In your router: + # :param + # : Overrides the default resource identifier `:id` (name of the dynamic + # segment used to generate the routes). You can access that segment from + # your controller using `params[<:param>]`. In your router: # - # resources :users, param: :name + # resources :users, param: :name # - # The +users+ resource here will have the following routes generated for it: + # The `users` resource here will have the following routes generated for it: # - # GET /users(.:format) - # POST /users(.:format) - # GET /users/new(.:format) - # GET /users/:name/edit(.:format) - # GET /users/:name(.:format) - # PATCH/PUT /users/:name(.:format) - # DELETE /users/:name(.:format) + # GET /users(.:format) + # POST /users(.:format) + # GET /users/new(.:format) + # GET /users/:name/edit(.:format) + # GET /users/:name(.:format) + # PATCH/PUT /users/:name(.:format) + # DELETE /users/:name(.:format) # - # You can override ActiveRecord::Base#to_param of a related - # model to construct a URL: + # You can override `ActiveRecord::Base#to_param` of a related model to + # construct a URL: # - # class User < ActiveRecord::Base - # def to_param - # name - # end - # end + # class User < ActiveRecord::Base + # def to_param + # name + # end + # end # - # user = User.find_by(name: 'Phusion') - # user_path(user) # => "/users/Phusion" + # user = User.find_by(name: 'Phusion') + # user_path(user) # => "/users/Phusion" # - # [:path] - # The path prefix for the routes. + # :path + # : The path prefix for the routes. # - # [:module] - # The namespace for :controller. + # :module + # : The namespace for :controller. # - # match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get - # # => Sekret::PostsController + # match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get + # # => Sekret::PostsController # - # See Scoping#namespace for its scope equivalent. + # See `Scoping#namespace` for its scope equivalent. # - # [:as] - # The name used to generate routing helpers. + # :as + # : The name used to generate routing helpers. # - # [:via] - # Allowed HTTP verb(s) for route. + # :via + # : Allowed HTTP verb(s) for route. # - # match 'path', to: 'c#a', via: :get - # match 'path', to: 'c#a', via: [:get, :post] - # match 'path', to: 'c#a', via: :all + # match 'path', to: 'c#a', via: :get + # match 'path', to: 'c#a', via: [:get, :post] + # match 'path', to: 'c#a', via: :all # - # [:to] - # Points to a +Rack+ endpoint. Can be an object that responds to - # +call+ or a string representing a controller's action. + # :to + # : Points to a `Rack` endpoint. Can be an object that responds to `call` or a + # string representing a controller's action. # - # match 'path', to: 'controller#action', via: :get - # match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get - # match 'path', to: RackApp, via: :get + # match 'path', to: 'controller#action', via: :get + # match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get + # match 'path', to: RackApp, via: :get # - # [:on] - # Shorthand for wrapping routes in a specific RESTful context. Valid - # values are +:member+, +:collection+, and +:new+. Only use within - # resource(s) block. For example: + # :on + # : Shorthand for wrapping routes in a specific RESTful context. Valid values + # are `:member`, `:collection`, and `:new`. Only use within `resource(s)` + # block. For example: # - # resource :bar do - # match 'foo', to: 'c#a', on: :member, via: [:get, :post] - # end + # resource :bar do + # match 'foo', to: 'c#a', on: :member, via: [:get, :post] + # end # - # Is equivalent to: + # Is equivalent to: # - # resource :bar do - # member do - # match 'foo', to: 'c#a', via: [:get, :post] - # end - # end + # resource :bar do + # member do + # match 'foo', to: 'c#a', via: [:get, :post] + # end + # end # - # [:constraints] - # Constrains parameters with a hash of regular expressions - # or an object that responds to matches?. In addition, constraints - # other than path can also be specified with any object - # that responds to === (e.g. String, Array, Range, etc.). + # :constraints + # : Constrains parameters with a hash of regular expressions or an object that + # responds to `matches?`. In addition, constraints other than path can also + # be specified with any object that responds to `===` (e.g. String, Array, + # Range, etc.). # - # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get + # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get # - # match 'json_only', constraints: { format: 'json' }, via: :get + # match 'json_only', constraints: { format: 'json' }, via: :get # - # class PermitList - # def matches?(request) request.remote_ip == '1.2.3.4' end - # end - # match 'path', to: 'c#a', constraints: PermitList.new, via: :get + # class PermitList + # def matches?(request) request.remote_ip == '1.2.3.4' end + # end + # match 'path', to: 'c#a', constraints: PermitList.new, via: :get # - # See Scoping#constraints for more examples with its scope - # equivalent. + # See `Scoping#constraints` for more examples with its scope equivalent. # - # [:defaults] - # Sets defaults for parameters + # :defaults + # : Sets defaults for parameters # - # # Sets params[:format] to 'jpg' by default - # match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get + # # Sets params[:format] to 'jpg' by default + # match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get # - # See Scoping#defaults for its scope equivalent. + # See `Scoping#defaults` for its scope equivalent. # - # [:anchor] - # Boolean to anchor a match pattern. Default is true. When set to - # false, the pattern matches any request prefixed with the given path. + # :anchor + # : Boolean to anchor a `match` pattern. Default is true. When set to false, + # the pattern matches any request prefixed with the given path. # - # # Matches any request starting with 'path' - # match 'path', to: 'c#a', anchor: false, via: :get + # # Matches any request starting with 'path' + # match 'path', to: 'c#a', anchor: false, via: :get + # + # :format + # : Allows you to specify the default value for optional `format` segment or + # disable it by supplying `false`. # - # [:format] - # Allows you to specify the default value for optional +format+ - # segment or disable it by supplying +false+. def match(path, options = nil) end # Mount a Rack-based application to be used within the application. # - # mount SomeRackApp, at: "some_route" + # mount SomeRackApp, at: "some_route" # # Alternatively: # - # mount(SomeRackApp => "some_route") + # mount(SomeRackApp => "some_route") # - # For options, see +match+, as +mount+ uses it internally. + # For options, see `match`, as `mount` uses it internally. # - # All mounted applications come with routing helpers to access them. - # These are named after the class specified, so for the above example - # the helper is either +some_rack_app_path+ or +some_rack_app_url+. - # To customize this helper's name, use the +:as+ option: + # All mounted applications come with routing helpers to access them. These are + # named after the class specified, so for the above example the helper is either + # `some_rack_app_path` or `some_rack_app_url`. To customize this helper's name, + # use the `:as` option: # - # mount(SomeRackApp => "some_route", as: "exciting") + # mount(SomeRackApp => "some_route", as: "exciting") # - # This will generate the +exciting_path+ and +exciting_url+ helpers - # which can be used to navigate to this mounted app. + # This will generate the `exciting_path` and `exciting_url` helpers which can be + # used to navigate to this mounted app. def mount(app, options = nil) if options path = options.delete(:at) @@ -701,7 +697,8 @@ def define_generate_prefix(app, name) prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys)) end - # We must actually delete prefix segment keys to avoid passing them to next url_for. + # We must actually delete prefix segment keys to avoid passing them to next + # url_for. _route.segment_keys.each { |k| options.delete(k) } _url_helpers.public_send("#{name}_path", prefix_options) end @@ -723,50 +720,50 @@ def optimize_routes_generation?; false; end end module HttpHelpers - # Define a route that only recognizes HTTP GET. - # For supported arguments, see match[rdoc-ref:Base#match] + # Define a route that only recognizes HTTP GET. For supported arguments, see + # [match](rdoc-ref:Base#match) # - # get 'bacon', to: 'food#bacon' + # get 'bacon', to: 'food#bacon' def get(*args, &block) map_method(:get, args, &block) end - # Define a route that only recognizes HTTP POST. - # For supported arguments, see match[rdoc-ref:Base#match] + # Define a route that only recognizes HTTP POST. For supported arguments, see + # [match](rdoc-ref:Base#match) # - # post 'bacon', to: 'food#bacon' + # post 'bacon', to: 'food#bacon' def post(*args, &block) map_method(:post, args, &block) end - # Define a route that only recognizes HTTP PATCH. - # For supported arguments, see match[rdoc-ref:Base#match] + # Define a route that only recognizes HTTP PATCH. For supported arguments, see + # [match](rdoc-ref:Base#match) # - # patch 'bacon', to: 'food#bacon' + # patch 'bacon', to: 'food#bacon' def patch(*args, &block) map_method(:patch, args, &block) end - # Define a route that only recognizes HTTP PUT. - # For supported arguments, see match[rdoc-ref:Base#match] + # Define a route that only recognizes HTTP PUT. For supported arguments, see + # [match](rdoc-ref:Base#match) # - # put 'bacon', to: 'food#bacon' + # put 'bacon', to: 'food#bacon' def put(*args, &block) map_method(:put, args, &block) end - # Define a route that only recognizes HTTP DELETE. - # For supported arguments, see match[rdoc-ref:Base#match] + # Define a route that only recognizes HTTP DELETE. For supported arguments, see + # [match](rdoc-ref:Base#match) # - # delete 'broccoli', to: 'food#broccoli' + # delete 'broccoli', to: 'food#broccoli' def delete(*args, &block) map_method(:delete, args, &block) end - # Define a route that only recognizes HTTP OPTIONS. - # For supported arguments, see match[rdoc-ref:Base#match] + # Define a route that only recognizes HTTP OPTIONS. For supported arguments, see + # [match](rdoc-ref:Base#match) # - # options 'carrots', to: 'food#carrots' + # options 'carrots', to: 'food#carrots' def options(*args, &block) map_method(:options, args, &block) end @@ -780,91 +777,90 @@ def map_method(method, args, &block) end end - # You may wish to organize groups of controllers under a namespace. - # Most commonly, you might group a number of administrative controllers - # under an +admin+ namespace. You would place these controllers under - # the app/controllers/admin directory, and you can group them - # together in your router: + # You may wish to organize groups of controllers under a namespace. Most + # commonly, you might group a number of administrative controllers under an + # `admin` namespace. You would place these controllers under the + # `app/controllers/admin` directory, and you can group them together in your + # router: # - # namespace "admin" do - # resources :posts, :comments - # end + # namespace "admin" do + # resources :posts, :comments + # end # # This will create a number of routes for each of the posts and comments - # controller. For +Admin::PostsController+, \Rails will create: + # controller. For `Admin::PostsController`, Rails will create: # - # GET /admin/posts - # GET /admin/posts/new - # POST /admin/posts - # GET /admin/posts/1 - # GET /admin/posts/1/edit - # PATCH/PUT /admin/posts/1 - # DELETE /admin/posts/1 + # GET /admin/posts + # GET /admin/posts/new + # POST /admin/posts + # GET /admin/posts/1 + # GET /admin/posts/1/edit + # PATCH/PUT /admin/posts/1 + # DELETE /admin/posts/1 # # If you want to route /posts (without the prefix /admin) to - # +Admin::PostsController+, you could use + # `Admin::PostsController`, you could use # - # scope module: "admin" do - # resources :posts - # end + # scope module: "admin" do + # resources :posts + # end # # or, for a single case # - # resources :posts, module: "admin" + # resources :posts, module: "admin" # - # If you want to route /admin/posts to +PostsController+ - # (without the Admin:: module prefix), you could use + # If you want to route /admin/posts to `PostsController` (without the `Admin::` + # module prefix), you could use # - # scope "/admin" do - # resources :posts - # end + # scope "/admin" do + # resources :posts + # end # # or, for a single case # - # resources :posts, path: "/admin/posts" + # resources :posts, path: "/admin/posts" # - # In each of these cases, the named routes remain the same as if you did - # not use scope. In the last case, the following paths map to - # +PostsController+: + # In each of these cases, the named routes remain the same as if you did not use + # scope. In the last case, the following paths map to `PostsController`: # - # GET /admin/posts - # GET /admin/posts/new - # POST /admin/posts - # GET /admin/posts/1 - # GET /admin/posts/1/edit - # PATCH/PUT /admin/posts/1 - # DELETE /admin/posts/1 + # GET /admin/posts + # GET /admin/posts/new + # POST /admin/posts + # GET /admin/posts/1 + # GET /admin/posts/1/edit + # PATCH/PUT /admin/posts/1 + # DELETE /admin/posts/1 module Scoping # Scopes a set of routes to the given default options. # # Take the following route definition as an example: # - # scope path: ":account_id", as: "account" do - # resources :projects - # end + # scope path: ":account_id", as: "account" do + # resources :projects + # end # - # This generates helpers such as +account_projects_path+, just like +resources+ does. - # The difference here being that the routes generated are like /:account_id/projects, - # rather than /accounts/:account_id/projects. + # This generates helpers such as `account_projects_path`, just like `resources` + # does. The difference here being that the routes generated are like + # /:account_id/projects, rather than /accounts/:account_id/projects. # - # === Options + # ### Options # - # Takes same options as Base#match and Resources#resources. + # Takes same options as `Base#match` and `Resources#resources`. # - # # route /posts (without the prefix /admin) to +Admin::PostsController+ - # scope module: "admin" do - # resources :posts - # end + # # route /posts (without the prefix /admin) to +Admin::PostsController+ + # scope module: "admin" do + # resources :posts + # end # - # # prefix the posts resource's requests with '/admin' - # scope path: "/admin" do - # resources :posts - # end + # # prefix the posts resource's requests with '/admin' + # scope path: "/admin" do + # resources :posts + # end # - # # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+ - # scope as: "sekret" do - # resources :posts - # end + # # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+ + # scope as: "sekret" do + # resources :posts + # end def scope(*args) options = args.extract_options!.dup scope = {} @@ -921,9 +917,9 @@ def scope(*args) # Scopes routes to a specific controller # - # controller "food" do - # match "bacon", action: :bacon, via: :get - # end + # controller "food" do + # match "bacon", action: :bacon, via: :get + # end def controller(controller) @scope = @scope.new(controller: controller) yield @@ -933,42 +929,42 @@ def controller(controller) # Scopes routes to a specific namespace. For example: # - # namespace :admin do - # resources :posts - # end + # namespace :admin do + # resources :posts + # end # # This generates the following routes: # - # admin_posts GET /admin/posts(.:format) admin/posts#index - # admin_posts 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 - # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update - # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy + # admin_posts GET /admin/posts(.:format) admin/posts#index + # admin_posts 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 + # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update + # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy # - # === Options + # ### Options # - # The +:path+, +:as+, +:module+, +:shallow_path+, and +:shallow_prefix+ - # options all default to the name of the namespace. + # The `:path`, `:as`, `:module`, `:shallow_path`, and `:shallow_prefix` options + # all default to the name of the namespace. # - # For options, see Base#match. For +:shallow_path+ option, see - # Resources#resources. + # For options, see `Base#match`. For `:shallow_path` option, see + # `Resources#resources`. # - # # accessible through /sekret/posts rather than /admin/posts - # namespace :admin, path: "sekret" do - # resources :posts - # end + # # accessible through /sekret/posts rather than /admin/posts + # namespace :admin, path: "sekret" do + # resources :posts + # end # - # # maps to +Sekret::PostsController+ rather than +Admin::PostsController+ - # namespace :admin, module: "sekret" do - # resources :posts - # end + # # maps to +Sekret::PostsController+ rather than +Admin::PostsController+ + # namespace :admin, module: "sekret" do + # resources :posts + # end # - # # generates +sekret_posts_path+ rather than +admin_posts_path+ - # namespace :admin, as: "sekret" do - # resources :posts - # end + # # generates +sekret_posts_path+ rather than +admin_posts_path+ + # namespace :admin, as: "sekret" do + # resources :posts + # end def namespace(path, options = {}, &block) path = path.to_s @@ -984,70 +980,74 @@ def namespace(path, options = {}, &block) end end - # === Parameter Restriction - # Allows you to constrain the nested routes based on a set of rules. - # For instance, in order to change the routes to allow for a dot character in the +id+ parameter: + # ### Parameter Restriction + # Allows you to constrain the nested routes based on a set of rules. For + # instance, in order to change the routes to allow for a dot character in the + # `id` parameter: # - # constraints(id: /\d+\.\d+/) do - # resources :posts - # end + # constraints(id: /\d+\.\d+/) do + # resources :posts + # end # - # Now routes such as +/posts/1+ will no longer be valid, but +/posts/1.1+ will be. - # The +id+ parameter must match the constraint passed in for this example. + # Now routes such as `/posts/1` will no longer be valid, but `/posts/1.1` will + # be. The `id` parameter must match the constraint passed in for this example. # # You may use this to also restrict other parameters: # - # resources :posts do - # constraints(post_id: /\d+\.\d+/) do - # resources :comments + # resources :posts do + # constraints(post_id: /\d+\.\d+/) do + # resources :comments + # end # end - # end # - # === Restricting based on IP + # ### Restricting based on IP # # Routes can also be constrained to an IP or a certain range of IP addresses: # - # constraints(ip: /192\.168\.\d+\.\d+/) do - # resources :posts - # end + # constraints(ip: /192\.168\.\d+\.\d+/) do + # resources :posts + # end # - # Any user connecting from the 192.168.* range will be able to see this resource, - # where as any user connecting outside of this range will be told there is no such route. + # Any user connecting from the 192.168.* range will be able to see this + # resource, where as any user connecting outside of this range will be told + # there is no such route. # - # === Dynamic request matching + # ### Dynamic request matching # # Requests to routes can be constrained based on specific criteria: # - # constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do - # resources :iphones - # end + # constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do + # resources :iphones + # end # - # You are able to move this logic out into a class if it is too complex for routes. - # This class must have a +matches?+ method defined on it which either returns +true+ - # if the user should be given access to that route, or +false+ if the user should not. + # You are able to move this logic out into a class if it is too complex for + # routes. This class must have a `matches?` method defined on it which either + # returns `true` if the user should be given access to that route, or `false` if + # the user should not. # - # class Iphone - # def self.matches?(request) - # /iPhone/.match?(request.env["HTTP_USER_AGENT"]) - # end - # end + # class Iphone + # def self.matches?(request) + # /iPhone/.match?(request.env["HTTP_USER_AGENT"]) + # end + # end # - # An expected place for this code would be +lib/constraints+. + # An expected place for this code would be `lib/constraints`. # # This class is then used like this: # - # constraints(Iphone) do - # resources :iphones - # end + # constraints(Iphone) do + # resources :iphones + # end def constraints(constraints = {}, &block) scope(constraints: constraints, &block) end # Allows you to set default parameters for a route, such as this: - # defaults id: 'home' do - # match 'scoped_pages/(:id)', to: 'pages#show' - # end - # Using this, the +:id+ parameter here will default to 'home'. + # defaults id: 'home' do + # match 'scoped_pages/(:id)', to: 'pages#show' + # end + # + # Using this, the `:id` parameter here will default to 'home'. def defaults(defaults = {}) @scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults)) yield @@ -1123,48 +1123,46 @@ def merge_to_scope(parent, child) end end - # Resource routing allows you to quickly declare all of the common routes - # for a given resourceful controller. Instead of declaring separate routes - # for your +index+, +show+, +new+, +edit+, +create+, +update+, and +destroy+ - # actions, a resourceful route declares them in a single line of code: + # Resource routing allows you to quickly declare all of the common routes for a + # given resourceful controller. Instead of declaring separate routes for your + # `index`, `show`, `new`, `edit`, `create`, `update`, and `destroy` actions, a + # resourceful route declares them in a single line of code: # - # resources :photos + # resources :photos # - # Sometimes, you have a resource that clients always look up without - # referencing an ID. A common example, /profile always shows the profile of - # the currently logged in user. In this case, you can use a singular resource - # to map /profile (rather than /profile/:id) to the show action. + # Sometimes, you have a resource that clients always look up without referencing + # an ID. A common example, /profile always shows the profile of the currently + # logged in user. In this case, you can use a singular resource to map /profile + # (rather than /profile/:id) to the show action. # - # resource :profile + # resource :profile # - # It's common to have resources that are logically children of other - # resources: + # It's common to have resources that are logically children of other resources: # - # resources :magazines do - # resources :ads - # end + # resources :magazines do + # resources :ads + # end # # You may wish to organize groups of controllers under a namespace. Most - # commonly, you might group a number of administrative controllers under - # an +admin+ namespace. You would place these controllers under the - # app/controllers/admin directory, and you can group them together - # in your router: + # commonly, you might group a number of administrative controllers under an + # `admin` namespace. You would place these controllers under the + # `app/controllers/admin` directory, and you can group them together in your + # router: # - # namespace "admin" do - # resources :posts, :comments - # end + # namespace "admin" do + # resources :posts, :comments + # end # - # By default the +:id+ parameter doesn't accept dots. If you need to - # use dots as part of the +:id+ parameter add a constraint which - # overrides this restriction, e.g: + # By default the `:id` parameter doesn't accept dots. If you need to use dots as + # part of the `:id` parameter add a constraint which overrides this restriction, + # e.g: # - # resources :articles, id: /[^\/]+/ - # - # This allows any character other than a slash as part of your +:id+. + # resources :articles, id: /[^\/]+/ # + # This allows any character other than a slash as part of your `:id`. module Resources - # CANONICAL_ACTIONS holds all actions that does not need a prefix or - # a path appended since they fit properly in their scope level. + # CANONICAL_ACTIONS holds all actions that does not need a prefix or a path + # appended since they fit properly in their scope level. VALID_ON_OPTIONS = [:new, :collection, :member] RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns] CANONICAL_ACTIONS = %w(index create new show update destroy) @@ -1227,8 +1225,8 @@ def singular alias :member_name :singular - # Checks for uncountable plurals, and appends "_index" if the plural - # and singular form are the same. + # Checks for uncountable plurals, and appends "_index" if the plural and + # singular form are the same. def collection_name singular == plural ? "#{plural}_index" : plural end @@ -1301,37 +1299,35 @@ def resources_path_names(options) @scope[:path_names].merge!(options) end - # Sometimes, you have a resource that clients always look up without - # referencing an ID. A common example, /profile always shows the - # profile of the currently logged in user. In this case, you can use - # a singular resource to map /profile (rather than /profile/:id) to - # the show action: + # Sometimes, you have a resource that clients always look up without referencing + # an ID. A common example, /profile always shows the profile of the currently + # logged in user. In this case, you can use a singular resource to map /profile + # (rather than /profile/:id) to the show action: # - # resource :profile + # resource :profile # - # This creates six different routes in your application, all mapping to - # the +Profiles+ controller (note that the controller is named after - # the plural): + # This creates six different routes in your application, all mapping to the + # `Profiles` controller (note that the controller is named after the plural): # - # GET /profile/new - # GET /profile - # GET /profile/edit - # PATCH/PUT /profile - # DELETE /profile - # POST /profile + # GET /profile/new + # GET /profile + # GET /profile/edit + # PATCH/PUT /profile + # DELETE /profile + # POST /profile # - # If you want instances of a model to work with this resource via - # record identification (e.g. in +form_with+ or +redirect_to+), you - # will need to call resolve[rdoc-ref:CustomUrls#resolve]: + # If you want instances of a model to work with this resource via record + # identification (e.g. in `form_with` or `redirect_to`), you will need to call + # [resolve](rdoc-ref:CustomUrls#resolve): # - # resource :profile - # resolve('Profile') { [:profile] } + # resource :profile + # resolve('Profile') { [:profile] } # - # # Enables this to work with singular routes: - # form_with(model: @profile) {} + # # Enables this to work with singular routes: + # form_with(model: @profile) {} # - # === Options - # Takes same options as resources[rdoc-ref:#resources] + # ### Options + # Takes same options as [resources](rdoc-ref:#resources) def resource(*resources, &block) options = resources.extract_options!.dup @@ -1361,143 +1357,147 @@ def resource(*resources, &block) self end - # In \Rails, a resourceful route provides a mapping between HTTP verbs - # and URLs and controller actions. By convention, each action also maps - # to particular CRUD operations in a database. A single entry in the - # routing file, such as + # In Rails, a resourceful route provides a mapping between HTTP verbs and URLs + # and controller actions. By convention, each action also maps to particular + # CRUD operations in a database. A single entry in the routing file, such as # - # resources :photos + # resources :photos # - # creates seven different routes in your application, all mapping to - # the +Photos+ controller: + # creates seven different routes in your application, all mapping to the + # `Photos` controller: # - # GET /photos - # GET /photos/new - # POST /photos - # GET /photos/:id - # GET /photos/:id/edit - # PATCH/PUT /photos/:id - # DELETE /photos/:id + # GET /photos + # GET /photos/new + # POST /photos + # GET /photos/:id + # GET /photos/:id/edit + # PATCH/PUT /photos/:id + # DELETE /photos/:id # # Resources can also be nested infinitely by using this block syntax: # - # resources :photos do - # resources :comments - # end - # - # This generates the following comments routes: - # - # GET /photos/:photo_id/comments - # GET /photos/:photo_id/comments/new - # POST /photos/:photo_id/comments - # GET /photos/:photo_id/comments/:id - # GET /photos/:photo_id/comments/:id/edit - # PATCH/PUT /photos/:photo_id/comments/:id - # DELETE /photos/:photo_id/comments/:id - # - # === Options - # Takes same options as match[rdoc-ref:Base#match] as well as: - # - # [:path_names] - # Allows you to change the segment component of the +edit+ and +new+ actions. - # Actions not specified are not changed. - # - # resources :posts, path_names: { new: "brand_new" } - # - # The above example will now change /posts/new to /posts/brand_new. - # - # [:path] - # Allows you to change the path prefix for the resource. - # - # resources :posts, path: 'postings' - # - # The resource and all segments will now route to /postings instead of /posts. - # - # [:only] - # Only generate routes for the given actions. - # - # resources :cows, only: :show - # resources :cows, only: [:show, :index] - # - # [:except] - # Generate all routes except for the given actions. - # - # resources :cows, except: :show - # resources :cows, except: [:show, :index] - # - # [:shallow] - # Generates shallow routes for nested resource(s). When placed on a parent resource, - # generates shallow routes for all nested resources. - # - # resources :posts, shallow: true do + # resources :photos do # resources :comments # end # - # Is the same as: + # This generates the following comments routes: # - # resources :posts do - # resources :comments, except: [:show, :edit, :update, :destroy] - # end - # resources :comments, only: [:show, :edit, :update, :destroy] + # GET /photos/:photo_id/comments + # GET /photos/:photo_id/comments/new + # POST /photos/:photo_id/comments + # GET /photos/:photo_id/comments/:id + # GET /photos/:photo_id/comments/:id/edit + # PATCH/PUT /photos/:photo_id/comments/:id + # DELETE /photos/:photo_id/comments/:id # - # This allows URLs for resources that otherwise would be deeply nested such - # as a comment on a blog post like /posts/a-long-permalink/comments/1234 - # to be shortened to just /comments/1234. + # ### Options + # Takes same options as [match](rdoc-ref:Base#match) as well as: # - # Set shallow: false on a child resource to ignore a parent's shallow parameter. + # :path_names + # : Allows you to change the segment component of the `edit` and `new` + # actions. Actions not specified are not changed. # - # [:shallow_path] - # Prefixes nested shallow routes with the specified path. + # resources :posts, path_names: { new: "brand_new" } # - # scope shallow_path: "sekret" do - # resources :posts do - # resources :comments, shallow: true - # end - # end + # The above example will now change /posts/new to /posts/brand_new. # - # The +comments+ resource here will have the following routes generated for it: + # :path + # : Allows you to change the path prefix for the resource. # - # post_comments GET /posts/:post_id/comments(.:format) - # post_comments POST /posts/:post_id/comments(.:format) - # new_post_comment GET /posts/:post_id/comments/new(.:format) - # edit_comment GET /sekret/comments/:id/edit(.:format) - # comment GET /sekret/comments/:id(.:format) - # comment PATCH/PUT /sekret/comments/:id(.:format) - # comment DELETE /sekret/comments/:id(.:format) + # resources :posts, path: 'postings' # - # [:shallow_prefix] - # Prefixes nested shallow route names with specified prefix. + # The resource and all segments will now route to /postings instead of + # /posts. # - # scope shallow_prefix: "sekret" do - # resources :posts do - # resources :comments, shallow: true - # end - # end + # :only + # : Only generate routes for the given actions. # - # The +comments+ resource here will have the following routes generated for it: + # resources :cows, only: :show + # resources :cows, only: [:show, :index] # - # post_comments GET /posts/:post_id/comments(.:format) - # post_comments POST /posts/:post_id/comments(.:format) - # new_post_comment GET /posts/:post_id/comments/new(.:format) - # edit_sekret_comment GET /comments/:id/edit(.:format) - # sekret_comment GET /comments/:id(.:format) - # sekret_comment PATCH/PUT /comments/:id(.:format) - # sekret_comment DELETE /comments/:id(.:format) + # :except + # : Generate all routes except for the given actions. # - # [:format] - # Allows you to specify the default value for optional +format+ - # segment or disable it by supplying +false+. + # resources :cows, except: :show + # resources :cows, except: [:show, :index] # - # [:param] - # Allows you to override the default param name of +:id+ in the URL. + # :shallow + # : Generates shallow routes for nested resource(s). When placed on a parent + # resource, generates shallow routes for all nested resources. # - # === Examples + # resources :posts, shallow: true do + # resources :comments + # end # - # # routes call +Admin::PostsController+ - # resources :posts, module: "admin" + # Is the same as: # - # # resource actions are at /admin/posts. - # resources :posts, path: "admin/posts" + # resources :posts do + # resources :comments, except: [:show, :edit, :update, :destroy] + # end + # resources :comments, only: [:show, :edit, :update, :destroy] + # + # This allows URLs for resources that otherwise would be deeply nested such + # as a comment on a blog post like `/posts/a-long-permalink/comments/1234` + # to be shortened to just `/comments/1234`. + # + # Set `shallow: false` on a child resource to ignore a parent's shallow + # parameter. + # + # :shallow_path + # : Prefixes nested shallow routes with the specified path. + # + # scope shallow_path: "sekret" do + # resources :posts do + # resources :comments, shallow: true + # end + # end + # + # The `comments` resource here will have the following routes generated for + # it: + # + # post_comments GET /posts/:post_id/comments(.:format) + # post_comments POST /posts/:post_id/comments(.:format) + # new_post_comment GET /posts/:post_id/comments/new(.:format) + # edit_comment GET /sekret/comments/:id/edit(.:format) + # comment GET /sekret/comments/:id(.:format) + # comment PATCH/PUT /sekret/comments/:id(.:format) + # comment DELETE /sekret/comments/:id(.:format) + # + # :shallow_prefix + # : Prefixes nested shallow route names with specified prefix. + # + # scope shallow_prefix: "sekret" do + # resources :posts do + # resources :comments, shallow: true + # end + # end + # + # The `comments` resource here will have the following routes generated for + # it: + # + # post_comments GET /posts/:post_id/comments(.:format) + # post_comments POST /posts/:post_id/comments(.:format) + # new_post_comment GET /posts/:post_id/comments/new(.:format) + # edit_sekret_comment GET /comments/:id/edit(.:format) + # sekret_comment GET /comments/:id(.:format) + # sekret_comment PATCH/PUT /comments/:id(.:format) + # sekret_comment DELETE /comments/:id(.:format) + # + # :format + # : Allows you to specify the default value for optional `format` segment or + # disable it by supplying `false`. + # + # :param + # : Allows you to override the default param name of `:id` in the URL. + # + # + # ### Examples + # + # # routes call +Admin::PostsController+ + # resources :posts, module: "admin" + # + # # resource actions are at /admin/posts. + # resources :posts, path: "admin/posts" def resources(*resources, &block) options = resources.extract_options!.dup @@ -1530,16 +1530,15 @@ def resources(*resources, &block) # To add a route to the collection: # - # resources :photos do - # collection do - # get 'search' + # resources :photos do + # collection do + # get 'search' + # end # end - # end # - # This will enable \Rails to recognize paths such as /photos/search - # with GET, and route to the search action of +PhotosController+. It will also - # create the search_photos_url and search_photos_path - # route helpers. + # This will enable Rails to recognize paths such as `/photos/search` with GET, + # and route to the search action of `PhotosController`. It will also create the + # `search_photos_url` and `search_photos_path` route helpers. def collection(&block) unless resource_scope? raise ArgumentError, "can't use collection outside resource(s) scope" @@ -1552,15 +1551,15 @@ def collection(&block) # To add a member route, add a member block into the resource block: # - # resources :photos do - # member do - # get 'preview' + # resources :photos do + # member do + # get 'preview' + # end # end - # end # - # This will recognize /photos/1/preview with GET, and route to the - # preview action of +PhotosController+. It will also create the - # preview_photo_url and preview_photo_path helpers. + # This will recognize `/photos/1/preview` with GET, and route to the preview + # action of `PhotosController`. It will also create the `preview_photo_url` and + # `preview_photo_path` helpers. def member(&block) unless resource_scope? raise ArgumentError, "can't use member outside resource(s) scope" @@ -1627,29 +1626,28 @@ def shallow? !parent_resource.singleton? && @scope[:shallow] end - # Loads another routes file with the given +name+ located inside the - # +config/routes+ directory. In that file, you can use the normal - # routing DSL, but do not surround it with a - # +Rails.application.routes.draw+ block. + # Loads another routes file with the given `name` located inside the + # `config/routes` directory. In that file, you can use the normal routing DSL, + # but *do not* surround it with a `Rails.application.routes.draw` block. # - # # config/routes.rb - # Rails.application.routes.draw do - # draw :admin # Loads `config/routes/admin.rb` - # draw "third_party/some_gem" # Loads `config/routes/third_party/some_gem.rb` - # end + # # config/routes.rb + # Rails.application.routes.draw do + # draw :admin # Loads `config/routes/admin.rb` + # draw "third_party/some_gem" # Loads `config/routes/third_party/some_gem.rb` + # end # - # # config/routes/admin.rb - # namespace :admin do - # resources :accounts - # end + # # config/routes/admin.rb + # namespace :admin do + # resources :accounts + # end # - # # config/routes/third_party/some_gem.rb - # mount SomeGem::Engine, at: "/some_gem" + # # config/routes/third_party/some_gem.rb + # mount SomeGem::Engine, at: "/some_gem" # - # CAUTION: Use this feature with care. Having multiple routes - # files can negatively impact discoverability and readability. For most - # applications — even those with a few hundred routes — it's easier for - # developers to have a single routes file. + # **CAUTION:** Use this feature with care. Having multiple routes files can + # negatively impact discoverability and readability. For most applications — + # even those with a few hundred routes — it's easier for developers to have a + # single routes file. def draw(name) path = @draw_paths.find do |_path| File.exist? "#{_path}/#{name}.rb" @@ -1666,12 +1664,12 @@ def draw(name) instance_eval(File.read(route_path), route_path.to_s) end - # Matches a URL pattern to one or more routes. - # For more information, see match[rdoc-ref:Base#match]. + # Matches a URL pattern to one or more routes. For more information, see + # [match](rdoc-ref:Base#match). # - # match 'path' => 'controller#action', via: :patch - # match 'path', to: 'controller#action', via: :post - # match 'path', 'otherpath', on: :member, via: :get + # match 'path' => 'controller#action', via: :patch + # match 'path', to: 'controller#action', via: :post + # match 'path', 'otherpath', on: :member, via: :get def match(path, *rest, &block) if rest.empty? && Hash === path options = path @@ -1706,19 +1704,19 @@ def match(path, *rest, &block) end end - # You can specify what \Rails should route "/" to with the root method: + # You can specify what Rails should route "/" to with the root method: # - # root to: 'pages#main' + # root to: 'pages#main' # - # For options, see +match+, as +root+ uses it internally. + # For options, see `match`, as `root` uses it internally. # # You can also pass a string which will expand # - # root 'pages#main' + # root 'pages#main' # - # You should put the root route at the top of config/routes.rb, - # because this means it will be matched first. As this is the most popular route - # of most \Rails applications, this is beneficial. + # You should put the root route at the top of `config/routes.rb`, because this + # means it will be matched first. As this is the most popular route of most + # Rails applications, this is beneficial. def root(path, options = {}) if path.is_a?(String) options[:to] = path @@ -1896,9 +1894,9 @@ def name_for_action(as, action) candidate = action_name.select(&:present?).join("_") unless candidate.empty? - # If a name was not explicitly given, we check if it is valid - # and return nil in case it isn't. Otherwise, we pass the invalid name - # forward so the underlying router engine treats it and raises an exception. + # If a name was not explicitly given, we check if it is valid and return nil in + # case it isn't. Otherwise, we pass the invalid name forward so the underlying + # router engine treats it and raises an exception. if as.nil? candidate unless !candidate.match?(/\A[_a-z]/i) || has_named_route?(candidate) else @@ -2034,83 +2032,81 @@ def match_root_route(options) end end - # Routing Concerns allow you to declare common routes that can be reused - # inside others resources and routes. + # Routing Concerns allow you to declare common routes that can be reused inside + # others resources and routes. # - # concern :commentable do - # resources :comments - # end + # concern :commentable do + # resources :comments + # end # - # concern :image_attachable do - # resources :images, only: :index - # end + # concern :image_attachable do + # resources :images, only: :index + # end # # These concerns are used in Resources routing: # - # resources :messages, concerns: [:commentable, :image_attachable] + # resources :messages, concerns: [:commentable, :image_attachable] # # or in a scope or namespace: # - # namespace :posts do - # concerns :commentable - # end + # namespace :posts do + # concerns :commentable + # end module Concerns # Define a routing concern using a name. # - # Concerns may be defined inline, using a block, or handled by - # another object, by passing that object as the second parameter. + # Concerns may be defined inline, using a block, or handled by another object, + # by passing that object as the second parameter. # - # The concern object, if supplied, should respond to call, - # which will receive two parameters: + # The concern object, if supplied, should respond to `call`, which will receive + # two parameters: # - # * The current mapper - # * A hash of options which the concern object may use + # * The current mapper + # * A hash of options which the concern object may use # - # Options may also be used by concerns defined in a block by accepting - # a block parameter. So, using a block, you might do something as - # simple as limit the actions available on certain resources, passing - # standard resource options through the concern: + # Options may also be used by concerns defined in a block by accepting a block + # parameter. So, using a block, you might do something as simple as limit the + # actions available on certain resources, passing standard resource options + # through the concern: # - # concern :commentable do |options| - # resources :comments, options - # end - # - # resources :posts, concerns: :commentable - # resources :archived_posts do - # # Don't allow comments on archived posts - # concerns :commentable, only: [:index, :show] - # end - # - # Or, using a callable object, you might implement something more - # specific to your application, which would be out of place in your - # routes file. - # - # # purchasable.rb - # class Purchasable - # def initialize(defaults = {}) - # @defaults = defaults + # concern :commentable do |options| + # resources :comments, options # end # - # def call(mapper, options = {}) - # options = @defaults.merge(options) - # mapper.resources :purchases - # mapper.resources :receipts - # mapper.resources :returns if options[:returnable] + # resources :posts, concerns: :commentable + # resources :archived_posts do + # # Don't allow comments on archived posts + # concerns :commentable, only: [:index, :show] # end - # end # - # # routes.rb - # concern :purchasable, Purchasable.new(returnable: true) + # Or, using a callable object, you might implement something more specific to + # your application, which would be out of place in your routes file. # - # resources :toys, concerns: :purchasable - # resources :electronics, concerns: :purchasable - # resources :pets do - # concerns :purchasable, returnable: false - # end + # # purchasable.rb + # class Purchasable + # def initialize(defaults = {}) + # @defaults = defaults + # end # - # Any routing helpers can be used inside a concern. If using a - # callable, they're accessible from the Mapper that's passed to - # call. + # def call(mapper, options = {}) + # options = @defaults.merge(options) + # mapper.resources :purchases + # mapper.resources :receipts + # mapper.resources :returns if options[:returnable] + # end + # end + # + # # routes.rb + # concern :purchasable, Purchasable.new(returnable: true) + # + # resources :toys, concerns: :purchasable + # resources :electronics, concerns: :purchasable + # resources :pets do + # concerns :purchasable, returnable: false + # end + # + # Any routing helpers can be used inside a concern. If using a callable, they're + # accessible from the Mapper that's passed to `call`. def concern(name, callable = nil, &block) callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) } @concerns[name] = callable @@ -2118,15 +2114,15 @@ def concern(name, callable = nil, &block) # Use the named concerns # - # resources :posts do - # concerns :commentable - # end + # resources :posts do + # concerns :commentable + # end # # Concerns also work in any routes helper that you want to use: # - # namespace :posts do - # concerns :commentable - # end + # namespace :posts do + # concerns :commentable + # end def concerns(*args) options = args.extract_options! args.flatten.each do |name| @@ -2140,53 +2136,55 @@ def concerns(*args) end module CustomUrls - # Define custom URL helpers that will be added to the application's - # routes. This allows you to override and/or replace the default behavior - # of routing helpers, e.g: + # Define custom URL helpers that will be added to the application's routes. This + # allows you to override and/or replace the default behavior of routing helpers, + # e.g: # - # direct :homepage do - # "https://rubyonrails.org" - # end + # direct :homepage do + # "https://rubyonrails.org" + # end # - # direct :commentable do |model| - # [ model, anchor: model.dom_id ] - # end + # direct :commentable do |model| + # [ model, anchor: model.dom_id ] + # end # - # direct :main do - # { controller: "pages", action: "index", subdomain: "www" } - # end + # direct :main do + # { controller: "pages", action: "index", subdomain: "www" } + # end # - # The return value from the block passed to +direct+ must be a valid set of - # arguments for +url_for+ which will actually build the URL string. This can - # be one of the following: + # The return value from the block passed to `direct` must be a valid set of + # arguments for `url_for` which will actually build the URL string. This can be + # one of the following: # - # * A string, which is treated as a generated URL - # * A hash, e.g. { controller: "pages", action: "index" } - # * An array, which is passed to +polymorphic_url+ - # * An Active Model instance - # * An Active Model class + # * A string, which is treated as a generated URL + # * A hash, e.g. `{ controller: "pages", action: "index" }` + # * An array, which is passed to `polymorphic_url` + # * An Active Model instance + # * An Active Model class # - # NOTE: Other URL helpers can be called in the block but be careful not to invoke - # your custom URL helper again otherwise it will result in a stack overflow error. # - # You can also specify default options that will be passed through to - # your URL helper definition, e.g: + # NOTE: Other URL helpers can be called in the block but be careful not to + # invoke your custom URL helper again otherwise it will result in a stack + # overflow error. # - # direct :browse, page: 1, size: 10 do |options| - # [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ] - # end + # You can also specify default options that will be passed through to your URL + # helper definition, e.g: # - # In this instance the +params+ object comes from the context in which the - # block is executed, e.g. generating a URL inside a controller action or a view. - # If the block is executed where there isn't a +params+ object such as this: + # direct :browse, page: 1, size: 10 do |options| + # [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ] + # end # - # Rails.application.routes.url_helpers.browse_path + # In this instance the `params` object comes from the context in which the block + # is executed, e.g. generating a URL inside a controller action or a view. If + # the block is executed where there isn't a `params` object such as this: # - # then it will raise a +NameError+. Because of this you need to be aware of the + # Rails.application.routes.url_helpers.browse_path + # + # then it will raise a `NameError`. Because of this you need to be aware of the # context in which you will use your custom URL helper when defining it. # - # NOTE: The +direct+ method can't be used inside of a scope block such as - # +namespace+ or +scope+ and will raise an error if it detects that it is. + # NOTE: The `direct` method can't be used inside of a scope block such as + # `namespace` or `scope` and will raise an error if it detects that it is. def direct(name, options = {}, &block) unless @scope.root? raise RuntimeError, "The direct method can't be used inside a routes scope block" @@ -2195,50 +2193,50 @@ def direct(name, options = {}, &block) @set.add_url_helper(name, options, &block) end - # Define custom polymorphic mappings of models to URLs. This alters the - # behavior of +polymorphic_url+ and consequently the behavior of - # +link_to+ and +form_for+ when passed a model instance, e.g: + # Define custom polymorphic mappings of models to URLs. This alters the behavior + # of `polymorphic_url` and consequently the behavior of `link_to` and `form_for` + # when passed a model instance, e.g: # - # resource :basket + # resource :basket # - # resolve "Basket" do - # [:basket] - # end + # resolve "Basket" do + # [:basket] + # end # - # This will now generate "/basket" when a +Basket+ instance is passed to - # +link_to+ or +form_for+ instead of the standard "/baskets/:id". + # This will now generate "/basket" when a `Basket` instance is passed to + # `link_to` or `form_for` instead of the standard "/baskets/:id". # - # NOTE: This custom behavior only applies to simple polymorphic URLs where - # a single model instance is passed and not more complicated forms, e.g: + # NOTE: This custom behavior only applies to simple polymorphic URLs where a + # single model instance is passed and not more complicated forms, e.g: # - # # config/routes.rb - # resource :profile - # namespace :admin do - # resources :users - # end + # # config/routes.rb + # resource :profile + # namespace :admin do + # resources :users + # end # - # resolve("User") { [:profile] } + # resolve("User") { [:profile] } # - # # app/views/application/_menu.html.erb - # link_to "Profile", @current_user - # link_to "Profile", [:admin, @current_user] + # # app/views/application/_menu.html.erb + # link_to "Profile", @current_user + # link_to "Profile", [:admin, @current_user] # - # The first +link_to+ will generate "/profile" but the second will generate - # the standard polymorphic URL of "/admin/users/1". + # The first `link_to` will generate "/profile" but the second will generate the + # standard polymorphic URL of "/admin/users/1". # - # You can pass options to a polymorphic mapping - the arity for the block - # needs to be two as the instance is passed as the first argument, e.g: + # You can pass options to a polymorphic mapping - the arity for the block needs + # to be two as the instance is passed as the first argument, e.g: # - # resolve "Basket", anchor: "items" do |basket, options| - # [:basket, options] - # end + # resolve "Basket", anchor: "items" do |basket, options| + # [:basket, options] + # end # - # This generates the URL "/basket#items" because when the last item in an - # array passed to +polymorphic_url+ is a hash then it's treated as options - # to the URL helper that gets called. + # This generates the URL "/basket#items" because when the last item in an array + # passed to `polymorphic_url` is a hash then it's treated as options to the URL + # helper that gets called. # - # NOTE: The +resolve+ method can't be used inside of a scope block such as - # +namespace+ or +scope+ and will raise an error if it detects that it is. + # NOTE: The `resolve` method can't be used inside of a scope block such as + # `namespace` or `scope` and will raise an error if it detects that it is. def resolve(*args, &block) unless @scope.root? raise RuntimeError, "The resolve method can't be used inside a routes scope block" diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index b92e02e2ca..20239ddbe1 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -1,104 +1,111 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Routing - # = Action Dispatch Routing \PolymorphicRoutes + # # Action Dispatch Routing PolymorphicRoutes # - # Polymorphic URL helpers are methods for smart resolution to a named route call when - # given an Active Record model instance. They are to be used in combination with - # ActionController::Resources. + # Polymorphic URL helpers are methods for smart resolution to a named route call + # when given an Active Record model instance. They are to be used in combination + # with ActionController::Resources. # - # These methods are useful when you want to generate the correct URL or path to a RESTful - # resource without having to know the exact type of the record in question. + # These methods are useful when you want to generate the correct URL or path to + # a RESTful resource without having to know the exact type of the record in + # question. # - # Nested resources and/or namespaces are also supported, as illustrated in the example: + # Nested resources and/or namespaces are also supported, as illustrated in the + # example: # - # polymorphic_url([:admin, @article, @comment]) + # polymorphic_url([:admin, @article, @comment]) # # results in: # - # admin_article_comment_url(@article, @comment) + # admin_article_comment_url(@article, @comment) # - # == Usage within the framework + # ## Usage within the framework # - # Polymorphic URL helpers are used in a number of places throughout the \Rails framework: + # Polymorphic URL helpers are used in a number of places throughout the Rails + # framework: # - # * url_for, so you can use it with a record as the argument, e.g. - # url_for(@article); - # * ActionView::Helpers::FormHelper uses polymorphic_path, so you can write - # form_for(@article) without having to specify :url parameter for the form - # action; - # * redirect_to (which, in fact, uses url_for) so you can write - # redirect_to(post) in your controllers; - # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs - # for feed entries. + # * `url_for`, so you can use it with a record as the argument, e.g. + # `url_for(@article)`; + # * ActionView::Helpers::FormHelper uses `polymorphic_path`, so you can write + # `form_for(@article)` without having to specify `:url` parameter for the + # form action; + # * `redirect_to` (which, in fact, uses `url_for`) so you can write + # `redirect_to(post)` in your controllers; + # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly + # specify URLs for feed entries. # - # == Prefixed polymorphic helpers # - # In addition to polymorphic_url and polymorphic_path methods, a - # number of prefixed helpers are available as a shorthand to action: "..." - # in options. Those are: + # ## Prefixed polymorphic helpers + # + # In addition to `polymorphic_url` and `polymorphic_path` methods, a number of + # prefixed helpers are available as a shorthand to `action: "..."` in options. + # Those are: + # + # * `edit_polymorphic_url`, `edit_polymorphic_path` + # * `new_polymorphic_url`, `new_polymorphic_path` # - # * edit_polymorphic_url, edit_polymorphic_path - # * new_polymorphic_url, new_polymorphic_path # # Example usage: # - # edit_polymorphic_path(@post) # => "/posts/1/edit" - # polymorphic_path(@post, format: :pdf) # => "/posts/1.pdf" + # edit_polymorphic_path(@post) # => "/posts/1/edit" + # polymorphic_path(@post, format: :pdf) # => "/posts/1.pdf" # - # == Usage with mounted engines + # ## Usage with mounted engines # # If you are using a mounted engine and you need to use a polymorphic_url # pointing at the engine's routes, pass in the engine's route proxy as the first # argument to the method. For example: # - # polymorphic_url([blog, @post]) # calls blog.post_path(@post) - # form_for([blog, @post]) # => "/blog/posts/1" + # polymorphic_url([blog, @post]) # calls blog.post_path(@post) + # form_for([blog, @post]) # => "/blog/posts/1" # module PolymorphicRoutes - # Constructs a call to a named RESTful route for the given record and returns the - # resulting URL string. For example: + # Constructs a call to a named RESTful route for the given record and returns + # the resulting URL string. For example: # - # # calls post_url(post) - # polymorphic_url(post) # => "http://example.com/posts/1" - # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1" - # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1" - # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1" - # polymorphic_url(Comment) # => "http://example.com/comments" + # # calls post_url(post) + # polymorphic_url(post) # => "http://example.com/posts/1" + # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1" + # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1" + # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1" + # polymorphic_url(Comment) # => "http://example.com/comments" # - # ==== Options + # #### Options # - # * :action - Specifies the action prefix for the named route: - # :new or :edit. Default is no prefix. - # * :routing_type - Allowed values are :path or :url. - # Default is :url. + # * `:action` - Specifies the action prefix for the named route: `:new` or + # `:edit`. Default is no prefix. + # * `:routing_type` - Allowed values are `:path` or `:url`. Default is `:url`. # - # Also includes all the options from url_for. These include such - # things as :anchor or :trailing_slash. Example usage - # is given below: # - # polymorphic_url([blog, post], anchor: 'my_anchor') - # # => "http://example.com/blogs/1/posts/1#my_anchor" - # polymorphic_url([blog, post], anchor: 'my_anchor', script_name: "/my_app") - # # => "http://example.com/my_app/blogs/1/posts/1#my_anchor" + # Also includes all the options from `url_for`. These include such things as + # `:anchor` or `:trailing_slash`. Example usage is given below: # - # For all of these options, see the documentation for {url_for}[rdoc-ref:ActionDispatch::Routing::UrlFor]. + # polymorphic_url([blog, post], anchor: 'my_anchor') + # # => "http://example.com/blogs/1/posts/1#my_anchor" + # polymorphic_url([blog, post], anchor: 'my_anchor', script_name: "/my_app") + # # => "http://example.com/my_app/blogs/1/posts/1#my_anchor" # - # ==== Functionality + # For all of these options, see the documentation for + # [url_for](rdoc-ref:ActionDispatch::Routing::UrlFor). # - # # an Article record - # polymorphic_url(record) # same as article_url(record) + # #### Functionality # - # # a Comment record - # polymorphic_url(record) # same as comment_url(record) + # # an Article record + # polymorphic_url(record) # same as article_url(record) # - # # it recognizes new records and maps to the collection - # record = Comment.new - # polymorphic_url(record) # same as comments_url() + # # a Comment record + # polymorphic_url(record) # same as comment_url(record) # - # # the class of a record will also map to the collection - # polymorphic_url(Comment) # same as comments_url() + # # it recognizes new records and maps to the collection + # record = Comment.new + # polymorphic_url(record) # same as comments_url() + # + # # the class of a record will also map to the collection + # polymorphic_url(Comment) # same as comments_url() # def polymorphic_url(record_or_hash_or_array, options = {}) if Hash === record_or_hash_or_array diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index b5d7a336ee..83c9e58bcb 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/array/extract_options" require "rack/utils" require "action_controller/metal/exceptions" @@ -146,55 +148,58 @@ def inspect module Redirection # Redirect any path to another path: # - # get "/stories" => redirect("/posts") + # get "/stories" => redirect("/posts") # - # This will redirect the user, while ignoring certain parts of the request, including query string, etc. - # /stories, /stories?foo=bar, etc all redirect to /posts. + # This will redirect the user, while ignoring certain parts of the request, + # including query string, etc. `/stories`, `/stories?foo=bar`, etc all redirect + # to `/posts`. # - # The redirect will use a 301 Moved Permanently status code by - # default. This can be overridden with the +:status+ option: + # The redirect will use a `301 Moved Permanently` status code by default. This + # can be overridden with the `:status` option: # - # get "/stories" => redirect("/posts", status: 307) + # get "/stories" => redirect("/posts", status: 307) # # You can also use interpolation in the supplied redirect argument: # - # get 'docs/:article', to: redirect('/wiki/%{article}') + # get 'docs/:article', to: redirect('/wiki/%{article}') # - # Note that if you return a path without a leading slash then the URL is prefixed with the - # current SCRIPT_NAME environment variable. This is typically '/' but may be different in - # a mounted engine or where the application is deployed to a subdirectory of a website. + # Note that if you return a path without a leading slash then the URL is + # prefixed with the current SCRIPT_NAME environment variable. This is typically + # '/' but may be different in a mounted engine or where the application is + # deployed to a subdirectory of a website. # # Alternatively you can use one of the other syntaxes: # - # The block version of redirect allows for the easy encapsulation of any logic associated with - # the redirect in question. Either the params and request are supplied as arguments, or just - # params, depending of how many arguments your block accepts. A string is required as a - # return value. + # The block version of redirect allows for the easy encapsulation of any logic + # associated with the redirect in question. Either the params and request are + # supplied as arguments, or just params, depending of how many arguments your + # block accepts. A string is required as a return value. # - # get 'jokes/:number', to: redirect { |params, request| - # path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp") - # "http://#{request.host_with_port}/#{path}" - # } + # get 'jokes/:number', to: redirect { |params, request| + # path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp") + # "http://#{request.host_with_port}/#{path}" + # } # - # Note that the do end syntax for the redirect block wouldn't work, as Ruby would pass - # the block to +get+ instead of +redirect+. Use { ... } instead. + # Note that the `do end` syntax for the redirect block wouldn't work, as Ruby + # would pass the block to `get` instead of `redirect`. Use `{ ... }` instead. # - # The options version of redirect allows you to supply only the parts of the URL which need - # to change, it also supports interpolation of the path similar to the first example. + # The options version of redirect allows you to supply only the parts of the URL + # which need to change, it also supports interpolation of the path similar to + # the first example. # - # get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}') - # get 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}') - # get '/stories', to: redirect(path: '/posts') + # get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}') + # get 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}') + # get '/stories', to: redirect(path: '/posts') # - # This will redirect the user, while changing only the specified parts of the request, - # for example the +path+ option in the last example. - # /stories, /stories?foo=bar, redirect to /posts and /posts?foo=bar respectively. + # This will redirect the user, while changing only the specified parts of the + # request, for example the `path` option in the last example. `/stories`, + # `/stories?foo=bar`, redirect to `/posts` and `/posts?foo=bar` respectively. # - # Finally, an object which responds to call can be supplied to redirect, allowing you to reuse - # common redirect routes. The call method must accept two arguments, params and request, and return - # a string. + # Finally, an object which responds to call can be supplied to redirect, + # allowing you to reuse common redirect routes. The call method must accept two + # arguments, params and request, and return a string. # - # get 'accounts/:name' => redirect(SubdomainRedirector.new('api')) + # get 'accounts/:name' => redirect(SubdomainRedirector.new('api')) # def redirect(*args, &block) options = args.extract_options! diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index ba4183fe06..fb041ad168 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/journey" require "active_support/core_ext/object/to_query" require "active_support/core_ext/module/redefine_method" @@ -12,10 +14,9 @@ module ActionDispatch module Routing # :stopdoc: class RouteSet - # Since the router holds references to many parts of the system - # like engines, controllers and the application itself, inspecting - # the route set can actually be really slow, therefore we default - # alias inspect to to_s. + # Since the router holds references to many parts of the system like engines, + # controllers and the application itself, inspecting the route set can actually + # be really slow, therefore we default alias inspect to to_s. alias inspect to_s class Dispatcher < Routing::Endpoint @@ -144,8 +145,8 @@ def length routes.length end - # Given a +name+, defines name_path and name_url helpers. - # Used by 'direct', 'resolve', and 'polymorphic' route helpers. + # Given a `name`, defines name_path and name_url helpers. Used by 'direct', + # 'resolve', and 'polymorphic' route helpers. def add_url_helper(name, defaults, &block) helper = CustomUrlHelper.new(name, defaults, &block) path_name = :"#{name}_path" @@ -301,18 +302,18 @@ def handle_positional_args(controller_options, inner_options, args, result, path end private - # Create a URL helper allowing ordered parameters to be associated - # with corresponding dynamic segments, so you can do: + # Create a URL helper allowing ordered parameters to be associated with + # corresponding dynamic segments, so you can do: # - # foo_url(bar, baz, bang) + # foo_url(bar, baz, bang) # # Instead of: # - # foo_url(bar: bar, baz: baz, bang: bang) + # foo_url(bar: bar, baz: baz, bang: bang) # # Also allow options hash, so you can do: # - # foo_url(bar, baz, bang, sort_by: 'baz') + # foo_url(bar, baz, bang, sort_by: 'baz') # def define_url_helper(mod, name, helper, url_strategy) mod.define_method(name) do |*args| @@ -471,10 +472,9 @@ module MountedHelpers include UrlFor end - # Contains all the mounted helpers across different - # engines and the `main_app` helper for the application. - # You can include this in your classes if you want to - # access routes for other engines. + # Contains all the mounted helpers across different engines and the `main_app` + # helper for the application. You can include this in your classes if you want + # to access routes for other engines. def mounted_helpers MountedHelpers end @@ -564,13 +564,11 @@ def url_options; {}; end url_helpers = routes.named_routes.url_helpers_module - # Make named_routes available in the module singleton - # as well, so one can do: + # Make named_routes available in the module singleton as well, so one can do: # Rails.application.routes.url_helpers.posts_path extend url_helpers - # Any class that includes this module will get all - # named routes... + # Any class that includes this module will get all named routes... include url_helpers if supports_path @@ -585,9 +583,8 @@ def url_options; {}; end redefine_singleton_method(:_routes) { routes } end - # And an instance method _routes. Note that - # UrlFor (included in this module) add extra - # conveniences for working with @_routes. + # And an instance method _routes. Note that UrlFor (included in this module) add + # extra conveniences for working with @_routes. define_method(:_routes) { @_routes || routes } define_method(:_generate_paths_by_default) do @@ -596,12 +593,12 @@ def url_options; {}; end private :_generate_paths_by_default - # If the module is included more than once (for example, in a subclass - # of an ancestor that includes the module), ensure that the `_routes` - # singleton and instance methods return the desired route set by - # including a new copy of the module (recursively if necessary). Note - # that this method is called for each inclusion, whereas the above - # `included` block is run only for the initial inclusion of each copy. + # If the module is included more than once (for example, in a subclass of an + # ancestor that includes the module), ensure that the `_routes` singleton and + # instance methods return the desired route set by including a new copy of the + # module (recursively if necessary). Note that this method is called for each + # inclusion, whereas the above `included` block is run only for the initial + # inclusion of each copy. def self.included(base) super if base.respond_to?(:_routes) && !base._routes.equal?(@_proxy._routes) @@ -717,14 +714,14 @@ def use_recall_for(key) 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 + # If an explicit :controller was given, always make :action explicit too, so + # that action expiry works as expected for things like # - # generate({controller: 'content'}, {controller: 'content', action: 'show'}) + # generate({controller: 'content'}, {controller: 'content', action: 'show'}) # - # (the above is from the unit tests). In the above case, because the - # controller was explicitly given, but no action, the action is implied to - # be "index", not the recalled action of "show". + # (the above is from the unit tests). In the above case, because the controller + # was explicitly given, but no action, the action is implied to be "index", not + # the recalled action of "show". if options[:controller] options[:action] ||= "index" @@ -736,10 +733,9 @@ def normalize_options! end end - # This pulls :controller, :action, and :id out of the recall. - # The recall key is only used if there is no key in the options - # or if the key in the options is identical. If any of - # :controller, :action or :id is not found, don't pull any + # This pulls :controller, :action, and :id out of the recall. The recall key is + # only used if there is no key in the options or if the key in the options is + # identical. If any of :controller, :action or :id is not found, don't pull any # more keys from the recall. def normalize_controller_action_id! use_recall_for(:controller) || return @@ -747,8 +743,8 @@ def normalize_controller_action_id! use_recall_for(:id) end - # if the current controller is "foo/bar/baz" and controller: "baz/bat" - # is specified, the controller becomes "foo/baz/bat" + # if the current controller is "foo/bar/baz" and controller: "baz/bat" is + # specified, the controller becomes "foo/baz/bat" def use_relative_controller! if !named_route && different_controller? && !controller.start_with?("/") old_parts = current_controller.split("/") @@ -790,8 +786,8 @@ def segment_keys end end - # Generate the path indicated by the arguments, and return an array of - # the keys that were not used to generate it. + # Generate the path indicated by the arguments, and return an array of the keys + # that were not used to generate it. def extra_keys(options, recall = {}) generate_extras(options, recall).last end @@ -828,7 +824,7 @@ def path_for(options, route_name = nil, reserved = RESERVED_OPTIONS) url_for(options, route_name, PATH, nil, reserved) end - # The +options+ argument must be a hash whose keys are *symbols*. + # The `options` argument must be a hash whose keys are **symbols**. def url_for(options, route_name = nil, url_strategy = UNKNOWN, method_name = nil, reserved = RESERVED_OPTIONS) options = default_url_options.merge options diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb index d6176b8e5b..fe9ba93cde 100644 --- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb +++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/array/extract_options" module ActionDispatch @@ -46,10 +48,10 @@ def method_missing(method, *args) end end - # Keeps the part of the script name provided by the global - # context via ENV["SCRIPT_NAME"], which `mount` doesn't know - # about since it depends on the specific request, but use our - # script name resolver for the mount point dependent part. + # Keeps the part of the script name provided by the global context via + # [ENV]("SCRIPT_NAME"), which `mount` doesn't know about since it depends on the + # specific request, but use our script name resolver for the mount point + # dependent part. def merge_script_names(previous_script_name, new_script_name) return new_script_name unless previous_script_name diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 55d92c29ef..43e89e3e6e 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -1,90 +1,93 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Routing - # = Action Dispatch Routing \UrlFor + # # Action Dispatch Routing UrlFor # - # In config/routes.rb you define URL-to-controller mappings, but the reverse + # In `config/routes.rb` you define URL-to-controller mappings, but the reverse # is also possible: a URL can be generated from one of your routing definitions. # URL generation functionality is centralized in this module. # - # See ActionDispatch::Routing for general information about routing and config/routes.rb. + # See ActionDispatch::Routing for general information about routing and + # `config/routes.rb`. # - # Tip: If you need to generate URLs from your models or some other place, + # **Tip:** If you need to generate URLs from your models or some other place, # then ActionDispatch::Routing::UrlFor is what you're looking for. Read on for - # an introduction. In general, this module should not be included on its own, - # as it is usually included by +url_helpers+ (as in Rails.application.routes.url_helpers). + # an introduction. In general, this module should not be included on its own, as + # it is usually included by `url_helpers` (as in + # `Rails.application.routes.url_helpers`). # - # == URL generation from parameters + # ## URL generation from parameters # - # As you may know, some functions, such as ActionController::Base#url_for - # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set - # of parameters. For example, you've probably had the chance to write code - # like this in one of your views: + # As you may know, some functions, such as `ActionController::Base#url_for` and + # ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set of + # parameters. For example, you've probably had the chance to write code like + # this in one of your views: # - # <%= link_to('Click here', controller: 'users', - # action: 'new', message: 'Welcome!') %> - # # => Click here + # <%= link_to('Click here', controller: 'users', + # action: 'new', message: 'Welcome!') %> + # # => Click here # - # +link_to+, and all other functions that require URL generation functionality, - # actually use ActionDispatch::Routing::UrlFor under the hood. And in particular, - # they use the ActionDispatch::Routing::UrlFor#url_for method. One can generate - # the same path as the above example by using the following code: + # `link_to`, and all other functions that require URL generation functionality, + # actually use ActionDispatch::Routing::UrlFor under the hood. And in + # particular, they use the ActionDispatch::Routing::UrlFor#url_for method. One + # can generate the same path as the above example by using the following code: # - # include ActionDispatch::Routing::UrlFor - # url_for(controller: 'users', - # action: 'new', - # message: 'Welcome!', - # only_path: true) - # # => "/users/new?message=Welcome%21" + # include ActionDispatch::Routing::UrlFor + # url_for(controller: 'users', + # action: 'new', + # message: 'Welcome!', + # only_path: true) + # # => "/users/new?message=Welcome%21" # - # Notice the only_path: true part. This is because UrlFor has no - # information about the website hostname that your \Rails app is serving. So if you - # want to include the hostname as well, then you must also pass the :host - # argument: + # Notice the `only_path: true` part. This is because UrlFor has no information + # about the website hostname that your Rails app is serving. So if you want to + # include the hostname as well, then you must also pass the `:host` argument: # - # include UrlFor - # url_for(controller: 'users', - # action: 'new', - # message: 'Welcome!', - # host: 'www.example.com') - # # => "http://www.example.com/users/new?message=Welcome%21" + # include UrlFor + # url_for(controller: 'users', + # action: 'new', + # message: 'Welcome!', + # host: 'www.example.com') + # # => "http://www.example.com/users/new?message=Welcome%21" # - # By default, all controllers and views have access to a special version of +url_for+, - # that already knows what the current hostname is. So if you use +url_for+ in your - # controllers or your views, then you don't need to explicitly pass the :host - # argument. + # By default, all controllers and views have access to a special version of + # `url_for`, that already knows what the current hostname is. So if you use + # `url_for` in your controllers or your views, then you don't need to explicitly + # pass the `:host` argument. # # For convenience, mailers also include ActionDispatch::Routing::UrlFor. So - # within mailers, you can use url_for. However, mailers cannot access - # incoming web requests in order to derive hostname information, so you have - # to provide the +:host+ option or set the default host using - # +default_url_options+. For more information on url_for in mailers see the - # ActionMailer::Base documentation. + # within mailers, you can use url_for. However, mailers cannot access incoming + # web requests in order to derive hostname information, so you have to provide + # the `:host` option or set the default host using `default_url_options`. For + # more information on url_for in mailers see the ActionMailer::Base + # documentation. # - # - # == URL generation for named routes + # ## URL generation for named routes # # UrlFor also allows one to access methods that have been auto-generated from # named routes. For example, suppose that you have a 'users' resource in your - # config/routes.rb: + # `config/routes.rb`: # - # resources :users + # resources :users # - # This generates, among other things, the method users_path. By default, - # this method is accessible from your controllers, views, and mailers. If you need - # to access this auto-generated method from other places (such as a model), then - # you can do that by including Rails.application.routes.url_helpers in your class: + # This generates, among other things, the method `users_path`. By default, this + # method is accessible from your controllers, views, and mailers. If you need to + # access this auto-generated method from other places (such as a model), then + # you can do that by including `Rails.application.routes.url_helpers` in your + # class: # - # class User < ActiveRecord::Base - # include Rails.application.routes.url_helpers + # class User < ActiveRecord::Base + # include Rails.application.routes.url_helpers # - # def base_uri - # user_path(self) + # def base_uri + # user_path(self) + # end # end - # end # - # User.find(1).base_uri # => "/users/1" + # User.find(1).base_uri # => "/users/1" # module UrlFor extend ActiveSupport::Concern @@ -110,66 +113,68 @@ def initialize(...) super end - # Hook overridden in controller to add request information - # with +default_url_options+. Application logic should not - # go into url_options. + # Hook overridden in controller to add request information with + # `default_url_options`. Application logic should not go into url_options. def url_options default_url_options end - # Generate a URL based on the options provided, +default_url_options+, and the - # routes defined in config/routes.rb. The following options are supported: + # Generate a URL based on the options provided, `default_url_options`, and the + # routes defined in `config/routes.rb`. The following options are supported: # - # * :only_path - If true, the relative URL is returned. Defaults to +false+. - # * :protocol - The protocol to connect to. Defaults to "http". - # * :host - Specifies the host the link should be targeted at. - # If :only_path is false, this option must be - # provided either explicitly, or via +default_url_options+. - # * :subdomain - Specifies the subdomain of the link, using the +tld_length+ - # to split the subdomain from the host. - # If false, removes all subdomains from the host part of the link. - # * :domain - Specifies the domain of the link, using the +tld_length+ - # to split the domain from the host. - # * :tld_length - Number of labels the TLD id composed of, only used if - # :subdomain or :domain are supplied. Defaults to - # ActionDispatch::Http::URL.tld_length, which in turn defaults to 1. - # * :port - Optionally specify the port to connect to. - # * :anchor - An anchor name to be appended to the path. - # * :params - The query parameters to be appended to the path. - # * :path_params - The query parameters that will only be used - # for the named dynamic segments of path. If unused, they will be discarded. - # * :trailing_slash - If true, adds a trailing slash, as in "/archive/2009/". - # * :script_name - Specifies application path relative to domain root. If provided, prepends application path. + # * `:only_path` - If true, the relative URL is returned. Defaults to `false`. + # * `:protocol` - The protocol to connect to. Defaults to `"http"`. + # * `:host` - Specifies the host the link should be targeted at. If + # `:only_path` is false, this option must be provided either explicitly, or + # via `default_url_options`. + # * `:subdomain` - Specifies the subdomain of the link, using the `tld_length` + # to split the subdomain from the host. If false, removes all subdomains + # from the host part of the link. + # * `:domain` - Specifies the domain of the link, using the `tld_length` to + # split the domain from the host. + # * `:tld_length` - Number of labels the TLD id composed of, only used if + # `:subdomain` or `:domain` are supplied. Defaults to + # `ActionDispatch::Http::URL.tld_length`, which in turn defaults to 1. + # * `:port` - Optionally specify the port to connect to. + # * `:anchor` - An anchor name to be appended to the path. + # * `:params` - The query parameters to be appended to the path. + # * `:path_params` - The query parameters that will only be used for the named + # dynamic segments of path. If unused, they will be discarded. + # * `:trailing_slash` - If true, adds a trailing slash, as in + # `"/archive/2009/"`. + # * `:script_name` - Specifies application path relative to domain root. If + # provided, prepends application path. # - # Any other key (:controller, :action, etc.) given to - # +url_for+ is forwarded to the Routes module. # - # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', port: '8080' - # # => 'http://somehost.org:8080/tasks/testing' - # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', anchor: 'ok', only_path: true - # # => '/tasks/testing#ok' - # url_for controller: 'tasks', action: 'testing', trailing_slash: true - # # => 'http://somehost.org/tasks/testing/' - # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', number: '33' - # # => 'http://somehost.org/tasks/testing?number=33' - # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp" - # # => 'http://somehost.org/myapp/tasks/testing' - # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp", only_path: true - # # => '/myapp/tasks/testing' + # Any other key (`:controller`, `:action`, etc.) given to `url_for` is forwarded + # to the Routes module. + # + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', port: '8080' + # # => 'http://somehost.org:8080/tasks/testing' + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', anchor: 'ok', only_path: true + # # => '/tasks/testing#ok' + # url_for controller: 'tasks', action: 'testing', trailing_slash: true + # # => 'http://somehost.org/tasks/testing/' + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', number: '33' + # # => 'http://somehost.org/tasks/testing?number=33' + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp" + # # => 'http://somehost.org/myapp/tasks/testing' + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp", only_path: true + # # => '/myapp/tasks/testing' # # Missing routes keys may be filled in from the current request's parameters - # (e.g. +:controller+, +:action+, +:id+, and any other parameters that are - # placed in the path). Given that the current action has been reached - # through GET /users/1: + # (e.g. `:controller`, `:action`, `:id`, and any other parameters that are + # placed in the path). Given that the current action has been reached through + # `GET /users/1`: # - # url_for(only_path: true) # => '/users/1' - # url_for(only_path: true, action: 'edit') # => '/users/1/edit' - # url_for(only_path: true, action: 'edit', id: 2) # => '/users/2/edit' + # url_for(only_path: true) # => '/users/1' + # url_for(only_path: true, action: 'edit') # => '/users/1/edit' + # url_for(only_path: true, action: 'edit', id: 2) # => '/users/2/edit' # - # Notice that no +:id+ parameter was provided to the first +url_for+ call - # and the helper used the one from the route's path. Any path parameter - # implicitly used by +url_for+ can always be overwritten like shown on the - # last +url_for+ calls. + # Notice that no `:id` parameter was provided to the first `url_for` call and + # the helper used the one from the route's path. Any path parameter implicitly + # used by `url_for` can always be overwritten like shown on the last `url_for` + # calls. def url_for(options = nil) full_url_for(options) end @@ -198,21 +203,21 @@ def full_url_for(options = nil) # :nodoc: # Allows calling direct or regular named route. # - # resources :buckets + # resources :buckets # - # direct :recordable do |recording| - # route_for(:bucket, recording.bucket) - # end + # direct :recordable do |recording| + # route_for(:bucket, recording.bucket) + # end # - # direct :threadable do |threadable| - # route_for(:recordable, threadable.parent) - # end + # direct :threadable do |threadable| + # route_for(:recordable, threadable.parent) + # end # - # This maintains the context of the original caller on - # whether to return a path or full URL, e.g: + # This maintains the context of the original caller on whether to return a path + # or full URL, e.g: # - # threadable_path(threadable) # => "/buckets/1" - # threadable_url(threadable) # => "http://example.com/buckets/1" + # threadable_path(threadable) # => "/buckets/1" + # threadable_url(threadable) # => "http://example.com/buckets/1" # def route_for(name, *args) public_send(:"#{name}_url", *args) diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb index 7a56279848..2af627a6e9 100644 --- a/actionpack/lib/action_dispatch/system_test_case.rb +++ b/actionpack/lib/action_dispatch/system_test_case.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + gem "capybara", ">= 3.26" require "capybara/dsl" @@ -12,103 +14,103 @@ require "action_dispatch/system_testing/test_helpers/setup_and_teardown" module ActionDispatch - # = System Testing + # # System Testing # - # System tests let you test applications in the browser. Because system - # tests use a real browser experience, you can test all of your JavaScript - # easily from your test suite. + # System tests let you test applications in the browser. Because system tests + # use a real browser experience, you can test all of your JavaScript easily from + # your test suite. # - # To create a system test in your application, extend your test class - # from ApplicationSystemTestCase. System tests use Capybara as a - # base and allow you to configure the settings through your - # application_system_test_case.rb file that is generated with a new - # application or scaffold. + # To create a system test in your application, extend your test class from + # `ApplicationSystemTestCase`. System tests use Capybara as a base and allow you + # to configure the settings through your `application_system_test_case.rb` file + # that is generated with a new application or scaffold. # # Here is an example system test: # - # require "application_system_test_case" + # require "application_system_test_case" # - # class Users::CreateTest < ApplicationSystemTestCase - # test "adding a new user" do - # visit users_path - # click_on 'New User' + # class Users::CreateTest < ApplicationSystemTestCase + # test "adding a new user" do + # visit users_path + # click_on 'New User' # - # fill_in 'Name', with: 'Arya' - # click_on 'Create User' + # fill_in 'Name', with: 'Arya' + # click_on 'Create User' # - # assert_text 'Arya' + # assert_text 'Arya' + # end # end - # end # - # When generating an application or scaffold, an +application_system_test_case.rb+ - # file will also be generated containing the base class for system testing. - # This is where you can change the driver, add Capybara settings, and other - # configuration for your system tests. + # When generating an application or scaffold, an + # `application_system_test_case.rb` file will also be generated containing the + # base class for system testing. This is where you can change the driver, add + # Capybara settings, and other configuration for your system tests. # - # require "test_helper" + # require "test_helper" # - # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase - # driven_by :selenium, using: :chrome, screen_size: [1400, 1400] - # end + # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + # driven_by :selenium, using: :chrome, screen_size: [1400, 1400] + # end # - # By default, +ActionDispatch::SystemTestCase+ is driven by the - # Selenium driver, with the Chrome browser, and a browser size of 1400x1400. + # By default, `ActionDispatch::SystemTestCase` is driven by the Selenium driver, + # with the Chrome browser, and a browser size of 1400x1400. # # Changing the driver configuration options is easy. Let's say you want to use - # the Firefox browser instead of Chrome. In your +application_system_test_case.rb+ - # file add the following: + # the Firefox browser instead of Chrome. In your + # `application_system_test_case.rb` file add the following: # - # require "test_helper" + # require "test_helper" # - # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase - # driven_by :selenium, using: :firefox - # end + # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + # driven_by :selenium, using: :firefox + # end # - # +driven_by+ has a required argument for the driver name. The keyword - # arguments are +:using+ for the browser and +:screen_size+ to change the - # size of the browser screen. These two options are not applicable for - # headless drivers and will be silently ignored if passed. + # `driven_by` has a required argument for the driver name. The keyword arguments + # are `:using` for the browser and `:screen_size` to change the size of the + # browser screen. These two options are not applicable for headless drivers and + # will be silently ignored if passed. # - # Headless browsers such as headless Chrome and headless Firefox are also supported. - # You can use these browsers by setting the +:using+ argument to +:headless_chrome+ or +:headless_firefox+. + # Headless browsers such as headless Chrome and headless Firefox are also + # supported. You can use these browsers by setting the `:using` argument to + # `:headless_chrome` or `:headless_firefox`. # - # To use a headless driver, like Cuprite, update your Gemfile to use - # Cuprite instead of Selenium and then declare the driver name in the - # +application_system_test_case.rb+ file. In this case, you would leave out - # the +:using+ option because the driver is headless, but you can still use - # +:screen_size+ to change the size of the browser screen, also you can use - # +:options+ to pass options supported by the driver. Please refer to your + # To use a headless driver, like Cuprite, update your Gemfile to use Cuprite + # instead of Selenium and then declare the driver name in the + # `application_system_test_case.rb` file. In this case, you would leave out the + # `:using` option because the driver is headless, but you can still use + # `:screen_size` to change the size of the browser screen, also you can use + # `:options` to pass options supported by the driver. Please refer to your # driver documentation to learn about supported options. # - # require "test_helper" - # require "capybara/cuprite" + # require "test_helper" + # require "capybara/cuprite" # - # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase - # driven_by :cuprite, screen_size: [1400, 1400], options: - # { js_errors: true } - # end + # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + # driven_by :cuprite, screen_size: [1400, 1400], options: + # { js_errors: true } + # end # - # Some drivers require browser capabilities to be passed as a block instead - # of through the +options+ hash. + # Some drivers require browser capabilities to be passed as a block instead of + # through the `options` hash. # # As an example, if you want to add mobile emulation on chrome, you'll have to - # create an instance of selenium's +Chrome::Options+ object and add - # capabilities with a block. + # create an instance of selenium's `Chrome::Options` object and add capabilities + # with a block. # - # The block will be passed an instance of ::Options where you can - # define the capabilities you want. Please refer to your driver documentation - # to learn about supported options. + # The block will be passed an instance of `::Options` where you can + # define the capabilities you want. Please refer to your driver documentation to + # learn about supported options. # - # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase - # driven_by :selenium, using: :chrome, screen_size: [1024, 768] do |driver_option| - # driver_option.add_emulation(device_name: 'iPhone 6') - # driver_option.add_extension('path/to/chrome_extension.crx') + # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + # driven_by :selenium, using: :chrome, screen_size: [1024, 768] do |driver_option| + # driver_option.add_emulation(device_name: 'iPhone 6') + # driver_option.add_extension('path/to/chrome_extension.crx') + # end # end - # end # - # Because +ActionDispatch::SystemTestCase+ is a shim between Capybara - # and \Rails, any driver that is supported by Capybara is supported by system - # tests as long as you include the required gems and files. + # Because `ActionDispatch::SystemTestCase` is a shim between Capybara and Rails, + # any driver that is supported by Capybara is supported by system tests as long + # as you include the required gems and files. class SystemTestCase < ActiveSupport::TestCase include Capybara::DSL include Capybara::Minitest::Assertions @@ -137,22 +139,22 @@ def self.start_application # :nodoc: # System Test configuration options # - # The default settings are Selenium, using Chrome, with a screen size - # of 1400x1400. + # The default settings are Selenium, using Chrome, with a screen size of + # 1400x1400. # # Examples: # - # driven_by :cuprite + # driven_by :cuprite # - # driven_by :selenium, screen_size: [800, 800] + # driven_by :selenium, screen_size: [800, 800] # - # driven_by :selenium, using: :chrome + # driven_by :selenium, using: :chrome # - # driven_by :selenium, using: :headless_chrome + # driven_by :selenium, using: :headless_chrome # - # driven_by :selenium, using: :firefox + # driven_by :selenium, using: :firefox # - # driven_by :selenium, using: :headless_firefox + # driven_by :selenium, using: :headless_firefox def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {}, &capabilities) driver_options = { using: using, screen_size: screen_size, options: options } diff --git a/actionpack/lib/action_dispatch/system_testing/browser.rb b/actionpack/lib/action_dispatch/system_testing/browser.rb index d86e66046f..8e364bd910 100644 --- a/actionpack/lib/action_dispatch/system_testing/browser.rb +++ b/actionpack/lib/action_dispatch/system_testing/browser.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module SystemTesting class Browser # :nodoc: @@ -35,8 +37,8 @@ def configure yield options if block_given? end - # driver_path is lazily initialized by default. Eagerly set it to - # avoid race conditions when using parallel tests. + # driver_path is lazily initialized by default. Eagerly set it to avoid race + # conditions when using parallel tests. def preload case type when :chrome diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb index 9a4fdeb73b..20979ac228 100644 --- a/actionpack/lib/action_dispatch/system_testing/driver.rb +++ b/actionpack/lib/action_dispatch/system_testing/driver.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module SystemTesting class Driver # :nodoc: diff --git a/actionpack/lib/action_dispatch/system_testing/server.rb b/actionpack/lib/action_dispatch/system_testing/server.rb index 4fc1f33767..ae3e2e9c2c 100644 --- a/actionpack/lib/action_dispatch/system_testing/server.rb +++ b/actionpack/lib/action_dispatch/system_testing/server.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module SystemTesting class Server # :nodoc: diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb index 44a469f043..46c896f44a 100644 --- a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module SystemTesting module TestHelpers @@ -7,27 +9,35 @@ module TestHelpers module ScreenshotHelper # Takes a screenshot of the current page in the browser. # - # +take_screenshot+ can be used at any point in your system tests to take - # a screenshot of the current state. This can be useful for debugging or - # automating visual testing. You can take multiple screenshots per test - # to investigate changes at different points during your test. These will be - # named with a sequential prefix (or 'failed' for failing tests) + # `take_screenshot` can be used at any point in your system tests to take a + # screenshot of the current state. This can be useful for debugging or + # automating visual testing. You can take multiple screenshots per test to + # investigate changes at different points during your test. These will be named + # with a sequential prefix (or 'failed' for failing tests) # - # The default screenshots directory is +tmp/screenshots+ but you can set a different - # one with +Capybara.save_path+ + # The default screenshots directory is `tmp/screenshots` but you can set a + # different one with `Capybara.save_path` + # + # You can use the `html` argument or set the + # `RAILS_SYSTEM_TESTING_SCREENSHOT_HTML` environment variable to save the HTML + # from the page that is being screenshotted so you can investigate the elements + # on the page at the time of the screenshot + # + # You can use the `screenshot` argument or set the + # `RAILS_SYSTEM_TESTING_SCREENSHOT` environment variable to control the output. + # Possible values are: + # `simple` (default) + # : Only displays the screenshot path. This is the default value. + # + # `inline` + # : Display the screenshot in the terminal using the iTerm image protocol + # (https://iterm2.com/documentation-images.html). + # + # `artifact` + # : Display the screenshot in the terminal, using the terminal artifact + # format (https://buildkite.github.io/terminal-to-html/inline-images/). # - # You can use the +html+ argument or set the +RAILS_SYSTEM_TESTING_SCREENSHOT_HTML+ - # environment variable to save the HTML from the page that is being screenshotted - # so you can investigate the elements on the page at the time of the screenshot # - # You can use the +screenshot+ argument or set the +RAILS_SYSTEM_TESTING_SCREENSHOT+ - # environment variable to control the output. Possible values are: - # * [+simple+ (default)] Only displays the screenshot path. - # This is the default value. - # * [+inline+] Display the screenshot in the terminal using the - # iTerm image protocol (https://iterm2.com/documentation-images.html). - # * [+artifact+] Display the screenshot in the terminal, using the terminal - # artifact format (https://buildkite.github.io/terminal-to-html/inline-images/). def take_screenshot(html: false, screenshot: nil) showing_html = html || html_from_env? @@ -37,10 +47,9 @@ def take_screenshot(html: false, screenshot: nil) show display_image(html: showing_html, screenshot_output: screenshot) end - # Takes a screenshot of the current page in the browser if the test - # failed. + # Takes a screenshot of the current page in the browser if the test failed. # - # +take_failed_screenshot+ is called during system test teardown. + # `take_failed_screenshot` is called during system test teardown. def take_failed_screenshot return unless failed? && supports_screenshot? && Capybara::Session.instance_created? diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb index 68d99a2a99..f0eb345942 100644 --- a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module SystemTesting module TestHelpers diff --git a/actionpack/lib/action_dispatch/testing/assertion_response.rb b/actionpack/lib/action_dispatch/testing/assertion_response.rb index 4363fc868a..bc81475b2d 100644 --- a/actionpack/lib/action_dispatch/testing/assertion_response.rb +++ b/actionpack/lib/action_dispatch/testing/assertion_response.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true +# :markup: markdown + 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. + # 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 @@ -14,9 +16,9 @@ class AssertionResponse 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). + # 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.rb b/actionpack/lib/action_dispatch/testing/assertions.rb index 5c4db02dc1..e32858ddcc 100644 --- a/actionpack/lib/action_dispatch/testing/assertions.rb +++ b/actionpack/lib/action_dispatch/testing/assertions.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "rails-dom-testing" require "action_dispatch/testing/assertions/response" require "action_dispatch/testing/assertions/routing" diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index a20940a2e2..d0ec6b7f76 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true +# :markup: markdown + module ActionDispatch module Assertions - # A small suite of assertions that test responses from \Rails applications. + # A small suite of assertions that test responses from Rails applications. module ResponseAssertions RESPONSE_PREDICATES = { # :nodoc: success: :successful?, @@ -13,20 +15,21 @@ module ResponseAssertions # Asserts that the response is one of the following types: # - # * :success - Status code was in the 200-299 range - # * :redirect - Status code was in the 300-399 range - # * :missing - Status code was 404 - # * :error - Status code was in the 500-599 range + # * `:success` - Status code was in the 200-299 range + # * `:redirect` - Status code was in the 300-399 range + # * `:missing` - Status code was 404 + # * `:error` - Status code was in the 500-599 range # - # You can also pass an explicit status number like assert_response(501) - # or its symbolic equivalent assert_response(:not_implemented). - # See +Rack::Utils::SYMBOL_TO_STATUS_CODE+ for a full list. # - # # Asserts that the response was a redirection - # assert_response :redirect + # You can also pass an explicit status number like `assert_response(501)` or its + # symbolic equivalent `assert_response(:not_implemented)`. See + # `Rack::Utils::SYMBOL_TO_STATUS_CODE` for a full list. # - # # Asserts that the response code was status code 401 (unauthorized) - # assert_response 401 + # # Asserts that the response was a redirection + # assert_response :redirect + # + # # Asserts that the response code was status code 401 (unauthorized) + # assert_response 401 def assert_response(type, message = nil) message ||= generate_response_message(type) @@ -39,21 +42,21 @@ def assert_response(type, message = nil) # Asserts that the response is a redirect to a URL matching the given options. # - # # Asserts that the redirection was to the "index" action on the WeblogController - # assert_redirected_to controller: "weblog", action: "index" + # # Asserts that the redirection was to the "index" action on the WeblogController + # assert_redirected_to controller: "weblog", action: "index" # - # # Asserts that the redirection was to the named route login_url - # assert_redirected_to login_url + # # Asserts that the redirection was to the named route login_url + # assert_redirected_to login_url # - # # Asserts that the redirection was to the URL for @customer - # assert_redirected_to @customer + # # Asserts that the redirection was to the URL for @customer + # assert_redirected_to @customer # - # # Asserts that the redirection matches the regular expression - # assert_redirected_to %r(\Ahttp://example.org) + # # Asserts that the redirection matches the regular expression + # assert_redirected_to %r(\Ahttp://example.org) # - # # Asserts that the redirection has the HTTP status code 301 (Moved - # # Permanently). - # assert_redirected_to "/some/path", status: :moved_permanently + # # Asserts that the redirection has the HTTP status code 301 (Moved + # # Permanently). + # assert_redirected_to "/some/path", status: :moved_permanently def assert_redirected_to(url_options = {}, options = {}, message = nil) options, message = {}, options unless options.is_a?(Hash) diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index d76f46fa50..bef88b1bdf 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "uri" require "active_support/core_ext/hash/indifferent_access" require "active_support/core_ext/string/access" @@ -7,7 +9,8 @@ module ActionDispatch module Assertions - # Suite of assertions to test routes generated by \Rails and the handling of requests made to them. + # Suite of assertions to test routes generated by Rails and the handling of + # requests made to them. module RoutingAssertions extend ActiveSupport::Concern @@ -71,18 +74,17 @@ def reset_routes(old_routes, old_integration_session) end module ClassMethods - # A helper to make it easier to test different route configurations. - # This method temporarily replaces @routes with a new RouteSet instance - # before each test. + # A helper to make it easier to test different route configurations. This method + # temporarily replaces @routes with a new RouteSet instance before each test. # - # The new instance is yielded to the passed block. Typically the block - # will create some routes using set.draw { match ... }: + # The new instance is yielded to the passed block. Typically the block will + # create some routes using `set.draw { match ... }`: # - # with_routing do |set| - # set.draw do - # resources :users + # with_routing do |set| + # set.draw do + # resources :users + # end # end - # end # def with_routing(&block) old_routes, old_controller = nil @@ -103,18 +105,18 @@ def setup # :nodoc: super end - # A helper to make it easier to test different route configurations. - # This method temporarily replaces @routes with a new RouteSet instance. + # A helper to make it easier to test different route configurations. This method + # temporarily replaces @routes with a new RouteSet instance. # - # The new instance is yielded to the passed block. Typically the block - # will create some routes using set.draw { match ... }: + # The new instance is yielded to the passed block. Typically the block will + # create some routes using `set.draw { match ... }`: # - # with_routing do |set| - # set.draw do - # resources :users + # with_routing do |set| + # set.draw do + # resources :users + # end + # assert_equal "/users", users_path # end - # assert_equal "/users", users_path - # end # def with_routing(&block) old_routes, old_controller = @routes, @controller @@ -123,36 +125,42 @@ def with_routing(&block) reset_routes(old_routes, old_controller) end - # 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+. + # 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`. # - # Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes - # requiring a specific HTTP method. The hash should contain a +:path+ with the incoming request path - # and a +:method+ containing the required HTTP verb. + # Pass a hash in the second argument (`path`) to specify the request method. + # This is useful for routes requiring a specific HTTP method. The hash should + # contain a `:path` with the incoming request path and a `:method` containing + # the required HTTP verb. # - # # Asserts that POSTing to /items will call the create action on ItemsController - # assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post}) + # # Asserts that POSTing to /items will call the create action on ItemsController + # assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post}) # - # You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used - # to assert that values in the query string will end up in the params hash correctly. To test query strings you must use the extras - # argument because appending the query string on the path directly will not work. For example: + # You can also pass in `extras` with a hash containing URL parameters that would + # normally be in the query string. This can be used to assert that values in the + # query string will end up in the params hash correctly. To test query strings + # you must use the extras argument because appending the query string on the + # path directly will not work. For example: # - # # Asserts that a path of '/items/list/1?view=print' returns the correct options - # assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" }) + # # Asserts that a path of '/items/list/1?view=print' returns the correct options + # assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" }) # - # The +message+ parameter allows you to pass in an error message that is displayed upon failure. + # The `message` parameter allows you to pass in an error message that is + # displayed upon failure. # - # # Check the default route (i.e., the index action) - # assert_recognizes({controller: 'items', action: 'index'}, 'items') + # # Check the default route (i.e., the index action) + # assert_recognizes({controller: 'items', action: 'index'}, 'items') # - # # Test a specific action - # assert_recognizes({controller: 'items', action: 'list'}, 'items/list') + # # Test a specific action + # assert_recognizes({controller: 'items', action: 'list'}, 'items/list') # - # # Test an action with a parameter - # assert_recognizes({controller: 'items', action: 'destroy', id: '1'}, 'items/destroy/1') + # # Test an action with a parameter + # assert_recognizes({controller: 'items', action: 'destroy', id: '1'}, 'items/destroy/1') # - # # Test a custom route - # assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1') + # # Test a custom route + # assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1') def assert_recognizes(expected_options, path, extras = {}, msg = nil) if path.is_a?(Hash) && path[:method].to_s == "all" [:get, :post, :put, :delete].each do |method| @@ -174,23 +182,25 @@ def assert_recognizes(expected_options, path, extras = {}, msg = nil) end end - # 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. + # 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. # - # The +defaults+ parameter is unused. + # The `defaults` parameter is unused. # - # # Asserts that the default action is generated for a route with no action - # assert_generates "/items", controller: "items", action: "index" + # # Asserts that the default action is generated for a route with no action + # assert_generates "/items", controller: "items", action: "index" # - # # Tests that the list action is properly routed - # assert_generates "/items/list", controller: "items", action: "list" + # # Tests that the list action is properly routed + # assert_generates "/items/list", controller: "items", action: "list" # - # # Tests the generation of a route with a parameter - # assert_generates "/items/list/1", { controller: "items", action: "list", id: "1" } + # # Tests the generation of a route with a parameter + # assert_generates "/items/list/1", { controller: "items", action: "list", id: "1" } # - # # Asserts that the generated route gives us our custom route - # assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" } + # # Asserts that the generated route gives us our custom route + # assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" } def assert_generates(expected_path, options, defaults = {}, extras = {}, message = nil) if expected_path.include?("://") fail_on(URI::InvalidURIError, message) do @@ -213,27 +223,28 @@ def assert_generates(expected_path, options, defaults = {}, extras = {}, message assert_equal(expected_path, generated_path, msg) end - # Asserts that path and options match both ways; in other words, it verifies that path generates - # options and then that options generates path. This essentially combines +assert_recognizes+ - # and +assert_generates+ into one step. + # Asserts that path and options match both ways; in other words, it verifies + # that `path` generates `options` and then that `options` generates `path`. This + # essentially combines `assert_recognizes` and `assert_generates` into one step. # - # The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The - # +message+ parameter allows you to specify a custom error message to display upon failure. + # The `extras` hash allows you to specify options that would normally be + # provided as a query string to the action. The `message` parameter allows you + # to specify a custom error message to display upon failure. # - # # Asserts a basic route: a controller with the default action (index) - # assert_routing '/home', controller: 'home', action: 'index' + # # Asserts a basic route: a controller with the default action (index) + # assert_routing '/home', controller: 'home', action: 'index' # - # # Test a route generated with a specific controller, action, and parameter (id) - # assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23 + # # Test a route generated with a specific controller, action, and parameter (id) + # assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23 # - # # Asserts a basic route (controller + default action), with an error message if it fails - # assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly' + # # Asserts a basic route (controller + default action), with an error message if it fails + # assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly' # - # # 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, providing a defaults hash + # assert_routing 'controller/action/9', {id: "9", item: "square"}, {controller: "controller", action: "action"}, {}, {item: "square"} # - # # Tests a route with an HTTP method - # assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" }) + # # 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 0a2aeb530b..1acec055f7 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "stringio" require "uri" require "rack/test" @@ -10,54 +12,53 @@ module ActionDispatch module Integration # :nodoc: module RequestHelpers - # Performs a GET request with the given parameters. See ActionDispatch::Integration::Session#process - # for more details. + # Performs a GET request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. def get(path, **args) process(:get, path, **args) end - # Performs a POST request with the given parameters. See ActionDispatch::Integration::Session#process - # for more details. + # Performs a POST request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. def post(path, **args) process(:post, path, **args) end - # Performs a PATCH request with the given parameters. See ActionDispatch::Integration::Session#process - # for more details. + # Performs a PATCH request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. def patch(path, **args) process(:patch, path, **args) end - # Performs a PUT request with the given parameters. See ActionDispatch::Integration::Session#process - # for more details. + # Performs a PUT request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. def put(path, **args) process(:put, path, **args) end - # Performs a DELETE request with the given parameters. See ActionDispatch::Integration::Session#process - # for more details. + # Performs a DELETE request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. def delete(path, **args) process(:delete, path, **args) end - # Performs a HEAD request with the given parameters. See ActionDispatch::Integration::Session#process - # for more details. + # Performs a HEAD request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. def head(path, **args) process(:head, path, **args) end - # Performs an OPTIONS request with the given parameters. See ActionDispatch::Integration::Session#process - # for more details. + # Performs an OPTIONS request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. def options(path, **args) process(:options, path, **args) end - # Follow a single redirect response. If the last response was not a - # redirect, an exception will be raised. Otherwise, the redirect is - # performed on the location header. If the redirection is a 307 or 308 redirect, - # the same HTTP verb will be used when redirecting, otherwise a GET request - # will be performed. Any arguments are passed to the - # underlying request. + # Follow a single redirect response. If the last response was not a redirect, an + # exception will be raised. Otherwise, the redirect is performed on the location + # header. If the redirection is a 307 or 308 redirect, the same HTTP verb will + # be used when redirecting, otherwise a GET request will be performed. Any + # arguments are passed to the underlying request. # # The HTTP_REFERER header will be set to the previous url. def follow_redirect!(headers: {}, **args) @@ -79,13 +80,13 @@ def follow_redirect!(headers: {}, **args) end end - # An instance of this class represents a set of requests and responses - # performed sequentially by a test process. Because you can instantiate - # multiple sessions and run them side-by-side, you can also mimic (to some - # limited extent) multiple simultaneous users interacting with your system. + # An instance of this class represents a set of requests and responses performed + # sequentially by a test process. Because you can instantiate multiple sessions + # and run them side-by-side, you can also mimic (to some limited extent) + # multiple simultaneous users interacting with your system. # # Typically, you will instantiate a new session using Runner#open_session, - # rather than instantiating a \Session directly. + # rather than instantiating a Session directly. class Session DEFAULT_HOST = "www.example.com" @@ -107,8 +108,8 @@ def host # The Accept header to send. attr_accessor :accept - # A map of the cookies returned by the last response, and which will be - # sent with the next request. + # A map of the cookies returned by the last response, and which will be sent + # with the next request. def cookies _mock_session.cookie_jar end @@ -127,7 +128,7 @@ def cookies include ActionDispatch::Routing::UrlFor - # Create and initialize a new \Session instance. + # Create and initialize a new Session instance. def initialize(app) super() @app = app @@ -147,11 +148,10 @@ def url_options end end - # Resets the instance. This can be used to reset the state information - # in an existing session instance, so it can be used from a clean-slate - # condition. + # Resets the instance. This can be used to reset the state information in an + # existing session instance, so it can be used from a clean-slate condition. # - # session.reset! + # session.reset! def reset! @https = false @controller = @request = @response = nil @@ -166,63 +166,61 @@ def reset! "*/*;q=0.5" unless defined? @named_routes_configured - # the helpers are made protected by default--we make them public for - # easier access during testing and troubleshooting. + # the helpers are made protected by default--we make them public for easier + # access during testing and troubleshooting. @named_routes_configured = true end end # Specify whether or not the session should mimic a secure HTTPS request. # - # session.https! - # session.https!(false) + # session.https! + # session.https!(false) def https!(flag = true) @https = flag end - # Returns +true+ if the session is mimicking a secure HTTPS request. + # Returns `true` if the session is mimicking a secure HTTPS request. # - # if session.https? - # ... - # end + # if session.https? + # ... + # end def https? @https end # Performs the actual request. # - # - +method+: The HTTP method (GET, POST, PATCH, PUT, DELETE, HEAD, OPTIONS) - # as a symbol. - # - +path+: The URI (as a String) on which you want to perform the - # request. - # - +params+: The HTTP parameters that you want to pass. This may - # be +nil+, - # a Hash, or a String that is appropriately encoded - # (application/x-www-form-urlencoded or - # multipart/form-data). - # - +headers+: Additional headers to pass, as a Hash. The headers will be - # merged into the Rack env hash. - # - +env+: Additional env to pass, as a Hash. The headers will be - # merged into the Rack env hash. - # - +xhr+: Set to +true+ if you want to make an Ajax request. - # Adds request headers characteristic of XMLHttpRequest e.g. HTTP_X_REQUESTED_WITH. - # The headers will be merged into the Rack env hash. - # - +as+: Used for encoding the request with different content type. - # Supports +:json+ by default and will set the appropriate request headers. - # The headers will be merged into the Rack env hash. + # * `method`: The HTTP method (GET, POST, PATCH, PUT, DELETE, HEAD, OPTIONS) + # as a symbol. + # * `path`: The URI (as a String) on which you want to perform the request. + # * `params`: The HTTP parameters that you want to pass. This may be `nil`, a + # Hash, or a String that is appropriately encoded + # (`application/x-www-form-urlencoded` or `multipart/form-data`). + # * `headers`: Additional headers to pass, as a Hash. The headers will be + # merged into the Rack env hash. + # * `env`: Additional env to pass, as a Hash. The headers will be merged into + # the Rack env hash. + # * `xhr`: Set to `true` if you want to make an Ajax request. Adds request + # headers characteristic of XMLHttpRequest e.g. HTTP_X_REQUESTED_WITH. The + # headers will be merged into the Rack env hash. + # * `as`: Used for encoding the request with different content type. Supports + # `:json` by default and will set the appropriate request headers. The + # headers will be merged into the Rack env hash. + # # # This method is rarely used directly. Use RequestHelpers#get, - # RequestHelpers#post, or other standard HTTP methods in integration - # tests. +#process+ is only required when using a request method that - # doesn't have a method defined in the integration tests. + # RequestHelpers#post, or other standard HTTP methods in integration tests. + # `#process` is only required when using a request method that doesn't have a + # method defined in the integration tests. # # This method returns the response status, after performing the request. - # Furthermore, if this method was called from an ActionDispatch::IntegrationTest object, - # then that object's @response instance variable will point to a Response object - # which one can use to inspect the details of the response. + # Furthermore, if this method was called from an ActionDispatch::IntegrationTest + # object, then that object's `@response` instance variable will point to a + # Response object which one can use to inspect the details of the response. # # Example: - # process :get, '/author', params: { since: 201501011400 } + # process :get, '/author', params: { since: 201501011400 } def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil) request_encoder = RequestEncoder.encoder(as) headers ||= {} @@ -283,8 +281,8 @@ def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: n session = Rack::Test::Session.new(_mock_session) - # NOTE: rack-test v0.5 doesn't build a default uri correctly - # Make sure requested path is always a full URI. + # NOTE: rack-test v0.5 doesn't build a default uri correctly Make sure requested + # path is always a full URI. session.request(build_full_uri(path, request_env), request_env) @request_count += 1 @@ -302,7 +300,7 @@ def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: n # Set the host name to use in the next request. # - # session.host! "www.example.com" + # session.host! "www.example.com" alias :host! :host= private @@ -344,16 +342,16 @@ def integration_session @integration_session ||= create_session(app) end - # Reset the current session. This is useful for testing multiple sessions - # in a single test case. + # Reset the current session. This is useful for testing multiple sessions in a + # single test case. def reset! @integration_session = create_session(app) end def create_session(app) klass = APP_SESSIONS[app] ||= Class.new(Integration::Session) { - # If the app is a Rails app, make url_helpers available on the session. - # This makes app.url_for and app.foo_path available in the console. + # If the app is a Rails app, make url_helpers available on the session. This + # makes app.url_for and app.foo_path available in the console. if app.respond_to?(:routes) && app.routes.is_a?(ActionDispatch::Routing::RouteSet) include app.routes.url_helpers include app.routes.mounted_helpers @@ -383,16 +381,15 @@ def #{method}(...) RUBY end - # Open a new session instance. If a block is given, the new session is - # yielded to the block before being returned. + # Open a new session instance. If a block is given, the new session is yielded + # to the block before being returned. # - # session = open_session do |sess| - # sess.extend(CustomAssertions) - # end + # session = open_session do |sess| + # sess.extend(CustomAssertions) + # end # - # By default, a single session is automatically created for you, but you - # can use this method to open multiple sessions that ought to be tested - # simultaneously. + # By default, a single session is automatically created for you, but you can use + # this method to open multiple sessions that ought to be tested simultaneously. def open_session dup.tap do |session| session.reset! @@ -409,8 +406,8 @@ def assertions=(assertions) # :nodoc: root_session ? root_session.assertions = assertions : super end - # Copy the instance variables from the current session instance into the - # test instance. + # Copy the instance variables from the current session instance into the test + # instance. def copy_session_variables! # :nodoc: @controller = @integration_session.controller @response = @integration_session.response @@ -443,199 +440,201 @@ def method_missing(method, ...) end end - # An integration test spans multiple controllers and actions, - # tying them all together to ensure they work together as expected. It tests - # more completely than either unit or functional tests do, exercising the - # entire stack, from the dispatcher to the database. + # An integration test spans multiple controllers and actions, tying them all + # together to ensure they work together as expected. It tests more completely + # than either unit or functional tests do, exercising the entire stack, from the + # dispatcher to the database. # - # At its simplest, you simply extend IntegrationTest and write your - # tests using the Integration::RequestHelpers#get and/or + # At its simplest, you simply extend `IntegrationTest` and write your tests + # using the Integration::RequestHelpers#get and/or # Integration::RequestHelpers#post methods: # - # require "test_helper" + # require "test_helper" # - # class ExampleTest < ActionDispatch::IntegrationTest - # fixtures :people + # class ExampleTest < ActionDispatch::IntegrationTest + # fixtures :people # - # def test_login - # # get the login page - # get "/login" - # assert_equal 200, status + # def test_login + # # get the login page + # get "/login" + # assert_equal 200, status # - # # post the login and follow through to the home page - # post "/login", params: { username: people(:jamis).username, - # password: people(:jamis).password } - # follow_redirect! - # assert_equal 200, status - # assert_equal "/home", path - # end - # end - # - # However, you can also have multiple session instances open per test, and - # even extend those instances with assertions and methods to create a very - # powerful testing DSL that is specific for your application. You can even - # reference any named routes you happen to have defined. - # - # require "test_helper" - # - # class AdvancedTest < ActionDispatch::IntegrationTest - # fixtures :people, :rooms - # - # def test_login_and_speak - # jamis, david = login(:jamis), login(:david) - # room = rooms(:office) - # - # jamis.enter(room) - # jamis.speak(room, "anybody home?") - # - # david.enter(room) - # david.speak(room, "hello!") + # # post the login and follow through to the home page + # post "/login", params: { username: people(:jamis).username, + # password: people(:jamis).password } + # follow_redirect! + # assert_equal 200, status + # assert_equal "/home", path + # end # end # - # private + # However, you can also have multiple session instances open per test, and even + # extend those instances with assertions and methods to create a very powerful + # testing DSL that is specific for your application. You can even reference any + # named routes you happen to have defined. # - # module CustomAssertions - # def enter(room) - # # reference a named route, for maximum internal consistency! - # get(room_url(id: room.id)) - # assert(...) - # ... - # end + # require "test_helper" # - # def speak(room, message) - # post "/say/#{room.id}", xhr: true, params: { message: message } - # assert(...) - # ... - # end + # class AdvancedTest < ActionDispatch::IntegrationTest + # fixtures :people, :rooms + # + # def test_login_and_speak + # jamis, david = login(:jamis), login(:david) + # room = rooms(:office) + # + # jamis.enter(room) + # jamis.speak(room, "anybody home?") + # + # david.enter(room) + # david.speak(room, "hello!") # end # - # def login(who) - # open_session do |sess| - # sess.extend(CustomAssertions) - # who = people(who) - # sess.post "/login", params: { username: who.username, - # password: who.password } - # assert(...) + # private + # + # module CustomAssertions + # def enter(room) + # # reference a named route, for maximum internal consistency! + # get(room_url(id: room.id)) + # assert(...) + # ... + # end + # + # def speak(room, message) + # post "/say/#{room.id}", xhr: true, params: { message: message } + # assert(...) + # ... + # end # end - # end - # end + # + # def login(who) + # open_session do |sess| + # sess.extend(CustomAssertions) + # who = people(who) + # sess.post "/login", params: { username: who.username, + # password: who.password } + # assert(...) + # end + # end + # end # # Another longer example would be: # # A simple integration test that exercises multiple controllers: # - # require "test_helper" + # require "test_helper" # - # class UserFlowsTest < ActionDispatch::IntegrationTest - # test "login and browse site" do - # # login via https - # https! - # get "/login" - # assert_response :success + # class UserFlowsTest < ActionDispatch::IntegrationTest + # test "login and browse site" do + # # login via https + # https! + # get "/login" + # assert_response :success # - # post "/login", params: { username: users(:david).username, password: users(:david).password } - # follow_redirect! - # assert_equal '/welcome', path - # assert_equal 'Welcome david!', flash[:notice] + # post "/login", params: { username: users(:david).username, password: users(:david).password } + # follow_redirect! + # assert_equal '/welcome', path + # assert_equal 'Welcome david!', flash[:notice] # - # https!(false) - # get "/articles/all" - # assert_response :success - # assert_select 'h1', 'Articles' + # https!(false) + # get "/articles/all" + # assert_response :success + # assert_select 'h1', 'Articles' + # end # end - # end # # As you can see the integration test involves multiple controllers and # exercises the entire stack from database to dispatcher. In addition you can - # have multiple session instances open simultaneously in a test and extend - # those instances with assertion methods to create a very powerful testing - # DSL (domain-specific language) just for your application. + # have multiple session instances open simultaneously in a test and extend those + # instances with assertion methods to create a very powerful testing DSL + # (domain-specific language) just for your application. # # Here's an example of multiple sessions and custom DSL in an integration test # - # require "test_helper" + # require "test_helper" # - # class UserFlowsTest < ActionDispatch::IntegrationTest - # test "login and browse site" do - # # User david logs in - # david = login(:david) - # # User guest logs in - # guest = login(:guest) + # class UserFlowsTest < ActionDispatch::IntegrationTest + # test "login and browse site" do + # # User david logs in + # david = login(:david) + # # User guest logs in + # guest = login(:guest) # - # # Both are now available in different sessions - # assert_equal 'Welcome david!', david.flash[:notice] - # assert_equal 'Welcome guest!', guest.flash[:notice] + # # Both are now available in different sessions + # assert_equal 'Welcome david!', david.flash[:notice] + # assert_equal 'Welcome guest!', guest.flash[:notice] # - # # User david can browse site - # david.browses_site - # # User guest can browse site as well - # guest.browses_site + # # User david can browse site + # david.browses_site + # # User guest can browse site as well + # guest.browses_site # - # # Continue with other assertions + # # Continue with other assertions + # end + # + # private + # + # module CustomDsl + # def browses_site + # get "/products/all" + # assert_response :success + # assert_select 'h1', 'Products' + # end + # end + # + # def login(user) + # open_session do |sess| + # sess.extend(CustomDsl) + # u = users(user) + # sess.https! + # sess.post "/login", params: { username: u.username, password: u.password } + # assert_equal '/welcome', sess.path + # sess.https!(false) + # end + # end # end # - # private + # See the [request helpers documentation] + # (rdoc-ref:ActionDispatch::Integration::RequestHelpers) for help + # on how to use `get`, etc. # - # module CustomDsl - # def browses_site - # get "/products/all" - # assert_response :success - # assert_select 'h1', 'Products' + # ### Changing the request encoding + # + # 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: Article.last.id, title: "Ahoy!" }, response.parsed_body) # end - # - # def login(user) - # open_session do |sess| - # sess.extend(CustomDsl) - # u = users(user) - # sess.https! - # sess.post "/login", params: { username: u.username, password: u.password } - # assert_equal '/welcome', sess.path - # sess.https!(false) - # end - # end - # end - # - # See the {request helpers documentation}[rdoc-ref:ActionDispatch::Integration::RequestHelpers] for help on how to - # use +get+, etc. - # - # === Changing the request encoding - # - # 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: Article.last.id, title: "Ahoy!" }, response.parsed_body) # end - # end # - # The +as+ option passes an "application/json" Accept header (thereby setting + # The `as` option passes an "application/json" Accept header (thereby setting # the request format to JSON unless overridden), sets the content type to # "application/json" and encodes the parameters as JSON. # - # Calling TestResponse#parsed_body on the response parses the response body based on the - # last response MIME type. + # Calling TestResponse#parsed_body on the response parses the response body + # based on the last response MIME type. # - # Out of the box, only :json is supported. But for any custom MIME - # types you've registered, you can add your own encoders with: + # Out of the box, only `:json` is supported. But for any custom MIME types + # you've registered, you can add your own encoders with: # - # ActionDispatch::IntegrationTest.register_encoder :wibble, - # param_encoder: -> params { params.to_wibble }, - # response_parser: -> body { body } + # 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 + # Where `param_encoder` defines how the params should be encoded and + # `response_parser` defines how the response body should be parsed through # TestResponse#parsed_body. # - # Consult the {Rails Testing Guide}[https://guides.rubyonrails.org/testing.html] for more. + # Consult the [Rails Testing Guide](https://guides.rubyonrails.org/testing.html) + # for more. class IntegrationTest < ActiveSupport::TestCase include TestProcess::FixtureFile diff --git a/actionpack/lib/action_dispatch/testing/request_encoder.rb b/actionpack/lib/action_dispatch/testing/request_encoder.rb index b1b0795cb5..f1b6ad82e8 100644 --- a/actionpack/lib/action_dispatch/testing/request_encoder.rb +++ b/actionpack/lib/action_dispatch/testing/request_encoder.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "nokogiri" module ActionDispatch diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index 4c340b3cd3..1905aafaef 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -1,21 +1,25 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/middleware/cookies" require "action_dispatch/middleware/flash" module ActionDispatch module TestProcess module FixtureFile - # Shortcut for Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.file_fixture_path, path), type): + # Shortcut for + # `Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.file_f + # ixture_path, path), type)`: # - # post :change_avatar, params: { avatar: file_fixture_upload('david.png', 'image/png') } + # post :change_avatar, params: { avatar: file_fixture_upload('david.png', 'image/png') } # - # Default fixture files location is test/fixtures/files. + # Default fixture files location is `test/fixtures/files`. # - # To upload binary files on Windows, pass :binary as the last parameter. - # This will not affect other platforms: + # To upload binary files on Windows, pass `:binary` as the last parameter. This + # will not affect other platforms: # - # post :change_avatar, params: { avatar: file_fixture_upload('david.png', 'image/png', :binary) } + # post :change_avatar, params: { avatar: file_fixture_upload('david.png', 'image/png', :binary) } def file_fixture_upload(path, mime_type = nil, binary = false) if self.class.file_fixture_path && !File.exist?(path) path = file_fixture(path) diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index a6a9afa21b..9609640c08 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# :markup: markdown + require "active_support/core_ext/hash/indifferent_access" require "rack/utils" @@ -11,7 +13,7 @@ class TestRequest < Request "HTTP_USER_AGENT" => "Rails Testing".b, ) - # Create a new test request with default +env+ values. + # Create a new test request with default `env` values. def self.create(env = {}) env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application env["rack.request.cookie_hash"] ||= {}.with_indifferent_access diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb index ed7aa7eda5..a5adf866e1 100644 --- a/actionpack/lib/action_dispatch/testing/test_response.rb +++ b/actionpack/lib/action_dispatch/testing/test_response.rb @@ -1,12 +1,13 @@ # frozen_string_literal: true +# :markup: markdown + require "action_dispatch/testing/request_encoder" module ActionDispatch - # Integration test methods such as Integration::RequestHelpers#get - # and Integration::RequestHelpers#post return objects of class - # TestResponse, which represent the HTTP response results of the requested - # controller actions. + # Integration test methods such as Integration::RequestHelpers#get and + # Integration::RequestHelpers#post return objects of class TestResponse, which + # represent the HTTP response results of the requested controller actions. # # See Response for more information on controller response objects. class TestResponse < Response @@ -17,35 +18,35 @@ def self.from_response(response) # Returns a parsed body depending on the response MIME type. When a parser # corresponding to the MIME type is not found, it returns the raw body. # - # ==== Examples - # get "/posts" - # response.content_type # => "text/html; charset=utf-8" - # response.parsed_body.class # => Nokogiri::HTML5::Document - # response.parsed_body.to_html # => "\n\n..." + # #### Examples + # get "/posts" + # response.content_type # => "text/html; charset=utf-8" + # response.parsed_body.class # => Nokogiri::HTML5::Document + # response.parsed_body.to_html # => "\n\n..." # - # assert_pattern { response.parsed_body.at("main") => { content: "Hello, world" } } + # assert_pattern { response.parsed_body.at("main") => { content: "Hello, world" } } # - # response.parsed_body.at("main") => {name:, content:} - # assert_equal "main", name - # assert_equal "Some main content", content + # response.parsed_body.at("main") => {name:, content:} + # assert_equal "main", name + # assert_equal "Some main content", content # - # get "/posts.json" - # response.content_type # => "application/json; charset=utf-8" - # response.parsed_body.class # => Array - # response.parsed_body # => [{"id"=>42, "title"=>"Title"},... + # get "/posts.json" + # response.content_type # => "application/json; charset=utf-8" + # response.parsed_body.class # => Array + # response.parsed_body # => [{"id"=>42, "title"=>"Title"},... # - # assert_pattern { response.parsed_body => [{ id: 42 }] } + # assert_pattern { response.parsed_body => [{ id: 42 }] } # - # get "/posts/42.json" - # response.content_type # => "application/json; charset=utf-8" - # response.parsed_body.class # => ActiveSupport::HashWithIndifferentAccess - # response.parsed_body # => {"id"=>42, "title"=>"Title"} + # get "/posts/42.json" + # response.content_type # => "application/json; charset=utf-8" + # response.parsed_body.class # => ActiveSupport::HashWithIndifferentAccess + # response.parsed_body # => {"id"=>42, "title"=>"Title"} # - # assert_pattern { response.parsed_body => [{ title: /title/i }] } + # assert_pattern { response.parsed_body => [{ title: /title/i }] } # - # response.parsed_body => {id:, title:} - # assert_equal 42, id - # assert_equal "Title", title + # response.parsed_body => {id:, title:} + # assert_equal 42, id + # assert_equal "Title", title def parsed_body @parsed_body ||= response_parser.call(body) end diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb index 057124751d..53cd40d189 100644 --- a/actionpack/lib/action_pack.rb +++ b/actionpack/lib/action_pack.rb @@ -3,24 +3,25 @@ #-- # Copyright (c) David Heinemeier Hansson # -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: +# 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 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. +# 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. #++ +# :markup: markdown + require "action_pack/version" diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb index 5d6e1aca1d..7d5795dc78 100644 --- a/actionpack/lib/action_pack/gem_version.rb +++ b/actionpack/lib/action_pack/gem_version.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true +# :markup: markdown + module ActionPack - # Returns the currently loaded version of Action Pack as a +Gem::Version+. + # Returns the currently loaded version of Action Pack as a `Gem::Version`. def self.gem_version Gem::Version.new VERSION::STRING end diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 6cf9424065..1d5cfac807 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true +# :markup: markdown + require_relative "gem_version" module ActionPack - # Returns the currently loaded version of Action Pack as a +Gem::Version+. + # Returns the currently loaded version of Action Pack as a `Gem::Version`. def self.version gem_version end