Merge pull request #50879 from rails/rm-docs-actionpack
Transform actionpack documentation to Markdown
This commit is contained in:
commit
6929568774
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
require "action_pack"
|
||||
require "active_support"
|
||||
require "active_support/rails"
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module AbstractController
|
||||
module AssetPaths # :nodoc:
|
||||
extend ActiveSupport::Concern
|
||||
|
@ -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
|
||||
# * <tt>Set</tt> - 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
|
||||
# * <tt>String</tt>
|
||||
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
|
||||
# * <tt>self</tt>
|
||||
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 <tt>action_methods.include?("foo")</tt> may return
|
||||
# false and <tt>available_action?("foo")</tt> 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
|
||||
# * <tt>action_name</tt> - 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
|
||||
# * <tt>name</tt> - 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
|
||||
# * <tt>action_name</tt> - An action name to find a method name for
|
||||
# #### Parameters
|
||||
# * `action_name` - An action name to find a method name for
|
||||
#
|
||||
# ==== Returns
|
||||
# * <tt>string</tt> - 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
|
||||
# * <tt>action_name</tt> - 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
|
||||
# * <tt>string</tt> - The name of the method that handles the action
|
||||
# * <tt>nil</tt> - No method name could be found.
|
||||
def method_for_action(action_name)
|
||||
if action_method?(action_name)
|
||||
action_name
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module AbstractController
|
||||
module Caching
|
||||
extend ActiveSupport::Concern
|
||||
|
@ -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 <tt>:views</tt>,
|
||||
# followed by <tt>ENV["RAILS_CACHE_ID"]</tt> or <tt>ENV["RAILS_APP_VERSION"]</tt> 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
|
||||
# <tt>pages/45/notes</tt>.
|
||||
# * Hash - Treated as an implicit call to +url_for+, like
|
||||
# <tt>{ controller: 'pages', action: 'notes', id: 45}</tt>
|
||||
# * Regexp - Will remove any fragment that matches, so
|
||||
# <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you
|
||||
# don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because
|
||||
# the actual filename matched looks like
|
||||
# <tt>./cache/filename/path.cache</tt>. 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 <tt>delete_matched</tt>, 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)
|
||||
|
@ -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:
|
||||
#
|
||||
# * <tt>after_action</tt>
|
||||
# * <tt>append_after_action</tt>
|
||||
# * <tt>append_around_action</tt>
|
||||
# * <tt>append_before_action</tt>
|
||||
# * <tt>around_action</tt>
|
||||
# * <tt>before_action</tt>
|
||||
# * <tt>prepend_after_action</tt>
|
||||
# * <tt>prepend_around_action</tt>
|
||||
# * <tt>prepend_before_action</tt>
|
||||
# * <tt>skip_after_action</tt>
|
||||
# * <tt>skip_around_action</tt>
|
||||
# * <tt>skip_before_action</tt>
|
||||
# * `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 <tt>:only => :index</tt> gets converted to
|
||||
# <tt>:if => proc {|c| c.action_name == "index" }</tt>.
|
||||
# The basic idea is that `:only => :index` gets converted to `:if => proc {|c|
|
||||
# c.action_name == "index" }`.
|
||||
#
|
||||
# Note that <tt>:only</tt> has priority over <tt>:if</tt> 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 <tt>:if</tt> has priority over <tt>:except</tt> 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
|
||||
# * <tt>only</tt> - The callback should be run only for this action.
|
||||
# * <tt>except</tt> - The callback should be run for all actions except this action.
|
||||
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
|
||||
# * <tt>callbacks</tt> - An array of callbacks, with an optional
|
||||
# options hash as the last parameter.
|
||||
# * <tt>block</tt> - 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
|
||||
# * <tt>name</tt> - The callback to be added.
|
||||
# * <tt>options</tt> - 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 <tt>AbstractController::Base#process_action</tt> to run the
|
||||
# <tt>process_action</tt> 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
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
require "action_dispatch/http/mime_type"
|
||||
|
||||
module AbstractController
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module AbstractController
|
||||
def self.deprecator # :nodoc:
|
||||
@deprecator ||= ActiveSupport::Deprecation.new
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module AbstractController
|
||||
class Error < StandardError # :nodoc:
|
||||
end
|
||||
|
@ -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
|
||||
# * <tt>method[, method]</tt> - 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 <tt>"foo".camelize</tt> 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 <tt>"foo/bar".camelize</tt> 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
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
require "active_support/benchmarkable"
|
||||
|
||||
module AbstractController
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
require "active_support/core_ext/module/introspection"
|
||||
|
||||
module AbstractController
|
||||
|
@ -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 <tt>self.response_body</tt>.
|
||||
# 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 <tt>render "foo"</tt> to
|
||||
# <tt>render action: "foo"</tt> and <tt>render "foo/bar"</tt> to
|
||||
# <tt>render file: "foo/bar"</tt>.
|
||||
# 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?
|
||||
|
@ -1,17 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
require "active_support/html_safe_translation"
|
||||
|
||||
module AbstractController
|
||||
module Translation
|
||||
# Delegates to <tt>I18n.translate</tt>.
|
||||
# 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 <tt>translate(".foo")</tt> from
|
||||
# <tt>PeopleController#index</tt>, it will convert the call to
|
||||
# <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
|
||||
# to translate many keys within the same controller / action and gives you a
|
||||
# simple framework for scoping them consistently.
|
||||
# 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 <tt>I18n.localize</tt>.
|
||||
# Delegates to `I18n.localize`.
|
||||
def localize(object, **options)
|
||||
I18n.localize(object, **options)
|
||||
end
|
||||
|
@ -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 <tt>_routes</tt> 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
|
||||
# <tt>_routes</tt> 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
|
||||
|
@ -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.
|
||||
#
|
||||
|
@ -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 <tt>render :json</tt> 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 <tt>render</tt> or <tt>redirect_to</tt> in
|
||||
# all actions, otherwise it will return <tt>204 No Content</tt>.
|
||||
# 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
|
||||
# <tt>redirect_to</tt> 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 <tt>MimeResponds</tt>. This
|
||||
# module gives you the <tt>respond_to</tt> method. Adding it is quite simple,
|
||||
# you just need to include the module in a specific controller or in
|
||||
# +ApplicationController+ in case you want it available in your entire
|
||||
# application:
|
||||
# `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
|
||||
]
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionController
|
||||
module ApiRendering
|
||||
extend ActiveSupport::Concern
|
||||
|
@ -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 <tt>app/views</tt> 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 <tt>app/views/posts/index.html.erb</tt> by default after populating the <tt>@posts</tt> 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
|
||||
# <tt>302 Moved</tt> 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
|
||||
# <input type="text" name="post[name]" value="david">
|
||||
# <input type="text" name="post[address]" value="hyacintvej">
|
||||
#
|
||||
# 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 <tt>params</tt> method which returns a hash. For example, an action that was performed through
|
||||
# <tt>/posts?category=All&limit=5</tt> will include <tt>{ "category" => "All", "limit" => "5" }</tt> in <tt>params</tt>.
|
||||
#
|
||||
# It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
|
||||
#
|
||||
# <input type="text" name="post[name]" value="david">
|
||||
# <input type="text" name="post[address]" value="hyacintvej">
|
||||
#
|
||||
# A request coming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
|
||||
# If the address input had been named <tt>post[address][street]</tt>, the <tt>params</tt> would have included
|
||||
# <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. 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 <tt>session</tt> 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 <tt>create</tt> 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 <tt>show</tt> 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 <tt>show</tt> 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 <tt>redirect_to</tt> 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
|
||||
]
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionController
|
||||
def self.deprecator # :nodoc:
|
||||
AbstractController.deprecator
|
||||
|
@ -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
|
||||
# * <tt>builder</tt> - 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
|
||||
|
@ -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)
|
||||
|
@ -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 <tt>config/routes.rb</tt>:
|
||||
# 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
|
||||
# <tt>response_body=</tt>, <tt>content_type=</tt>, and <tt>status=</tt>. 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
|
||||
# <tt>Controller</tt>. For instance, +PostsController+ returns <tt>posts</tt>.
|
||||
# Namespaces are left out, so +Admin::PostsController+ returns <tt>posts</tt> 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
|
||||
# * <tt>string</tt>
|
||||
# #### 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
|
||||
|
@ -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 <tt>only:</tt> or <tt>except:</tt>).
|
||||
# Only browsers matched in the hash or named set passed to <tt>versions:</tt> 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 <tt>:modern</tt> 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
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionController
|
||||
module BasicImplicitRender # :nodoc:
|
||||
def send_action(method, *args)
|
||||
|
@ -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
|
||||
# <tt>304 Not Modified</tt> 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 <tt>304 Not Modified</tt> 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 <tt>304 Not Modified</tt> 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 <tt>304 Not Modified</tt>
|
||||
# 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 <tt>template: false</tt> 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 <tt>304 Not Modified</tt> 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 <tt>maximum(:updated_at)</tt> (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 <tt>Cache-Control: public, no-cache</tt> 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 <tt>304 Not Modified</tt> 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 <tt>maximum(:updated_at)</tt> (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 <tt>Cache-Control: public, no-cache</tt> 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 <tt>no-cache</tt>. 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 <tt>no-store</tt>. 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
|
||||
|
@ -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
|
||||
# <tt>only: :index</tt> 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
|
||||
|
@ -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
|
||||
|
@ -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. <tt>send_file(params[:path])</tt> 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:
|
||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
||||
# Defaults to <tt>File.basename(path)</tt>.
|
||||
# * <tt>:type</tt> - specifies an HTTP content type.
|
||||
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example +:json+.
|
||||
# If omitted, the type will be inferred from the file extension specified in <tt>:filename</tt>.
|
||||
# If no content type is registered for the extension, the default type +application/octet-stream+ will be used.
|
||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
||||
# Valid values are <tt>"inline"</tt> and <tt>"attachment"</tt> (default).
|
||||
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
|
||||
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser to guess the filename from
|
||||
# the URL, which is necessary for i18n filenames on certain browsers
|
||||
# (setting <tt>:filename</tt> 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 <tt>Content-*</tt> 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
|
||||
# <tt>render plain: data</tt>, but also allows you to specify whether
|
||||
# the browser should display the response as a file attachment (i.e. in a
|
||||
# download dialog) or as inline data. You may also set the content type,
|
||||
# the 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:
|
||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
||||
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to +application/octet-stream+.
|
||||
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example +:json+.
|
||||
# If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
|
||||
# If no content type is registered for the extension, the default type +application/octet-stream+ will be used.
|
||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
||||
# Valid values are <tt>"inline"</tt> and <tt>"attachment"</tt> (default).
|
||||
# * <tt>:status</tt> - 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 <tt>Content-*</tt> 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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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."
|
||||
|
||||
|
@ -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 <tt>alert</tt> and <tt>notice</tt> 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.
|
||||
|
@ -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`."
|
||||
|
@ -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 <tt>#helpers</tt>
|
||||
# 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., <tt>MyController</tt> will automatically
|
||||
# include <tt>MyHelper</tt>. 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 <tt>EventsController</tt>, the <tt>format_time</tt> method can be called:
|
||||
# FormattedTimeHelper can now be included in a controller, using the `helper`
|
||||
# class method:
|
||||
#
|
||||
# <% @events.each do |event| -%>
|
||||
# <p>
|
||||
# <%= format_time(event.time, :short, "N/A") %> | <%= event.name %>
|
||||
# </p>
|
||||
# <% 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| -%>
|
||||
# <p>
|
||||
# <%= format_time(event.time, :short, "N/A") %> | <%= event.name %>
|
||||
# </p>
|
||||
# <% 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 <tt>name=</tt> 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
|
||||
# * <tt>attrs</tt> - 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
|
||||
# * <tt>args</tt> - A list of helpers
|
||||
# #### Parameters
|
||||
# * `args` - A list of helpers
|
||||
#
|
||||
#
|
||||
# #### Returns
|
||||
# * `array` - A normalized list of modules for the list of helpers provided.
|
||||
#
|
||||
# ==== Returns
|
||||
# * <tt>array</tt> - 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 <tt>app/helpers/**/*_helper.rb</tt>
|
||||
# Extract helper names from files in `app/helpers/***/**_helper.rb`
|
||||
def all_application_helpers
|
||||
all_helpers_from_path(helpers_path)
|
||||
end
|
||||
|
@ -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
|
||||
# <tt>"Token"</tt> or <tt>"Bearer"</tt>. 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 <tt>"abc"</tt>, and the options are
|
||||
# <tt>{nonce: "def"}</tt>.
|
||||
# Then the returned token is `"abc"`, and the options are `{nonce: "def"}`.
|
||||
#
|
||||
# Returns an +Array+ of <tt>[String, Hash]</tt> 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 <tt>"</tt> 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 <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
|
||||
# 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('"', "")}")
|
||||
|
@ -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 <tt>204 No Content</tt>.
|
||||
# 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
|
||||
# <tt>204 No Content</tt>:
|
||||
# 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 <tt>204 No Content</tt>.
|
||||
# load, then we implicitly respond with `204 No Content`.
|
||||
module ImplicitRender
|
||||
# :stopdoc:
|
||||
include BasicImplicitRender
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
||||
# * <tt>:type</tt> - specifies an HTTP content type.
|
||||
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
|
||||
# If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
|
||||
# If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
|
||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
||||
# Valid values are 'inline' and 'attachment' (default).
|
||||
# * `: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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
#
|
||||
# <person>
|
||||
# <id>...</id>
|
||||
# ...
|
||||
# <company>
|
||||
# <person>
|
||||
# <id>...</id>
|
||||
# <name>...</name>
|
||||
# ...
|
||||
# </company>
|
||||
# </person>
|
||||
# <company>
|
||||
# <id>...</id>
|
||||
# <name>...</name>
|
||||
# ...
|
||||
# </company>
|
||||
# </person>
|
||||
#
|
||||
# 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):
|
||||
#
|
||||
# <person>
|
||||
# <name>...</name>
|
||||
# <company>
|
||||
# <person>
|
||||
# <name>...</name>
|
||||
# </company>
|
||||
# </person>
|
||||
# <company>
|
||||
# <name>...</name>
|
||||
# </company>
|
||||
# </person>
|
||||
#
|
||||
# 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 <tt>:tablet</tt>,
|
||||
# <tt>:phone</tt>, or <tt>:desktop</tt>.
|
||||
# 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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
# <tt>attribute_names</tt>.
|
||||
# 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
|
||||
# <tt>User.new(params[:user])</tt>), 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
|
||||
# * <tt>:format</tt> - The list of formats in which the parameters wrapper
|
||||
# will be enabled.
|
||||
# * <tt>:include</tt> - The list of attribute names which parameters wrapper
|
||||
# will wrap into a nested hash.
|
||||
# * <tt>:exclude</tt> - 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
|
||||
|
@ -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
|
||||
# <tt>only: :index</tt> 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
|
||||
|
@ -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 <tt>before_action</tt> filters with <tt>only:</tt> and <tt>except:</tt>.
|
||||
# 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 <tt>to:</tt> and constrained to the window of time given by <tt>within:</tt>.
|
||||
# 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 <tt>by:</tt> 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 <tt>429 Too Many Requests</tt> response. You can specialize this by passing a callable
|
||||
# in the <tt>with:</tt> 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 <tt>ActiveSupport::Cache</tt> store and defaults to <tt>config.action_controller.cache_store</tt>, which
|
||||
# itself defaults to the global <tt>config.cache_store</tt>. 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 <tt>store</tt> 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
|
||||
|
@ -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:
|
||||
#
|
||||
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
|
||||
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
|
||||
# * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) or a protocol relative reference (like <tt>//</tt>) - Is passed straight through as the target for redirection.
|
||||
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
|
||||
# * <tt>Proc</tt> - 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 <tt>302 Found</tt> header unless otherwise specified using the <tt>:status</tt> 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 <tt>303 See Other</tt> 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 <tt>config/initializers/new_framework_defaults_7_0.rb</tt>
|
||||
# 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 <tt>allow_other_host: true</tt>, 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 <tt>fallback_location</tt> 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
|
||||
# * <tt>:allow_other_host</tt> - 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 <tt>request.host</tt>:
|
||||
# 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. <tt>url_for(@post)</tt>.
|
||||
# However, #url_from is meant to take an external parameter to verify as in <tt>url_from(params[:redirect_url])</tt>.
|
||||
# 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"
|
||||
|
@ -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 <tt>Responder#api_behavior</tt>
|
||||
# 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 <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
|
||||
# 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
|
||||
# <tt>Mime::Type.register</tt>.
|
||||
# `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 <tt>:json</tt> 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 <tt>nil</tt>, 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
|
||||
# <tt>ActionView::Rendering.render_to_body</tt>, 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
|
||||
|
@ -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: "<h1>Hello, World</h1>"
|
||||
# class Greeting
|
||||
# def render_in(view_context)
|
||||
# view_context.render html: "<h1>Hello, World</h1>"
|
||||
# end
|
||||
#
|
||||
# def format
|
||||
# :html
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# def format
|
||||
# :html
|
||||
# end
|
||||
# end
|
||||
# render(Greeting.new)
|
||||
# # => "<h1>Hello, World</h1>"
|
||||
#
|
||||
# render(Greeting.new)
|
||||
# # => "<h1>Hello, World</h1>"
|
||||
# render(renderable: Greeting.new)
|
||||
# # => "<h1>Hello, World</h1>"
|
||||
#
|
||||
# render(renderable: Greeting.new)
|
||||
# # => "<h1>Hello, World</h1>"
|
||||
# #### 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 <b>not</b> 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: "<h1>Hello, <%= @name %>!</h1>"
|
||||
# # => renders "<h1>Hello, World!</h1>"
|
||||
#
|
||||
# @name = "World"
|
||||
# render inline: "<h1>Hello, <%= @name %>!</h1>"
|
||||
# # => renders "<h1>Hello, World!</h1>"
|
||||
# `: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: "<h1>Hello, World!</h1>".html_safe
|
||||
# # => renders "<h1>Hello, World!</h1>"
|
||||
#
|
||||
# render html: "<h1>Hello, World!</h1>".html_safe
|
||||
# # => renders "<h1>Hello, World!</h1>"
|
||||
# render html: "<h1>Hello, World!</h1>"
|
||||
# # => renders "<h1>Hello, World!</h1>"
|
||||
#
|
||||
# render html: "<h1>Hello, World!</h1>"
|
||||
# # => 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 "<h1>Hello, World</h1>"
|
||||
#
|
||||
# render renderable: Greeting.new
|
||||
# # => renders "<h1>Hello, World</h1>"
|
||||
#
|
||||
# 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: "<h1>Hello, <%= @name %>!</h1>", assigns: { name: "World" }
|
||||
# # => renders "<h1>Hello, World!</h1>"
|
||||
# render inline: "<h1>Hello, <%= @name %>!</h1>", assigns: { name: "World" }
|
||||
# # => renders "<h1>Hello, World!</h1>"
|
||||
#
|
||||
# [+:locals+]
|
||||
# Hash of local variable assignments for the template.
|
||||
# `:locals`
|
||||
# : Hash of local variable assignments for the template.
|
||||
#
|
||||
# render inline: "<h1>Hello, <%= name %>!</h1>", locals: { name: "World" }
|
||||
# # => renders "<h1>Hello, World!</h1>"
|
||||
# render inline: "<h1>Hello, <%= name %>!</h1>", locals: { name: "World" }
|
||||
# # => renders "<h1>Hello, World!</h1>"
|
||||
#
|
||||
# [+: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: "<h1>Hello, World!</h1>", layout: true
|
||||
# # => renders "<h1>Hello, World!</h1>" with the default layout
|
||||
# render inline: "<h1>Hello, World!</h1>", layout: true
|
||||
# # => renders "<h1>Hello, World!</h1>" 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(*)
|
||||
|
@ -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 <tt>protect_from_forgery</tt> 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 <script> tag to reference a JavaScript
|
||||
# URL on your site. When your JavaScript response loads on their site, it executes.
|
||||
# With carefully crafted JavaScript on their end, sensitive data in your JavaScript
|
||||
# response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
|
||||
# Ajax) requests are allowed to make requests for JavaScript responses.
|
||||
# an exception: a third-party site can use a <script> tag to reference a
|
||||
# JavaScript URL on your site. When your JavaScript response loads on their
|
||||
# site, it executes. With carefully crafted JavaScript on their end, sensitive
|
||||
# data in your JavaScript response may be extracted. To prevent this, only
|
||||
# XmlHttpRequest (known as XHR or Ajax) requests are allowed to make requests
|
||||
# for JavaScript responses.
|
||||
#
|
||||
# Subclasses of ActionController::Base are protected by default with the
|
||||
# <tt>:exception</tt> strategy, which raises an
|
||||
# `:exception` strategy, which raises an
|
||||
# ActionController::InvalidAuthenticityToken error on unverified requests.
|
||||
#
|
||||
# APIs may want to disable this behavior since they are typically designed to be
|
||||
# state-less: that is, the request API client handles the session instead of \Rails.
|
||||
# One way to achieve this is to use the <tt>:null_session</tt> strategy instead,
|
||||
# state-less: that is, the request API client handles the session instead of
|
||||
# Rails. One way to achieve this is to use the `:null_session` strategy instead,
|
||||
# which allows unverified requests to be handled, but with an empty session:
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# protect_from_forgery with: :null_session
|
||||
# end
|
||||
# class ApplicationController < ActionController::Base
|
||||
# protect_from_forgery with: :null_session
|
||||
# end
|
||||
#
|
||||
# Note that API only applications don't include this module or a session middleware
|
||||
# by default, and so don't require CSRF protection to be configured.
|
||||
# Note that API only applications don't include this module or a session
|
||||
# middleware by default, and so don't require CSRF protection to be configured.
|
||||
#
|
||||
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
|
||||
# value of this token must be added to every layout that renders forms by including
|
||||
# <tt>csrf_meta_tags</tt> in the HTML +head+.
|
||||
# The token parameter is named `authenticity_token` by default. The name and
|
||||
# value of this token must be added to every layout that renders forms by
|
||||
# including `csrf_meta_tags` in the HTML `head`.
|
||||
#
|
||||
# Learn more about CSRF attacks and securing your application in the
|
||||
# {Ruby on Rails Security Guide}[https://guides.rubyonrails.org/security.html].
|
||||
# Learn more about CSRF attacks and securing your application in the [Ruby on
|
||||
# Rails Security Guide](https://guides.rubyonrails.org/security.html).
|
||||
module RequestForgeryProtection
|
||||
CSRF_TOKEN = "action_controller.csrf_token"
|
||||
|
||||
@ -65,8 +69,8 @@ module RequestForgeryProtection
|
||||
include AbstractController::Callbacks
|
||||
|
||||
included do
|
||||
# Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+
|
||||
# sets it to <tt>:authenticity_token</tt> by default.
|
||||
# Sets the token parameter name for RequestForgery. Calling
|
||||
# `protect_from_forgery` sets it to `:authenticity_token` by default.
|
||||
config_accessor :request_forgery_protection_token
|
||||
self.request_forgery_protection_token ||= :authenticity_token
|
||||
|
||||
@ -74,7 +78,8 @@ module RequestForgeryProtection
|
||||
config_accessor :forgery_protection_strategy
|
||||
self.forgery_protection_strategy = nil
|
||||
|
||||
# Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
|
||||
# Controls whether request forgery protection is turned on or not. Turned off by
|
||||
# default only in test mode.
|
||||
config_accessor :allow_forgery_protection
|
||||
self.allow_forgery_protection = true if allow_forgery_protection.nil?
|
||||
|
||||
@ -99,79 +104,96 @@ module RequestForgeryProtection
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Turn on request forgery protection. Bear in mind that GET and HEAD requests are not checked.
|
||||
# Turn on request forgery protection. Bear in mind that GET and HEAD requests
|
||||
# are not checked.
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# protect_from_forgery
|
||||
# end
|
||||
# class ApplicationController < ActionController::Base
|
||||
# protect_from_forgery
|
||||
# end
|
||||
#
|
||||
# class FooController < ApplicationController
|
||||
# protect_from_forgery except: :index
|
||||
# end
|
||||
# class FooController < ApplicationController
|
||||
# protect_from_forgery except: :index
|
||||
# end
|
||||
#
|
||||
# You can disable forgery protection on a controller using skip_forgery_protection:
|
||||
# You can disable forgery protection on a controller using
|
||||
# skip_forgery_protection:
|
||||
#
|
||||
# class BarController < ApplicationController
|
||||
# skip_forgery_protection
|
||||
# end
|
||||
# class BarController < ApplicationController
|
||||
# skip_forgery_protection
|
||||
# end
|
||||
#
|
||||
# Valid Options:
|
||||
#
|
||||
# * <tt>:only</tt> / <tt>:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>.
|
||||
# * <tt>:if</tt> / <tt>:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
|
||||
# * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the
|
||||
# protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful
|
||||
# when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth).
|
||||
# * `:only` / `:except` - Only apply forgery protection to a subset of
|
||||
# actions. For example `only: [ :create, :create_all ]`.
|
||||
# * `:if` / `:unless` - Turn off the forgery protection entirely depending on
|
||||
# the passed Proc or method reference.
|
||||
# * `:prepend` - By default, the verification of the authentication token will
|
||||
# be added at the position of the protect_from_forgery call in your
|
||||
# application. This means any callbacks added before are run first. This is
|
||||
# useful when you want your forgery protection to depend on other callbacks,
|
||||
# like authentication methods (Oauth vs Cookie auth).
|
||||
#
|
||||
# If you need to add verification to the beginning of the callback chain,
|
||||
# use `prepend: true`.
|
||||
# * `:with` - Set the method to handle unverified request. Note if
|
||||
# `default_protect_from_forgery` is true, Rails call protect_from_forgery
|
||||
# with `with :exception`.
|
||||
#
|
||||
# If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
|
||||
# * <tt>:with</tt> - Set the method to handle unverified request.
|
||||
# Note if <tt>default_protect_from_forgery</tt> is true, Rails call protect_from_forgery with <tt>with :exception</tt>.
|
||||
#
|
||||
# Built-in unverified request handling methods are:
|
||||
# * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
|
||||
# * <tt>:reset_session</tt> - Resets the session.
|
||||
# * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
|
||||
# * `:exception` - Raises ActionController::InvalidAuthenticityToken
|
||||
# exception.
|
||||
# * `:reset_session` - Resets the session.
|
||||
# * `:null_session` - Provides an empty session during request but doesn't
|
||||
# reset it completely. Used as default if `:with` option is not specified.
|
||||
#
|
||||
# You can also implement custom strategy classes for unverified request handling:
|
||||
#
|
||||
# class CustomStrategy
|
||||
# def initialize(controller)
|
||||
# @controller = controller
|
||||
# end
|
||||
# You can also implement custom strategy classes for unverified request
|
||||
# handling:
|
||||
#
|
||||
# def handle_unverified_request
|
||||
# # Custom behavior for unverfied request
|
||||
# end
|
||||
# end
|
||||
# class CustomStrategy
|
||||
# def initialize(controller)
|
||||
# @controller = controller
|
||||
# end
|
||||
#
|
||||
# def handle_unverified_request
|
||||
# # Custom behavior for unverfied request
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# protect_from_forgery with: CustomStrategy
|
||||
# end
|
||||
#
|
||||
# * `:store` - Set the strategy to store and retrieve CSRF tokens.
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# protect_from_forgery with: CustomStrategy
|
||||
# end
|
||||
# * <tt>:store</tt> - Set the strategy to store and retrieve CSRF tokens.
|
||||
#
|
||||
# Built-in session token strategies are:
|
||||
# * <tt>:session</tt> - Store the CSRF token in the session. Used as default if <tt>:store</tt> option is not specified.
|
||||
# * <tt>:cookie</tt> - Store the CSRF token in an encrypted cookie.
|
||||
# * `:session` - Store the CSRF token in the session. Used as default if
|
||||
# `:store` option is not specified.
|
||||
# * `:cookie` - Store the CSRF token in an encrypted cookie.
|
||||
#
|
||||
#
|
||||
# You can also implement custom strategy classes for CSRF token storage:
|
||||
#
|
||||
# class CustomStore
|
||||
# def fetch(request)
|
||||
# # Return the token from a custom location
|
||||
# class CustomStore
|
||||
# def fetch(request)
|
||||
# # Return the token from a custom location
|
||||
# end
|
||||
#
|
||||
# def store(request, csrf_token)
|
||||
# # Store the token in a custom location
|
||||
# end
|
||||
#
|
||||
# def reset(request)
|
||||
# # Delete the stored session token
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# def store(request, csrf_token)
|
||||
# # Store the token in a custom location
|
||||
# class ApplicationController < ActionController::Base
|
||||
# protect_from_forgery store: CustomStore.new
|
||||
# end
|
||||
#
|
||||
# def reset(request)
|
||||
# # Delete the stored session token
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# protect_from_forgery store: CustomStore.new
|
||||
# end
|
||||
def protect_from_forgery(options = {})
|
||||
options = options.reverse_merge(prepend: false)
|
||||
|
||||
@ -186,9 +208,9 @@ def protect_from_forgery(options = {})
|
||||
|
||||
# Turn off request forgery protection. This is a wrapper for:
|
||||
#
|
||||
# skip_before_action :verify_authenticity_token
|
||||
# skip_before_action :verify_authenticity_token
|
||||
#
|
||||
# See +skip_before_action+ for allowed options.
|
||||
# See `skip_before_action` for allowed options.
|
||||
def skip_forgery_protection(options = {})
|
||||
skip_before_action :verify_authenticity_token, options.reverse_merge(raise: false)
|
||||
end
|
||||
@ -232,7 +254,8 @@ def initialize(controller)
|
||||
@controller = controller
|
||||
end
|
||||
|
||||
# This is the method that defines the application behavior when a request is found to be unverified.
|
||||
# This is the method that defines the application behavior when a request is
|
||||
# found to be unverified.
|
||||
def handle_unverified_request
|
||||
request = @controller.request
|
||||
request.session = NullSessionHash.new(request)
|
||||
@ -354,16 +377,15 @@ def commit_csrf_token(request) # :doc:
|
||||
end
|
||||
|
||||
private
|
||||
# The actual before_action that is used to verify the CSRF token.
|
||||
# Don't override this directly. Provide your own forgery protection
|
||||
# strategy instead. If you override, you'll disable same-origin
|
||||
# <tt><script></tt> verification.
|
||||
# The actual before_action that is used to verify the CSRF token. Don't override
|
||||
# this directly. Provide your own forgery protection strategy instead. If you
|
||||
# override, you'll disable same-origin `<script>` verification.
|
||||
#
|
||||
# Lean on the protect_from_forgery declaration to mark which actions are
|
||||
# due for same-origin request verification. If protect_from_forgery is
|
||||
# enabled on an action, this before_action flags its after_action to
|
||||
# verify that JavaScript responses are for XHR requests, ensuring they
|
||||
# follow the browser's same-origin policy.
|
||||
# Lean on the protect_from_forgery declaration to mark which actions are due for
|
||||
# same-origin request verification. If protect_from_forgery is enabled on an
|
||||
# action, this before_action flags its after_action to verify that JavaScript
|
||||
# responses are for XHR requests, ensuring they follow the browser's same-origin
|
||||
# policy.
|
||||
def verify_authenticity_token # :doc:
|
||||
mark_for_same_origin_verification!
|
||||
|
||||
@ -400,9 +422,9 @@ def unverified_request_warning_message # :nodoc:
|
||||
private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
|
||||
# :startdoc:
|
||||
|
||||
# If +verify_authenticity_token+ was run (indicating that we have
|
||||
# forgery protection enabled for this request) then also verify that
|
||||
# we aren't serving an unauthorized cross-origin response.
|
||||
# If `verify_authenticity_token` was run (indicating that we have
|
||||
# forgery protection enabled for this request) then also verify that we aren't
|
||||
# serving an unauthorized cross-origin response.
|
||||
def verify_same_origin_request # :doc:
|
||||
if marked_for_same_origin_verification? && non_xhr_javascript_response?
|
||||
if logger && log_warning_on_csrf_failure
|
||||
@ -417,8 +439,8 @@ def mark_for_same_origin_verification! # :doc:
|
||||
@_marked_for_same_origin_verification = request.get?
|
||||
end
|
||||
|
||||
# If the +verify_authenticity_token+ before_action ran, verify that
|
||||
# JavaScript responses are only served to same-origin GET requests.
|
||||
# If the `verify_authenticity_token` before_action ran, verify that JavaScript
|
||||
# responses are only served to same-origin GET requests.
|
||||
def marked_for_same_origin_verification? # :doc:
|
||||
@_marked_for_same_origin_verification ||= false
|
||||
end
|
||||
@ -432,9 +454,11 @@ def non_xhr_javascript_response? # :doc:
|
||||
|
||||
# Returns true or false if a request is verified. Checks:
|
||||
#
|
||||
# * Is it a GET or HEAD request? GETs should be safe and idempotent
|
||||
# * Does the form_authenticity_token match the given token value from the params?
|
||||
# * Does the +X-CSRF-Token+ header match the form_authenticity_token?
|
||||
# * Is it a GET or HEAD request? GETs should be safe and idempotent
|
||||
# * Does the form_authenticity_token match the given token value from the
|
||||
# params?
|
||||
# * Does the `X-CSRF-Token` header match the form_authenticity_token?
|
||||
#
|
||||
def verified_request? # :doc:
|
||||
!protect_against_forgery? || request.get? || request.head? ||
|
||||
(valid_request_origin? && any_authenticity_token_valid?)
|
||||
@ -457,9 +481,8 @@ def form_authenticity_token(form_options: {}) # :doc:
|
||||
masked_authenticity_token(form_options: form_options)
|
||||
end
|
||||
|
||||
# Creates a masked version of the authenticity token that varies
|
||||
# on each request. The masking is used to mitigate SSL attacks
|
||||
# like BREACH.
|
||||
# Creates a masked version of the authenticity token that varies on each
|
||||
# request. The masking is used to mitigate SSL attacks like BREACH.
|
||||
def masked_authenticity_token(form_options: {})
|
||||
action, method = form_options.values_at(:action, :method)
|
||||
|
||||
@ -473,9 +496,8 @@ def masked_authenticity_token(form_options: {})
|
||||
mask_token(raw_token)
|
||||
end
|
||||
|
||||
# Checks the client's masked token to see if it matches the
|
||||
# session token. Essentially the inverse of
|
||||
# +masked_authenticity_token+.
|
||||
# Checks the client's masked token to see if it matches the session token.
|
||||
# Essentially the inverse of `masked_authenticity_token`.
|
||||
def valid_authenticity_token?(session, encoded_masked_token) # :doc:
|
||||
if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
|
||||
return false
|
||||
@ -487,14 +509,12 @@ def valid_authenticity_token?(session, encoded_masked_token) # :doc:
|
||||
return false
|
||||
end
|
||||
|
||||
# See if it's actually a masked token or not. In order to
|
||||
# deploy this code, we should be able to handle any unmasked
|
||||
# tokens that we've issued without error.
|
||||
# See if it's actually a masked token or not. In order to deploy this code, we
|
||||
# should be able to handle any unmasked tokens that we've issued without error.
|
||||
|
||||
if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
|
||||
# This is actually an unmasked token. This is expected if
|
||||
# you have just upgraded to masked tokens, but should stop
|
||||
# happening shortly after installing this gem.
|
||||
# This is actually an unmasked token. This is expected if you have just upgraded
|
||||
# to masked tokens, but should stop happening shortly after installing this gem.
|
||||
compare_with_real_token masked_token
|
||||
|
||||
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
|
||||
@ -509,8 +529,7 @@ def valid_authenticity_token?(session, encoded_masked_token) # :doc:
|
||||
end
|
||||
|
||||
def unmask_token(masked_token) # :doc:
|
||||
# Split the token into the one-time pad and the encrypted
|
||||
# value and decrypt it.
|
||||
# Split the token into the one-time pad and the encrypted value and decrypt it.
|
||||
one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
|
||||
encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
|
||||
xor_byte_strings(one_time_pad, encrypted_csrf_token)
|
||||
@ -602,8 +621,8 @@ def protect_against_forgery? # :doc:
|
||||
Rails.application.config.action_controller.forgery_protection_origin_check setting.
|
||||
MSG
|
||||
|
||||
# Checks if the request originated from the same origin by looking at the
|
||||
# Origin header.
|
||||
# Checks if the request originated from the same origin by looking at the Origin
|
||||
# header.
|
||||
def valid_request_origin? # :doc:
|
||||
if forgery_protection_origin_check
|
||||
# We accept blank origin headers because some user agents don't send it.
|
||||
|
@ -1,21 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionController # :nodoc:
|
||||
# = Action Controller \Rescue
|
||||
# # Action Controller Rescue
|
||||
#
|
||||
# This module is responsible for providing
|
||||
# {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from]
|
||||
# to controllers, wrapping actions to handle configured errors, and
|
||||
# configuring when detailed exceptions must be shown.
|
||||
# [rescue_from](rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from) to
|
||||
# controllers, wrapping actions to handle configured errors, and configuring
|
||||
# when detailed exceptions must be shown.
|
||||
module Rescue
|
||||
extend ActiveSupport::Concern
|
||||
include ActiveSupport::Rescuable
|
||||
|
||||
# Override this method if you want to customize when detailed
|
||||
# exceptions must be shown. This method is only called when
|
||||
# +consider_all_requests_local+ is +false+. By default, it returns
|
||||
# +false+, but someone may set it to <tt>request.local?</tt> so local
|
||||
# requests in production still show the detailed exception pages.
|
||||
# Override this method if you want to customize when detailed exceptions must be
|
||||
# shown. This method is only called when `consider_all_requests_local` is
|
||||
# `false`. By default, it returns `false`, but someone may set it to
|
||||
# `request.local?` so local requests in production still show the detailed
|
||||
# exception pages.
|
||||
def show_detailed_exceptions?
|
||||
false
|
||||
end
|
||||
|
@ -1,208 +1,210 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionController # :nodoc:
|
||||
# = Action Controller \Streaming
|
||||
# # Action Controller Streaming
|
||||
#
|
||||
# Allows views to be streamed back to the client as they are rendered.
|
||||
#
|
||||
# By default, \Rails renders views by first rendering the template
|
||||
# and then the layout. The response is sent to the client after the whole
|
||||
# template is rendered, all queries are made, and the layout is processed.
|
||||
# By default, Rails renders views by first rendering the template and then the
|
||||
# layout. The response is sent to the client after the whole template is
|
||||
# rendered, all queries are made, and the layout is processed.
|
||||
#
|
||||
# \Streaming inverts the rendering flow by rendering the layout first and
|
||||
# Streaming inverts the rendering flow by rendering the layout first and
|
||||
# subsequently each part of the layout as they are processed. This allows the
|
||||
# header of the HTML (which is usually in the layout) to be streamed back
|
||||
# to client very quickly, enabling JavaScripts and stylesheets to be loaded
|
||||
# earlier than usual.
|
||||
# header of the HTML (which is usually in the layout) to be streamed back to
|
||||
# client very quickly, enabling JavaScripts and stylesheets to be loaded earlier
|
||||
# than usual.
|
||||
#
|
||||
# Several Rack middlewares may not work and you need to be careful when streaming.
|
||||
# This is covered in more detail below, see the Streaming@Middlewares section.
|
||||
# Several Rack middlewares may not work and you need to be careful when
|
||||
# streaming. This is covered in more detail below, see the Streaming@Middlewares
|
||||
# section.
|
||||
#
|
||||
# \Streaming can be added to a given template easily, all you need to do is
|
||||
# to pass the +:stream+ option to +render+.
|
||||
# Streaming can be added to a given template easily, all you need to do is to
|
||||
# pass the `:stream` option to `render`.
|
||||
#
|
||||
# class PostsController
|
||||
# def index
|
||||
# @posts = Post.all
|
||||
# render stream: true
|
||||
# class PostsController
|
||||
# def index
|
||||
# @posts = Post.all
|
||||
# render stream: true
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# == When to use streaming
|
||||
# ## When to use streaming
|
||||
#
|
||||
# \Streaming may be considered to be overkill for lightweight actions like
|
||||
# +new+ or +edit+. The real benefit of streaming is on expensive actions
|
||||
# that, for example, do a lot of queries on the database.
|
||||
# Streaming may be considered to be overkill for lightweight actions like `new`
|
||||
# or `edit`. The real benefit of streaming is on expensive actions that, for
|
||||
# example, do a lot of queries on the database.
|
||||
#
|
||||
# In such actions, you want to delay queries execution as much as you can.
|
||||
# For example, imagine the following +dashboard+ action:
|
||||
# In such actions, you want to delay queries execution as much as you can. For
|
||||
# example, imagine the following `dashboard` action:
|
||||
#
|
||||
# def dashboard
|
||||
# @posts = Post.all
|
||||
# @pages = Page.all
|
||||
# @articles = Article.all
|
||||
# end
|
||||
# def dashboard
|
||||
# @posts = Post.all
|
||||
# @pages = Page.all
|
||||
# @articles = Article.all
|
||||
# end
|
||||
#
|
||||
# Most of the queries here are happening in the controller. In order to benefit
|
||||
# from streaming you would want to rewrite it as:
|
||||
#
|
||||
# def dashboard
|
||||
# # Allow lazy execution of the queries
|
||||
# @posts = Post.all
|
||||
# @pages = Page.all
|
||||
# @articles = Article.all
|
||||
# render stream: true
|
||||
# end
|
||||
# def dashboard
|
||||
# # Allow lazy execution of the queries
|
||||
# @posts = Post.all
|
||||
# @pages = Page.all
|
||||
# @articles = Article.all
|
||||
# render stream: true
|
||||
# end
|
||||
#
|
||||
# Notice that +:stream+ only works with templates. \Rendering +:json+
|
||||
# or +:xml+ with +:stream+ won't work.
|
||||
# Notice that `:stream` only works with templates. Rendering `:json` or `:xml`
|
||||
# with `:stream` won't work.
|
||||
#
|
||||
# == Communication between layout and template
|
||||
# ## Communication between layout and template
|
||||
#
|
||||
# When streaming, rendering happens top-down instead of inside-out.
|
||||
# \Rails starts with the layout, and the template is rendered later,
|
||||
# when its +yield+ is reached.
|
||||
# When streaming, rendering happens top-down instead of inside-out. Rails starts
|
||||
# with the layout, and the template is rendered later, when its `yield` is
|
||||
# reached.
|
||||
#
|
||||
# This means that, if your application currently relies on instance
|
||||
# variables set in the template to be used in the layout, they won't
|
||||
# work once you move to streaming. The proper way to communicate
|
||||
# between layout and template, regardless of whether you use streaming
|
||||
# or not, is by using +content_for+, +provide+, and +yield+.
|
||||
# This means that, if your application currently relies on instance variables
|
||||
# set in the template to be used in the layout, they won't work once you move to
|
||||
# streaming. The proper way to communicate between layout and template,
|
||||
# regardless of whether you use streaming or not, is by using `content_for`,
|
||||
# `provide`, and `yield`.
|
||||
#
|
||||
# Take a simple example where the layout expects the template to tell
|
||||
# which title to use:
|
||||
# Take a simple example where the layout expects the template to tell which
|
||||
# title to use:
|
||||
#
|
||||
# <html>
|
||||
# <head><title><%= yield :title %></title></head>
|
||||
# <body><%= yield %></body>
|
||||
# </html>
|
||||
# <html>
|
||||
# <head><title><%= yield :title %></title></head>
|
||||
# <body><%= yield %></body>
|
||||
# </html>
|
||||
#
|
||||
# You would use +content_for+ in your template to specify the title:
|
||||
# You would use `content_for` in your template to specify the title:
|
||||
#
|
||||
# <%= content_for :title, "Main" %>
|
||||
# Hello
|
||||
# <%= content_for :title, "Main" %>
|
||||
# Hello
|
||||
#
|
||||
# And the final result would be:
|
||||
#
|
||||
# <html>
|
||||
# <head><title>Main</title></head>
|
||||
# <body>Hello</body>
|
||||
# </html>
|
||||
# <html>
|
||||
# <head><title>Main</title></head>
|
||||
# <body>Hello</body>
|
||||
# </html>
|
||||
#
|
||||
# However, if +content_for+ is called several times, the final result
|
||||
# would have all calls concatenated. For instance, if we have the following
|
||||
# template:
|
||||
# However, if `content_for` is called several times, the final result would have
|
||||
# all calls concatenated. For instance, if we have the following template:
|
||||
#
|
||||
# <%= content_for :title, "Main" %>
|
||||
# Hello
|
||||
# <%= content_for :title, " page" %>
|
||||
# <%= content_for :title, "Main" %>
|
||||
# Hello
|
||||
# <%= content_for :title, " page" %>
|
||||
#
|
||||
# The final result would be:
|
||||
#
|
||||
# <html>
|
||||
# <head><title>Main page</title></head>
|
||||
# <body>Hello</body>
|
||||
# </html>
|
||||
# <html>
|
||||
# <head><title>Main page</title></head>
|
||||
# <body>Hello</body>
|
||||
# </html>
|
||||
#
|
||||
# This means that, if you have <code>yield :title</code> in your layout
|
||||
# and you want to use streaming, you would have to render the whole template
|
||||
# (and eventually trigger all queries) before streaming the title and all
|
||||
# assets, which defeats the purpose of streaming. Alternatively, you can use
|
||||
# a helper called +provide+ that does the same as +content_for+ but tells the
|
||||
# layout to stop searching for other entries and continue rendering.
|
||||
# This means that, if you have `yield :title` in your layout and you want to use
|
||||
# streaming, you would have to render the whole template (and eventually trigger
|
||||
# all queries) before streaming the title and all assets, which defeats the
|
||||
# purpose of streaming. Alternatively, you can use a helper called `provide`
|
||||
# that does the same as `content_for` but tells the layout to stop searching for
|
||||
# other entries and continue rendering.
|
||||
#
|
||||
# For instance, the template above using +provide+ would be:
|
||||
# For instance, the template above using `provide` would be:
|
||||
#
|
||||
# <%= provide :title, "Main" %>
|
||||
# Hello
|
||||
# <%= content_for :title, " page" %>
|
||||
# <%= provide :title, "Main" %>
|
||||
# Hello
|
||||
# <%= content_for :title, " page" %>
|
||||
#
|
||||
# Resulting in:
|
||||
#
|
||||
# <html>
|
||||
# <head><title>Main</title></head>
|
||||
# <body>Hello</body>
|
||||
# </html>
|
||||
# <html>
|
||||
# <head><title>Main</title></head>
|
||||
# <body>Hello</body>
|
||||
# </html>
|
||||
#
|
||||
# That said, when streaming, you need to properly check your templates
|
||||
# and choose when to use +provide+ and +content_for+.
|
||||
# That said, when streaming, you need to properly check your templates and
|
||||
# choose when to use `provide` and `content_for`.
|
||||
#
|
||||
# See also ActionView::Helpers::CaptureHelper for more information.
|
||||
#
|
||||
# == Headers, cookies, session, and flash
|
||||
# ## Headers, cookies, session, and flash
|
||||
#
|
||||
# When streaming, the HTTP headers are sent to the client right before
|
||||
# it renders the first line. This means that, modifying headers, cookies,
|
||||
# session or flash after the template starts rendering will not propagate
|
||||
# to the client.
|
||||
# When streaming, the HTTP headers are sent to the client right before it
|
||||
# renders the first line. This means that, modifying headers, cookies, session
|
||||
# or flash after the template starts rendering will not propagate to the client.
|
||||
#
|
||||
# == Middlewares
|
||||
# ## Middlewares
|
||||
#
|
||||
# Middlewares that need to manipulate the body won't work with streaming.
|
||||
# You should disable those middlewares whenever streaming in development
|
||||
# or production. For instance, +Rack::Bug+ won't work when streaming as it
|
||||
# needs to inject contents in the HTML body.
|
||||
# Middlewares that need to manipulate the body won't work with streaming. You
|
||||
# should disable those middlewares whenever streaming in development or
|
||||
# production. For instance, `Rack::Bug` won't work when streaming as it needs to
|
||||
# inject contents in the HTML body.
|
||||
#
|
||||
# Also +Rack::Cache+ won't work with streaming as it does not support
|
||||
# streaming bodies yet. Whenever streaming +Cache-Control+ is automatically
|
||||
# set to "no-cache".
|
||||
# Also `Rack::Cache` won't work with streaming as it does not support streaming
|
||||
# bodies yet. Whenever streaming `Cache-Control` is automatically set to
|
||||
# "no-cache".
|
||||
#
|
||||
# == Errors
|
||||
# ## Errors
|
||||
#
|
||||
# When it comes to streaming, exceptions get a bit more complicated. This
|
||||
# happens because part of the template was already rendered and streamed to
|
||||
# the client, making it impossible to render a whole exception page.
|
||||
# happens because part of the template was already rendered and streamed to the
|
||||
# client, making it impossible to render a whole exception page.
|
||||
#
|
||||
# Currently, when an exception happens in development or production, \Rails
|
||||
# will automatically stream to the client:
|
||||
# Currently, when an exception happens in development or production, Rails will
|
||||
# automatically stream to the client:
|
||||
#
|
||||
# "><script>window.location = "/500.html"</script></html>
|
||||
# "><script>window.location = "/500.html"</script></html>
|
||||
#
|
||||
# The first two characters (<tt>"></tt>) 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 <tt>:tcp_nodelay</tt>.
|
||||
# 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)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionController
|
||||
module Testing
|
||||
# Behavior specific to functional tests
|
||||
|
@ -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 <tt>_routes</tt> 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
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
require "rails"
|
||||
require "action_controller"
|
||||
require "action_dispatch/railtie"
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionController
|
||||
module Railties
|
||||
module Helpers
|
||||
|
@ -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
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionController
|
||||
module TemplateAssertions # :nodoc:
|
||||
def assert_template(options = {}, message = nil)
|
||||
|
@ -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:
|
||||
#
|
||||
# <b>@controller</b>::
|
||||
# The controller instance that will be tested.
|
||||
# <b>@request</b>::
|
||||
# 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.
|
||||
# <b>@response</b>::
|
||||
# An ActionDispatch::TestResponse object, representing the response
|
||||
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
|
||||
# after calling +post+. If the various assert methods are not sufficient, then you
|
||||
# may use this object to inspect the HTTP response in detail.
|
||||
# **@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 <tt>redirect_to_url</tt>.
|
||||
# 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
|
||||
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
|
||||
# - +session+: A hash of parameters to store in the session. This may be +nil+.
|
||||
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
|
||||
# * `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
|
||||
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
|
||||
# - +session+: A hash of parameters to store in the session. This may be +nil+.
|
||||
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
|
||||
# - +format+: Request format. Defaults to +nil+. Can be string or symbol.
|
||||
# - +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."
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
require "rack/version"
|
||||
|
||||
module ActionDispatch
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionDispatch
|
||||
def self.deprecator # :nodoc:
|
||||
@deprecator ||= ActiveSupport::Deprecation.new
|
||||
|
@ -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 <tt>304 Not Modified</tt> 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 <tt>W/</tt>).
|
||||
# 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 <tt>W/</tt>).
|
||||
# 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)
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionDispatch
|
||||
module Http
|
||||
class ContentDisposition # :nodoc:
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionDispatch
|
||||
module Http
|
||||
module FilterRedirect
|
||||
|
@ -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 <tt>KeyError</tt> 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
|
||||
# <tt>headers_or_env</tt> 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 <tt>headers_or_env</tt> to original instance
|
||||
# entries; duplicate keys are overwritten with the values from
|
||||
# <tt>headers_or_env</tt>.
|
||||
# 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)
|
||||
|
@ -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?
|
||||
|
@ -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 <tt>'text'</tt>, returns <tt>[Mime[:json], Mime[:xml], Mime[:ics],
|
||||
# Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]</tt>.
|
||||
# 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 <tt>'application'</tt>, returns <tt>[Mime[:html], Mime[:js],
|
||||
# Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]</tt>.
|
||||
# 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
|
||||
|
@ -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 )
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
# :enddoc:
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
require "rack/cache"
|
||||
require "rack/cache/context"
|
||||
require "active_support/cache"
|
||||
|
@ -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 <tt>bin/rails routes</tt>:
|
||||
# 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" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
|
||||
# send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; 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") ||
|
||||
|
@ -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 <tt>render</tt>.
|
||||
# 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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 <tt>tld_length</tt>, 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 <tt>["dev", "www"]</tt> would be
|
||||
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
|
||||
# such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
|
||||
# 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 <tt>"dev.www"</tt> would be
|
||||
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
|
||||
# such as 2 to catch <tt>"www"</tt> instead of <tt>"www.rubyonrails"</tt>
|
||||
# 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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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) }
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
require "strscan"
|
||||
|
||||
module ActionDispatch
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionDispatch
|
||||
module Journey # :nodoc:
|
||||
module NFA # :nodoc:
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
require "action_dispatch/journey/scanner"
|
||||
require "action_dispatch/journey/nodes/node"
|
||||
|
||||
|
@ -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|
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionDispatch
|
||||
# :stopdoc:
|
||||
module Journey
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
module ActionDispatch
|
||||
class LogSubscriber < ActiveSupport::LogSubscriber
|
||||
def redirect(event)
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# :markup: markdown
|
||||
|
||||
require "uri"
|
||||
require "active_support/actionable_error"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user