diff options
Diffstat (limited to 'actionpack/lib')
114 files changed, 2337 insertions, 2042 deletions
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 1e57cbaac4..8bd965b198 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -1,6 +1,6 @@ -require 'action_pack' -require 'active_support/rails' -require 'active_support/i18n' +require "action_pack" +require "active_support/rails" +require "active_support/i18n" module AbstractController extend ActiveSupport::Autoload diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index d4317399ed..8e588812f8 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -1,9 +1,9 @@ -require 'erubis' -require 'abstract_controller/error' -require 'active_support/configurable' -require 'active_support/descendants_tracker' -require 'active_support/core_ext/module/anonymous' -require 'active_support/core_ext/module/attr_internal' +require "erubis" +require "abstract_controller/error" +require "active_support/configurable" +require "active_support/descendants_tracker" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/attr_internal" module AbstractController # Raised when a non-existing controller action is triggered. @@ -94,7 +94,7 @@ module AbstractController # ==== Returns # * <tt>String</tt> def controller_path - @controller_path ||= name.sub(/Controller$/, ''.freeze).underscore unless anonymous? + @controller_path ||= name.sub(/Controller$/, "".freeze).underscore unless anonymous? end # Refresh the cached action_methods when a new action_method is added. @@ -150,6 +150,13 @@ module AbstractController _find_action_name(action_name) end + # Tests if a response body is set. Used to determine if the + # +process_action+ callback needs to be terminated in + # +AbstractController::Callbacks+. + def performed? + response_body + end + # Returns true if the given controller is capable of rendering # a path. A subclass of +AbstractController::Base+ # may return false. An Email controller for example does not diff --git a/actionpack/lib/abstract_controller/caching.rb b/actionpack/lib/abstract_controller/caching.rb index 0dea50889a..d222880922 100644 --- a/actionpack/lib/abstract_controller/caching.rb +++ b/actionpack/lib/abstract_controller/caching.rb @@ -29,11 +29,14 @@ module AbstractController extend ConfigMethods config_accessor :default_static_extension - self.default_static_extension ||= '.html' + self.default_static_extension ||= ".html" config_accessor :perform_caching self.perform_caching = true if perform_caching.nil? + config_accessor :enable_fragment_cache_logging + self.enable_fragment_cache_logging = false + class_attribute :_view_cache_dependencies self._view_cache_dependencies = [] helper_method :view_cache_dependencies if respond_to?(:helper_method) diff --git a/actionpack/lib/abstract_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb index 3257a731ed..c85b4adba1 100644 --- a/actionpack/lib/abstract_controller/caching/fragments.rb +++ b/actionpack/lib/abstract_controller/caching/fragments.rb @@ -51,7 +51,7 @@ module AbstractController # end # end def fragment_cache_key(value = nil, &key) - self.fragment_cache_keys += [key || ->{ value }] + self.fragment_cache_keys += [key || -> { value }] end end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index d63ce9c1c3..73775e12c2 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -9,7 +9,7 @@ module AbstractController included do define_callbacks :process_action, - terminator: ->(controller, result_lambda) { result_lambda.call if result_lambda.is_a?(Proc); controller.response_body }, + terminator: ->(controller, result_lambda) { result_lambda.call if result_lambda.is_a?(Proc); controller.performed? }, skip_after_callbacks_if_terminated: true end @@ -49,7 +49,7 @@ module AbstractController def _normalize_callback_option(options, from, to) # :nodoc: if from = options[from] _from = Array(from).map(&:to_s).to_set - from = proc {|c| _from.include? c.action_name } + from = proc { |c| _from.include? c.action_name } options[to] = Array(options[to]).unshift(from) end end @@ -62,7 +62,7 @@ module AbstractController # impossible to skip a callback defined using an anonymous proc # using #skip_action_callback. def skip_action_callback(*names) - ActiveSupport::Deprecation.warn('`skip_action_callback` is deprecated and will be removed in Rails 5.1. Please use skip_before_action, skip_after_action or skip_around_action instead.') + ActiveSupport::Deprecation.warn("`skip_action_callback` is deprecated and will be removed in Rails 5.1. Please use skip_before_action, skip_after_action or skip_around_action instead.") skip_before_action(*names, raise: false) skip_after_action(*names, raise: false) skip_around_action(*names, raise: false) @@ -194,7 +194,7 @@ module AbstractController define_method "prepend_#{callback}_action" do |*names, &blk| _insert_callbacks(names, blk) do |name, options| - set_callback(:process_action, callback, name, options.merge(:prepend => true)) + set_callback(:process_action, callback, name, options.merge(prepend: true)) end end diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb index 55654be224..57714b0588 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -16,7 +16,7 @@ module AbstractController end Mime::Type.register_callback do |mime| - generate_method_for_mime(mime) unless self.instance_methods.include?(mime.to_sym) + generate_method_for_mime(mime) unless instance_methods.include?(mime.to_sym) end protected diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index ab4355296b..ef3be7af83 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -1,4 +1,4 @@ -require 'active_support/dependencies' +require "active_support/dependencies" module AbstractController module Helpers @@ -171,25 +171,25 @@ module AbstractController end private - # Makes all the (instance) methods in the helper module available to templates - # rendered through this controller. - # - # ==== Parameters - # * <tt>module</tt> - The module to include into the current helper module - # for the class - def add_template_helper(mod) - _helpers.module_eval { include mod } - end + # Makes all the (instance) methods in the helper module available to templates + # rendered through this controller. + # + # ==== Parameters + # * <tt>module</tt> - The module to include into the current helper module + # for the class + def add_template_helper(mod) + _helpers.module_eval { include mod } + end - def default_helper_module! - module_name = name.sub(/Controller$/, ''.freeze) - module_path = module_name.underscore - helper module_path - rescue LoadError => e - raise e unless e.is_missing? "helpers/#{module_path}_helper" - rescue NameError => e - raise e unless e.missing_name? "#{module_name}Helper" - end + def default_helper_module! + module_name = name.sub(/Controller$/, "".freeze) + module_path = module_name.underscore + helper module_path + rescue LoadError => e + raise e unless e.is_missing? "helpers/#{module_path}_helper" + rescue NameError => e + raise e unless e.missing_name? "#{module_name}Helper" + end end end end diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index a6fb0dbe1d..2cb22cb53d 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,9 +1,9 @@ -require 'abstract_controller/error' -require 'active_support/concern' -require 'active_support/core_ext/class/attribute' -require 'action_view' -require 'action_view/view_paths' -require 'set' +require "abstract_controller/error" +require "active_support/concern" +require "active_support/core_ext/class/attribute" +require "action_view" +require "action_view/view_paths" +require "set" module AbstractController class DoubleRenderError < Error @@ -122,7 +122,7 @@ module AbstractController def _normalize_render(*args, &block) options = _normalize_args(*args, &block) #TODO: remove defined? when we restore AP <=> AV dependency - if defined?(request) && request.variant.present? + if defined?(request) && !request.nil? && request.variant.present? options[:variant] = request.variant end _normalize_options(options) diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb index 56b8ce895e..9e3858802a 100644 --- a/actionpack/lib/abstract_controller/translation.rb +++ b/actionpack/lib/abstract_controller/translation.rb @@ -9,8 +9,8 @@ module AbstractController # 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.to_s.first == '.' - path = controller_path.tr('/', '.') + if key.to_s.first == "." + path = controller_path.tr("/", ".") defaults = [:"#{path}#{key}"] defaults << options[:default] if options[:default] options[:default] = defaults diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 62f5905205..50f20aa789 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -1,8 +1,8 @@ -require 'active_support/rails' -require 'abstract_controller' -require 'action_dispatch' -require 'action_controller/metal/live' -require 'action_controller/metal/strong_parameters' +require "active_support/rails" +require "abstract_controller" +require "action_dispatch" +require "action_controller/metal/live" +require "action_controller/metal/strong_parameters" module ActionController extend ActiveSupport::Autoload @@ -23,6 +23,7 @@ module ActionController autoload :Cookies autoload :DataStreaming autoload :EtagWithTemplateDigest + autoload :EtagWithFlash autoload :Flash autoload :ForceSSL autoload :Head @@ -40,6 +41,7 @@ module ActionController autoload :Rescue autoload :Streaming autoload :StrongParameters + autoload :ParameterEncoding autoload :Testing autoload :UrlFor end @@ -48,14 +50,14 @@ module ActionController autoload :ApiRendering end - autoload :TestCase, 'action_controller/test_case' - autoload :TemplateAssertions, 'action_controller/test_case' + autoload :TestCase, "action_controller/test_case" + autoload :TemplateAssertions, "action_controller/test_case" end # Common Active Support usage in Action Controller -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/load_error' -require 'active_support/core_ext/module/attr_internal' -require 'active_support/core_ext/name_error' -require 'active_support/core_ext/uri' -require 'active_support/inflector' +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/load_error" +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/name_error" +require "active_support/core_ext/uri" +require "active_support/inflector" diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index 6bbebb7b4c..5cd8d77ddb 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -1,6 +1,6 @@ -require 'action_view' -require 'action_controller' -require 'action_controller/log_subscriber' +require "action_view" +require "action_controller" +require "action_controller/log_subscriber" module ActionController # API Controller is a lightweight version of <tt>ActionController::Base</tt>, diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index d546d7260c..ca8066cd82 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1,4 +1,4 @@ -require 'action_view' +require "action_view" require "action_controller/log_subscriber" require "action_controller/metal/params_wrapper" @@ -32,7 +32,7 @@ module ActionController # 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. + # 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 @@ -51,8 +51,8 @@ module ActionController # == Parameters # # All request parameters, whether they come from a query string in the URL or form data submitted through a POST request are - # available through the params method which returns a hash. For example, an action that was performed through - # <tt>/posts?category=All&limit=5</tt> will include <tt>{ "category" => "All", "limit" => "5" }</tt> in params. + # 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: # @@ -60,7 +60,7 @@ module ActionController # <input type="text" name="post[address]" value="hyacintvej"> # # A request stemming 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 params would have included + # 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 @@ -213,11 +213,12 @@ module ActionController Renderers::All, ConditionalGet, EtagWithTemplateDigest, + EtagWithFlash, Caching, MimeResponds, ImplicitRender, StrongParameters, - + ParameterEncoding, Cookies, Flash, FormBuilder, diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index a0917b4fdb..d29a5fe68f 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -51,7 +51,7 @@ module ActionController def unpermitted_parameters(event) debug do unpermitted_keys = event.payload[:keys] - "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.join(", ")}" + "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.map { |e| ":#{e}" }.join(", ")}" end end @@ -59,7 +59,7 @@ module ActionController expire_fragment expire_page write_page).each do |method| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{method}(event) - return unless logger.info? + return unless logger.info? && ActionController::Base.enable_fragment_cache_logging key_or_path = event.payload[:key] || event.payload[:path] human_name = #{method.to_s.humanize.inspect} info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)") diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index f6e67b02d7..075e4504c2 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/array/extract_options' -require 'action_dispatch/middleware/stack' -require 'action_dispatch/http/request' -require 'action_dispatch/http/response' +require "active_support/core_ext/array/extract_options" +require "action_dispatch/middleware/stack" +require "action_dispatch/http/request" +require "action_dispatch/http/response" module ActionController # Extend ActionDispatch middleware stack to make it aware of options @@ -34,29 +34,29 @@ module ActionController private - INCLUDE = ->(list, action) { list.include? action } - EXCLUDE = ->(list, action) { !list.include? action } - NULL = ->(list, action) { true } - - def build_middleware(klass, args, block) - options = args.extract_options! - only = Array(options.delete(:only)).map(&:to_s) - except = Array(options.delete(:except)).map(&:to_s) - args << options unless options.empty? - - strategy = NULL - list = nil - - if only.any? - strategy = INCLUDE - list = only - elsif except.any? - strategy = EXCLUDE - list = except - end + INCLUDE = ->(list, action) { list.include? action } + EXCLUDE = ->(list, action) { !list.include? action } + NULL = ->(list, action) { true } + + def build_middleware(klass, args, block) + options = args.extract_options! + only = Array(options.delete(:only)).map(&:to_s) + except = Array(options.delete(:except)).map(&:to_s) + args << options unless options.empty? + + strategy = NULL + list = nil + + if only.any? + strategy = INCLUDE + list = only + elsif except.any? + strategy = EXCLUDE + list = except + end - Middleware.new(get_class(klass), args, list, strategy, block) - end + Middleware.new(get_class(klass), args, list, strategy, block) + end end # <tt>ActionController::Metal</tt> is the simplest possible controller, providing a @@ -130,7 +130,7 @@ module ActionController # ==== Returns # * <tt>string</tt> def self.controller_name - @controller_name ||= name.demodulize.sub(/Controller$/, '').underscore + @controller_name ||= name.demodulize.sub(/Controller$/, "").underscore end def self.make_response!(request) @@ -139,15 +139,19 @@ module ActionController end end + def self.encoding_for_param(action, param) # :nodoc: + ::Encoding::UTF_8 + end + # Delegates to the class' <tt>controller_name</tt> def controller_name self.class.controller_name end attr_internal :response, :request - delegate :session, :to => "@_request" + delegate :session, to: "@_request" delegate :headers, :status=, :location=, :content_type=, - :status, :location, :content_type, :to => "@_response" + :status, :location, :content_type, to: "@_response" def initialize @_request = nil diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 480e265e44..89bf60a0bb 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/hash/keys' +require "active_support/core_ext/hash/keys" module ActionController module ConditionalGet @@ -129,7 +129,7 @@ module ActionController # * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the # +:weak_etag+ option. # * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response. - # requests that set If-None-Match header may return a 304 Not Modified + # Requests that set If-None-Match header may return a 304 Not Modified # response if it matches the ETag exactly. A weak ETag indicates semantic # equivalence, not byte-for-byte equality, so they're good for caching # HTML pages in browser caches. They can't be used for responses that @@ -232,20 +232,21 @@ module ActionController # The method will also ensure an HTTP Date header for client compatibility. def expires_in(seconds, options = {}) response.cache_control.merge!( - :max_age => seconds, - :public => options.delete(:public), - :must_revalidate => options.delete(:must_revalidate) + max_age: seconds, + public: options.delete(:public), + must_revalidate: options.delete(:must_revalidate) ) options.delete(:private) - response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"} + response.cache_control[:extras] = options.map { |k,v| "#{k}=#{v}" } response.date = Time.now unless response.date? end - # Sets an HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should - # occur by the browser or intermediate caches (like caching proxy servers). + # 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. + # Intermediate/browser caches may still store the asset. def expires_now - response.cache_control.replace(:no_cache => true) + response.cache_control.replace(no_cache: true) end # Cache or yield the block. The cache is supposed to never expire. diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index 6cd6130032..f089c8423b 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -1,4 +1,4 @@ -require 'action_controller/metal/exceptions' +require "action_controller/metal/exceptions" module ActionController #:nodoc: # Methods for sending arbitrary data and for streaming files to the browser, @@ -8,8 +8,8 @@ module ActionController #:nodoc: include ActionController::Rendering - DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc: - DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc: + DEFAULT_SEND_FILE_TYPE = "application/octet-stream".freeze #:nodoc: + DEFAULT_SEND_FILE_DISPOSITION = "attachment".freeze #:nodoc: protected # Sends the file. This uses a server-appropriate method (such as X-Sendfile) @@ -64,12 +64,13 @@ module ActionController #:nodoc: # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 # for the Cache-Control header spec. def send_file(path, options = {}) #:doc: - raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path) + raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path) options[:filename] ||= File.basename(path) unless options[:url_based_filename] send_file_headers! options self.status = options[:status] || 200 + self.content_type = options[:type] if options.key?(:type) self.content_type = options[:content_type] if options.key?(:content_type) response.send_file path end @@ -122,7 +123,7 @@ module ActionController #:nodoc: else if !type_provided && options[:filename] # If type wasn't provided, try guessing from file extension. - content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type + content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete(".")) || content_type end self.content_type = content_type end @@ -131,10 +132,10 @@ module ActionController #:nodoc: unless disposition.nil? disposition = disposition.to_s disposition += %(; filename="#{options[:filename]}") if options[:filename] - headers['Content-Disposition'] = disposition + headers["Content-Disposition"] = disposition end - headers['Content-Transfer-Encoding'] = 'binary' + headers["Content-Transfer-Encoding"] = "binary" response.sending_file = true diff --git a/actionpack/lib/action_controller/metal/etag_with_flash.rb b/actionpack/lib/action_controller/metal/etag_with_flash.rb new file mode 100644 index 0000000000..474d75f02e --- /dev/null +++ b/actionpack/lib/action_controller/metal/etag_with_flash.rb @@ -0,0 +1,16 @@ +module ActionController + # 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 + # that the etag for a response should be computed with the content of the flash + # in mind. This does that by including the content of the flash as a component + # in the etag that's generated for a response. + module EtagWithFlash + extend ActiveSupport::Concern + + include ActionController::ConditionalGet + + included do + etag { flash unless flash.empty? } + end + end +end diff --git a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb index 669cf55bca..6c103bb042 100644 --- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb +++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb @@ -33,18 +33,24 @@ module ActionController end private - def determine_template_etag(options) - if template = pick_template_for_etag(options) - lookup_and_digest_template(template) + def determine_template_etag(options) + if template = pick_template_for_etag(options) + lookup_and_digest_template(template) + end end - end - def pick_template_for_etag(options) - options.fetch(:template) { "#{controller_name}/#{action_name}" } - 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. + def pick_template_for_etag(options) + unless options[:template] == false + options[:template] || "#{controller_path}/#{action_name}" + end + end - def lookup_and_digest_template(template) - ActionView::Digestor.digest name: template, finder: lookup_context - end + def lookup_and_digest_template(template) + ActionView::Digestor.digest name: template, finder: lookup_context + end end end diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index 5c0ada37be..56a4b085e2 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -35,7 +35,7 @@ module ActionController class MethodNotAllowed < ActionControllerError #:nodoc: def initialize(*allowed_methods) - super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.") + super("Only #{allowed_methods.to_sentence(locale: :en)} requests are allowed.") end end @@ -49,7 +49,7 @@ module ActionController end class SessionOverflowError < ActionControllerError #:nodoc: - DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.' + DEFAULT_MESSAGE = "Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data." def initialize(message = nil) super(message || DEFAULT_MESSAGE) diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index ea8e91ce24..b8976497a4 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/hash/slice' +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" module ActionController # This module provides a method which will redirect the browser to use HTTPS @@ -76,10 +76,10 @@ module ActionController def force_ssl_redirect(host_or_options = nil) unless request.ssl? options = { - :protocol => 'https://', - :host => request.host, - :path => request.fullpath, - :status => :moved_permanently + protocol: "https://", + host: request.host, + path: request.fullpath, + status: :moved_permanently } if host_or_options.is_a?(Hash) diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 5e9832fd4e..86b5eb20d7 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -18,7 +18,7 @@ module ActionController # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list of valid +status+ symbols. def head(status, options = {}) if status.is_a?(Hash) - msg = status[:status] ? 'The :status option' : 'The implicit :ok status' + msg = status[:status] ? "The :status option" : "The implicit :ok status" options, status = status, status.delete(:status) ActiveSupport::Deprecation.warn(<<-MSG.squish) @@ -33,7 +33,7 @@ module ActionController content_type = options.delete(:content_type) options.each do |key, value| - headers[key.to_s.dasherize.split('-').each { |v| v[0] = v[0].chr.upcase }.join('-')] = value.to_s + headers[key.to_s.dasherize.split("-").each { |v| v[0] = v[0].chr.upcase }.join("-")] = value.to_s end self.status = status @@ -41,7 +41,7 @@ module ActionController self.response_body = "" - if include_content?(self.response_code) + if include_content?(response_code) self.content_type = content_type || (Mime[formats.first] if formats) self.response.charset = false end @@ -50,15 +50,15 @@ module ActionController end private - def include_content?(status) - case status - when 100..199 - false - when 204, 205, 304 - false - else - true + def include_content?(status) + case status + when 100..199 + false + when 204, 205, 304 + false + else + true + end end - end end end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 295f0cb66f..476d081239 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -108,10 +108,10 @@ module ActionController end private - # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt> - def all_application_helpers - all_helpers_from_path(helpers_path) - end + # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt> + def all_application_helpers + all_helpers_from_path(helpers_path) + end end # Provides a proxy to access helper methods from outside the view. diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 4639348509..a335bf109e 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -1,5 +1,5 @@ -require 'base64' -require 'active_support/security_utils' +require "base64" +require "active_support/security_utils" module ActionController # Makes it dead easy to do HTTP Basic, Digest and Token authentication. @@ -99,23 +99,23 @@ module ActionController end def has_basic_credentials?(request) - request.authorization.present? && (auth_scheme(request).downcase == 'basic') + request.authorization.present? && (auth_scheme(request).downcase == "basic") end def user_name_and_password(request) - decode_credentials(request).split(':', 2) + decode_credentials(request).split(":", 2) end def decode_credentials(request) - ::Base64.decode64(auth_param(request) || '') + ::Base64.decode64(auth_param(request) || "") end def auth_scheme(request) - request.authorization.to_s.split(' ', 2).first + request.authorization.to_s.split(" ", 2).first end def auth_param(request) - request.authorization.to_s.split(' ', 2).second + request.authorization.to_s.split(" ", 2).second end def encode_credentials(user_name, password) @@ -208,7 +208,7 @@ module ActionController password = password_procedure.call(credentials[:username]) return false unless password - method = request.get_header('rack.methodoverride.original_method') || request.get_header('REQUEST_METHOD') + method = request.get_header("rack.methodoverride.original_method") || request.get_header("REQUEST_METHOD") uri = credentials[:uri] [true, false].any? do |trailing_question_mark| @@ -226,17 +226,17 @@ module ActionController # 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 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':')) - ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':')) + ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":")) + ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":")) end def ha1(credentials, password) - ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':')) + ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":")) end def encode_credentials(http_method, credentials, password, password_is_ha1) credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1) - "Digest " + credentials.sort_by {|x| x[0].to_s }.map {|v| "#{v[0]}='#{v[1]}'" }.join(', ') + "Digest " + credentials.sort_by { |x| x[0].to_s }.map { |v| "#{v[0]}='#{v[1]}'" }.join(", ") end def decode_credentials_header(request) @@ -244,9 +244,9 @@ module ActionController end def decode_credentials(header) - ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, '').split(',').map do |pair| - key, value = pair.split('=', 2) - [key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')] + ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, "").split(",").map do |pair| + key, value = pair.split("=", 2) + [key.strip, value.to_s.gsub(/^"|"$/,"").delete('\'')] end] end @@ -324,7 +324,6 @@ module ActionController def opaque(secret_key) ::Digest::MD5.hexdigest(secret_key) end - end # Makes it dead easy to do HTTP Token authentication. @@ -406,7 +405,7 @@ module ActionController # # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] module Token - TOKEN_KEY = 'token=' + TOKEN_KEY = "token=" TOKEN_REGEX = /^(Token|Bearer)\s+/ AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/ extend self @@ -476,14 +475,14 @@ module ActionController # This removes the <tt>"</tt> characters wrapping the value. def rewrite_param_values(array_params) - array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, '' } + array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, "" } end # This method takes an authorization body and splits up the key-value # pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt> # delimiters defined in +AUTHN_PAIR_DELIMITERS+. def raw_params(auth) - _raw_params = auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/) + _raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/) if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}}) _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}" diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index 6192fc0f9c..8615c16c6f 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/strip' +require "active_support/core_ext/string/strip" module ActionController # Handles implicit rendering for a controller action that does not @@ -27,7 +27,6 @@ module ActionController # Finally, if we DON'T find a template AND the request isn't a browser page # load, then we implicitly respond with 204 No Content. module ImplicitRender - # :stopdoc: include BasicImplicitRender @@ -49,7 +48,7 @@ module ActionController "NOTE! For XHR/Ajax or API requests, this action would normally " \ "respond with 204 No Content: an empty white screen. Since you're " \ "loading it in a web browser, we assume that you expected to " \ - "actually render a template, not… nothing, so we're showing an " \ + "actually render a template, not nothing, so we're showing an " \ "error to be extra-clear. If you expect 204 No Content, carry on. " \ "That's what you'll get from an XHR or API request. Give it a shot." @@ -62,8 +61,8 @@ module ActionController def method_for_action(action_name) super || if template_exists?(action_name.to_s, _prefixes) - "default_render" - end + "default_render" + end end private diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 624a6d5b76..2ede96c667 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -1,5 +1,5 @@ -require 'benchmark' -require 'abstract_controller/logger' +require "benchmark" +require "abstract_controller/logger" module ActionController # Adds instrumentation to several ends in ActionController::Base. It also provides @@ -16,13 +16,13 @@ module ActionController def process_action(*args) raw_payload = { - :controller => self.class.name, - :action => self.action_name, - :params => request.filtered_parameters, - :headers => request.headers, - :format => request.format.ref, - :method => request.request_method, - :path => request.fullpath + controller: self.class.name, + action: action_name, + params: request.filtered_parameters, + headers: request.headers, + format: request.format.ref, + method: request.request_method, + path: request.fullpath } ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup) @@ -48,7 +48,7 @@ module ActionController def send_file(path, options={}) ActiveSupport::Notifications.instrument("send_file.action_controller", - options.merge(:path => path)) do + options.merge(path: path)) do super end end @@ -72,7 +72,7 @@ module ActionController # A hook invoked every time a before callback is halted. def halted_callback_hook(filter) - ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter) + ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter) end # A hook which allows you to clean up any time, wrongly taken into account in diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 5d395cd8bd..26a16104db 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -1,6 +1,6 @@ -require 'action_dispatch/http/response' -require 'delegate' -require 'active_support/json' +require "action_dispatch/http/response" +require "delegate" +require "active_support/json" module ActionController # Mix this module into your controller, and all actions in that controller @@ -84,7 +84,6 @@ module ActionController # Note: SSEs are not currently supported by IE. However, they are supported # by Chrome, Firefox, Opera, and Safari. class SSE - WHITELISTED_OPTIONS = %w( retry event id ) def initialize(stream, options = {}) @@ -206,7 +205,12 @@ module ActionController private def each_chunk(&block) - while str = @buf.pop + loop do + str = nil + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + str = @buf.pop + end + break unless str yield str end end @@ -215,18 +219,18 @@ module ActionController class Response < ActionDispatch::Response #:nodoc: all private - def before_committed - super - jar = request.cookie_jar - # The response can be committed multiple times - jar.write self unless committed? - end + def before_committed + super + jar = request.cookie_jar + # The response can be committed multiple times + jar.write self unless committed? + end - def build_buffer(response, body) - buf = Live::Buffer.new response - body.each { |part| buf.write part } - buf - end + def build_buffer(response, body) + buf = Live::Buffer.new response + body.each { |part| buf.write part } + buf + end end def process(name) diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 2e89af1a5e..f6aabcb102 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -1,4 +1,4 @@ -require 'abstract_controller/collector' +require "abstract_controller/collector" module ActionController #:nodoc: module MimeResponds @@ -280,8 +280,8 @@ module ActionController #:nodoc: def any(*args, &block) if block_given? - if args.any? && args.none?{ |a| a == @variant } - args.each{ |v| @variants[v] = block } + if args.any? && args.none? { |a| a == @variant } + args.each { |v| @variants[v] = block } else @variants[:any] = block end diff --git a/actionpack/lib/action_controller/metal/parameter_encoding.rb b/actionpack/lib/action_controller/metal/parameter_encoding.rb new file mode 100644 index 0000000000..c457fd0d06 --- /dev/null +++ b/actionpack/lib/action_controller/metal/parameter_encoding.rb @@ -0,0 +1,30 @@ +module ActionController + # Allows encoding to be specified per parameter per action. + module ParameterEncoding + extend ActiveSupport::Concern + + module ClassMethods + def inherited(klass) # :nodoc: + super + klass.setup_param_encode + end + + def setup_param_encode # :nodoc: + @_parameter_encodings = {} + end + + def encoding_for_param(action, param) # :nodoc: + if @_parameter_encodings[action.to_s] && @_parameter_encodings[action.to_s][param.to_s] + @_parameter_encodings[action.to_s][param.to_s] + else + super + end + end + + def parameter_encoding(action, param_name, encoding) + @_parameter_encodings[action.to_s] ||= {} + @_parameter_encodings[action.to_s][param_name.to_s] = encoding + end + end + end +end diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index c38fc40b81..9d1b740025 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -1,7 +1,7 @@ -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' +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 # Wraps the parameters hash into a nested hash. This will allow clients to @@ -71,7 +71,7 @@ module ActionController EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8) - require 'mutex_m' + require "mutex_m" class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc: include Mutex_m @@ -128,30 +128,30 @@ module ActionController 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. - # - # 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 #:nodoc: - return nil if klass.anonymous? - model_name = klass.name.sub(/Controller$/, '').classify - - begin - if model_klass = model_name.safe_constantize - model_klass - else - namespaces = model_name.split("::") - namespaces.delete_at(-2) - break if namespaces.last == model_name - model_name = namespaces.join("::") - end - end until model_klass + # 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. + def _default_wrap_model #:nodoc: + return nil if klass.anonymous? + model_name = klass.name.sub(/Controller$/, "").classify + + begin + if model_klass = model_name.safe_constantize + model_klass + else + namespaces = model_name.split("::") + namespaces.delete_at(-2) + break if namespaces.last == model_name + model_name = namespaces.join("::") + end + end until model_klass - model_klass - end + model_klass + end end included do @@ -198,9 +198,9 @@ module ActionController when Hash options = name_or_model_or_options when false - options = options.merge(:format => []) + options = options.merge(format: []) when Symbol, String - options = options.merge(:name => name_or_model_or_options) + options = options.merge(name: name_or_model_or_options) else model = name_or_model_or_options end diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 3c7cc15627..2bd4296aff 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -111,7 +111,7 @@ module ActionController `fallback_location` represents the location to use if the request has no HTTP referer information. MESSAGE - request.headers["Referer"] or raise RedirectBackError + request.headers["Referer"] || raise(RedirectBackError) when Proc _compute_redirect_to_location request, options.call else diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 1735609cd9..f8a037189c 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -1,4 +1,4 @@ -require 'set' +require "set" module ActionController # See <tt>Renderers.add</tt> @@ -71,8 +71,6 @@ module ActionController # format.csv { render csv: @csvable, filename: @csvable.name } # end # end - # To use renderers and their mime types in more concise ways, see - # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> def self.add(key, &block) define_method(_render_with_renderer_method_name(key), &block) RENDERERS << key.to_sym @@ -94,7 +92,6 @@ module ActionController end module ClassMethods - # Adds, by name, a renderer or renderers to the +_renderers+ available # to call within controller actions. # diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index cce6fe7787..f8f91ed41c 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/filters' +require "active_support/core_ext/string/filters" module ActionController module Rendering @@ -32,7 +32,7 @@ module ActionController # Check for double render errors and set the content_type after rendering. def render(*args) #:nodoc: - raise ::AbstractController::DoubleRenderError if self.response_body + raise ::AbstractController::DoubleRenderError if response_body super end @@ -49,84 +49,84 @@ module ActionController end def render_to_body(options = {}) - super || _render_in_priorities(options) || ' ' + super || _render_in_priorities(options) || " " end private - def _render_in_priorities(options) - RENDER_FORMATS_IN_PRIORITY.each do |format| - return options[format] if options.key?(format) - end + def _render_in_priorities(options) + RENDER_FORMATS_IN_PRIORITY.each do |format| + return options[format] if options.key?(format) + end - nil - end + nil + end - def _set_html_content_type - self.content_type = Mime[:html].to_s - end + def _set_html_content_type + self.content_type = Mime[:html].to_s + end - def _set_rendered_content_type(format) - unless response.content_type - self.content_type = format.to_s + def _set_rendered_content_type(format) + unless response.content_type + self.content_type = format.to_s + end end - end - # Normalize arguments by catching blocks and setting them on :update. - def _normalize_args(action=nil, options={}, &blk) #:nodoc: - options = super - options[:update] = blk if block_given? - options - end + # Normalize arguments by catching blocks and setting them on :update. + def _normalize_args(action=nil, options={}, &blk) #:nodoc: + options = super + options[:update] = blk if block_given? + options + end - # Normalize both text and status options. - def _normalize_options(options) #:nodoc: - _normalize_text(options) + # Normalize both text and status options. + def _normalize_options(options) #:nodoc: + _normalize_text(options) - if options[:text] - ActiveSupport::Deprecation.warn <<-WARNING.squish + if options[:text] + ActiveSupport::Deprecation.warn <<-WARNING.squish `render :text` is deprecated because it does not actually render a `text/plain` response. Switch to `render plain: 'plain text'` to render as `text/plain`, `render html: '<strong>HTML</strong>'` to render as `text/html`, or `render body: 'raw'` to match the deprecated behavior and render with the default Content-Type, which is - `text/plain`. + `text/html`. WARNING - end + end - if options[:html] - options[:html] = ERB::Util.html_escape(options[:html]) - end + if options[:html] + options[:html] = ERB::Util.html_escape(options[:html]) + end - if options.delete(:nothing) - ActiveSupport::Deprecation.warn("`:nothing` option is deprecated and will be removed in Rails 5.1. Use `head` method to respond with empty response body.") - options[:body] = nil - end + if options.delete(:nothing) + ActiveSupport::Deprecation.warn("`:nothing` option is deprecated and will be removed in Rails 5.1. Use `head` method to respond with empty response body.") + options[:body] = nil + end - if options[:status] - options[:status] = Rack::Utils.status_code(options[:status]) - end + if options[:status] + options[:status] = Rack::Utils.status_code(options[:status]) + end - super - end + super + end - def _normalize_text(options) - RENDER_FORMATS_IN_PRIORITY.each do |format| - if options.key?(format) && options[format].respond_to?(:to_text) - options[format] = options[format].to_text + def _normalize_text(options) + RENDER_FORMATS_IN_PRIORITY.each do |format| + if options.key?(format) && options[format].respond_to?(:to_text) + options[format] = options[format].to_text + end end end - end - # Process controller specific options, as status, content-type and location. - def _process_options(options) #:nodoc: - status, content_type, location = options.values_at(:status, :content_type, :location) + # Process controller specific options, as status, content-type and location. + def _process_options(options) #:nodoc: + status, content_type, location = options.values_at(:status, :content_type, :location) - self.status = status if status - self.content_type = content_type if content_type - self.headers["Location"] = url_for(location) if location + self.status = status if status + self.content_type = content_type if content_type + self.headers["Location"] = url_for(location) if location - super - end + super + end end end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index f7e8d06f10..3d3c121280 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -1,6 +1,6 @@ -require 'rack/session/abstract/id' -require 'action_controller/metal/exceptions' -require 'active_support/security_utils' +require "rack/session/abstract/id" +require "action_controller/metal/exceptions" +require "active_support/security_utils" module ActionController #:nodoc: class InvalidAuthenticityToken < ActionControllerError #:nodoc: @@ -109,10 +109,10 @@ module ActionController #:nodoc: # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>. # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference. # * <tt>:prepend</tt> - By default, the verification of the authentication token 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). + # 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 <tt>prepend: true</tt>. + # 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. # # Valid unverified request handling methods are: @@ -130,11 +130,11 @@ module ActionController #:nodoc: private - def protection_method_class(name) - ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify) - rescue NameError - raise ArgumentError, 'Invalid request forgery protection method, use :null_session, :exception, or :reset_session' - end + def protection_method_class(name) + ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify) + rescue NameError + raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, or :reset_session" + end end module ProtectionMethods @@ -154,26 +154,26 @@ module ActionController #:nodoc: protected - class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: - def initialize(req) - super(nil, req) - @data = {} - @loaded = true - end + class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: + def initialize(req) + super(nil, req) + @data = {} + @loaded = true + end - # no-op - def destroy; end + # no-op + def destroy; end - def exists? - true + def exists? + true + end end - end - class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc: - def write(*) - # nothing + class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc: + def write(*) + # nothing + end end - end end class ResetSession @@ -235,7 +235,9 @@ module ActionController #:nodoc: # we aren't serving an unauthorized cross-origin response. def verify_same_origin_request if marked_for_same_origin_verification? && non_xhr_javascript_response? - logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING if logger + if logger && log_warning_on_csrf_failure + logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING + end raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING end end @@ -380,7 +382,7 @@ module ActionController #:nodoc: def xor_byte_strings(s1, s2) s2_bytes = s2.bytes s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 } - s2_bytes.pack('C*') + s2_bytes.pack("C*") end # The form's authenticity parameter. Override to provide your own. @@ -406,7 +408,7 @@ module ActionController #:nodoc: def normalize_action_path(action_path) uri = URI.parse(action_path) - uri.path.chomp('/') + uri.path.chomp("/") end end end diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb index f1c967b982..2d99e4045b 100644 --- a/actionpack/lib/action_controller/metal/rescue.rb +++ b/actionpack/lib/action_controller/metal/rescue.rb @@ -6,17 +6,6 @@ module ActionController #:nodoc: extend ActiveSupport::Concern include ActiveSupport::Rescuable - def rescue_with_handler(exception) - if exception.cause - handler_index = index_of_handler_for_rescue(exception) || Float::INFINITY - cause_handler_index = index_of_handler_for_rescue(exception.cause) - if cause_handler_index && cause_handler_index <= handler_index - exception = exception.cause - end - end - super(exception) - end - # Override this method if you want to customize when detailed # exceptions must be shown. This method is only called when # consider_all_requests_local is false. By default, it returns @@ -30,8 +19,8 @@ module ActionController #:nodoc: def process_action(*args) super rescue Exception => exception - request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions? - rescue_with_handler(exception) || raise(exception) + request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions? + rescue_with_handler(exception) || raise end end end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index a6115674aa..481f19f1ef 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -1,4 +1,4 @@ -require 'rack/chunked' +require "rack/chunked" module ActionController #:nodoc: # Allows views to be streamed back to the client as they are rendered. diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 08049d7af8..387c2aa0b9 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,12 +1,14 @@ -require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/hash/transform_values' -require 'active_support/core_ext/array/wrap' -require 'active_support/core_ext/string/filters' -require 'active_support/rescuable' -require 'action_dispatch/http/upload' -require 'rack/test' -require 'stringio' -require 'set' +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/hash/transform_values" +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/string/filters" +require "active_support/core_ext/object/to_query" +require "active_support/rescuable" +require "action_dispatch/http/upload" +require "rack/test" +require "stringio" +require "set" +require "yaml" module ActionController # Raised when a required parameter is missing. @@ -31,13 +33,13 @@ module ActionController # # params = ActionController::Parameters.new(a: "123", b: "456") # params.permit(:c) - # # => ActionController::UnpermittedParameters: found unpermitted parameters: a, b + # # => ActionController::UnpermittedParameters: found unpermitted parameters: :a, :b class UnpermittedParameters < IndexError attr_reader :params # :nodoc: def initialize(params) # :nodoc: @params = params - super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.join(", ")}") + super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.map { |e| ":#{e}" }.join(", ")}") end end @@ -58,8 +60,7 @@ module ActionController # }) # # permitted = params.require(:person).permit(:name, :age) - # permitted # => {"name"=>"Francesco", "age"=>22} - # permitted.class # => ActionController::Parameters + # permitted # => <ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true> # permitted.permitted? # => true # # Person.first.update!(permitted) @@ -87,7 +88,7 @@ module ActionController # # params = ActionController::Parameters.new(a: "123", b: "456") # params.permit(:c) - # # => {} + # # => <ActionController::Parameters {} permitted: true> # # ActionController::Parameters.action_on_unpermitted_parameters = :raise # @@ -107,6 +108,8 @@ module ActionController # params["key"] # => "value" class Parameters cattr_accessor :permit_all_parameters, instance_accessor: false + self.permit_all_parameters = false + cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?, @@ -255,7 +258,7 @@ module ActionController # either present or the singleton +false+, returns said value: # # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person) - # # => {"name"=>"Francesco"} + # # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false> # # Otherwise raises <tt>ActionController::ParameterMissing</tt>: # @@ -276,12 +279,12 @@ module ActionController # returned: # # params = ActionController::Parameters.new(user: { ... }, profile: { ... }) - # user_params, profile_params = params.require(:user, :profile) + # user_params, profile_params = params.require([:user, :profile]) # # Otherwise, the method re-raises the first exception found: # # params = ActionController::Parameters.new(user: {}, profile: {}) - # user_params, profile_params = params.require(:user, :profile) + # user_params, profile_params = params.require([:user, :profile]) # # ActionController::ParameterMissing: param is missing or the value is empty: user # # Technically this method can be used to fetch terminal values: @@ -374,13 +377,13 @@ module ActionController # }) # # params.require(:person).permit(:contact) - # # => {} + # # => <ActionController::Parameters {} permitted: true> # # params.require(:person).permit(contact: :phone) - # # => {"contact"=>{"phone"=>"555-1234"}} + # # => <ActionController::Parameters {"contact"=><ActionController::Parameters {"phone"=>"555-1234"} permitted: true>} permitted: true> # # params.require(:person).permit(contact: [ :email, :phone ]) - # # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}} + # # => <ActionController::Parameters {"contact"=><ActionController::Parameters {"email"=>"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true> def permit(*filters) params = self.class.new @@ -402,7 +405,7 @@ module ActionController # returns +nil+. # # params = ActionController::Parameters.new(person: { name: 'Francesco' }) - # params[:person] # => {"name"=>"Francesco"} + # params[:person] # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false> # params[:none] # => nil def [](key) convert_hashes_to_parameters(key, @parameters[key]) @@ -421,7 +424,7 @@ module ActionController # is given, then that will be run and its result returned. # # params = ActionController::Parameters.new(person: { name: 'Francesco' }) - # params.fetch(:person) # => {"name"=>"Francesco"} + # params.fetch(:person) # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false> # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none # params.fetch(:none, 'Francesco') # => "Francesco" # params.fetch(:none) { 'Francesco' } # => "Francesco" @@ -441,12 +444,12 @@ module ActionController # Extracts the nested parameter from the given +keys+ by calling +dig+ # at each step. Returns +nil+ if any intermediate step is +nil+. # - # params = ActionController::Parameters.new(foo: { bar: { baz: 1 } }) - # params.dig(:foo, :bar, :baz) # => 1 - # params.dig(:foo, :zot, :xyz) # => nil + # params = ActionController::Parameters.new(foo: { bar: { baz: 1 } }) + # params.dig(:foo, :bar, :baz) # => 1 + # params.dig(:foo, :zot, :xyz) # => nil # - # params2 = ActionController::Parameters.new(foo: [10, 11, 12]) - # params2.dig(:foo, 1) # => 11 + # params2 = ActionController::Parameters.new(foo: [10, 11, 12]) + # params2.dig(:foo, 1) # => 11 def dig(*keys) convert_value_to_parameters(@parameters.dig(*keys)) end @@ -457,8 +460,8 @@ module ActionController # don't exist, returns an empty hash. # # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) - # params.slice(:a, :b) # => {"a"=>1, "b"=>2} - # params.slice(:d) # => {} + # params.slice(:a, :b) # => <ActionController::Parameters {"a"=>1, "b"=>2} permitted: false> + # params.slice(:d) # => <ActionController::Parameters {} permitted: false> def slice(*keys) new_instance_with_inherited_permitted_status(@parameters.slice(*keys)) end @@ -474,8 +477,8 @@ module ActionController # filters out the given +keys+. # # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) - # params.except(:a, :b) # => {"c"=>3} - # params.except(:d) # => {"a"=>1,"b"=>2,"c"=>3} + # params.except(:a, :b) # => <ActionController::Parameters {"c"=>3} permitted: false> + # params.except(:d) # => <ActionController::Parameters {"a"=>1, "b"=>2, "c"=>3} permitted: false> def except(*keys) new_instance_with_inherited_permitted_status(@parameters.except(*keys)) end @@ -483,8 +486,8 @@ module ActionController # Removes and returns the key/value pairs matching the given keys. # # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) - # params.extract!(:a, :b) # => {"a"=>1, "b"=>2} - # params # => {"c"=>3} + # params.extract!(:a, :b) # => <ActionController::Parameters {"a"=>1, "b"=>2} permitted: false> + # params # => <ActionController::Parameters {"c"=>3} permitted: false> def extract!(*keys) new_instance_with_inherited_permitted_status(@parameters.extract!(*keys)) end @@ -494,7 +497,7 @@ module ActionController # # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) # params.transform_values { |x| x * 2 } - # # => {"a"=>2, "b"=>4, "c"=>6} + # # => <ActionController::Parameters {"a"=>2, "b"=>4, "c"=>6} permitted: false> def transform_values(&block) if block new_instance_with_inherited_permitted_status( @@ -571,25 +574,11 @@ module ActionController convert_value_to_parameters(@parameters.values_at(*keys)) end - # Returns an exact copy of the <tt>ActionController::Parameters</tt> - # instance. +permitted+ state is kept on the duped object. - # - # params = ActionController::Parameters.new(a: 1) - # params.permit! - # params.permitted? # => true - # copy_params = params.dup # => {"a"=>1} - # copy_params.permitted? # => true - def dup - super.tap do |duplicate| - duplicate.permitted = @permitted - end - end - # Returns a new <tt>ActionController::Parameters</tt> with all keys from # +other_hash+ merges into current hash. def merge(other_hash) new_instance_with_inherited_permitted_status( - @parameters.merge(other_hash) + @parameters.merge(other_hash.to_h) ) end @@ -604,6 +593,37 @@ module ActionController "<#{self.class} #{@parameters} permitted: #{@permitted}>" end + def self.hook_into_yaml_loading # :nodoc: + # Wire up YAML format compatibility with Rails 4.2 and Psych 2.0.8 and 2.0.9+. + # Makes the YAML parser call `init_with` when it encounters the keys below + # instead of trying its own parsing routines. + YAML.load_tags["!ruby/hash-with-ivars:ActionController::Parameters"] = name + YAML.load_tags["!ruby/hash:ActionController::Parameters"] = name + end + hook_into_yaml_loading + + def init_with(coder) # :nodoc: + case coder.tag + when "!ruby/hash:ActionController::Parameters" + # YAML 2.0.8's format where hash instance variables weren't stored. + @parameters = coder.map.with_indifferent_access + @permitted = false + when "!ruby/hash-with-ivars:ActionController::Parameters" + # YAML 2.0.9's Hash subclass format where keys and values + # were stored under an elements hash and `permitted` within an ivars hash. + @parameters = coder.map["elements"].with_indifferent_access + @permitted = coder.map["ivars"][:@permitted] + when "!ruby/object:ActionController::Parameters" + # YAML's Object format. Only needed because of the format + # backwardscompability above, otherwise equivalent to YAML's initialization. + @parameters, @permitted = coder.map["parameters"], coder.map["permitted"] + end + end + + # Undefine `to_param` such that it gets caught in the `method_missing` + # deprecation cycle below. + undef_method :to_param + def method_missing(method_sym, *args, &block) if @parameters.respond_to?(method_sym) message = <<-DEPRECATE.squish @@ -704,7 +724,7 @@ module ActionController end def unpermitted_keys(params) - self.keys - params.keys - self.always_permitted_parameters + keys - params.keys - always_permitted_parameters end # @@ -735,7 +755,7 @@ module ActionController ] def permitted_scalar?(value) - PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)} + PERMITTED_SCALAR_TYPES.any? { |type| value.is_a?(type) } end def permitted_scalar_filter(params, key) @@ -751,7 +771,7 @@ module ActionController end def array_of_permitted_scalars?(value) - if value.is_a?(Array) && value.all? {|element| permitted_scalar?(element)} + if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) } yield value end end @@ -782,6 +802,11 @@ module ActionController end end end + + def initialize_copy(source) + super + @parameters = @parameters.dup + end end # == Strong \Parameters @@ -797,7 +822,7 @@ module ActionController # # class PeopleController < ActionController::Base # # Using "Person.create(params[:person])" would raise an - # # ActiveModel::ForbiddenAttributes exception because it'd + # # ActiveModel::ForbiddenAttributesError exception because it'd # # be using mass assignment without an explicit permit step. # # This is the recommended form: # def create diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index ac37b00010..9bb416178a 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -13,7 +13,7 @@ module ActionController module ClassMethods def before_filters - _process_action_callbacks.find_all{|x| x.kind == :before}.map(&:name) + _process_action_callbacks.find_all { |x| x.kind == :before }.map(&:name) end end end diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index dbf7241a14..9f3cc099d6 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -27,10 +27,10 @@ module ActionController def url_options @_url_options ||= { - :host => request.host, - :port => request.optional_port, - :protocol => request.protocol, - :_recall => request.path_parameters + host: request.host, + port: request.optional_port, + protocol: request.protocol, + _recall: request.path_parameters }.merge!(super).freeze if (same_origin = _routes.equal?(request.routes)) || diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 28b20052b5..6513a556ee 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -11,7 +11,7 @@ module ActionController config.eager_load_namespaces << ActionController - initializer "action_controller.assets_config", :group => :all do |app| + initializer "action_controller.assets_config", group: :all do |app| app.config.action_controller.assets_dir ||= app.config.paths["public"].first end diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb index 5ff4a658ad..0739f16965 100644 --- a/actionpack/lib/action_controller/renderer.rb +++ b/actionpack/lib/action_controller/renderer.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/hash/keys' +require "active_support/core_ext/hash/keys" module ActionController - # ActionController::Renderer allows to render arbitrary templates + # ActionController::Renderer allows you to render arbitrary templates # without requirement of being in controller actions. # # You get a concrete renderer class by invoking ActionController::Base#renderer. @@ -37,11 +37,11 @@ module ActionController attr_reader :defaults, :controller DEFAULTS = { - http_host: 'example.org', + http_host: "example.org", https: false, - method: 'get', - script_name: '', - input: '' + method: "get", + script_name: "", + input: "" }.freeze # Create a new renderer instance for a specific controller class. @@ -69,7 +69,7 @@ module ActionController # Render templates with any options from ActionController::Base#render_to_string. def render(*args) - raise 'missing controller' unless controller + raise "missing controller" unless controller request = ActionDispatch::Request.new @env request.routes = controller._routes @@ -88,17 +88,17 @@ module ActionController end RACK_KEY_TRANSLATION = { - http_host: 'HTTP_HOST', - https: 'HTTPS', - method: 'REQUEST_METHOD', - script_name: 'SCRIPT_NAME', - input: 'rack.input' + http_host: "HTTP_HOST", + https: "HTTPS", + method: "REQUEST_METHOD", + script_name: "SCRIPT_NAME", + input: "rack.input" } IDENTITY = ->(_) { _ } RACK_VALUE_TRANSLATION = { - https: ->(v) { v ? 'on' : 'off' }, + https: ->(v) { v ? "on" : "off" }, method: ->(v) { v.upcase }, } diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index ed2edcbe06..f4ec13c831 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -1,13 +1,12 @@ -require 'rack/session/abstract/id' -require 'active_support/core_ext/hash/conversions' -require 'active_support/core_ext/object/to_query' -require 'active_support/core_ext/module/anonymous' -require 'active_support/core_ext/hash/keys' -require 'action_controller/template_assertions' -require 'rails-dom-testing' +require "rack/session/abstract/id" +require "active_support/core_ext/hash/conversions" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/hash/keys" +require "action_controller/template_assertions" +require "rails-dom-testing" module ActionController - # :stopdoc: class Metal include Testing::Functional end @@ -27,18 +26,20 @@ module ActionController # Please use ActionDispatch::IntegrationTest going forward. class TestRequest < ActionDispatch::TestRequest #:nodoc: DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup - DEFAULT_ENV.delete 'PATH_INFO' + DEFAULT_ENV.delete "PATH_INFO" def self.new_session TestSession.new end + attr_reader :controller_class + # Create a new test request with default `env` values - def self.create + def self.create(controller_class) env = {} env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application env["rack.request.cookie_hash"] = {}.with_indifferent_access - new(default_env.merge(env), new_session) + new(default_env.merge(env), new_session, controller_class) end def self.default_env @@ -46,13 +47,14 @@ module ActionController end private_class_method :default_env - def initialize(env, session) + def initialize(env, session, controller_class) super(env) self.session = session - self.session_options = TestSession::DEFAULT_OPTIONS + self.session_options = TestSession::DEFAULT_OPTIONS.dup + @controller_class = controller_class @custom_param_parsers = { - xml: lambda { |raw_post| Hash.from_xml(raw_post)['hash'] } + xml: lambda { |raw_post| Hash.from_xml(raw_post)["hash"] } } end @@ -61,7 +63,7 @@ module ActionController end def content_type=(type) - set_header 'CONTENT_TYPE', type + set_header "CONTENT_TYPE", type end def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys) @@ -83,7 +85,7 @@ module ActionController end if get? - if self.query_string.blank? + if query_string.blank? self.query_string = non_path_parameters.to_query end else @@ -91,8 +93,8 @@ module ActionController self.content_type = ENCODER.content_type data = ENCODER.build_multipart non_path_parameters else - fetch_header('CONTENT_TYPE') do |k| - set_header k, 'application/x-www-form-urlencoded' + fetch_header("CONTENT_TYPE") do |k| + set_header k, "application/x-www-form-urlencoded" end case content_mime_type.to_sym @@ -110,8 +112,8 @@ module ActionController end end - set_header 'CONTENT_LENGTH', data.length.to_s - set_header 'rack.input', StringIO.new(data) + set_header "CONTENT_LENGTH", data.length.to_s + set_header "rack.input", StringIO.new(data) end fetch_header("PATH_INFO") do |k| @@ -152,9 +154,9 @@ module ActionController private - def params_parsers - super.merge @custom_param_parsers - end + def params_parsers + super.merge @custom_param_parsers + end end class LiveTestResponse < Live::Response @@ -208,10 +210,18 @@ module ActionController end # Superclass for ActionController functional tests. Functional tests allow you to - # test a single controller action per test method. This should not be confused with - # integration tests (see ActionDispatch::IntegrationTest), which are more like - # "stories" that can involve multiple controllers and multiple actions (i.e. multiple - # different HTTP requests). + # test a single controller action per test method. + # + # == Use integration style controller tests over functional style controller 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. # # == Basic example # @@ -320,7 +330,6 @@ module ActionController 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. # @@ -419,11 +428,11 @@ module ActionController `get :index, xhr: true` and `post :create, xhr: true` MSG - @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - @request.env['HTTP_ACCEPT'] ||= [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ') + @request.env["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" + @request.env["HTTP_ACCEPT"] ||= [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ") __send__(*args).tap do - @request.env.delete 'HTTP_X_REQUESTED_WITH' - @request.env.delete 'HTTP_ACCEPT' + @request.env.delete "HTTP_X_REQUESTED_WITH" + @request.env.delete "HTTP_ACCEPT" end end alias xhr :xml_http_request @@ -440,6 +449,8 @@ module ActionController # - +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: # @@ -460,12 +471,12 @@ module ActionController check_required_ivars if kwarg_request?(args) - parameters, session, body, flash, http_method, format, xhr = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr) + parameters, session, body, flash, http_method, format, xhr, as = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr, :as) else http_method, parameters, session, flash = args format = nil - if parameters.is_a?(String) && http_method != 'HEAD' + if parameters.is_a?(String) && http_method != "HEAD" body = parameters parameters = nil end @@ -476,7 +487,7 @@ module ActionController end if body - @request.set_header 'RAW_POST_DATA', body + @request.set_header "RAW_POST_DATA", body end if http_method @@ -487,23 +498,28 @@ module ActionController parameters ||= {} - if format - parameters[:format] = format - end - @html_document = nil - self.cookies.update @request.cookies - self.cookies.update_cookies_from_jar - @request.set_header 'HTTP_COOKIE', cookies.to_header - @request.delete_header 'action_dispatch.cookies' + cookies.update(@request.cookies) + cookies.update_cookies_from_jar + @request.set_header "HTTP_COOKIE", cookies.to_header + @request.delete_header "action_dispatch.cookies" - @request = TestRequest.new scrub_env!(@request.env), @request.session + @request = TestRequest.new scrub_env!(@request.env), @request.session, @controller.class @response = build_response @response_klass @response.request = @request @controller.recycle! - @request.set_header 'REQUEST_METHOD', http_method + @request.set_header "REQUEST_METHOD", http_method + + if as + @request.content_type = Mime[as].to_s + format ||= as + end + + if format + parameters[:format] = format + end parameters = parameters.symbolize_keys @@ -517,9 +533,9 @@ module ActionController @request.flash.update(flash || {}) if xhr - @request.set_header 'HTTP_X_REQUESTED_WITH', 'XMLHttpRequest' - @request.fetch_header('HTTP_ACCEPT') do |k| - @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ') + @request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest" + @request.fetch_header("HTTP_ACCEPT") do |k| + @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ") end end @@ -527,34 +543,37 @@ module ActionController @request.set_header k, @controller.config.relative_url_root end - @controller.recycle! - @controller.dispatch(action, @request, @response) - @request = @controller.request - @response = @controller.response + begin + @controller.recycle! + @controller.dispatch(action, @request, @response) + ensure + @request = @controller.request + @response = @controller.response - @request.delete_header 'HTTP_COOKIE' + @request.delete_header "HTTP_COOKIE" - if @request.have_cookie_jar? - unless @request.cookie_jar.committed? - @request.cookie_jar.write(@response) - self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies)) + if @request.have_cookie_jar? + unless @request.cookie_jar.committed? + @request.cookie_jar.write(@response) + cookies.update(@request.cookie_jar.instance_variable_get(:@cookies)) + end end - end - @response.prepare! + @response.prepare! - if flash_value = @request.flash.to_session_value - @request.session['flash'] = flash_value - else - @request.session.delete('flash') - end + if flash_value = @request.flash.to_session_value + @request.session["flash"] = flash_value + else + @request.session.delete("flash") + end - if xhr - @request.delete_header 'HTTP_X_REQUESTED_WITH' - @request.delete_header 'HTTP_ACCEPT' - end - @request.query_string = '' + if xhr + @request.delete_header "HTTP_X_REQUESTED_WITH" + @request.delete_header "HTTP_ACCEPT" + end + @request.query_string = "" - @response.sent! + @response.sent! + end @response end @@ -589,7 +608,7 @@ module ActionController end end - @request = TestRequest.create + @request = TestRequest.create(@controller.class) @response = build_response @response_klass @response.request = @request @@ -608,71 +627,67 @@ module ActionController include ActionDispatch::Assertions class_attribute :_controller_class setup :setup_controller_request_and_response + ActiveSupport.run_load_hooks(:action_controller_test_case, self) end private - def scrub_env!(env) - env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ } - env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ } - env.delete 'action_dispatch.request.query_parameters' - env.delete 'action_dispatch.request.request_parameters' - env - end + def scrub_env!(env) + env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ } + env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ } + env.delete "action_dispatch.request.query_parameters" + env.delete "action_dispatch.request.request_parameters" + env["rack.input"] = StringIO.new + env + end - def process_with_kwargs(http_method, action, *args) - if kwarg_request?(args) - args.first.merge!(method: http_method) - process(action, *args) - else - non_kwarg_request_warning if args.any? + def process_with_kwargs(http_method, action, *args) + if kwarg_request?(args) + args.first.merge!(method: http_method) + process(action, *args) + else + non_kwarg_request_warning if args.any? - args = args.unshift(http_method) - process(action, *args) + args = args.unshift(http_method) + process(action, *args) + end end - end - REQUEST_KWARGS = %i(params session flash method body xhr) - def kwarg_request?(args) - args[0].respond_to?(:keys) && ( - (args[0].key?(:format) && args[0].keys.size == 1) || - args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) } - ) - end + REQUEST_KWARGS = %i(params session flash method body xhr) + def kwarg_request?(args) + args[0].respond_to?(:keys) && ( + (args[0].key?(:format) && args[0].keys.size == 1) || + args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) } + ) + end - def non_kwarg_request_warning - ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc) - ActionController::TestCase HTTP request methods will accept only - keyword arguments in future Rails versions. + def non_kwarg_request_warning + ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc) + ActionController::TestCase HTTP request methods will accept only + keyword arguments in future Rails versions. - Examples: + Examples: - get :show, params: { id: 1 }, session: { user_id: 1 } - process :update, method: :post, params: { id: 1 } - MSG - end + get :show, params: { id: 1 }, session: { user_id: 1 } + process :update, method: :post, params: { id: 1 } + MSG + end - def document_root_element - html_document.root - end + def document_root_element + html_document.root + end - def check_required_ivars - # Sanity 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." + def check_required_ivars + # Sanity 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." + end end end - end - - def html_format?(parameters) - return true unless parameters.key?(:format) - Mime.fetch(parameters[:format]) { Mime['html'] }.html? - end end include Behavior end - # :startdoc: end diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 01d49475de..60cee6acbd 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -21,15 +21,15 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require 'active_support' -require 'active_support/rails' -require 'active_support/core_ext/module/attribute_accessors' +require "active_support" +require "active_support/rails" +require "active_support/core_ext/module/attribute_accessors" -require 'action_pack' -require 'rack' +require "action_pack" +require "rack" module Rack - autoload :Test, 'rack/test' + autoload :Test, "rack/test" end module ActionDispatch @@ -39,17 +39,18 @@ module ActionDispatch end eager_autoload do - autoload_under 'http' do + autoload_under "http" do autoload :Request autoload :Response end end - autoload_under 'middleware' do + autoload_under "middleware" do autoload :RequestId autoload :Callbacks autoload :Cookies autoload :DebugExceptions + autoload :DebugLocks autoload :ExceptionWrapper autoload :Executor autoload :Flash @@ -63,7 +64,7 @@ module ActionDispatch end autoload :Journey - autoload :MiddlewareStack, 'action_dispatch/middleware/stack' + autoload :MiddlewareStack, "action_dispatch/middleware/stack" autoload :Routing module Http @@ -75,23 +76,23 @@ module ActionDispatch autoload :Parameters autoload :ParameterFilter autoload :Upload - autoload :UploadedFile, 'action_dispatch/http/upload' + autoload :UploadedFile, "action_dispatch/http/upload" autoload :URL end module Session - autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store' - autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' - autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store' - autoload :CacheStore, 'action_dispatch/middleware/session/cache_store' + autoload :AbstractStore, "action_dispatch/middleware/session/abstract_store" + autoload :CookieStore, "action_dispatch/middleware/session/cookie_store" + autoload :MemCacheStore, "action_dispatch/middleware/session/mem_cache_store" + autoload :CacheStore, "action_dispatch/middleware/session/cache_store" end mattr_accessor :test_app - autoload_under 'testing' do + autoload_under "testing" do autoload :Assertions autoload :Integration - autoload :IntegrationTest, 'action_dispatch/testing/integration' + autoload :IntegrationTest, "action_dispatch/testing/integration" autoload :TestProcess autoload :TestRequest autoload :TestResponse @@ -99,7 +100,7 @@ module ActionDispatch end end -autoload :Mime, 'action_dispatch/http/mime_type' +autoload :Mime, "action_dispatch/http/mime_type" ActiveSupport.on_load(:action_view) do ActionView::Base.default_formats ||= Mime::SET.symbols diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 9fa2e38ae3..985e0fb972 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -2,9 +2,8 @@ module ActionDispatch module Http module Cache module Request - - HTTP_IF_MODIFIED_SINCE = 'HTTP_IF_MODIFIED_SINCE'.freeze - HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze + HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE".freeze + HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH".freeze def if_modified_since if since = get_header(HTTP_IF_MODIFIED_SINCE) @@ -27,7 +26,7 @@ module ActionDispatch def etag_matches?(etag) if etag validators = if_none_match_etags - validators.include?(etag) || validators.include?('*') + validators.include?(etag) || validators.include?("*") end end @@ -102,11 +101,11 @@ module ActionDispatch end def weak_etag=(weak_validators) - set_header 'ETag', generate_weak_etag(weak_validators) + set_header "ETag", generate_weak_etag(weak_validators) end def strong_etag=(strong_validators) - set_header 'ETag', generate_strong_etag(strong_validators) + set_header "ETag", generate_strong_etag(strong_validators) end def etag?; etag; end @@ -123,7 +122,7 @@ module ActionDispatch private - DATE = 'Date'.freeze + DATE = "Date".freeze LAST_MODIFIED = "Last-Modified".freeze SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate]) @@ -137,7 +136,7 @@ module ActionDispatch def cache_control_segments if cache_control = _cache_control - cache_control.delete(' ').split(',') + cache_control.delete(" ").split(",") else [] end @@ -147,10 +146,10 @@ module ActionDispatch cache_control = {} cache_control_segments.each do |segment| - directive, argument = segment.split('=', 2) + directive, argument = segment.split("=", 2) if SPECIAL_KEYS.include? directive - key = directive.tr('-', '_') + key = directive.tr("-", "_") cache_control[key.to_sym] = argument || true else cache_control[:extras] ||= [] diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 041eca48ca..e5874a39f6 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -1,4 +1,4 @@ -require 'action_dispatch/http/parameter_filter' +require "action_dispatch/http/parameter_filter" module ActionDispatch module Http @@ -70,7 +70,7 @@ module ActionDispatch ParameterFilter.new(filters) end - KV_RE = '[^&;=]+' + KV_RE = "[^&;=]+" PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})} def filtered_query_string query_string.gsub(PAIR_RE) do |_| diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb index f4b806b8b5..fc3c44582a 100644 --- a/actionpack/lib/action_dispatch/http/filter_redirect.rb +++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb @@ -1,8 +1,7 @@ module ActionDispatch module Http module FilterRedirect - - FILTERED = '[FILTERED]'.freeze # :nodoc: + FILTERED = "[FILTERED]".freeze # :nodoc: def filtered_location # :nodoc: if location_filter_match? @@ -16,7 +15,7 @@ module ActionDispatch def location_filters if request - request.get_header('action_dispatch.redirect_filter') || [] + request.get_header("action_dispatch.redirect_filter") || [] else [] end @@ -31,7 +30,6 @@ module ActionDispatch end end end - end end end diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 69a934b7cd..3c03976f03 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -3,7 +3,7 @@ module ActionDispatch # 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.new(env) + # headers = ActionDispatch::Http::Headers.from_hash(env) # headers["Content-Type"] # => "text/plain" # headers["User-Agent"] # => "curl/7.43.0" # @@ -86,7 +86,7 @@ module ActionDispatch @req.fetch_header(env_name(key)) do return default unless default == DEFAULT return yield if block_given? - raise NameError, key + raise KeyError, key end end @@ -115,16 +115,16 @@ module ActionDispatch private - # 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 key =~ HTTP_HEADER - key = key.upcase.tr('-', '_') - key = "HTTP_" + key unless CGI_VARIABLES.include?(key) + # 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 key =~ HTTP_HEADER + key = key.upcase.tr("-", "_") + key = "HTTP_" + key unless CGI_VARIABLES.include?(key) + end + key end - key - end end end end diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 0a58ce2b96..d0c9413efa 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/module/attribute_accessors' +require "active_support/core_ext/module/attribute_accessors" module ActionDispatch module Http @@ -16,7 +16,7 @@ module ActionDispatch # X-Post-Data-Format HTTP header if present. def content_mime_type fetch_header("action_dispatch.request.content_type") do |k| - v = if get_header('CONTENT_TYPE') =~ /^([^,\;]*)/ + v = if get_header("CONTENT_TYPE") =~ /^([^,\;]*)/ Mime::Type.lookup($1.strip.downcase) else nil @@ -30,13 +30,13 @@ module ActionDispatch end def has_content_type? - has_header? 'CONTENT_TYPE' + has_header? "CONTENT_TYPE" end # Returns the accepted MIME type for the request. def accepts fetch_header("action_dispatch.request.accepts") do |k| - header = get_header('HTTP_ACCEPT').to_s.strip + header = get_header("HTTP_ACCEPT").to_s.strip v = if header.empty? [content_mime_type] @@ -152,23 +152,23 @@ module ActionDispatch protected - BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ + BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ - def valid_accept_header - (xhr? && (accept.present? || content_mime_type)) || - (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS) - end + def valid_accept_header + (xhr? && (accept.present? || content_mime_type)) || + (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS) + end - def use_accept_header - !self.class.ignore_accept_header - end + def use_accept_header + !self.class.ignore_accept_header + end - def format_from_path_extension - path = get_header('action_dispatch.original_path') || get_header('PATH_INFO') - if match = path && path.match(/\.(\w+)\z/) - Mime[match.captures.first] + def format_from_path_extension + path = get_header("action_dispatch.original_path") || get_header("PATH_INFO") + if match = path && path.match(/\.(\w+)\z/) + Mime[match.captures.first] + end end - end end end end diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 4672ea7199..b9121a577c 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -1,8 +1,8 @@ # -*- frozen-string-literal: true -*- -require 'singleton' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/string/starts_ends_with' +require "singleton" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/string/starts_ends_with" module Mime class Mimes @@ -99,7 +99,7 @@ module Mime def initialize(index, name, q = nil) @index = index @name = name - q ||= 0.0 if @name == '*/*'.freeze # default wildcard match to end of list + q ||= 0.0 if @name == "*/*".freeze # default wildcard match to end of list @q = ((q || 1.0).to_f * 100).to_i end @@ -114,7 +114,7 @@ module Mime def self.sort!(list) list.sort! - text_xml_idx = find_item_by_name list, 'text/xml' + text_xml_idx = find_item_by_name list, "text/xml" app_xml_idx = find_item_by_name list, Mime[:xml].to_s # Take care of the broken text/xml entry by renaming or deleting it @@ -141,7 +141,7 @@ module Mime type = list[idx] break if type.q < app_xml.q - if type.name.ends_with? '+xml' + if type.name.ends_with? "+xml" list[app_xml_idx], list[idx] = list[idx], app_xml app_xml_idx = idx end @@ -195,12 +195,12 @@ module Mime end def parse(accept_header) - if !accept_header.include?(',') + if !accept_header.include?(",") accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact else list, index = [], 0 - accept_header.split(',').each do |header| + accept_header.split(",").each do |header| params, q = header.split(PARAMETER_SEPARATOR_REGEXP) next unless params @@ -306,31 +306,31 @@ module Mime protected - attr_reader :string, :synonyms + attr_reader :string, :synonyms private - def to_ary; end - def to_a; end + def to_ary; end + def to_a; end - def method_missing(method, *args) - if method.to_s.ends_with? '?' - method[0..-2].downcase.to_sym == to_sym - else - super + def method_missing(method, *args) + if method.to_s.ends_with? "?" + method[0..-2].downcase.to_sym == to_sym + else + super + end end - end - def respond_to_missing?(method, include_private = false) #:nodoc: - method.to_s.ends_with? '?' - end + def respond_to_missing?(method, include_private = false) #:nodoc: + method.to_s.ends_with? "?" + end end class AllType < Type include Singleton def initialize - super '*/*', :all + super "*/*", :all end def all?; true; end @@ -352,14 +352,14 @@ module Mime def ref; end def respond_to_missing?(method, include_private = false) - method.to_s.ends_with? '?' + method.to_s.ends_with? "?" end private - def method_missing(method, *args) - false if method.to_s.ends_with? '?' - end + def method_missing(method, *args) + false if method.to_s.ends_with? "?" + end end end -require 'action_dispatch/http/mime_types' +require "action_dispatch/http/mime_types" diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb index e826551f4b..01fe35f5c6 100644 --- a/actionpack/lib/action_dispatch/http/parameter_filter.rb +++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb @@ -1,7 +1,9 @@ +require "active_support/core_ext/object/duplicable" + module ActionDispatch module Http class ParameterFilter - FILTERED = '[FILTERED]'.freeze # :nodoc: + FILTERED = "[FILTERED]".freeze # :nodoc: def initialize(filters = []) @filters = filters @@ -37,8 +39,8 @@ module ActionDispatch deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.".freeze) } deep_strings, strings = strings.partition { |s| s.include?("\\.".freeze) } - regexps << Regexp.new(strings.join('|'.freeze), true) unless strings.empty? - deep_regexps << Regexp.new(deep_strings.join('|'.freeze), true) unless deep_strings.empty? + regexps << Regexp.new(strings.join("|".freeze), true) unless strings.empty? + deep_regexps << Regexp.new(deep_strings.join("|".freeze), true) unless deep_strings.empty? new regexps, deep_regexps, blocks end @@ -58,7 +60,7 @@ module ActionDispatch parents.push(key) if deep_regexps if regexps.any? { |r| key =~ r } value = FILTERED - elsif deep_regexps && (joined = parents.join('.')) && deep_regexps.any? { |r| joined =~ r } + elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| joined =~ r } value = FILTERED elsif value.is_a?(Hash) value = call(value, parents) diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index ff5031d7d5..31ef0af791 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -3,12 +3,12 @@ module ActionDispatch module Parameters extend ActiveSupport::Concern - PARAMETERS_KEY = 'action_dispatch.request.path_parameters' + PARAMETERS_KEY = "action_dispatch.request.path_parameters" DEFAULT_PARSERS = { Mime[:json].symbol => -> (raw_post) { data = ActiveSupport::JSON.decode(raw_post) - data.is_a?(Hash) ? data : {:_json => data} + data.is_a?(Hash) ? data : { _json: data } } } @@ -37,14 +37,22 @@ module ActionDispatch query_parameters.dup end params.merge!(path_parameters) + params = set_custom_encoding(params) set_header("action_dispatch.request.parameters", params) params end alias :params :parameters def path_parameters=(parameters) #:nodoc: - delete_header('action_dispatch.request.parameters') + delete_header("action_dispatch.request.parameters") + + # 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 + rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e + raise ActionController::BadRequest.new("Invalid path parameters: #{e.message}") end # Returns a hash with the \parameters used to form the \path of the request. @@ -57,24 +65,39 @@ module ActionDispatch private - def parse_formatted_parameters(parsers) - return yield if content_length.zero? + def set_custom_encoding(params) + action = params[:action] + params.each do |k, v| + if v.is_a?(String) && v.encoding != encoding_template(action, k) + params[k] = v.force_encoding(encoding_template(action, k)) + end + end + + params + end + + def encoding_template(action, param) + controller_class.encoding_for_param(action, param) + end + + def parse_formatted_parameters(parsers) + return yield if content_length.zero? || content_mime_type.nil? - strategy = parsers.fetch(content_mime_type.symbol) { return yield } + strategy = parsers.fetch(content_mime_type.symbol) { return yield } - begin - strategy.call(raw_post) - rescue # JSON or Ruby code block errors - my_logger = logger || ActiveSupport::Logger.new($stderr) - my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}" + begin + strategy.call(raw_post) + rescue # JSON or Ruby code block errors + my_logger = logger || ActiveSupport::Logger.new($stderr) + my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}" - raise ParamsParser::ParseError + raise ParamsParser::ParseError + end end - end - def params_parsers - ActionDispatch::Request.parameter_parsers - end + def params_parsers + ActionDispatch::Request.parameter_parsers + end end end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index b0ed681623..e4ef9783f3 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -1,16 +1,16 @@ -require 'stringio' - -require 'active_support/inflector' -require 'action_dispatch/http/headers' -require 'action_controller/metal/exceptions' -require 'rack/request' -require 'action_dispatch/http/cache' -require 'action_dispatch/http/mime_negotiation' -require 'action_dispatch/http/parameters' -require 'action_dispatch/http/filter_parameters' -require 'action_dispatch/http/upload' -require 'action_dispatch/http/url' -require 'active_support/core_ext/array/conversions' +require "stringio" + +require "active_support/inflector" +require "action_dispatch/http/headers" +require "action_controller/metal/exceptions" +require "rack/request" +require "action_dispatch/http/cache" +require "action_dispatch/http/mime_negotiation" +require "action_dispatch/http/parameters" +require "action_dispatch/http/filter_parameters" +require "action_dispatch/http/upload" +require "action_dispatch/http/url" +require "active_support/core_ext/array/conversions" module ActionDispatch class Request @@ -22,8 +22,8 @@ module ActionDispatch include ActionDispatch::Http::URL include Rack::Request::Env - autoload :Session, 'action_dispatch/request/session' - autoload :Utils, 'action_dispatch/request/utils' + autoload :Session, "action_dispatch/request/session" + autoload :Utils, "action_dispatch/request/utils" LOCALHOST = Regexp.union [/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/] @@ -66,29 +66,18 @@ module ActionDispatch def commit_cookie_jar! # :nodoc: end - def check_path_parameters! - # If any of the path parameters has an invalid encoding then - # raise since it's likely to trigger errors further on. - path_parameters.each do |key, value| - next unless value.respond_to?(:valid_encoding?) - unless value.valid_encoding? - raise ActionController::BadRequest, "Invalid parameter encoding: #{key} => #{value.inspect}" - end - end - end - PASS_NOT_FOUND = Class.new { # :nodoc: def self.action(_); self; end - def self.call(_); [404, {'X-Cascade' => 'pass'}, []]; end + def self.call(_); [404, { "X-Cascade" => "pass" }, []]; end + def self.encoding_for_param(action, param); ::Encoding::UTF_8; end } def controller_class - check_path_parameters! params = path_parameters if params.key?(:controller) controller_param = params[:controller].underscore - params[:action] ||= 'index' + params[:action] ||= "index" const_name = "#{controller_param.camelize}Controller" ActiveSupport::Dependencies.constantize(const_name) else @@ -160,11 +149,11 @@ module ActionDispatch end def controller_instance # :nodoc: - get_header('action_controller.instance'.freeze) + get_header("action_controller.instance".freeze) end def controller_instance=(controller) # :nodoc: - set_header('action_controller.instance'.freeze, controller) + set_header("action_controller.instance".freeze, controller) end def http_auth_salt @@ -175,7 +164,7 @@ module ActionDispatch # We're treating `nil` as "unset", and we want the default setting to be # `true`. This logic should be extracted to `env_config` and calculated # once. - !(get_header('action_dispatch.show_exceptions'.freeze) == false) + !(get_header("action_dispatch.show_exceptions".freeze) == false) end # Returns a symbol form of the #request_method @@ -187,7 +176,7 @@ module ActionDispatch # even if it was overridden by middleware. See #request_method for # more information. def method - @method ||= check_method(get_header("rack.methodoverride.original_method") || get_header('REQUEST_METHOD')) + @method ||= check_method(get_header("rack.methodoverride.original_method") || get_header("REQUEST_METHOD")) end # Returns a symbol form of the #method @@ -249,7 +238,7 @@ module ActionDispatch # (case-insensitive), which may need to be manually added depending on the # choice of JavaScript libraries and frameworks. def xml_http_request? - get_header('HTTP_X_REQUESTED_WITH') =~ /XMLHttpRequest/i + get_header("HTTP_X_REQUESTED_WITH") =~ /XMLHttpRequest/i end alias :xhr? :xml_http_request? @@ -288,24 +277,24 @@ module ActionDispatch # Returns the lowercase name of the HTTP server software. def server_software - (get_header('SERVER_SOFTWARE') && /^([a-zA-Z]+)/ =~ get_header('SERVER_SOFTWARE')) ? $1.downcase : nil + (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. def raw_post - unless has_header? 'RAW_POST_DATA' + unless has_header? "RAW_POST_DATA" raw_post_body = body - set_header('RAW_POST_DATA', raw_post_body.read(content_length)) + set_header("RAW_POST_DATA", raw_post_body.read(content_length)) raw_post_body.rewind if raw_post_body.respond_to?(:rewind) end - get_header 'RAW_POST_DATA' + get_header "RAW_POST_DATA" end # The request body is an IO input stream. If the RAW_POST_DATA environment # variable is already set, wrap it in a StringIO. def body - if raw_post = get_header('RAW_POST_DATA') + if raw_post = get_header("RAW_POST_DATA") raw_post.force_encoding(Encoding::BINARY) StringIO.new(raw_post) else @@ -326,7 +315,7 @@ module ActionDispatch end def body_stream #:nodoc: - get_header('rack.input') + get_header("rack.input") end # TODO This should be broken apart into AD::Request::Session and probably @@ -379,10 +368,10 @@ module ActionDispatch # 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') || - get_header('X_HTTP_AUTHORIZATION') || - get_header('REDIRECT_X_HTTP_AUTHORIZATION') + get_header("HTTP_AUTHORIZATION") || + get_header("X-HTTP_AUTHORIZATION") || + get_header("X_HTTP_AUTHORIZATION") || + get_header("REDIRECT_X_HTTP_AUTHORIZATION") end # True if the request came from localhost, 127.0.0.1, or ::1. @@ -403,7 +392,7 @@ module ActionDispatch end def ssl? - super || scheme == 'wss'.freeze + super || scheme == "wss".freeze end private diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 1515d59df3..e8173e2a99 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,7 +1,7 @@ -require 'active_support/core_ext/module/attribute_accessors' -require 'action_dispatch/http/filter_redirect' -require 'action_dispatch/http/cache' -require 'monitor' +require "active_support/core_ext/module/attribute_accessors" +require "action_dispatch/http/filter_redirect" +require "action_dispatch/http/cache" +require "monitor" module ActionDispatch # :nodoc: # Represents an HTTP response generated by a controller action. Use it to @@ -41,7 +41,7 @@ module ActionDispatch # :nodoc: def []=(k,v) if @response.sending? || @response.sent? - raise ActionDispatch::IllegalStateError, 'header already sent' + raise ActionDispatch::IllegalStateError, "header already sent" end super @@ -67,7 +67,7 @@ module ActionDispatch # :nodoc: alias_method :headers, :header - delegate :[], :[]=, :to => :@header + delegate :[], :[]=, to: :@header def each(&block) sending! @@ -103,8 +103,8 @@ module ActionDispatch # :nodoc: def body @str_body ||= begin - buf = '' - each { |chunk| buf << chunk } + buf = "" + each { |chunk| buf << chunk } buf end end @@ -224,8 +224,10 @@ module ActionDispatch # :nodoc: # Sets the HTTP content type. def content_type=(content_type) - header_info = parse_content_type - set_content_type content_type.to_s, header_info.charset || self.class.default_charset + return unless content_type + new_header_info = parse_content_type(content_type.to_s) + prev_header_info = parsed_content_type_header + set_content_type new_header_info.mime_type, new_header_info.charset || prev_header_info.charset || self.class.default_charset end # Sets the HTTP response's content MIME type. For example, in the controller @@ -238,7 +240,7 @@ module ActionDispatch # :nodoc: # information. def content_type - parse_content_type.mime_type + parsed_content_type_header.mime_type end def sending_file=(v) @@ -253,7 +255,7 @@ module ActionDispatch # :nodoc: # response.charset = 'utf-16' # => 'utf-16' # response.charset = nil # => 'utf-8' def charset=(charset) - header_info = parse_content_type + header_info = parsed_content_type_header if false == charset set_header CONTENT_TYPE, header_info.mime_type else @@ -265,7 +267,7 @@ module ActionDispatch # :nodoc: # The charset of the response. HTML wants to know the encoding of the # content you're giving them, so we need to send that along. def charset - header_info = parse_content_type + header_info = parsed_content_type_header header_info.charset || self.class.default_charset end @@ -329,7 +331,7 @@ module ActionDispatch # :nodoc: # Stream the file's contents if Rack::Sendfile isn't present. def each - File.open(to_path, 'rb') do |file| + File.open(to_path, "rb") do |file| while chunk = file.read(16384) yield chunk end @@ -389,7 +391,7 @@ module ActionDispatch # :nodoc: if header = get_header(SET_COOKIE) header = header.split("\n") if header.respond_to?(:to_str) header.each do |cookie| - if pair = cookie.split(';').first + if pair = cookie.split(";").first key, value = pair.split("=").map { |v| Rack::Utils.unescape(v) } cookies[key] = value end @@ -403,8 +405,7 @@ module ActionDispatch # :nodoc: ContentTypeHeader = Struct.new :mime_type, :charset NullContentTypeHeader = ContentTypeHeader.new nil, nil - def parse_content_type - content_type = get_header CONTENT_TYPE + def parse_content_type(content_type) if content_type type, charset = content_type.split(/;\s*charset=/) type = nil if type.empty? @@ -414,8 +415,14 @@ module ActionDispatch # :nodoc: end end + # Small internal convenience method to get the parsed version of the current + # content type header. + def parsed_content_type_header + parse_content_type(get_header(CONTENT_TYPE)) + end + def set_content_type(content_type, charset) - type = (content_type || '').dup + type = (content_type || "").dup type << "; charset=#{charset}" if charset set_header CONTENT_TYPE, type end @@ -450,7 +457,7 @@ module ActionDispatch # :nodoc: def assign_default_content_type_and_charset! return if content_type - ct = parse_content_type + ct = parsed_content_type_header set_content_type(ct.mime_type || Mime[:html].to_s, ct.charset || self.class.default_charset) end @@ -475,7 +482,7 @@ module ActionDispatch # :nodoc: end def respond_to?(method, include_private = false) - if method.to_s == 'to_path' + if method.to_s == "to_path" @response.stream.respond_to?(method) else super @@ -494,7 +501,7 @@ module ActionDispatch # :nodoc: def handle_no_content! if NO_CONTENT_CODES.include?(@status) @header.delete CONTENT_TYPE - @header.delete 'Content-Length' + @header.delete "Content-Length" end end diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index a221f4c5af..9aa73c862b 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -25,7 +25,7 @@ module ActionDispatch def initialize(hash) # :nodoc: @tempfile = hash[:tempfile] - raise(ArgumentError, ':tempfile is required') unless @tempfile + raise(ArgumentError, ":tempfile is required") unless @tempfile @original_filename = hash[:filename] if @original_filename diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 37f41ae988..06ffa983d1 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/module/attribute_accessors' +require "active_support/core_ext/module/attribute_accessors" module ActionDispatch module Http @@ -42,7 +42,7 @@ module ActionDispatch # # 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('.') + extract_subdomains(host, tld_length).join(".") end def url_for(options) @@ -59,7 +59,7 @@ module ActionDispatch port = options[:port] unless host - raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true' + raise ArgumentError, "Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true" end build_host_url(host, port, protocol, options, path_for(options)) @@ -92,17 +92,17 @@ module ActionDispatch end def extract_domain_from(host, tld_length) - host.split('.').last(1 + tld_length).join('.') + host.split(".").last(1 + tld_length).join(".") end def extract_subdomains_from(host, tld_length) - parts = host.split('.') + parts = host.split(".") parts[0..-(tld_length + 2)] end def add_trailing_slash(path) # includes querysting - if path.include?('?') + if path.include?("?") path.sub!(/\?/, '/\&') # does not have a .format elsif !path.include?(".") @@ -162,7 +162,7 @@ module ActionDispatch if subdomain == true return _host if domain.nil? - host << extract_subdomains_from(_host, tld_length).join('.') + host << extract_subdomains_from(_host, tld_length).join(".") elsif subdomain host << subdomain.to_param end @@ -192,11 +192,7 @@ module ActionDispatch # Returns the complete URL used for this request. # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' # req.url # => "http://example.com" def url protocol + host_with_port + fullpath @@ -204,61 +200,52 @@ module ActionDispatch # Returns 'https://' if this is an SSL request and 'http://' otherwise. # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' # req.protocol # => "http://" # - # req = Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on' # req.protocol # => "https://" def protocol - @protocol ||= ssl? ? 'https://' : 'http://' + @protocol ||= ssl? ? "https://" : "http://" end - # Returns the \host for this request, such as "example.com". + # Returns the \host and port for this request, such as "example.com:8080". # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' # req.raw_host_with_port # => "example.com" # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # 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" def raw_host_with_port if forwarded = x_forwarded_host.presence forwarded.split(/,\s?/).last else - get_header('HTTP_HOST') || "#{server_name || server_addr}:#{get_header('SERVER_PORT')}" + get_header("HTTP_HOST") || "#{server_name || server_addr}:#{get_header('SERVER_PORT')}" end end - # Returns the host for this request, such as example.com. + # Returns the host for this request, such as "example.com". # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.host # => "example.com" def host - raw_host_with_port.sub(/:\d+$/, ''.freeze) + raw_host_with_port.sub(/:\d+$/, "".freeze) end # Returns a \host:\port string for this request, such as "example.com" or - # "example.com:8080". + # "example.com:8080". Port is only included if it is not a default port + # (80 or 443) # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.host_with_port # => "example.com" # - # req = Request.new 'HTTP_HOST' => 'example.com:80' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' # req.host_with_port # => "example.com" # - # req = Request.new 'HTTP_HOST' => '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}" @@ -266,14 +253,10 @@ module ActionDispatch # Returns the port number of this request as an integer. # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' # req.port # => 80 # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.port # => 8080 def port @port ||= begin @@ -287,29 +270,21 @@ module ActionDispatch # Returns the standard \port number for this request's protocol. # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.standard_port # => 80 def standard_port case protocol - when 'https://' then 443 + when "https://" then 443 else 80 end end # Returns whether this request is using the standard port # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com:80' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' # req.standard_port? # => true # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.standard_port? # => false def standard_port? port == standard_port @@ -318,14 +293,10 @@ module ActionDispatch # Returns a number \port suffix like 8080 if the \port number of this request # is not the default HTTP \port 80 or HTTPS \port 443. # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com:80' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' # req.optional_port # => nil # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.optional_port # => 8080 def optional_port standard_port? ? nil : port @@ -334,21 +305,24 @@ module ActionDispatch # Returns a string \port suffix, including colon, like ":8080" if the \port # number of this request is not the default HTTP \port 80 or HTTPS \port 443. # - # class Request < Rack::Request - # include ActionDispatch::Http::URL - # end - # - # req = Request.new 'HTTP_HOST' => 'example.com:80' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' # req.port_string # => "" # - # req = Request.new 'HTTP_HOST' => 'example.com:8080' + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' # req.port_string # => ":8080" def port_string - standard_port? ? '' : ":#{port}" + 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' => '8080' + # req.server_port # => 8080 def server_port - get_header('SERVER_PORT').to_i + 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 diff --git a/actionpack/lib/action_dispatch/journey.rb b/actionpack/lib/action_dispatch/journey.rb index ad42713482..d1cfc51f3e 100644 --- a/actionpack/lib/action_dispatch/journey.rb +++ b/actionpack/lib/action_dispatch/journey.rb @@ -1,5 +1,5 @@ -require 'action_dispatch/journey/router' -require 'action_dispatch/journey/gtg/builder' -require 'action_dispatch/journey/gtg/simulator' -require 'action_dispatch/journey/nfa/builder' -require 'action_dispatch/journey/nfa/simulator' +require "action_dispatch/journey/router" +require "action_dispatch/journey/gtg/builder" +require "action_dispatch/journey/gtg/simulator" +require "action_dispatch/journey/nfa/builder" +require "action_dispatch/journey/nfa/simulator" diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 200477b002..dc8b24b089 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -1,4 +1,4 @@ -require 'action_controller/metal/exceptions' +require "action_controller/metal/exceptions" module ActionDispatch module Journey @@ -44,8 +44,12 @@ module ActionDispatch return [route.format(parameterized_parts), params] end - message = "No route matches #{Hash[constraints.sort_by{|k,v| k.to_s}].inspect}" - message << " missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty? + unmatched_keys = (missing_keys || []) & constraints.keys + missing_keys = (missing_keys || []) - unmatched_keys + + message = "No route matches #{Hash[constraints.sort_by { |k,v| k.to_s }].inspect}" + message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty? + message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty? raise ActionController::UrlGenerationError, message end diff --git a/actionpack/lib/action_dispatch/journey/gtg/builder.rb b/actionpack/lib/action_dispatch/journey/gtg/builder.rb index 450588cda6..9990c66627 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/builder.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/builder.rb @@ -1,4 +1,4 @@ -require 'action_dispatch/journey/gtg/transition_table' +require "action_dispatch/journey/gtg/transition_table" module ActionDispatch module Journey # :nodoc: @@ -75,7 +75,7 @@ module ActionDispatch when Nodes::Unary nullable?(node.left) else - raise ArgumentError, 'unknown nullable: %s' % node.class.name + raise ArgumentError, "unknown nullable: %s" % node.class.name end end @@ -96,7 +96,7 @@ module ActionDispatch when Nodes::Terminal nullable?(node) ? [] : [node] else - raise ArgumentError, 'unknown firstpos: %s' % node.class.name + raise ArgumentError, "unknown firstpos: %s" % node.class.name end end @@ -117,7 +117,7 @@ module ActionDispatch when Nodes::Unary lastpos(node.left) else - raise ArgumentError, 'unknown lastpos: %s' % node.class.name + raise ArgumentError, "unknown lastpos: %s" % node.class.name end end diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb index 94b0a24344..d692f6415c 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb @@ -1,4 +1,4 @@ -require 'strscan' +require "strscan" module ActionDispatch module Journey # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index d7ce6042c2..0be18dc26f 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -1,4 +1,4 @@ -require 'action_dispatch/journey/nfa/dot' +require "action_dispatch/journey/nfa/dot" module ActionDispatch module Journey # :nodoc: @@ -72,20 +72,20 @@ module ActionDispatch end def to_svg - svg = IO.popen('dot -Tsvg', 'w+') { |f| + svg = IO.popen("dot -Tsvg", "w+") { |f| f.write(to_dot) f.close_write f.readlines } 3.times { svg.shift } - svg.join.sub(/width="[^"]*"/, '').sub(/height="[^"]*"/, '') + svg.join.sub(/width="[^"]*"/, "").sub(/height="[^"]*"/, "") end - def visualizer(paths, title = 'FSM') - viz_dir = File.join File.dirname(__FILE__), '..', 'visualizer' - fsm_js = File.read File.join(viz_dir, 'fsm.js') - fsm_css = File.read File.join(viz_dir, 'fsm.css') - erb = File.read File.join(viz_dir, 'index.html.erb') + def visualizer(paths, title = "FSM") + viz_dir = File.join File.dirname(__FILE__), "..", "visualizer" + fsm_js = File.read File.join(viz_dir, "fsm.js") + fsm_css = File.read File.join(viz_dir, "fsm.css") + erb = File.read File.join(viz_dir, "index.html.erb") states = "function tt() { return #{to_json}; }" fun_routes = paths.sample(3).map do |ast| @@ -93,10 +93,10 @@ module ActionDispatch case n when Nodes::Symbol case n.left - when ':id' then rand(100).to_s - when ':format' then %w{ xml json }.sample + when ":id" then rand(100).to_s + when ":format" then %w{ xml json }.sample else - 'omg' + "omg" end when Nodes::Terminal then n.symbol else @@ -115,7 +115,7 @@ module ActionDispatch svg = svg javascripts = javascripts - require 'erb' + require "erb" template = ERB.new erb template.result(binding) end @@ -148,7 +148,7 @@ module ActionDispatch when Regexp @regexp_states else - raise ArgumentError, 'unknown symbol: %s' % sym.class + raise ArgumentError, "unknown symbol: %s" % sym.class end end end diff --git a/actionpack/lib/action_dispatch/journey/nfa/builder.rb b/actionpack/lib/action_dispatch/journey/nfa/builder.rb index ee6494c3e4..19e5752ae5 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/builder.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/builder.rb @@ -1,5 +1,5 @@ -require 'action_dispatch/journey/nfa/transition_table' -require 'action_dispatch/journey/gtg/transition_table' +require "action_dispatch/journey/nfa/transition_table" +require "action_dispatch/journey/gtg/transition_table" module ActionDispatch module Journey # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb index 7063b44bb5..8119e5d9da 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/dot.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb @@ -18,7 +18,7 @@ module ActionDispatch # (memos || []).map { |v| " #{k} -> #{v.object_id};" } #}.uniq - <<-eodot + <<-eodot digraph nfa { rankdir=LR; node [shape = doublecircle]; @@ -26,7 +26,7 @@ digraph nfa { node [shape = circle]; #{edges.join "\n"} } - eodot + eodot end end end diff --git a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb index b23270db3c..324d0eed15 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb @@ -1,4 +1,4 @@ -require 'strscan' +require "strscan" module ActionDispatch module Journey # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb index 0ccab21801..4737adc724 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb @@ -1,4 +1,4 @@ -require 'action_dispatch/journey/nfa/dot' +require "action_dispatch/journey/nfa/dot" module ActionDispatch module Journey # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb index 2793c5668d..0d874a84c9 100644 --- a/actionpack/lib/action_dispatch/journey/nodes/node.rb +++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb @@ -1,4 +1,4 @@ -require 'action_dispatch/journey/visitors' +require "action_dispatch/journey/visitors" module ActionDispatch module Journey # :nodoc: @@ -18,7 +18,7 @@ module ActionDispatch end def to_s - Visitors::String::INSTANCE.accept(self, '') + Visitors::String::INSTANCE.accept(self, "") end def to_dot @@ -30,7 +30,7 @@ module ActionDispatch end def name - left.tr '*:'.freeze, ''.freeze + left.tr "*:".freeze, "".freeze end def type @@ -80,7 +80,7 @@ module ActionDispatch def initialize(left) super @regexp = DEFAULT_EXP - @name = left.tr '*:'.freeze, ''.freeze + @name = left.tr "*:".freeze, "".freeze end def default_regexp? @@ -104,7 +104,7 @@ module ActionDispatch def type; :STAR; end def name - left.name.tr '*:', '' + left.name.tr "*:", "" end end diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb index 9012297400..01ff2109cb 100644 --- a/actionpack/lib/action_dispatch/journey/parser.rb +++ b/actionpack/lib/action_dispatch/journey/parser.rb @@ -4,195 +4,193 @@ # from Racc grammer file "". # -require 'racc/parser.rb' +require "racc/parser.rb" - -require 'action_dispatch/journey/parser_extras' +require "action_dispatch/journey/parser_extras" module ActionDispatch module Journey class Parser < Racc::Parser -##### State transition tables begin ### - -racc_action_table = [ - 13, 15, 14, 7, 21, 16, 8, 19, 13, 15, - 14, 7, 17, 16, 8, 13, 15, 14, 7, 24, - 16, 8, 13, 15, 14, 7, 19, 16, 8 ] - -racc_action_check = [ - 2, 2, 2, 2, 17, 2, 2, 2, 0, 0, - 0, 0, 1, 0, 0, 19, 19, 19, 19, 20, - 19, 19, 7, 7, 7, 7, 22, 7, 7 ] - -racc_action_pointer = [ - 6, 12, -2, nil, nil, nil, nil, 20, nil, nil, - nil, nil, nil, nil, nil, nil, nil, 4, nil, 13, - 13, nil, 17, nil, nil ] - -racc_action_default = [ - -19, -19, -2, -3, -4, -5, -6, -19, -10, -11, - -12, -13, -14, -15, -16, -17, -18, -19, -1, -19, - -19, 25, -8, -9, -7 ] - -racc_goto_table = [ - 1, 22, 18, 23, nil, nil, nil, 20 ] - -racc_goto_check = [ - 1, 2, 1, 3, nil, nil, nil, 1 ] - -racc_goto_pointer = [ - nil, 0, -18, -16, nil, nil, nil, nil, nil, nil, - nil ] - -racc_goto_default = [ - nil, nil, 2, 3, 4, 5, 6, 9, 10, 11, - 12 ] - -racc_reduce_table = [ - 0, 0, :racc_error, - 2, 11, :_reduce_1, - 1, 11, :_reduce_2, - 1, 11, :_reduce_none, - 1, 12, :_reduce_none, - 1, 12, :_reduce_none, - 1, 12, :_reduce_none, - 3, 15, :_reduce_7, - 3, 13, :_reduce_8, - 3, 13, :_reduce_9, - 1, 16, :_reduce_10, - 1, 14, :_reduce_none, - 1, 14, :_reduce_none, - 1, 14, :_reduce_none, - 1, 14, :_reduce_none, - 1, 19, :_reduce_15, - 1, 17, :_reduce_16, - 1, 18, :_reduce_17, - 1, 20, :_reduce_18 ] - -racc_reduce_n = 19 - -racc_shift_n = 25 - -racc_token_table = { - false => 0, - :error => 1, - :SLASH => 2, - :LITERAL => 3, - :SYMBOL => 4, - :LPAREN => 5, - :RPAREN => 6, - :DOT => 7, - :STAR => 8, - :OR => 9 } - -racc_nt_base = 10 - -racc_use_result_var = false - -Racc_arg = [ - racc_action_table, - racc_action_check, - racc_action_default, - racc_action_pointer, - racc_goto_table, - racc_goto_check, - racc_goto_default, - racc_goto_pointer, - racc_nt_base, - racc_reduce_table, - racc_token_table, - racc_shift_n, - racc_reduce_n, - racc_use_result_var ] - -Racc_token_to_s_table = [ - "$end", - "error", - "SLASH", - "LITERAL", - "SYMBOL", - "LPAREN", - "RPAREN", - "DOT", - "STAR", - "OR", - "$start", - "expressions", - "expression", - "or", - "terminal", - "group", - "star", - "symbol", - "literal", - "slash", - "dot" ] - -Racc_debug_parser = false - -##### State transition tables end ##### - -# reduce 0 omitted - -def _reduce_1(val, _values) - Cat.new(val.first, val.last) -end - -def _reduce_2(val, _values) - val.first -end - -# reduce 3 omitted - -# reduce 4 omitted - -# reduce 5 omitted - -# reduce 6 omitted - -def _reduce_7(val, _values) - Group.new(val[1]) -end - -def _reduce_8(val, _values) - Or.new([val.first, val.last]) -end - -def _reduce_9(val, _values) - Or.new([val.first, val.last]) -end - -def _reduce_10(val, _values) - Star.new(Symbol.new(val.last)) -end - -# reduce 11 omitted - -# reduce 12 omitted - -# reduce 13 omitted - -# reduce 14 omitted - -def _reduce_15(val, _values) - Slash.new('/') -end - -def _reduce_16(val, _values) - Symbol.new(val.first) -end - -def _reduce_17(val, _values) - Literal.new(val.first) -end - -def _reduce_18(val, _values) - Dot.new(val.first) -end - -def _reduce_none(val, _values) - val[0] -end - + ##### State transition tables begin ### + + racc_action_table = [ + 13, 15, 14, 7, 21, 16, 8, 19, 13, 15, + 14, 7, 17, 16, 8, 13, 15, 14, 7, 24, + 16, 8, 13, 15, 14, 7, 19, 16, 8 ] + + racc_action_check = [ + 2, 2, 2, 2, 17, 2, 2, 2, 0, 0, + 0, 0, 1, 0, 0, 19, 19, 19, 19, 20, + 19, 19, 7, 7, 7, 7, 22, 7, 7 ] + + racc_action_pointer = [ + 6, 12, -2, nil, nil, nil, nil, 20, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 4, nil, 13, + 13, nil, 17, nil, nil ] + + racc_action_default = [ + -19, -19, -2, -3, -4, -5, -6, -19, -10, -11, + -12, -13, -14, -15, -16, -17, -18, -19, -1, -19, + -19, 25, -8, -9, -7 ] + + racc_goto_table = [ + 1, 22, 18, 23, nil, nil, nil, 20 ] + + racc_goto_check = [ + 1, 2, 1, 3, nil, nil, nil, 1 ] + + racc_goto_pointer = [ + nil, 0, -18, -16, nil, nil, nil, nil, nil, nil, + nil ] + + racc_goto_default = [ + nil, nil, 2, 3, 4, 5, 6, 9, 10, 11, + 12 ] + + racc_reduce_table = [ + 0, 0, :racc_error, + 2, 11, :_reduce_1, + 1, 11, :_reduce_2, + 1, 11, :_reduce_none, + 1, 12, :_reduce_none, + 1, 12, :_reduce_none, + 1, 12, :_reduce_none, + 3, 15, :_reduce_7, + 3, 13, :_reduce_8, + 3, 13, :_reduce_9, + 1, 16, :_reduce_10, + 1, 14, :_reduce_none, + 1, 14, :_reduce_none, + 1, 14, :_reduce_none, + 1, 14, :_reduce_none, + 1, 19, :_reduce_15, + 1, 17, :_reduce_16, + 1, 18, :_reduce_17, + 1, 20, :_reduce_18 ] + + racc_reduce_n = 19 + + racc_shift_n = 25 + + racc_token_table = { + false => 0, + :error => 1, + :SLASH => 2, + :LITERAL => 3, + :SYMBOL => 4, + :LPAREN => 5, + :RPAREN => 6, + :DOT => 7, + :STAR => 8, + :OR => 9 } + + racc_nt_base = 10 + + racc_use_result_var = false + + Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + + Racc_token_to_s_table = [ + "$end", + "error", + "SLASH", + "LITERAL", + "SYMBOL", + "LPAREN", + "RPAREN", + "DOT", + "STAR", + "OR", + "$start", + "expressions", + "expression", + "or", + "terminal", + "group", + "star", + "symbol", + "literal", + "slash", + "dot" ] + + Racc_debug_parser = false + + ##### State transition tables end ##### + + # reduce 0 omitted + + def _reduce_1(val, _values) + Cat.new(val.first, val.last) + end + + def _reduce_2(val, _values) + val.first + end + + # reduce 3 omitted + + # reduce 4 omitted + + # reduce 5 omitted + + # reduce 6 omitted + + def _reduce_7(val, _values) + Group.new(val[1]) + end + + def _reduce_8(val, _values) + Or.new([val.first, val.last]) + end + + def _reduce_9(val, _values) + Or.new([val.first, val.last]) + end + + def _reduce_10(val, _values) + Star.new(Symbol.new(val.last)) + end + + # reduce 11 omitted + + # reduce 12 omitted + + # reduce 13 omitted + + # reduce 14 omitted + + def _reduce_15(val, _values) + Slash.new("/") + end + + def _reduce_16(val, _values) + Symbol.new(val.first) + end + + def _reduce_17(val, _values) + Literal.new(val.first) + end + + def _reduce_18(val, _values) + Dot.new(val.first) + end + + def _reduce_none(val, _values) + val[0] + end end # class Parser - end # module Journey - end # module ActionDispatch + end # module Journey +end # module ActionDispatch diff --git a/actionpack/lib/action_dispatch/journey/parser_extras.rb b/actionpack/lib/action_dispatch/journey/parser_extras.rb index fff0299812..ec26e634e8 100644 --- a/actionpack/lib/action_dispatch/journey/parser_extras.rb +++ b/actionpack/lib/action_dispatch/journey/parser_extras.rb @@ -1,5 +1,5 @@ -require 'action_dispatch/journey/scanner' -require 'action_dispatch/journey/nodes/node' +require "action_dispatch/journey/scanner" +require "action_dispatch/journey/nodes/node" module ActionDispatch module Journey # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb index 018b89a2b7..0902b9233e 100644 --- a/actionpack/lib/action_dispatch/journey/path/pattern.rb +++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb @@ -4,7 +4,7 @@ module ActionDispatch class Pattern # :nodoc: attr_reader :spec, :requirements, :anchored - def self.from_string string + def self.from_string(string) build(string, {}, "/.?", true) end @@ -98,7 +98,7 @@ module ActionDispatch end def visit_STAR(node) - re = @matchers[node.left.to_sym] || '.+' + re = @matchers[node.left.to_sym] || ".+" "(#{re})" end @@ -175,7 +175,7 @@ module ActionDispatch if @requirements.key?(node) re = /#{@requirements[node]}|/ - @offsets.push((re.match('').length - 1) + @offsets.last) + @offsets.push((re.match("").length - 1) + @offsets.last) else @offsets << @offsets.last end diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index cfd6681dd1..a9713ff292 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -29,16 +29,15 @@ module ActionDispatch class All def self.call(_); true; end - def self.verb; ''; end + def self.verb; ""; end end - VERB_TO_CLASS = VERBS.each_with_object({ :all => All }) do |verb, hash| + VERB_TO_CLASS = VERBS.each_with_object(all: All) do |verb, hash| klass = const_get verb hash[verb] = klass hash[verb.downcase] = klass hash[verb.downcase.to_sym] = klass end - end def self.verb_matcher(verb) @@ -164,17 +163,17 @@ module ActionDispatch end def verb - verbs.join('|') + verbs.join("|") end private - def verbs - @request_method_match.map(&:verb) - end + def verbs + @request_method_match.map(&:verb) + end - def match_verb(request) - @request_method_match.any? { |m| m.call request } - end + def match_verb(request) + @request_method_match.any? { |m| m.call request } + end end end end diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 06cdce1724..d0ef549335 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -1,14 +1,14 @@ -require 'action_dispatch/journey/router/utils' -require 'action_dispatch/journey/routes' -require 'action_dispatch/journey/formatter' +require "action_dispatch/journey/router/utils" +require "action_dispatch/journey/routes" +require "action_dispatch/journey/formatter" before = $-w $-w = false -require 'action_dispatch/journey/parser' +require "action_dispatch/journey/parser" $-w = before -require 'action_dispatch/journey/route' -require 'action_dispatch/journey/path/pattern' +require "action_dispatch/journey/route" +require "action_dispatch/journey/path/pattern" module ActionDispatch module Journey # :nodoc: @@ -29,7 +29,7 @@ module ActionDispatch script_name = req.script_name unless route.path.anchored - req.script_name = (script_name.to_s + match.to_s).chomp('/') + req.script_name = (script_name.to_s + match.to_s).chomp("/") req.path_info = match.post_match req.path_info = "/" + req.path_info unless req.path_info.start_with? "/" end @@ -38,7 +38,7 @@ module ActionDispatch status, headers, body = route.app.serve(req) - if 'pass' == headers['X-Cascade'] + if "pass" == headers["X-Cascade"] req.script_name = script_name req.path_info = path_info req.path_parameters = set_params @@ -48,7 +48,7 @@ module ActionDispatch return [status, headers, body] end - return [404, {'X-Cascade' => 'pass'}, ['Not Found']] + return [404, { "X-Cascade" => "pass" }, ["Not Found"]] end def recognize(rails_req) @@ -72,7 +72,9 @@ module ActionDispatch private def partitioned_routes - routes.partitioned_routes + routes.partition { |r| + r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? } + } end def ast @@ -92,7 +94,7 @@ module ActionDispatch simulator.memos(path) { [] } end - def find_routes req + def find_routes(req) routes = filter_routes(req.path_info).concat custom_routes.find_all { |r| r.path.match(req.path_info) } diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb index 9793ca1c7a..ce5d350763 100644 --- a/actionpack/lib/action_dispatch/journey/router/utils.rb +++ b/actionpack/lib/action_dispatch/journey/router/utils.rb @@ -14,10 +14,10 @@ module ActionDispatch # normalize_path("/%ab") # => "/%AB" def self.normalize_path(path) path = "/#{path}" - path.squeeze!('/'.freeze) - path.sub!(%r{/+\Z}, ''.freeze) + path.squeeze!("/".freeze) + path.sub!(%r{/+\Z}, "".freeze) path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase } - path = '/' if path == ''.freeze + path = "/" if path == "".freeze path end @@ -28,7 +28,7 @@ module ActionDispatch US_ASCII = Encoding::US_ASCII UTF_8 = Encoding::UTF_8 EMPTY = "".force_encoding(US_ASCII).freeze - DEC2HEX = (0..255).to_a.map{ |i| ENCODE % i }.map{ |s| s.force_encoding(US_ASCII) } + DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) } ALPHA = "a-zA-Z".freeze DIGIT = "0-9".freeze @@ -55,12 +55,12 @@ module ActionDispatch def unescape_uri(uri) encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding - uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack('C') }.force_encoding(encoding) + uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack("C") }.force_encoding(encoding) end protected def escape(component, pattern) - component.gsub(pattern){ |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII) + component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII) end def percent_encode(unsafe) diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb index 19e0bc03d6..4b8c8ab063 100644 --- a/actionpack/lib/action_dispatch/journey/scanner.rb +++ b/actionpack/lib/action_dispatch/journey/scanner.rb @@ -1,4 +1,4 @@ -require 'strscan' +require "strscan" module ActionDispatch module Journey # :nodoc: @@ -50,7 +50,7 @@ module ActionDispatch when text = @ss.scan(/(?<!\\):\w+/) [:SYMBOL, text] when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\:|\\\(|\\\))+/) - [:LITERAL, text.tr('\\', '')] + [:LITERAL, text.tr('\\', "")] # any char when text = @ss.scan(/./) [:LITERAL, text] diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index 306d2e674a..452dc84cc5 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -37,7 +37,7 @@ module ActionDispatch @parameters.each do |index| param = parts[index] value = hash[param.name] - return ''.freeze unless value + return "".freeze unless value parts[index] = param.escape value end @@ -57,7 +57,7 @@ module ActionDispatch private - def visit node + def visit(node) send(DISPATCH_CACHE[node.type], node) end @@ -97,7 +97,7 @@ module ActionDispatch visit(node, seed) end - def visit node, seed + def visit(node, seed) send(DISPATCH_CACHE[node.type], node, seed) end @@ -166,28 +166,28 @@ module ActionDispatch class String < FunctionalVisitor # :nodoc: private - def binary(node, seed) - visit(node.right, visit(node.left, seed)) - end + def binary(node, seed) + visit(node.right, visit(node.left, seed)) + end - def nary(node, seed) - last_child = node.children.last - node.children.inject(seed) { |s, c| - string = visit(c, s) - string << "|".freeze unless last_child == c - string - } - end + def nary(node, seed) + last_child = node.children.last + node.children.inject(seed) { |s, c| + string = visit(c, s) + string << "|".freeze unless last_child == c + string + } + end - def terminal(node, seed) - seed + node.left - end + def terminal(node, seed) + seed + node.left + end - def visit_GROUP(node, seed) - visit(node.left, seed << "(".freeze) << ")".freeze - end + def visit_GROUP(node, seed) + visit(node.left, seed << "(".freeze) << ")".freeze + end - INSTANCE = new + INSTANCE = new end class Dot < FunctionalVisitor # :nodoc: diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index c782779b34..fef246532b 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -15,8 +15,8 @@ module ActionDispatch ActiveSupport::Reloader.to_complete(*args, &block) end - deprecate to_prepare: 'use ActiveSupport::Reloader.to_prepare instead', - to_cleanup: 'use ActiveSupport::Reloader.to_complete instead' + deprecate to_prepare: "use ActiveSupport::Reloader.to_prepare instead", + to_cleanup: "use ActiveSupport::Reloader.to_complete instead" def before(*args, &block) set_callback(:call, :before, *args, &block) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index f2f3150b56..6f4fab396a 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -1,13 +1,13 @@ -require 'active_support/core_ext/hash/keys' -require 'active_support/key_generator' -require 'active_support/message_verifier' -require 'active_support/json' -require 'rack/utils' +require "active_support/core_ext/hash/keys" +require "active_support/key_generator" +require "active_support/message_verifier" +require "active_support/json" +require "rack/utils" module ActionDispatch class Request def cookie_jar - fetch_header('action_dispatch.cookies'.freeze) do + fetch_header("action_dispatch.cookies".freeze) do self.cookie_jar = Cookies::CookieJar.build(self, cookies) end end @@ -20,11 +20,11 @@ module ActionDispatch } def have_cookie_jar? - has_header? 'action_dispatch.cookies'.freeze + has_header? "action_dispatch.cookies".freeze end def cookie_jar=(jar) - set_header 'action_dispatch.cookies'.freeze, jar + set_header "action_dispatch.cookies".freeze, jar end def key_generator @@ -237,9 +237,9 @@ module ActionDispatch private - def upgrade_legacy_signed_cookies? - request.secret_token.present? && request.secret_key_base.present? - end + def upgrade_legacy_signed_cookies? + request.secret_token.present? && request.secret_key_base.present? + end end # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream @@ -338,13 +338,13 @@ module ActionDispatch end def to_header - @cookies.map { |k,v| "#{escape(k)}=#{escape(v)}" }.join '; ' + @cookies.map { |k,v| "#{escape(k)}=#{escape(v)}" }.join "; " end def handle_options(options) #:nodoc: options[:path] ||= "/" - if options[:domain] == :all || options[:domain] == 'all' + if options[:domain] == :all || options[:domain] == "all" # if there is a provided tld length then we use it otherwise default domain regexp domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP @@ -355,7 +355,7 @@ module ActionDispatch end elsif options[:domain].is_a? Array # if host matches one of the supplied domains without a dot in front of it - options[:domain] = options[:domain].find {|domain| request.host.include? domain.sub(/^\./, '') } + options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") } end end @@ -367,12 +367,12 @@ module ActionDispatch value = options[:value] else value = options - options = { :value => value } + options = { value: value } end handle_options(options) - if @cookies[name.to_s] != value or options[:expires] + if @cookies[name.to_s] != value || options[:expires] @cookies[name.to_s] = value @set_cookies[name.to_s] = options @delete_cookies.delete(name.to_s) @@ -406,7 +406,7 @@ module ActionDispatch # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie def clear(options = {}) - @cookies.each_key{ |k| delete(k, options) } + @cookies.each_key { |k| delete(k, options) } end def write(headers) @@ -420,26 +420,26 @@ module ActionDispatch private - def escape(string) - ::Rack::Utils.escape(string) - end + def escape(string) + ::Rack::Utils.escape(string) + end - def make_set_cookie_header(header) - header = @set_cookies.inject(header) { |m, (k, v)| - if write_cookie?(v) - ::Rack::Utils.add_cookie_to_header(m, k, v) - else - m - end - } - @delete_cookies.inject(header) { |m, (k, v)| - ::Rack::Utils.add_remove_cookie_to_header(m, k, v) - } - end + def make_set_cookie_header(header) + header = @set_cookies.inject(header) { |m, (k, v)| + if write_cookie?(v) + ::Rack::Utils.add_cookie_to_header(m, k, v) + else + m + end + } + @delete_cookies.inject(header) { |m, (k, v)| + ::Rack::Utils.add_remove_cookie_to_header(m, k, v) + } + end - def write_cookie?(cookie) - request.ssl? || !cookie[:secure] || always_write_cookie - end + def write_cookie?(cookie) + request.ssl? || !cookie[:secure] || always_write_cookie + end end class AbstractCookieJar # :nodoc: @@ -528,7 +528,7 @@ module ActionDispatch end def digest - request.cookies_digest || 'SHA1' + request.cookies_digest || "SHA1" end def key_generator @@ -576,8 +576,8 @@ module ActionDispatch "Read the upgrade documentation to learn more about this new config option." end - secret = key_generator.generate_key(request.encrypted_cookie_salt || '') - sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || '') + secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len] + sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "") @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) end diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 51a471fb23..ee644f41c8 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -1,16 +1,16 @@ -require 'action_dispatch/http/request' -require 'action_dispatch/middleware/exception_wrapper' -require 'action_dispatch/routing/inspector' -require 'action_view' -require 'action_view/base' +require "action_dispatch/http/request" +require "action_dispatch/middleware/exception_wrapper" +require "action_dispatch/routing/inspector" +require "action_view" +require "action_view/base" -require 'pp' +require "pp" module ActionDispatch # This middleware is responsible for logging exceptions and # showing a debugging page in case the request is local. class DebugExceptions - RESCUES_TEMPLATE_PATH = File.expand_path('../templates', __FILE__) + RESCUES_TEMPLATE_PATH = File.expand_path("../templates", __FILE__) class DebugView < ActionView::Base def debug_params(params) @@ -19,7 +19,7 @@ module ActionDispatch clean_params.delete("controller") if clean_params.empty? - 'None' + "None" else PP.pp(clean_params, "", 200) end @@ -27,15 +27,23 @@ module ActionDispatch def debug_headers(headers) if headers.present? - headers.inspect.gsub(',', ",\n") + headers.inspect.gsub(",", ",\n") else - 'None' + "None" end end def debug_hash(object) object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") end + + def render(*) + if logger = ActionView::Base.logger + logger.silence { super } + else + super + end + end end def initialize(app, routes_app = nil, response_format = :default) @@ -48,7 +56,7 @@ module ActionDispatch request = ActionDispatch::Request.new env _, headers, body = response = @app.call(env) - if headers['X-Cascade'] == 'pass' + if headers["X-Cascade"] == "pass" body.close if body.respond_to?(:close) raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" end @@ -61,125 +69,129 @@ module ActionDispatch private - def render_exception(request, exception) - backtrace_cleaner = request.get_header('action_dispatch.backtrace_cleaner') - wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) - log_error(request, wrapper) - - if request.get_header('action_dispatch.show_detailed_exceptions') - case @response_format - when :api - render_for_api_application(request, wrapper) - when :default - render_for_default_application(request, wrapper) + def render_exception(request, exception) + backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner") + wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) + log_error(request, wrapper) + + if request.get_header("action_dispatch.show_detailed_exceptions") + content_type = request.formats.first + + if api_request?(content_type) + render_for_api_request(content_type, wrapper) + else + render_for_browser_request(request, wrapper) + end + else + raise exception end - else - raise exception end - end - def render_for_default_application(request, wrapper) - template = create_template(request, wrapper) - file = "rescues/#{wrapper.rescue_template}" + def render_for_browser_request(request, wrapper) + template = create_template(request, wrapper) + file = "rescues/#{wrapper.rescue_template}" - if request.xhr? - body = template.render(template: file, layout: false, formats: [:text]) - format = "text/plain" - else - body = template.render(template: file, layout: 'rescues/layout') - format = "text/html" + if request.xhr? + body = template.render(template: file, layout: false, formats: [:text]) + format = "text/plain" + else + body = template.render(template: file, layout: "rescues/layout") + format = "text/html" + end + render(wrapper.status_code, body, format) end - render(wrapper.status_code, body, format) - end - - def render_for_api_application(request, wrapper) - body = { - status: wrapper.status_code, - error: Rack::Utils::HTTP_STATUS_CODES.fetch( - wrapper.status_code, - Rack::Utils::HTTP_STATUS_CODES[500] - ), - exception: wrapper.exception.inspect, - traces: wrapper.traces - } - - content_type = request.formats.first - to_format = "to_#{content_type.to_sym}" - - if content_type && body.respond_to?(to_format) - formatted_body = body.public_send(to_format) - format = content_type - else - formatted_body = body.to_json - format = Mime[:json] - end - - render(wrapper.status_code, formatted_body, format) - end - def create_template(request, wrapper) - traces = wrapper.traces + def render_for_api_request(content_type, wrapper) + body = { + status: wrapper.status_code, + error: Rack::Utils::HTTP_STATUS_CODES.fetch( + wrapper.status_code, + Rack::Utils::HTTP_STATUS_CODES[500] + ), + exception: wrapper.exception.inspect, + traces: wrapper.traces + } + + to_format = "to_#{content_type.to_sym}" + + if content_type && body.respond_to?(to_format) + formatted_body = body.public_send(to_format) + format = content_type + else + formatted_body = body.to_json + format = Mime[:json] + end - trace_to_show = 'Application Trace' - if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error' - trace_to_show = 'Full Trace' + render(wrapper.status_code, formatted_body, format) end - if source_to_show = traces[trace_to_show].first - source_to_show_id = source_to_show[:id] + def create_template(request, wrapper) + traces = wrapper.traces + + trace_to_show = "Application Trace" + if traces[trace_to_show].empty? && wrapper.rescue_template != "routing_error" + trace_to_show = "Full Trace" + end + + if source_to_show = traces[trace_to_show].first + source_to_show_id = source_to_show[:id] + end + + DebugView.new([RESCUES_TEMPLATE_PATH], + request: request, + exception: wrapper.exception, + traces: traces, + show_source_idx: source_to_show_id, + trace_to_show: trace_to_show, + routes_inspector: routes_inspector(wrapper.exception), + source_extracts: wrapper.source_extracts, + line_number: wrapper.line_number, + file: wrapper.file + ) end - DebugView.new([RESCUES_TEMPLATE_PATH], - request: request, - exception: wrapper.exception, - traces: traces, - show_source_idx: source_to_show_id, - trace_to_show: trace_to_show, - routes_inspector: routes_inspector(wrapper.exception), - source_extracts: wrapper.source_extracts, - line_number: wrapper.line_number, - file: wrapper.file - ) - end + def render(status, body, format) + [status, { "Content-Type" => "#{format}; charset=#{Response.default_charset}", "Content-Length" => body.bytesize.to_s }, [body]] + end - def render(status, body, format) - [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] - end + def log_error(request, wrapper) + logger = logger(request) + return unless logger - def log_error(request, wrapper) - logger = logger(request) - return unless logger + exception = wrapper.exception - exception = wrapper.exception + trace = wrapper.application_trace + trace = wrapper.framework_trace if trace.empty? - trace = wrapper.application_trace - trace = wrapper.framework_trace if trace.empty? + ActiveSupport::Deprecation.silence do + logger.fatal " " + logger.fatal "#{exception.class} (#{exception.message}):" + log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code) + logger.fatal " " + log_array logger, trace + end + end - ActiveSupport::Deprecation.silence do - logger.fatal " " - logger.fatal "#{exception.class} (#{exception.message}):" - log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code) - logger.fatal " " - log_array logger, trace + def log_array(logger, array) + array.map { |line| logger.fatal line } end - end - def log_array(logger, array) - array.map { |line| logger.fatal line } - end + def logger(request) + request.logger || ActionView::Base.logger || stderr_logger + end - def logger(request) - request.logger || ActionView::Base.logger || stderr_logger - end + def stderr_logger + @stderr_logger ||= ActiveSupport::Logger.new($stderr) + end - def stderr_logger - @stderr_logger ||= ActiveSupport::Logger.new($stderr) - end + def routes_inspector(exception) + if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error)) + ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes) + end + end - def routes_inspector(exception) - if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error)) - ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes) + def api_request?(content_type) + @response_format == :api && !content_type.html? end - end end end diff --git a/actionpack/lib/action_dispatch/middleware/debug_locks.rb b/actionpack/lib/action_dispatch/middleware/debug_locks.rb new file mode 100644 index 0000000000..74b952528e --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/debug_locks.rb @@ -0,0 +1,122 @@ +module ActionDispatch + # This middleware can be used to diagnose deadlocks in the autoload interlock. + # + # To use it, insert it near the top of the middleware stack, using + # <tt>config/application.rb</tt>: + # + # config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks + # + # After restarting the application and re-triggering the deadlock condition, + # <tt>/rails/locks</tt> will show a summary of all threads currently known to + # the interlock, which lock level they are holding or awaiting, and their + # current backtrace. + # + # Generally a deadlock will be caused by the interlock conflicting with some + # other external lock or blocking I/O call. These cannot be automatically + # identified, but should be visible in the displayed backtraces. + # + # NOTE: The formatting and content of this middleware's output is intended for + # human consumption, and should be expected to change between releases. + # + # This middleware exposes operational details of the server, with no access + # control. It should only be enabled when in use, and removed thereafter. + class DebugLocks + def initialize(app, path = "/rails/locks") + @app = app + @path = path + end + + def call(env) + req = ActionDispatch::Request.new env + + if req.get? + path = req.path_info.chomp("/".freeze) + if path == @path + return render_details(req) + end + end + + @app.call(env) + end + + private + def render_details(req) + threads = ActiveSupport::Dependencies.interlock.raw_state do |threads| + # The Interlock itself comes to a complete halt as long as this block + # is executing. That gives us a more consistent picture of everything, + # but creates a pretty strong Observer Effect. + # + # Most directly, that means we need to do as little as possible in + # this block. More widely, it means this middleware should remain a + # strictly diagnostic tool (to be used when something has gone wrong), + # and not for any sort of general monitoring. + + threads.each.with_index do |(thread, info), idx| + info[:index] = idx + info[:backtrace] = thread.backtrace + end + + threads + end + + str = threads.map do |thread, info| + if info[:exclusive] + lock_state = "Exclusive" + elsif info[:sharing] > 0 + lock_state = "Sharing" + lock_state << " x#{info[:sharing]}" if info[:sharing] > 1 + else + lock_state = "No lock" + end + + if info[:waiting] + lock_state << " (yielded share)" + end + + msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n" + + if info[:sleeper] + msg << " Waiting in #{info[:sleeper]}" + msg << " to #{info[:purpose].to_s.inspect}" unless info[:purpose].nil? + msg << "\n" + + if info[:compatible] + compat = info[:compatible].map { |c| c == false ? "share" : c.to_s.inspect } + msg << " may be pre-empted for: #{compat.join(', ')}\n" + end + + blockers = threads.values.select { |binfo| blocked_by?(info, binfo, threads.values) } + msg << " blocked by: #{blockers.map { |i| i[:index] }.join(', ')}\n" if blockers.any? + end + + blockees = threads.values.select { |binfo| blocked_by?(binfo, info, threads.values) } + msg << " blocking: #{blockees.map { |i| i[:index] }.join(', ')}\n" if blockees.any? + + msg << "\n#{info[:backtrace].join("\n")}\n" if info[:backtrace] + end.join("\n\n---\n\n\n") + + [200, { "Content-Type" => "text/plain", "Content-Length" => str.size }, [str]] + end + + def blocked_by?(victim, blocker, all_threads) + return false if victim.equal?(blocker) + + case victim[:sleeper] + when :start_sharing + blocker[:exclusive] || + (!victim[:waiting] && blocker[:compatible] && !blocker[:compatible].include?(false)) + when :start_exclusive + blocker[:sharing] > 0 || + blocker[:exclusive] || + (blocker[:compatible] && !blocker[:compatible].include?(victim[:purpose])) + when :yield_shares + blocker[:exclusive] + when :stop_exclusive + blocker[:exclusive] || + victim[:compatible] && + victim[:compatible].include?(blocker[:purpose]) && + all_threads.all? { |other| !other[:compatible] || blocker.equal?(other) || other[:compatible].include?(blocker[:purpose]) } + end + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index 59edc66086..9b44c4483e 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -1,33 +1,33 @@ -require 'active_support/core_ext/module/attribute_accessors' -require 'rack/utils' +require "active_support/core_ext/module/attribute_accessors" +require "rack/utils" module ActionDispatch class ExceptionWrapper cattr_accessor :rescue_responses @@rescue_responses = Hash.new(:internal_server_error) @@rescue_responses.merge!( - 'ActionController::RoutingError' => :not_found, - 'AbstractController::ActionNotFound' => :not_found, - 'ActionController::MethodNotAllowed' => :method_not_allowed, - 'ActionController::UnknownHttpMethod' => :method_not_allowed, - 'ActionController::NotImplemented' => :not_implemented, - 'ActionController::UnknownFormat' => :not_acceptable, - 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, - 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity, - 'ActionDispatch::ParamsParser::ParseError' => :bad_request, - 'ActionController::BadRequest' => :bad_request, - 'ActionController::ParameterMissing' => :bad_request, - 'Rack::Utils::ParameterTypeError' => :bad_request, - 'Rack::Utils::InvalidParameterError' => :bad_request + "ActionController::RoutingError" => :not_found, + "AbstractController::ActionNotFound" => :not_found, + "ActionController::MethodNotAllowed" => :method_not_allowed, + "ActionController::UnknownHttpMethod" => :method_not_allowed, + "ActionController::NotImplemented" => :not_implemented, + "ActionController::UnknownFormat" => :not_acceptable, + "ActionController::InvalidAuthenticityToken" => :unprocessable_entity, + "ActionController::InvalidCrossOriginRequest" => :unprocessable_entity, + "ActionDispatch::ParamsParser::ParseError" => :bad_request, + "ActionController::BadRequest" => :bad_request, + "ActionController::ParameterMissing" => :bad_request, + "Rack::QueryParser::ParameterTypeError" => :bad_request, + "Rack::QueryParser::InvalidParameterError" => :bad_request ) cattr_accessor :rescue_templates - @@rescue_templates = Hash.new('diagnostics') + @@rescue_templates = Hash.new("diagnostics") @@rescue_templates.merge!( - 'ActionView::MissingTemplate' => 'missing_template', - 'ActionController::RoutingError' => 'routing_error', - 'AbstractController::ActionNotFound' => 'unknown_action', - 'ActionView::Template::Error' => 'template_error' + "ActionView::MissingTemplate" => "missing_template", + "ActionController::RoutingError" => "routing_error", + "AbstractController::ActionNotFound" => "unknown_action", + "ActionView::Template::Error" => "template_error" ) attr_reader :backtrace_cleaner, :exception, :line_number, :file @@ -100,49 +100,49 @@ module ActionDispatch private - def backtrace - Array(@exception.backtrace) - end + def backtrace + Array(@exception.backtrace) + end - def original_exception(exception) - if @@rescue_responses.has_key?(exception.cause.class.name) - exception.cause - else - exception + def original_exception(exception) + if @@rescue_responses.has_key?(exception.cause.class.name) + exception.cause + else + exception + end end - end - def clean_backtrace(*args) - if backtrace_cleaner - backtrace_cleaner.clean(backtrace, *args) - else - backtrace + def clean_backtrace(*args) + if backtrace_cleaner + backtrace_cleaner.clean(backtrace, *args) + else + backtrace + end end - end - def source_fragment(path, line) - return unless Rails.respond_to?(:root) && Rails.root - full_path = Rails.root.join(path) - if File.exist?(full_path) - File.open(full_path, "r") do |file| - start = [line - 3, 0].max - lines = file.each_line.drop(start).take(6) - Hash[*(start+1..(lines.count+start)).zip(lines).flatten] + def source_fragment(path, line) + return unless Rails.respond_to?(:root) && Rails.root + full_path = Rails.root.join(path) + if File.exist?(full_path) + File.open(full_path, "r") do |file| + start = [line - 3, 0].max + lines = file.each_line.drop(start).take(6) + Hash[*(start+1..(lines.count+start)).zip(lines).flatten] + end end end - end - def extract_file_and_line_number(trace) - # Split by the first colon followed by some digits, which works for both - # Windows and Unix path styles. - file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace - [file, line.to_i] - end + def extract_file_and_line_number(trace) + # Split by the first colon followed by some digits, which works for both + # Windows and Unix path styles. + file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace + [file, line.to_i] + end - def expand_backtrace - @exception.backtrace.unshift( - @exception.to_s.split("\n") - ).flatten! - end + def expand_backtrace + @exception.backtrace.unshift( + @exception.to_s.split("\n") + ).flatten! + end end end diff --git a/actionpack/lib/action_dispatch/middleware/executor.rb b/actionpack/lib/action_dispatch/middleware/executor.rb index 06245b403b..3d43f97a2b 100644 --- a/actionpack/lib/action_dispatch/middleware/executor.rb +++ b/actionpack/lib/action_dispatch/middleware/executor.rb @@ -1,4 +1,4 @@ -require 'rack/body_proxy' +require "rack/body_proxy" module ActionDispatch class Executor diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 80703940ed..6900934712 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/hash/keys' +require "active_support/core_ext/hash/keys" module ActionDispatch # The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed @@ -36,7 +36,7 @@ module ActionDispatch # # See docs on the FlashHash class for more details about the flash. class Flash - KEY = 'action_dispatch.request.flash_hash'.freeze + KEY = "action_dispatch.request.flash_hash".freeze module RequestMethods # Access the contents of the flash. Use <tt>flash["notice"]</tt> to @@ -60,14 +60,14 @@ module ActionDispatch session = self.session || {} flash_hash = self.flash_hash - if flash_hash && (flash_hash.present? || session.key?('flash')) + if flash_hash && (flash_hash.present? || session.key?("flash")) session["flash"] = flash_hash.to_session_value self.flash = flash_hash.dup end if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?) - session.key?('flash') && session['flash'].nil? - session.delete('flash') + session.key?("flash") && session["flash"].nil? + session.delete("flash") end end @@ -118,8 +118,8 @@ module ActionDispatch end new(flashes, flashes.keys) when Hash # Rails 4.0 - flashes = value['flashes'] - if discard = value['discard'] + flashes = value["flashes"] + if discard = value["discard"] flashes.except!(*discard) end new(flashes, flashes.keys) @@ -133,7 +133,7 @@ module ActionDispatch def to_session_value #:nodoc: flashes_to_keep = @flashes.except(*@discard) return nil if flashes_to_keep.empty? - { 'discard' => [], 'flashes' => flashes_to_keep } + { "discard" => [], "flashes" => flashes_to_keep } end def initialize(flashes = {}, discard = []) #:nodoc: @@ -277,15 +277,15 @@ module ActionDispatch end protected - def now_is_loaded? - @now - end + def now_is_loaded? + @now + end - def stringify_array(array) - array.map do |item| - item.kind_of?(Symbol) ? item.to_s : item + def stringify_array(array) + array.map do |item| + item.kind_of?(Symbol) ? item.to_s : item + end end - end end def self.new(app) app; end diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index faf3262b8f..5f96b80e87 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -1,4 +1,4 @@ -require 'action_dispatch/http/request' +require "action_dispatch/http/request" module ActionDispatch # ActionDispatch::ParamsParser works for all the requests having any Content-Length @@ -10,7 +10,6 @@ module ActionDispatch # 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 = nil, original_exception = nil) if message ActiveSupport::Deprecation.warn("Passing #message is deprecated and has no effect. " \ @@ -37,7 +36,7 @@ module ActionDispatch # The +parsers+ argument can take Hash of parsers where key is identifying # content mime type, and value is a lambda that is going to process data. def self.new(app, parsers = {}) - ActiveSupport::Deprecation.warn('ActionDispatch::ParamsParser is deprecated and will be removed in Rails 5.1. Configure the parameter parsing in ActionDispatch::Request.parameter_parsers.') + ActiveSupport::Deprecation.warn("ActionDispatch::ParamsParser is deprecated and will be removed in Rails 5.1. Configure the parameter parsing in ActionDispatch::Request.parameter_parsers.") parsers = parsers.transform_keys { |key| key.respond_to?(:symbol) ? key.symbol : key } ActionDispatch::Request.parameter_parsers = ActionDispatch::Request::DEFAULT_PARSERS.merge(parsers) app diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb index 0f27984550..46f0f675b9 100644 --- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb @@ -20,36 +20,36 @@ module ActionDispatch request = ActionDispatch::Request.new(env) status = request.path_info[1..-1].to_i content_type = request.formats.first - body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) } + body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) } render(status, content_type, body) end private - def render(status, content_type, body) - format = "to_#{content_type.to_sym}" if content_type - if format && body.respond_to?(format) - render_format(status, content_type, body.public_send(format)) - else - render_html(status) + def render(status, content_type, body) + format = "to_#{content_type.to_sym}" if content_type + if format && body.respond_to?(format) + render_format(status, content_type, body.public_send(format)) + else + render_html(status) + end end - end - def render_format(status, content_type, body) - [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}", - 'Content-Length' => body.bytesize.to_s}, [body]] - end + def render_format(status, content_type, body) + [status, { "Content-Type" => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}", + "Content-Length" => body.bytesize.to_s }, [body]] + end - def render_html(status) - path = "#{public_path}/#{status}.#{I18n.locale}.html" - path = "#{public_path}/#{status}.html" unless (found = File.exist?(path)) + def render_html(status) + path = "#{public_path}/#{status}.#{I18n.locale}.html" + path = "#{public_path}/#{status}.html" unless (found = File.exist?(path)) - if found || File.exist?(path) - render_format(status, 'text/html', File.read(path)) - else - [404, { "X-Cascade" => "pass" }, []] + if found || File.exist?(path) + render_format(status, "text/html", File.read(path)) + else + [404, { "X-Cascade" => "pass" }, []] + end end - end end end diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index 112bde6596..90c64037aa 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -43,10 +43,10 @@ module ActionDispatch class << self attr_accessor :default_reloader # :nodoc: - deprecate to_prepare: 'use ActiveSupport::Reloader.to_prepare instead', - to_cleanup: 'use ActiveSupport::Reloader.to_complete instead', - prepare!: 'use Rails.application.reloader.prepare! instead', - cleanup!: 'use Rails.application.reloader.reload! instead of cleanup + prepare' + deprecate to_prepare: "use ActiveSupport::Reloader.to_prepare instead", + to_cleanup: "use ActiveSupport::Reloader.to_complete instead", + prepare!: "use Rails.application.reloader.prepare! instead", + cleanup!: "use Rails.application.reloader.reload! instead of cleanup + prepare" end self.default_reloader = ActiveSupport::Reloader diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 31b75498b6..523eeb5b05 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -1,4 +1,4 @@ -require 'ipaddr' +require "ipaddr" module ActionDispatch # This middleware calculates the IP address of the remote client that is @@ -176,8 +176,6 @@ module ActionDispatch @proxies.any? { |proxy| proxy === ip } end end - end - end end diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index 1555ff72af..1925ffd9dd 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -1,9 +1,10 @@ -require 'securerandom' -require 'active_support/core_ext/string/access' +require "securerandom" +require "active_support/core_ext/string/access" module ActionDispatch - # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through - # ActionDispatch::Request#uuid or the alias ActionDispatch::Request#request_id) and sends the same id to the client via the X-Request-Id header. + # Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible + # through <tt>ActionDispatch::Request#request_id</tt> or the alias <tt>ActionDispatch::Request#uuid</tt>) and sends + # the same id to the client via the X-Request-Id header. # # The unique request id is either based on the X-Request-Id header in the request, which would typically be generated # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the @@ -12,7 +13,7 @@ module ActionDispatch # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files # from multiple pieces of the stack. class RequestId - X_REQUEST_ID = "X-Request-Id".freeze # :nodoc: + X_REQUEST_ID = "X-Request-Id".freeze #:nodoc: def initialize(app) @app = app diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 5fb5953811..60920ea6c8 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -1,13 +1,12 @@ -require 'rack/utils' -require 'rack/request' -require 'rack/session/abstract/id' -require 'action_dispatch/middleware/cookies' -require 'action_dispatch/request/session' +require "rack/utils" +require "rack/request" +require "rack/session/abstract/id" +require "action_dispatch/middleware/cookies" +require "action_dispatch/request/session" module ActionDispatch module Session class SessionRestoreError < StandardError #:nodoc: - def initialize(const_error = nil) if const_error ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \ @@ -28,7 +27,7 @@ module ActionDispatch module Compatibility def initialize(app, options = {}) - options[:key] ||= '_session_id' + options[:key] ||= "_session_id" super end @@ -46,9 +45,9 @@ module ActionDispatch end private - def make_request(env) - ActionDispatch::Request.new env - end + def make_request(env) + ActionDispatch::Request.new env + end end module StaleSessionCheck @@ -94,9 +93,9 @@ module ActionDispatch private - def set_cookie(request, session_id, cookie) - request.cookie_jar[key] = cookie - end + def set_cookie(request, session_id, cookie) + request.cookie_jar[key] = cookie + end end end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb index 589ae46e38..71274bc13a 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb @@ -1,4 +1,4 @@ -require 'action_dispatch/middleware/session/abstract_store' +require "action_dispatch/middleware/session/abstract_store" module ActionDispatch module Session @@ -19,7 +19,7 @@ module ActionDispatch # Get a session from the cache. def find_session(env, sid) - unless sid and session = @cache.read(cache_key(sid)) + unless sid && (session = @cache.read(cache_key(sid))) sid, session = generate_sid, {} end [sid, session] @@ -29,7 +29,7 @@ module ActionDispatch def write_session(env, sid, session, options) key = cache_key(sid) if session - @cache.write(key, session, :expires_in => options[:expire_after]) + @cache.write(key, session, expires_in: options[:expire_after]) else @cache.delete(key) end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index dec9c60ef2..8409109ede 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -1,6 +1,6 @@ -require 'active_support/core_ext/hash/keys' -require 'action_dispatch/middleware/session/abstract_store' -require 'rack/session/cookie' +require "active_support/core_ext/hash/keys" +require "action_dispatch/middleware/session/abstract_store" +require "rack/session/cookie" module ActionDispatch module Session @@ -64,7 +64,7 @@ module ActionDispatch # <tt>:httponly</tt>. class CookieStore < AbstractStore def initialize(app, options={}) - super(app, options.merge!(:cookie_only => true)) + super(app, options.merge!(cookie_only: true)) end def delete_session(req, session_id, options) @@ -84,46 +84,46 @@ module ActionDispatch private - def extract_session_id(req) - stale_session_check! do - unpacked_cookie_data(req)["session_id"] + def extract_session_id(req) + stale_session_check! do + unpacked_cookie_data(req)["session_id"] + end end - end - def unpacked_cookie_data(req) - req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k| - v = stale_session_check! do - if data = get_cookie(req) - data.stringify_keys! + def unpacked_cookie_data(req) + req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k| + v = stale_session_check! do + if data = get_cookie(req) + data.stringify_keys! + end + data || {} end - data || {} + req.set_header k, v end - req.set_header k, v end - end - def persistent_session_id!(data, sid=nil) - data ||= {} - data["session_id"] ||= sid || generate_sid - data - end + def persistent_session_id!(data, sid=nil) + data ||= {} + data["session_id"] ||= sid || generate_sid + data + end - def write_session(req, sid, session_data, options) - session_data["session_id"] = sid - session_data - end + def write_session(req, sid, session_data, options) + session_data["session_id"] = sid + session_data + end - def set_cookie(request, session_id, cookie) - cookie_jar(request)[@key] = cookie - end + def set_cookie(request, session_id, cookie) + cookie_jar(request)[@key] = cookie + end - def get_cookie(req) - cookie_jar(req)[@key] - end + def get_cookie(req) + cookie_jar(req)[@key] + end - def cookie_jar(request) - request.cookie_jar.signed_or_encrypted - end + def cookie_jar(request) + request.cookie_jar.signed_or_encrypted + end end end end diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb index cb19786f0b..ee2b1f26ad 100644 --- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb @@ -1,6 +1,6 @@ -require 'action_dispatch/middleware/session/abstract_store' +require "action_dispatch/middleware/session/abstract_store" begin - require 'rack/session/dalli' + require "rack/session/dalli" rescue LoadError => e $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" raise e diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 64695f9738..90f26a1c33 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -1,5 +1,5 @@ -require 'action_dispatch/http/request' -require 'action_dispatch/middleware/exception_wrapper' +require "action_dispatch/http/request" +require "action_dispatch/middleware/exception_wrapper" module ActionDispatch # This middleware rescues any exception returned by the application @@ -15,7 +15,7 @@ module ActionDispatch # If any exception happens inside the exceptions app, this middleware # catches the exceptions and returns a FAILSAFE_RESPONSE. class ShowExceptions - FAILSAFE_RESPONSE = [500, { 'Content-Type' => 'text/plain' }, + FAILSAFE_RESPONSE = [500, { "Content-Type" => "text/plain" }, ["500 Internal Server Error\n" \ "If you are the administrator of this website, then please read this web " \ "application's log file and/or the web server's log file to find out what " \ @@ -39,22 +39,22 @@ module ActionDispatch private - def render_exception(request, exception) - backtrace_cleaner = request.get_header 'action_dispatch.backtrace_cleaner' - wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) - status = wrapper.status_code - request.set_header "action_dispatch.exception", wrapper.exception - request.set_header "action_dispatch.original_path", request.path_info - request.path_info = "/#{status}" - response = @exceptions_app.call(request.env) - response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response - rescue Exception => failsafe_error - $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}" - FAILSAFE_RESPONSE - end + def render_exception(request, exception) + backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner" + wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) + status = wrapper.status_code + request.set_header "action_dispatch.exception", wrapper.exception + request.set_header "action_dispatch.original_path", request.path_info + request.path_info = "/#{status}" + response = @exceptions_app.call(request.env) + response[1]["X-Cascade"] == "pass" ? pass_response(status) : response + rescue Exception => failsafe_error + $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}" + FAILSAFE_RESPONSE + end - def pass_response(status) - [status, {"Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0"}, []] - end + def pass_response(status) + [status, { "Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0" }, []] + end end end diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index ab3077b308..992daab3aa 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -18,17 +18,18 @@ module ActionDispatch # Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable. # # Set `config.ssl_options` with `hsts: { … }` to configure HSTS: - # * `expires`: How long, in seconds, these settings will stick. Defaults to - # `180.days` (recommended). The minimum required to qualify for browser - # preload lists is `18.weeks`. + # * `expires`: How long, in seconds, these settings will stick. The minimum + # required to qualify for browser preload lists is `18.weeks`. Defaults to + # `180.days` (recommended). # * `subdomains`: Set to `true` to tell the browser to apply these settings # to all subdomains. This protects your cookies from interception by a - # vulnerable site on a subdomain. Defaults to `true`. + # vulnerable site on a subdomain. Defaults to `false`. # * `preload`: Advertise that this site may be included in browsers' # preloaded HSTS lists. HSTS protects your site on every visit *except the # first visit* since it hasn't seen your HSTS header yet. To close this # gap, browser vendors include a baked-in list of HSTS-enabled sites. # Go to https://hstspreload.appspot.com to submit your site for inclusion. + # Defaults to `false`. # # To turn off HSTS, omitting the header is not enough. Browsers will remember the # original HSTS directive until it expires. Instead, use the header to tell browsers to @@ -92,7 +93,7 @@ module ActionDispatch private def set_hsts_header!(headers) - headers['Strict-Transport-Security'.freeze] ||= @hsts_header + headers["Strict-Transport-Security".freeze] ||= @hsts_header end def normalize_hsts_options(options) @@ -118,10 +119,10 @@ module ActionDispatch end def flag_cookies_as_secure!(headers) - if cookies = headers['Set-Cookie'.freeze] + if cookies = headers["Set-Cookie".freeze] cookies = cookies.split("\n".freeze) - headers['Set-Cookie'.freeze] = cookies.map { |cookie| + headers["Set-Cookie".freeze] = cookies.map { |cookie| if cookie !~ /;\s*secure\s*(;|$)/i "#{cookie}; secure" else @@ -132,12 +133,20 @@ module ActionDispatch end def redirect_to_https(request) - [ @redirect.fetch(:status, 301), - { 'Content-Type' => 'text/html', - 'Location' => https_location_for(request) }, + [ @redirect.fetch(:status, redirection_status(request)), + { "Content-Type" => "text/html", + "Location" => https_location_for(request) }, @redirect.fetch(:body, []) ] end + def redirection_status(request) + if request.get? || request.head? + 301 # Issue a permanent redirect via a GET request. + else + 307 # Issue a fresh request redirect to preserve the HTTP method. + end + end + def https_location_for(request) host = @redirect[:host] || request.host port = @redirect[:port] || request.port diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 0b4bee5462..466eb8b3f1 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -102,32 +102,32 @@ module ActionDispatch private - def assert_index(index, where) - index = get_class index - i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index } - raise "No such middleware to insert #{where}: #{index.inspect}" unless i - i - end + def assert_index(index, where) + index = get_class index + i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index } + raise "No such middleware to insert #{where}: #{index.inspect}" unless i + i + end - def get_class(klass) - if klass.is_a?(String) || klass.is_a?(Symbol) - classcache = ActiveSupport::Dependencies::Reference - converted_klass = classcache[klass.to_s] - ActiveSupport::Deprecation.warn <<-eowarn + def get_class(klass) + if klass.is_a?(String) || klass.is_a?(Symbol) + classcache = ActiveSupport::Dependencies::Reference + converted_klass = classcache[klass.to_s] + ActiveSupport::Deprecation.warn <<-eowarn Passing strings or symbols to the middleware builder is deprecated, please change them to actual class references. For example: "#{klass}" => #{converted_klass} - eowarn - converted_klass - else - klass + eowarn + converted_klass + else + klass + end end - end - def build_middleware(klass, args, block) - Middleware.new(get_class(klass), args, block) - end + def build_middleware(klass, args, block) + Middleware.new(get_class(klass), args, block) + end end end diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 2c5721dc22..fbf2a5fd0b 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -1,5 +1,5 @@ -require 'rack/utils' -require 'active_support/core_ext/uri' +require "rack/utils" +require "active_support/core_ext/uri" module ActionDispatch # This middleware returns a file's contents from disk in the body response. @@ -13,8 +13,8 @@ module ActionDispatch # located at `public/assets/application.js` if the file exists. If the file # does not exist, a 404 "File not Found" response will be returned. class FileHandler - def initialize(root, index: 'index', headers: {}) - @root = root.chomp('/') + def initialize(root, index: "index", headers: {}) + @root = root.chomp("/") @file_server = ::Rack::File.new(@root, headers) @index = index end @@ -33,7 +33,7 @@ module ActionDispatch paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"] if match = paths.detect { |p| - path = File.join(@root, p.force_encoding('UTF-8'.freeze)) + path = File.join(@root, p.force_encoding("UTF-8".freeze)) begin File.file?(path) && File.readable?(path) rescue SystemCallError @@ -46,7 +46,7 @@ module ActionDispatch end def call(env) - serve ActionDispatch::Request.new env + serve(Rack::Request.new(env)) end def serve(request) @@ -59,13 +59,13 @@ module ActionDispatch if status == 304 return [status, headers, body] end - headers['Content-Encoding'] = 'gzip' - headers['Content-Type'] = content_type(path) + headers["Content-Encoding"] = "gzip" + headers["Content-Type"] = content_type(path) else status, headers, body = @file_server.call(request.env) end - headers['Vary'] = 'Accept-Encoding' if gzip_path + headers["Vary"] = "Accept-Encoding" if gzip_path return [status, headers, body] ensure @@ -78,11 +78,11 @@ module ActionDispatch end def content_type(path) - ::Rack::Mime.mime_type(::File.extname(path), 'text/plain'.freeze) + ::Rack::Mime.mime_type(::File.extname(path), "text/plain".freeze) end def gzip_encoding_accepted?(request) - request.accept_encoding =~ /\bgzip\b/i + request.accept_encoding.any? { |enc, quality| enc =~ /\bgzip\b/i } end def gzip_file_path(path) @@ -106,12 +106,12 @@ module ActionDispatch # produce a directory traversal using this middleware. Only 'GET' and 'HEAD' # requests will result in a file being returned. class Static - def initialize(app, path, deprecated_cache_control = :not_set, index: 'index', headers: {}) + def initialize(app, path, deprecated_cache_control = :not_set, index: "index", headers: {}) if deprecated_cache_control != :not_set ActiveSupport::Deprecation.warn("The `cache_control` argument is deprecated," \ "replaced by `headers: { 'Cache-Control' => #{deprecated_cache_control} }`, " \ " and will be removed in Rails 5.1.") - headers['Cache-Control'.freeze] = deprecated_cache_control + headers["Cache-Control".freeze] = deprecated_cache_control end @app = app @@ -119,10 +119,10 @@ module ActionDispatch end def call(env) - req = ActionDispatch::Request.new env + req = Rack::Request.new env if req.get? || req.head? - path = req.path_info.chomp('/'.freeze) + path = req.path_info.chomp("/".freeze) if match = @file_handler.match?(path) req.path_info = match return @file_handler.serve(req) diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index e9e6a2e597..48cc91bbfa 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -8,20 +8,20 @@ module ActionDispatch config.action_dispatch.show_exceptions = true config.action_dispatch.tld_length = 1 config.action_dispatch.ignore_accept_header = false - config.action_dispatch.rescue_templates = { } - config.action_dispatch.rescue_responses = { } + config.action_dispatch.rescue_templates = {} + config.action_dispatch.rescue_responses = {} config.action_dispatch.default_charset = nil config.action_dispatch.rack_cache = false - config.action_dispatch.http_auth_salt = 'http authentication' - config.action_dispatch.signed_cookie_salt = 'signed cookie' - config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie' - config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie' + config.action_dispatch.http_auth_salt = "http authentication" + config.action_dispatch.signed_cookie_salt = "signed cookie" + config.action_dispatch.encrypted_cookie_salt = "encrypted cookie" + config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie" config.action_dispatch.perform_deep_munge = true config.action_dispatch.default_headers = { - 'X-Frame-Options' => 'SAMEORIGIN', - 'X-XSS-Protection' => '1; mode=block', - 'X-Content-Type-Options' => 'nosniff' + "X-Frame-Options" => "SAMEORIGIN", + "X-XSS-Protection" => "1; mode=block", + "X-Content-Type-Options" => "nosniff" } config.eager_load_namespaces << ActionDispatch diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index 47568f6ad0..b883ca0f61 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -1,4 +1,4 @@ -require 'rack/session/abstract/id' +require "rack/session/abstract/id" module ActionDispatch class Request @@ -204,26 +204,26 @@ module ActionDispatch private - def load_for_read! - load! if !loaded? && exists? - end + def load_for_read! + load! if !loaded? && exists? + end - def load_for_write! - load! unless loaded? - end + def load_for_write! + load! unless loaded? + end - def load! - id, session = @by.load_session @req - options[:id] = id - @delegate.replace(stringify_keys(session)) - @loaded = true - end + def load! + id, session = @by.load_session @req + options[:id] = id + @delegate.replace(stringify_keys(session)) + @loaded = true + end - def stringify_keys(other) - other.each_with_object({}) { |(key, value), hash| - hash[key.to_s] = value - } - end + def stringify_keys(other) + other.each_with_object({}) { |(key, value), hash| + hash[key.to_s] = value + } + end end end end diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb index bb3df3c311..282bdbd2be 100644 --- a/actionpack/lib/action_dispatch/request/utils.rb +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -1,7 +1,6 @@ module ActionDispatch class Request class Utils # :nodoc: - mattr_accessor :perform_deep_munge self.perform_deep_munge = true @@ -64,4 +63,3 @@ module ActionDispatch end end end - diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 67f441dfec..61ebd0b8db 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -89,7 +89,7 @@ module ActionDispatch # # Example: # - # # In routes.rb + # # In config/routes.rb # get '/login' => 'accounts#login', as: 'login' # # # With render, redirect_to, tests, etc. @@ -101,7 +101,7 @@ module ActionDispatch # # Use <tt>root</tt> as a shorthand to name a route for the root path "/". # - # # In routes.rb + # # In config/routes.rb # root to: 'blogs#index' # # # would recognize http://www.example.com/ as @@ -114,15 +114,15 @@ module ActionDispatch # Note: when using +controller+, the route is simply named after the # method you call on the block parameter rather than map. # - # # In routes.rb + # # In config/routes.rb # controller :blog do # get 'blog/show' => :list # get 'blog/delete' => :delete - # get 'blog/edit/:id' => :edit + # get 'blog/edit' => :edit # end # # # provides named routes for show, delete, and edit - # link_to @article.title, show_path(id: @article.id) + # link_to @article.title, blog_show_path(id: @article.id) # # == Pretty URLs # @@ -196,7 +196,7 @@ module ActionDispatch # # Rails.application.reload_routes! # - # This will clear all named routes and reload routes.rb if the file has been modified from + # This will clear all named routes and reload config/routes.rb if the file has been modified from # last load. To absolutely force reloading, use <tt>reload!</tt>. # # == Testing Routes diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 2459a45827..b91ffb8419 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -1,5 +1,5 @@ -require 'delegate' -require 'active_support/core_ext/string/strip' +require "delegate" +require "active_support/core_ext/string/strip" module ActionDispatch module Routing @@ -33,11 +33,11 @@ module ActionDispatch end def controller - parts.include?(:controller) ? ':controller' : requirements[:controller] + parts.include?(:controller) ? ":controller" : requirements[:controller] end def action - parts.include?(:action) ? ':action' : requirements[:action] + parts.include?(:action) ? ":action" : requirements[:action] end def internal? @@ -80,48 +80,48 @@ module ActionDispatch private - def normalize_filter(filter) - if filter.is_a?(Hash) && filter[:controller] - { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ } - elsif filter - { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ } + def normalize_filter(filter) + if filter.is_a?(Hash) && filter[:controller] + { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ } + elsif filter + { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ } + end end - end - def filter_routes(filter) - if filter - @routes.select do |route| - route_wrapper = RouteWrapper.new(route) - filter.any? { |default, value| route_wrapper.send(default) =~ value } + def filter_routes(filter) + if filter + @routes.select do |route| + route_wrapper = RouteWrapper.new(route) + filter.any? { |default, value| route_wrapper.send(default) =~ value } + end + else + @routes end - else - @routes end - end - def collect_routes(routes) - routes.collect do |route| - RouteWrapper.new(route) - end.reject(&:internal?).collect do |route| - collect_engine_routes(route) + def collect_routes(routes) + routes.collect do |route| + RouteWrapper.new(route) + end.reject(&:internal?).collect do |route| + collect_engine_routes(route) - { name: route.name, - verb: route.verb, - path: route.path, - reqs: route.reqs } + { name: route.name, + verb: route.verb, + path: route.path, + reqs: route.reqs } + end end - end - def collect_engine_routes(route) - name = route.endpoint - return unless route.engine? - return if @engines[name] + def collect_engine_routes(route) + name = route.endpoint + return unless route.engine? + return if @engines[name] - routes = route.rack_app.routes - if routes.is_a?(ActionDispatch::Routing::RouteSet) - @engines[name] = collect_routes(routes.routes) + routes = route.rack_app.routes + if routes.is_a?(ActionDispatch::Routing::RouteSet) + @engines[name] = collect_routes(routes.routes) + end end - end end class ConsoleFormatter @@ -161,7 +161,7 @@ module ActionDispatch private def draw_section(routes) - header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length) + header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length) name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max) routes.map do |r| diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 4c2a4cfeb0..4ec1b8ee1f 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,9 +1,9 @@ -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/enumerable' -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/regexp' -require 'action_dispatch/routing/redirection' -require 'action_dispatch/routing/endpoint' +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/regexp" +require "action_dispatch/routing/redirection" +require "action_dispatch/routing/endpoint" module ActionDispatch module Routing @@ -41,7 +41,7 @@ module ActionDispatch end def serve(req) - return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req) + return [ 404, { "X-Cascade" => "pass" }, [] ] unless matches?(req) @strategy.call @app, req end @@ -93,7 +93,7 @@ module ActionDispatch end def self.optional_format?(path, format) - format != false && !path.include?(':format') && !path.end_with?('/') + format != false && !path.include?(":format") && !path.end_with?("/") end def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options) @@ -106,7 +106,7 @@ module ActionDispatch @ast = ast @anchor = anchor @via = via - @internal = options[:internal] + @internal = options.delete(:internal) path_params = ast.find_all(&:symbol?).map(&:to_sym) @@ -120,7 +120,7 @@ module ActionDispatch if options_constraints.is_a?(Hash) @defaults = Hash[options_constraints.find_all { |key, default| - URL_OPTIONS.include?(key) && (String === default || Fixnum === default) + URL_OPTIONS.include?(key) && (String === default || Integer === default) }].merge @defaults @blocks = blocks constraints.merge! options_constraints @@ -138,7 +138,7 @@ module ActionDispatch @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options)) if path_params.include?(:action) && !@requirements.key?(:action) - @defaults[:action] ||= 'index' + @defaults[:action] ||= "index" end @required_defaults = (split_options[:required_defaults] || []).map(&:first) @@ -270,7 +270,7 @@ module ActionDispatch { requirements: { format: Regexp.compile(formatted) }, defaults: { format: formatted } } else - { requirements: { }, defaults: { } } + { requirements: {}, defaults: {} } end end @@ -333,7 +333,7 @@ module ActionDispatch def split_to(to) if to =~ /#/ - to.split('#') + to.split("#") else [] end @@ -614,7 +614,7 @@ module ActionDispatch target_as = name_for_action(options[:as], path) options[:via] ||= :all - match(path, options.merge(:to => app, :anchor => false, :format => false)) + match(path, options.merge(to: app, anchor: false, format: false)) define_generate_prefix(app, target_as) if rails_app self @@ -661,7 +661,7 @@ module ActionDispatch super(options) else prefix_options = options.slice(*_route.segment_keys) - prefix_options[:relative_url_root] = ''.freeze + prefix_options[:relative_url_root] = "".freeze # we must actually delete prefix segment keys to avoid passing them to next url_for _route.segment_keys.each { |k| options.delete(k) } _routes.url_helpers.send("#{name}_path", prefix_options) @@ -716,11 +716,7 @@ module ActionDispatch def map_method(method, args, &block) options = args.extract_options! options[:via] = method - if options.key?(:defaults) - defaults(options.delete(:defaults)) { match(*args, options, &block) } - else - match(*args, options, &block) - end + match(*args, options, &block) self end end @@ -814,7 +810,7 @@ module ActionDispatch options = args.extract_options!.dup scope = {} - options[:path] = args.flatten.join('/') if args.any? + options[:path] = args.flatten.join("/") if args.any? options[:constraints] ||= {} unless nested_scope? @@ -824,7 +820,7 @@ module ActionDispatch if options[:constraints].is_a?(Hash) defaults = options[:constraints].select do |k, v| - URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum)) + URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer)) end options[:defaults] = defaults.merge(options[:defaults] || {}) @@ -838,7 +834,7 @@ module ActionDispatch end if options.key? :anchor - raise ArgumentError, 'anchor is ignored unless passed to `match`' + raise ArgumentError, "anchor is ignored unless passed to `match`" end @scope.options.each do |option| @@ -985,7 +981,7 @@ module ActionDispatch # resources :iphones # end def constraints(constraints = {}) - scope(:constraints => constraints) { yield } + scope(constraints: constraints) { yield } end # Allows you to set default parameters for a route, such as this: @@ -1062,6 +1058,10 @@ module ActionDispatch def merge_shallow_scope(parent, child) #:nodoc: child ? true : false end + + def merge_to_scope(parent, child) + child + end end # Resource routing allows you to quickly declare all of the common routes @@ -1553,11 +1553,17 @@ module ActionDispatch # match 'path' => 'controller#action', via: patch # match 'path', to: 'controller#action', via: :post # match 'path', 'otherpath', on: :member, via: :get - def match(path, *rest) + def match(path, *rest, &block) if rest.empty? && Hash === path options = path path, to = options.find { |name, _value| name.is_a?(String) } + if path.nil? + ActiveSupport::Deprecation.warn "Omitting the route path is deprecated. "\ + "Specify the path with a String or a Symbol instead." + path = "" + end + case to when Symbol options[:action] = to @@ -1578,110 +1584,13 @@ module ActionDispatch paths = [path] + rest end - if options[:on] && !VALID_ON_OPTIONS.include?(options[:on]) - raise ArgumentError, "Unknown scope #{on.inspect} given to :on" - end - - if @scope[:controller] && @scope[:action] - options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}" - end - - controller = options.delete(:controller) || @scope[:controller] - option_path = options.delete :path - to = options.delete :to - via = Mapping.check_via Array(options.delete(:via) { - @scope[:via] - }) - formatted = options.delete(:format) { @scope[:format] } - anchor = options.delete(:anchor) { true } - options_constraints = options.delete(:constraints) || {} - - path_types = paths.group_by(&:class) - path_types.fetch(String, []).each do |_path| - route_options = options.dup - if _path && option_path - ActiveSupport::Deprecation.warn <<-eowarn -Specifying strings for both :path and the route path is deprecated. Change things like this: - - match #{_path.inspect}, :path => #{option_path.inspect} - -to this: - - match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{path.inspect} - eowarn - route_options[:action] = _path - route_options[:as] = _path - _path = option_path - end - to = get_to_from_path(_path, to, route_options[:action]) - decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints) - end - - path_types.fetch(Symbol, []).each do |action| - route_options = options.dup - decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints) - end - - self - end - - def get_to_from_path(path, to, action) - return to if to || action - - path_without_format = path.sub(/\(\.:format\)$/, '') - if using_match_shorthand?(path_without_format) - path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_") + if options.key?(:defaults) + defaults(options.delete(:defaults)) { map_match(paths, options, &block) } else - nil + map_match(paths, options, &block) end end - def using_match_shorthand?(path) - path =~ %r{^/?[-\w]+/[-\w/]+$} - end - - def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc: - if on = options.delete(:on) - send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } - else - case @scope.scope_level - when :resources - nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } - when :resource - member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } - else - add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints) - end - end - end - - def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc: - path = path_for_action(action, _path) - raise ArgumentError, "path is required" if path.blank? - - action = action.to_s - - default_action = options.delete(:action) || @scope[:action] - - if action =~ /^[\w\-\/]+$/ - default_action ||= action.tr('-', '_') unless action.include?("/") - else - action = nil - end - - as = if !options.fetch(:as, true) # if it's set to nil or false - options.delete(:as) - else - name_for_action(options.delete(:as), action) - end - - path = Mapping.normalize_path URI.parser.escape(path), formatted - ast = Journey::Parser.parse path - - mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options) - @set.add_route(mapping, ast, as, anchor) - end - # You can specify what Rails should route "/" to with the root method: # # root to: 'pages#main' @@ -1698,7 +1607,7 @@ to this: def root(path, options = {}) if path.is_a?(String) options[:to] = path - elsif path.is_a?(Hash) and options.empty? + elsif path.is_a?(Hash) && options.empty? options = path else raise ArgumentError, "must be called with a path and/or options" @@ -1787,7 +1696,7 @@ to this: end def resource_scope(resource) #:nodoc: - @scope = @scope.new(:scope_level_resource => resource) + @scope = @scope.new(scope_level_resource: resource) controller(resource.resource_scope) { yield } ensure @@ -1795,7 +1704,7 @@ to this: end def nested_options #:nodoc: - options = { :as => parent_resource.member_name } + options = { as: parent_resource.member_name } options[:constraints] = { parent_resource.nested_param => param_constraint } if param_constraint? @@ -1822,8 +1731,8 @@ to this: end def shallow_scope #:nodoc: - scope = { :as => @scope[:shallow_prefix], - :path => @scope[:shallow_path] } + scope = { as: @scope[:shallow_prefix], + path: @scope[:shallow_path] } @scope = @scope.new scope yield @@ -1852,8 +1761,8 @@ to this: prefix = action end - if prefix && prefix != '/' && !prefix.empty? - Mapper.normalize_name prefix.to_s.tr('-', '_') + if prefix && prefix != "/" && !prefix.empty? + Mapper.normalize_name prefix.to_s.tr("-", "_") end end @@ -1869,7 +1778,7 @@ to this: end action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name) - candidate = action_name.select(&:present?).join('_') + candidate = action_name.select(&:present?).join("_") unless candidate.empty? # If a name was not explicitly given, we check if it is valid @@ -1898,19 +1807,131 @@ to this: def api_only? @set.api_only? end + private - def path_scope(path) - @scope = @scope.new(path: merge_path_scope(@scope[:path], path)) - yield - ensure - @scope = @scope.parent - end + def path_scope(path) + @scope = @scope.new(path: merge_path_scope(@scope[:path], path)) + yield + ensure + @scope = @scope.parent + end - def match_root_route(options) - name = has_named_route?(:root) ? nil : :root - match '/', { :as => name, :via => :get }.merge!(options) - end + def map_match(paths, options) + if options[:on] && !VALID_ON_OPTIONS.include?(options[:on]) + raise ArgumentError, "Unknown scope #{on.inspect} given to :on" + end + + if @scope[:to] + options[:to] ||= @scope[:to] + end + + if @scope[:controller] && @scope[:action] + options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}" + end + + controller = options.delete(:controller) || @scope[:controller] + option_path = options.delete :path + to = options.delete :to + via = Mapping.check_via Array(options.delete(:via) { + @scope[:via] + }) + formatted = options.delete(:format) { @scope[:format] } + anchor = options.delete(:anchor) { true } + options_constraints = options.delete(:constraints) || {} + + path_types = paths.group_by(&:class) + path_types.fetch(String, []).each do |_path| + route_options = options.dup + if _path && option_path + ActiveSupport::Deprecation.warn <<-eowarn +Specifying strings for both :path and the route path is deprecated. Change things like this: + + match #{_path.inspect}, :path => #{option_path.inspect} + +to this: + + match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{_path.inspect} + eowarn + route_options[:action] = _path + route_options[:as] = _path + _path = option_path + end + to = get_to_from_path(_path, to, route_options[:action]) + decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints) + end + + path_types.fetch(Symbol, []).each do |action| + route_options = options.dup + decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints) + end + + self + end + + def get_to_from_path(path, to, action) + return to if to || action + + path_without_format = path.sub(/\(\.:format\)$/, "") + if using_match_shorthand?(path_without_format) + path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_") + else + nil + end + end + + def using_match_shorthand?(path) + path =~ %r{^/?[-\w]+/[-\w/]+$} + end + + def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc: + if on = options.delete(:on) + send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } + else + case @scope.scope_level + when :resources + nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } + when :resource + member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } + else + add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints) + end + end + end + + def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc: + path = path_for_action(action, _path) + raise ArgumentError, "path is required" if path.blank? + + action = action.to_s + + default_action = options.delete(:action) || @scope[:action] + + if action =~ /^[\w\-\/]+$/ + default_action ||= action.tr("-", "_") unless action.include?("/") + else + action = nil + end + + as = if !options.fetch(:as, true) # if it's set to nil or false + options.delete(:as) + else + name_for_action(options.delete(:as), action) + end + + path = Mapping.normalize_path URI.parser.escape(path), formatted + ast = Journey::Parser.parse path + + mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options) + @set.add_route(mapping, ast, as, anchor) + end + + def match_root_route(options) + name = has_named_route?(name_for_action(:root, nil)) ? nil : :root + args = ["/", { as: name, via: :get }.merge!(options)] + + match(*args) + end end # Routing Concerns allow you to declare common routes that can be reused @@ -2021,7 +2042,7 @@ to this: class Scope # :nodoc: OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module, :controller, :action, :path_names, :constraints, - :shallow, :blocks, :defaults, :via, :format, :options] + :shallow, :blocks, :defaults, :via, :format, :options, :to] RESOURCE_SCOPES = [:resource, :resources] RESOURCE_METHOD_SCOPES = [:collection, :member, :new] @@ -2101,7 +2122,7 @@ to this: def initialize(set) #:nodoc: @set = set - @scope = Scope.new({ :path_names => @set.resources_path_names }) + @scope = Scope.new(path_names: @set.resources_path_names) @concerns = {} end diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 9934f5547a..4f1aaeefc8 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -4,7 +4,7 @@ module ActionDispatch # given an Active Record model instance. They are to be used in combination with # ActionController::Resources. # - # These methods are useful when you want to generate correct URL or path to a RESTful + # These methods are useful when you want to generate the correct URL or path to a RESTful # resource without having to know the exact type of the record in question. # # Nested resources and/or namespaces are also supported, as illustrated in the example: @@ -79,7 +79,7 @@ module ActionDispatch # polymorphic_url([blog, post], anchor: 'my_anchor', script_name: "/my_app") # # => "http://example.com/my_app/blogs/1/posts/1#my_anchor" # - # For all of these options, see the documentation for <tt>url_for</tt>. + # For all of these options, see the documentation for {url_for}[rdoc-ref:ActionDispatch::Routing::UrlFor]. # # ==== Functionality # @@ -134,7 +134,6 @@ module ActionDispatch opts end - %w(edit new).each do |action| module_eval <<-EOT, __FILE__, __LINE__ + 1 def #{action}_polymorphic_url(record_or_hash, options = {}) @@ -149,176 +148,175 @@ module ActionDispatch private - def polymorphic_url_for_action(action, record_or_hash, options) - polymorphic_url(record_or_hash, options.merge(:action => action)) - end + def polymorphic_url_for_action(action, record_or_hash, options) + polymorphic_url(record_or_hash, options.merge(action: action)) + end - def polymorphic_path_for_action(action, record_or_hash, options) - polymorphic_path(record_or_hash, options.merge(:action => action)) - end + def polymorphic_path_for_action(action, record_or_hash, options) + polymorphic_path(record_or_hash, options.merge(action: action)) + end - class HelperMethodBuilder # :nodoc: - CACHE = { 'path' => {}, 'url' => {} } + class HelperMethodBuilder # :nodoc: + CACHE = { "path" => {}, "url" => {} } - def self.get(action, type) - type = type.to_s - CACHE[type].fetch(action) { build action, type } - end + def self.get(action, type) + type = type.to_s + CACHE[type].fetch(action) { build action, type } + end - def self.url; CACHE['url'.freeze][nil]; end - def self.path; CACHE['path'.freeze][nil]; end + def self.url; CACHE["url".freeze][nil]; end + def self.path; CACHE["path".freeze][nil]; end - def self.build(action, type) - prefix = action ? "#{action}_" : "" - suffix = type - if action.to_s == 'new' - HelperMethodBuilder.singular prefix, suffix - else - HelperMethodBuilder.plural prefix, suffix + def self.build(action, type) + prefix = action ? "#{action}_" : "" + suffix = type + if action.to_s == "new" + HelperMethodBuilder.singular prefix, suffix + else + HelperMethodBuilder.plural prefix, suffix + end end - end - def self.singular(prefix, suffix) - new(->(name) { name.singular_route_key }, prefix, suffix) - end + def self.singular(prefix, suffix) + new(->(name) { name.singular_route_key }, prefix, suffix) + end - def self.plural(prefix, suffix) - new(->(name) { name.route_key }, prefix, suffix) - end + def self.plural(prefix, suffix) + new(->(name) { name.route_key }, prefix, suffix) + end - def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options) - builder = get action, type + def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options) + builder = get action, type + + case record_or_hash_or_array + when Array + record_or_hash_or_array = record_or_hash_or_array.compact + if record_or_hash_or_array.empty? + raise ArgumentError, "Nil location provided. Can't build URI." + end + if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy) + recipient = record_or_hash_or_array.shift + end + + method, args = builder.handle_list record_or_hash_or_array + when String, Symbol + method, args = builder.handle_string record_or_hash_or_array + when Class + method, args = builder.handle_class record_or_hash_or_array - case record_or_hash_or_array - when Array - record_or_hash_or_array = record_or_hash_or_array.compact - if record_or_hash_or_array.empty? + when nil raise ArgumentError, "Nil location provided. Can't build URI." + else + method, args = builder.handle_model record_or_hash_or_array end - if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy) - recipient = record_or_hash_or_array.shift - end - - method, args = builder.handle_list record_or_hash_or_array - when String, Symbol - method, args = builder.handle_string record_or_hash_or_array - when Class - method, args = builder.handle_class record_or_hash_or_array - when nil - raise ArgumentError, "Nil location provided. Can't build URI." - else - method, args = builder.handle_model record_or_hash_or_array + if options.empty? + recipient.send(method, *args) + else + recipient.send(method, *args, options) + end end + attr_reader :suffix, :prefix - if options.empty? - recipient.send(method, *args) - else - recipient.send(method, *args, options) + def initialize(key_strategy, prefix, suffix) + @key_strategy = key_strategy + @prefix = prefix + @suffix = suffix end - end - - attr_reader :suffix, :prefix - - def initialize(key_strategy, prefix, suffix) - @key_strategy = key_strategy - @prefix = prefix - @suffix = suffix - end - - def handle_string(record) - [get_method_for_string(record), []] - end - - def handle_string_call(target, str) - target.send get_method_for_string str - end - def handle_class(klass) - [get_method_for_class(klass), []] - end + def handle_string(record) + [get_method_for_string(record), []] + end - def handle_class_call(target, klass) - target.send get_method_for_class klass - end + def handle_string_call(target, str) + target.send get_method_for_string str + end - def handle_model(record) - args = [] + def handle_class(klass) + [get_method_for_class(klass), []] + end - model = record.to_model - named_route = if model.persisted? - args << model - get_method_for_string model.model_name.singular_route_key - else - get_method_for_class model - end + def handle_class_call(target, klass) + target.send get_method_for_class klass + end - [named_route, args] - end + def handle_model(record) + args = [] - def handle_model_call(target, model) - method, args = handle_model model - target.send(method, *args) - end + model = record.to_model + named_route = if model.persisted? + args << model + get_method_for_string model.model_name.singular_route_key + else + get_method_for_class model + end - def handle_list(list) - record_list = list.dup - record = record_list.pop + [named_route, args] + end - args = [] + def handle_model_call(target, model) + method, args = handle_model model + target.send(method, *args) + end - route = record_list.map { |parent| - case parent + def handle_list(list) + record_list = list.dup + record = record_list.pop + + args = [] + + route = record_list.map { |parent| + case parent + when Symbol, String + parent.to_s + when Class + args << parent + parent.model_name.singular_route_key + else + args << parent.to_model + parent.to_model.model_name.singular_route_key + end + } + + route << + case record when Symbol, String - parent.to_s + record.to_s when Class - args << parent - parent.model_name.singular_route_key + @key_strategy.call record.model_name else - args << parent.to_model - parent.to_model.model_name.singular_route_key + model = record.to_model + if model.persisted? + args << model + model.model_name.singular_route_key + else + @key_strategy.call model.model_name + end end - } - - route << - case record - when Symbol, String - record.to_s - when Class - @key_strategy.call record.model_name - else - model = record.to_model - if model.persisted? - args << model - model.model_name.singular_route_key - else - @key_strategy.call model.model_name - end - end - route << suffix + route << suffix - named_route = prefix + route.join("_") - [named_route, args] - end + named_route = prefix + route.join("_") + [named_route, args] + end - private + private - def get_method_for_class(klass) - name = @key_strategy.call klass.model_name - get_method_for_string name - end + def get_method_for_class(klass) + name = @key_strategy.call klass.model_name + get_method_for_string name + end - def get_method_for_string(str) - "#{prefix}#{str}_#{suffix}" - end + def get_method_for_string(str) + "#{prefix}#{str}_#{suffix}" + end - [nil, 'new', 'edit'].each do |action| - CACHE['url'][action] = build action, 'url' - CACHE['path'][action] = build action, 'path' + [nil, "new", "edit"].each do |action| + CACHE["url"][action] = build action, "url" + CACHE["path"][action] = build action, "path" + end end - end end end end diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index d6987f4d09..87bcceccc0 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -1,9 +1,9 @@ -require 'action_dispatch/http/request' -require 'active_support/core_ext/uri' -require 'active_support/core_ext/array/extract_options' -require 'rack/utils' -require 'action_controller/metal/exceptions' -require 'action_dispatch/routing/endpoint' +require "action_dispatch/http/request" +require "active_support/core_ext/uri" +require "active_support/core_ext/array/extract_options" +require "rack/utils" +require "action_controller/metal/exceptions" +require "action_dispatch/routing/endpoint" module ActionDispatch module Routing @@ -22,7 +22,6 @@ module ActionDispatch end def serve(req) - req.check_path_parameters! uri = URI.parse(path(req.path_parameters, req)) unless uri.host @@ -40,9 +39,9 @@ module ActionDispatch body = %(<html><body>You are being <a href="#{ERB::Util.unwrapped_html_escape(uri.to_s)}">redirected</a>.</body></html>) headers = { - 'Location' => uri.to_s, - 'Content-Type' => 'text/html', - 'Content-Length' => body.length.to_s + "Location" => uri.to_s, + "Content-Type" => "text/html", + "Content-Length" => body.length.to_s } [ status, headers, [body] ] @@ -58,19 +57,19 @@ module ActionDispatch private def relative_path?(path) - path && !path.empty? && path[0] != '/' + path && !path.empty? && path[0] != "/" end def escape(params) - Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }] + Hash[params.map { |k,v| [k, Rack::Utils.escape(v)] }] end def escape_fragment(params) - Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_fragment(v)] }] + Hash[params.map { |k,v| [k, Journey::Router::Utils.escape_fragment(v)] }] end def escape_path(params) - Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_path(v)] }] + Hash[params.map { |k,v| [k, Journey::Router::Utils.escape_path(v)] }] end end @@ -104,11 +103,11 @@ module ActionDispatch def path(params, request) url_options = { - :protocol => request.protocol, - :host => request.host, - :port => request.optional_port, - :path => request.path, - :params => request.query_parameters + protocol: request.protocol, + host: request.host, + port: request.optional_port, + path: request.path, + params: request.query_parameters }.merge! options if !params.empty? && url_options[:path].match(/%\{\w*\}/) @@ -129,12 +128,11 @@ module ActionDispatch end def inspect - "redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})" + "redirect(#{status}, #{options.map { |k,v| "#{k}: #{v}" }.join(', ')})" end end module Redirection - # Redirect any path to another path: # # get "/stories" => redirect("/posts") diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index ed7130b58e..a1bc357c8b 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,12 +1,12 @@ -require 'action_dispatch/journey' -require 'active_support/concern' -require 'active_support/core_ext/object/to_query' -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/array/extract_options' -require 'action_controller/metal/exceptions' -require 'action_dispatch/http/request' -require 'action_dispatch/routing/endpoint' +require "action_dispatch/journey" +require "active_support/concern" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/module/remove_method" +require "active_support/core_ext/array/extract_options" +require "action_controller/metal/exceptions" +require "action_dispatch/http/request" +require "action_dispatch/routing/endpoint" module ActionDispatch module Routing @@ -34,7 +34,7 @@ module ActionDispatch if @raise_on_name_error raise else - return [404, {'X-Cascade' => 'pass'}, []] + return [404, { "X-Cascade" => "pass" }, []] end end @@ -59,7 +59,7 @@ module ActionDispatch private - def controller(_); @controller_class; end + def controller(_); @controller_class; end end # A NamedRouteCollection instance is a collection of named routes, and also @@ -180,40 +180,40 @@ module ActionDispatch private - def optimized_helper(args) - params = parameterize_args(args) do - raise_generation_error(args) - end + def optimized_helper(args) + params = parameterize_args(args) do + raise_generation_error(args) + end - @route.format params - end + @route.format params + end - def optimize_routes_generation?(t) - t.send(:optimize_routes_generation?) - end + def optimize_routes_generation?(t) + t.send(:optimize_routes_generation?) + end - def parameterize_args(args) - params = {} - @arg_size.times { |i| - key = @required_parts[i] - value = args[i].to_param - yield key if value.nil? || value.empty? - params[key] = value - } - params - end + def parameterize_args(args) + params = {} + @arg_size.times { |i| + key = @required_parts[i] + value = args[i].to_param + yield key if value.nil? || value.empty? + params[key] = value + } + params + end - def raise_generation_error(args) - missing_keys = [] - params = parameterize_args(args) { |missing_key| - missing_keys << missing_key - } - constraints = Hash[@route.requirements.merge(params).sort_by{|k,v| k.to_s}] - message = "No route matches #{constraints.inspect}" - message << " missing required keys: #{missing_keys.sort.inspect}" + def raise_generation_error(args) + missing_keys = [] + params = parameterize_args(args) { |missing_key| + missing_keys << missing_key + } + constraints = Hash[@route.requirements.merge(params).sort_by { |k,v| k.to_s }] + message = "No route matches #{constraints.inspect}" + message << ", missing required keys: #{missing_keys.sort.inspect}" - raise ActionController::UrlGenerationError, message - end + raise ActionController::UrlGenerationError, message + end end def initialize(route, options, route_name, url_strategy) @@ -264,38 +264,39 @@ module ActionDispatch end private - # Create a url helper allowing ordered parameters to be associated - # with corresponding dynamic segments, so you can do: - # - # foo_url(bar, baz, bang) - # - # Instead of: - # - # foo_url(bar: bar, baz: baz, bang: bang) - # - # Also allow options hash, so you can do: - # - # foo_url(bar, baz, bang, sort_by: 'baz') - # - def define_url_helper(mod, route, name, opts, route_key, url_strategy) - helper = UrlHelper.create(route, opts, route_key, url_strategy) - mod.module_eval do - define_method(name) do |*args| - last = args.last - options = case last - when Hash - args.pop - when ActionController::Parameters - if last.permitted? - args.pop.to_h - else - raise ArgumentError, ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE - end - end - helper.call self, args, options + # Create a url helper allowing ordered parameters to be associated + # with corresponding dynamic segments, so you can do: + # + # foo_url(bar, baz, bang) + # + # Instead of: + # + # foo_url(bar: bar, baz: baz, bang: bang) + # + # Also allow options hash, so you can do: + # + # foo_url(bar, baz, bang, sort_by: 'baz') + # + def define_url_helper(mod, route, name, opts, route_key, url_strategy) + helper = UrlHelper.create(route, opts, route_key, url_strategy) + mod.module_eval do + define_method(name) do |*args| + last = args.last + options = \ + case last + when Hash + args.pop + when ActionController::Parameters + if last.permitted? + args.pop.to_h + else + raise ArgumentError, ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE + end + end + helper.call self, args, options + end end end - end end # strategy for building urls to send to the client @@ -310,7 +311,7 @@ module ActionDispatch alias :routes :set def self.default_resources_path_names - { :new => 'new', :edit => 'edit' } + { new: "new", edit: "edit" } end def self.new_with_config(config) @@ -581,12 +582,12 @@ module ActionDispatch # be "index", not the recalled action of "show". if options[:controller] - options[:action] ||= 'index' + options[:action] ||= "index" options[:controller] = options[:controller].to_s end if options.key?(:action) - options[:action] = (options[:action] || 'index').to_s + options[:action] = (options[:action] || "index").to_s end end @@ -596,8 +597,8 @@ module ActionDispatch # :controller, :action or :id is not found, don't pull any # more keys from the recall. def normalize_controller_action_id! - use_recall_for(:controller) or return - use_recall_for(:action) or return + use_recall_for(:controller) || return + use_recall_for(:action) || return use_recall_for(:id) end @@ -605,7 +606,7 @@ module ActionDispatch # is specified, the controller becomes "foo/baz/bat" def use_relative_controller! if !named_route && different_controller? && !controller.start_with?("/") - old_parts = current_controller.split('/') + old_parts = current_controller.split("/") size = controller.count("/") + 1 parts = old_parts[0...-size] << controller @options[:controller] = parts.join("/") @@ -670,7 +671,7 @@ module ActionDispatch end def find_script_name(options) - options.delete(:script_name) || find_relative_url_root(options) || '' + options.delete(:script_name) || find_relative_url_root(options) || "" end def find_relative_url_root(options) @@ -731,7 +732,7 @@ module ActionDispatch extras = environment[:extras] || {} begin - env = Rack::MockRequest.env_for(path, {:method => method}) + env = Rack::MockRequest.env_for(path, method: method) rescue URI::InvalidURIError => e raise ActionController::RoutingError, e.message end diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb index 040ea04046..ee847eaeed 100644 --- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb +++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/array/extract_options' +require "active_support/core_ext/array/extract_options" module ActionDispatch module Routing @@ -19,7 +19,7 @@ module ActionDispatch end end - def respond_to?(method, include_private = false) + def respond_to_missing?(method, include_private = false) super || @helpers.respond_to?(method) end diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 5ee138e6c6..a1ac5a2b6c 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -194,20 +194,20 @@ module ActionDispatch protected - def optimize_routes_generation? - _routes.optimize_routes_generation? && default_url_options.empty? - end + def optimize_routes_generation? + _routes.optimize_routes_generation? && default_url_options.empty? + end - def _with_routes(routes) - old_routes, @_routes = @_routes, routes - yield - ensure - @_routes = old_routes - end + def _with_routes(routes) + old_routes, @_routes = @_routes, routes + yield + ensure + @_routes = old_routes + end - def _routes_context - self - end + def _routes_context + self + end end end end diff --git a/actionpack/lib/action_dispatch/testing/assertion_response.rb b/actionpack/lib/action_dispatch/testing/assertion_response.rb index 3fb81ff083..c37726957e 100644 --- a/actionpack/lib/action_dispatch/testing/assertion_response.rb +++ b/actionpack/lib/action_dispatch/testing/assertion_response.rb @@ -1,14 +1,7 @@ module ActionDispatch - # This is a class that abstracts away an asserted response. - # It purposely does not inherit from Response, because it doesn't need it. - # That means it does not have headers or a body. - # - # As an input to the initializer, we take a Fixnum, a String, or a Symbol. - # If it's a Fixnum or String, we figure out what its symbolized name. - # If it's a Symbol, we figure out what its corresponding code is. - # The resulting code will be a Fixnum, for real HTTP codes, and it will - # be a String for the pseudo-HTTP codes, such as: - # :success, :missing, :redirect and :error + # This is a class that abstracts away an asserted response. It purposely + # does not inherit from Response because it doesn't need it. That means it + # does not have headers or a body. class AssertionResponse attr_reader :code, :name @@ -19,6 +12,9 @@ module ActionDispatch error: "5XX" } + # Accepts a specific response status code as an Integer (404) or String + # ('404') or a response status range as a Symbol pseudo-code (:success, + # indicating any 200-299 status code). def initialize(code_or_name) if code_or_name.is_a?(Symbol) @name = code_or_name @@ -38,12 +34,12 @@ module ActionDispatch private - def code_from_name(name) - GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name] - end + def code_from_name(name) + GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name] + end - def name_from_code(code) - GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code] - end + def name_from_code(code) + GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code] + end end end diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb index fae266273e..b362931ef7 100644 --- a/actionpack/lib/action_dispatch/testing/assertions.rb +++ b/actionpack/lib/action_dispatch/testing/assertions.rb @@ -1,9 +1,9 @@ -require 'rails-dom-testing' +require "rails-dom-testing" module ActionDispatch module Assertions - autoload :ResponseAssertions, 'action_dispatch/testing/assertions/response' - autoload :RoutingAssertions, 'action_dispatch/testing/assertions/routing' + autoload :ResponseAssertions, "action_dispatch/testing/assertions/response" + autoload :RoutingAssertions, "action_dispatch/testing/assertions/routing" extend ActiveSupport::Concern diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index cd55b7d975..a2eaccd9ef 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -79,11 +79,16 @@ module ActionDispatch def generate_response_message(expected, actual = @response.response_code) "Expected response to be a <#{code_with_name(expected)}>,"\ " but was a <#{code_with_name(actual)}>" - .concat location_if_redirected + .concat(location_if_redirected).concat(response_body_if_short) + end + + def response_body_if_short + return "" if @response.body.size > 500 + "\nResponse body: #{@response.body}" end def location_if_redirected - return '' unless @response.redirection? && @response.location.present? + return "" unless @response.redirection? && @response.location.present? location = normalize_argument_to_redirection(@response.location) " redirect to <#{location}>" end diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 44ad2c10d8..e53bc6af12 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -1,7 +1,7 @@ -require 'uri' -require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/string/access' -require 'action_controller/metal/exceptions' +require "uri" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/string/access" +require "action_controller/metal/exceptions" module ActionDispatch module Assertions @@ -82,7 +82,7 @@ module ActionDispatch expected_path = uri.path.to_s.empty? ? "/" : uri.path end else - expected_path = "/#{expected_path}" unless expected_path.first == '/' + expected_path = "/#{expected_path}" unless expected_path.first == "/" end # Load routes.rb if it hasn't been loaded. @@ -127,7 +127,7 @@ module ActionDispatch options[:controller] = "/#{controller}" end - generate_options = options.dup.delete_if{ |k, _| defaults.key?(k) } + generate_options = options.dup.delete_if { |k, _| defaults.key?(k) } assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message) end @@ -184,7 +184,7 @@ module ActionDispatch end # Assume given controller - request = ActionController::TestRequest.create + request = ActionController::TestRequest.create @controller.class if path =~ %r{://} fail_on(URI::InvalidURIError, msg) do @@ -202,7 +202,7 @@ module ActionDispatch request.request_method = method if method params = fail_on(ActionController::RoutingError, msg) do - @routes.recognize_path(path, { :method => method, :extras => extras }) + @routes.recognize_path(path, method: method, extras: extras) end request.path_parameters = params.with_indifferent_access diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 384254b131..720651fa1f 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -1,10 +1,12 @@ -require 'stringio' -require 'uri' -require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/core_ext/object/try' -require 'active_support/core_ext/string/strip' -require 'rack/test' -require 'minitest' +require "stringio" +require "uri" +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/object/try" +require "active_support/core_ext/string/strip" +require "rack/test" +require "minitest" + +require "action_dispatch/testing/request_encoder" module ActionDispatch module Integration #:nodoc: @@ -122,7 +124,7 @@ module ActionDispatch # params: { ref_id: 14 }, # headers: { "X-Test-Header" => "testvalue" } def request_via_redirect(http_method, path, *args) - ActiveSupport::Deprecation.warn('`request_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn("`request_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.") process_with_kwargs(http_method, path, *args) follow_redirect! while redirect? @@ -132,35 +134,35 @@ module ActionDispatch # Performs a GET request, following any subsequent redirect. # See +request_via_redirect+ for more information. def get_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn("`get_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.") request_via_redirect(:get, path, *args) end # Performs a POST request, following any subsequent redirect. # See +request_via_redirect+ for more information. def post_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn("`post_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.") request_via_redirect(:post, path, *args) end # Performs a PATCH request, following any subsequent redirect. # See +request_via_redirect+ for more information. def patch_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn("`patch_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.") request_via_redirect(:patch, path, *args) end # Performs a PUT request, following any subsequent redirect. # See +request_via_redirect+ for more information. def put_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn("`put_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.") request_via_redirect(:put, path, *args) end # Performs a DELETE request, following any subsequent redirect. # See +request_via_redirect+ for more information. def delete_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn("`delete_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.") request_via_redirect(:delete, path, *args) end end @@ -177,14 +179,14 @@ module ActionDispatch DEFAULT_HOST = "www.example.com" include Minitest::Assertions - include TestProcess, RequestHelpers, Assertions + include RequestHelpers, Assertions %w( status status_message headers body redirect? ).each do |method| - delegate method, :to => :response, :allow_nil => true + delegate method, to: :response, allow_nil: true end %w( path ).each do |method| - delegate method, :to => :request, :allow_nil => true + delegate method, to: :request, allow_nil: true end # The hostname used in the last request. @@ -235,7 +237,7 @@ module ActionDispatch url_options.reverse_merge!(@app.routes.default_url_options) end - url_options.reverse_merge!(:host => host, :protocol => https? ? "https" : "http") + url_options.reverse_merge!(host: host, protocol: https? ? "https" : "http") end end @@ -296,11 +298,11 @@ module ActionDispatch process(http_method, path, *args) else non_kwarg_request_warning if args.any? - process(http_method, path, { params: args[0], headers: args[1] }) + process(http_method, path, params: args[0], headers: args[1]) end end - REQUEST_KWARGS = %i(params headers env xhr) + REQUEST_KWARGS = %i(params headers env xhr as) def kwarg_request?(args) args[0].respond_to?(:keys) && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) } end @@ -317,29 +319,36 @@ module ActionDispatch params: { id: 1 }, headers: { 'X-Extra-Header' => '123' }, env: { 'action_dispatch.custom' => 'custom' }, - xhr: true + xhr: true, + as: :json MSG end # Performs the actual request. def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil) request_encoder = RequestEncoder.encoder(as) + headers ||= {} + + if method == :get && as == :json && params + headers["X-Http-Method-Override"] = "GET" + method = :post + end if path =~ %r{://} - location = URI.parse(path) - https! URI::HTTPS === location if location.scheme - if url_host = location.host - default = Rack::Request::DEFAULT_PORTS[location.scheme] - url_host += ":#{location.port}" if default != location.port - host! url_host + path = build_expanded_path(path, request_encoder) do |location| + https! URI::HTTPS === location if location.scheme + + if url_host = location.host + default = Rack::Request::DEFAULT_PORTS[location.scheme] + url_host += ":#{location.port}" if default != location.port + host! url_host + end end - path = request_encoder.append_format_to location.path - path = location.query ? "#{path}?#{location.query}" : path - else - path = request_encoder.append_format_to path + elsif as + path = build_expanded_path(path, request_encoder) end - hostname, port = host.split(':') + hostname, port = host.split(":") request_env = { :method => method, @@ -357,15 +366,17 @@ module ActionDispatch "HTTP_ACCEPT" => accept } + wrapped_headers = Http::Headers.from_hash({}) + wrapped_headers.merge!(headers) if headers + if xhr - headers ||= {} - headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - headers['HTTP_ACCEPT'] ||= [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ') + wrapped_headers["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" + wrapped_headers["HTTP_ACCEPT"] ||= [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ") end # this modifies the passed request_env directly - if headers.present? - Http::Headers.from_hash(request_env).merge!(headers) + if wrapped_headers.present? + Http::Headers.from_hash(request_env).merge!(wrapped_headers) end if env.present? Http::Headers.from_hash(request_env).merge!(env) @@ -382,7 +393,6 @@ module ActionDispatch response = _mock_session.last_response @response = ActionDispatch::TestResponse.from_response(response) @response.request = @request - @response.response_parser = RequestEncoder.parser(@response.content_type) @html_document = nil @url_options = nil @@ -395,54 +405,11 @@ module ActionDispatch "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}" end - class RequestEncoder # :nodoc: - @encoders = {} - - attr_reader :response_parser - - def initialize(mime_name, param_encoder, response_parser, url_encoded_form = false) - @mime = Mime[mime_name] - - unless @mime - raise ArgumentError, "Can't register a request encoder for " \ - "unregistered MIME Type: #{mime_name}. See `Mime::Type.register`." - end - - @url_encoded_form = url_encoded_form - @path_format = ".#{@mime.symbol}" unless @url_encoded_form - @response_parser = response_parser || -> body { body } - @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc - end - - def append_format_to(path) - path << @path_format unless @url_encoded_form - path - end - - def content_type - @mime.to_s - end - - def encode_params(params) - @param_encoder.call(params) - end - - def self.parser(content_type) - mime = Mime::Type.lookup(content_type) - encoder(mime ? mime.ref : nil).response_parser - end - - def self.encoder(name) - @encoders[name] || WWWFormEncoder - end - - def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil) - @encoders[mime_name] = new(mime_name, param_encoder, response_parser) - end - - register_encoder :json, response_parser: -> body { JSON.parse(body) } - - WWWFormEncoder = new(:url_encoded_form, -> params { params }, nil, true) + def build_expanded_path(path, request_encoder) + location = URI.parse(path) + yield location if block_given? + path = request_encoder.append_format_to location.path + location.query ? "#{path}?#{location.query}" : path end end @@ -453,9 +420,13 @@ module ActionDispatch attr_reader :app + def initialize(*args, &blk) + super(*args, &blk) + @integration_session = nil + end + def before_setup # :nodoc: @app = nil - @integration_session = nil super end @@ -489,7 +460,7 @@ module ActionDispatch xml_http_request xhr get_via_redirect post_via_redirect).each do |method| define_method(method) do |*args| # reset the html_document variable, except for cookies/assigns calls - unless method == 'cookies' || method == 'assigns' + unless method == "cookies" || method == "assigns" @html_document = nil end @@ -531,7 +502,7 @@ module ActionDispatch integration_session.default_url_options = options end - def respond_to?(method, include_private = false) + def respond_to_missing?(method, include_private = false) integration_session.respond_to?(method, include_private) || super end @@ -700,42 +671,48 @@ module ActionDispatch # end # end # + # See the {request helpers documentation}[rdoc-ref:ActionDispatch::Integration::RequestHelpers] for help on how to + # use +get+, etc. + # + # === Changing the request encoding + # # You can also test your JSON API easily by setting what the request should # be encoded as: # - # require 'test_helper' + # require "test_helper" # # class ApiTest < ActionDispatch::IntegrationTest - # test 'creates articles' do + # test "creates articles" do # assert_difference -> { Article.count } do - # post articles_path, params: { article: { title: 'Ahoy!' } }, as: :json + # post articles_path, params: { article: { title: "Ahoy!" } }, as: :json # end # # assert_response :success - # assert_equal({ id: Arcticle.last.id, title: 'Ahoy!' }, response.parsed_body) + # assert_equal({ id: Arcticle.last.id, title: "Ahoy!" }, response.parsed_body) # end # end # - # The `as` option sets the format to JSON, sets the content type to - # 'application/json' and encodes the parameters as JSON. + # The +as+ option sets the format to JSON, sets the content type to + # "application/json" and encodes the parameters as JSON. # - # Calling `parsed_body` on the response parses the response body as what - # the last request was encoded as. If the request wasn't encoded `as` something, - # it's the same as calling `body`. + # Calling +parsed_body+ on the response parses the response body based on the + # last response MIME type. # - # For any custom MIME Types you've registered, you can even add your own encoders with: + # For any custom MIME types you've registered, you can even add your own encoders with: # # ActionDispatch::IntegrationTest.register_encoder :wibble, # param_encoder: -> params { params.to_wibble }, # response_parser: -> body { body } # - # Where `param_encoder` defines how the params should be encoded and - # `response_parser` defines how the response body should be parsed through - # `parsed_body`. + # Where +param_encoder+ defines how the params should be encoded and + # +response_parser+ defines how the response body should be parsed through + # +parsed_body+. # # Consult the Rails Testing Guide for more. class IntegrationTest < ActiveSupport::TestCase + include TestProcess + module UrlOptions extend ActiveSupport::Concern def url_options @@ -758,7 +735,11 @@ module ActionDispatch module ClassMethods def app - defined?(@@app) ? @@app : ActionDispatch.test_app + if defined?(@@app) && @@app + @@app + else + ActionDispatch.test_app + end end def app=(app) @@ -766,7 +747,7 @@ module ActionDispatch end def register_encoder(*args) - Integration::Session::RequestEncoder.register_encoder(*args) + RequestEncoder.register_encoder(*args) end end diff --git a/actionpack/lib/action_dispatch/testing/request_encoder.rb b/actionpack/lib/action_dispatch/testing/request_encoder.rb new file mode 100644 index 0000000000..b0b994b2d0 --- /dev/null +++ b/actionpack/lib/action_dispatch/testing/request_encoder.rb @@ -0,0 +1,54 @@ +module ActionDispatch + class RequestEncoder # :nodoc: + @encoders = {} + + attr_reader :response_parser + + def initialize(mime_name, param_encoder, response_parser, url_encoded_form = false) + @mime = Mime[mime_name] + + unless @mime + raise ArgumentError, "Can't register a request encoder for " \ + "unregistered MIME Type: #{mime_name}. See `Mime::Type.register`." + end + + @url_encoded_form = url_encoded_form + @path_format = ".#{@mime.symbol}" unless @url_encoded_form + @response_parser = response_parser || -> body { body } + @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc + end + + def append_format_to(path) + if @url_encoded_form + path + else + path + @path_format + end + end + + def content_type + @mime.to_s + end + + def encode_params(params) + @param_encoder.call(params) + end + + def self.parser(content_type) + mime = Mime::Type.lookup(content_type) + encoder(mime ? mime.ref : nil).response_parser + end + + def self.encoder(name) + @encoders[name] || WWWFormEncoder + end + + def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil) + @encoders[mime_name] = new(mime_name, param_encoder, response_parser) + end + + register_encoder :json, response_parser: -> body { JSON.parse(body) } + + WWWFormEncoder = new(:url_encoded_form, -> params { params }, nil, true) + end +end diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index 1ecd7d14a7..8b03b776fa 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -1,5 +1,5 @@ -require 'action_dispatch/middleware/cookies' -require 'action_dispatch/middleware/flash' +require "action_dispatch/middleware/cookies" +require "action_dispatch/middleware/flash" module ActionDispatch module TestProcess @@ -34,7 +34,8 @@ module ActionDispatch # # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png', :binary) def fixture_file_upload(path, mime_type = nil, binary = false) - if self.class.respond_to?(:fixture_path) && self.class.fixture_path + if self.class.respond_to?(:fixture_path) && self.class.fixture_path && + !File.exist?(path) path = File.join(self.class.fixture_path, path) end Rack::Test::UploadedFile.new(path, mime_type, binary) diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index 46523a8600..91b25ec155 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -1,12 +1,12 @@ -require 'active_support/core_ext/hash/indifferent_access' -require 'rack/utils' +require "active_support/core_ext/hash/indifferent_access" +require "rack/utils" module ActionDispatch class TestRequest < Request - DEFAULT_ENV = Rack::MockRequest.env_for('/', - 'HTTP_HOST' => 'test.host', - 'REMOTE_ADDR' => '0.0.0.0', - 'HTTP_USER_AGENT' => 'Rails Testing', + DEFAULT_ENV = Rack::MockRequest.env_for("/", + "HTTP_HOST" => "test.host", + "REMOTE_ADDR" => "0.0.0.0", + "HTTP_USER_AGENT" => "Rails Testing", ) # Create a new test request with default `env` values @@ -22,23 +22,23 @@ module ActionDispatch private_class_method :default_env def request_method=(method) - set_header('REQUEST_METHOD', method.to_s.upcase) + super(method.to_s.upcase) end def host=(host) - set_header('HTTP_HOST', host) + set_header("HTTP_HOST", host) end def port=(number) - set_header('SERVER_PORT', number.to_i) + set_header("SERVER_PORT", number.to_i) end def request_uri=(uri) - set_header('REQUEST_URI', uri) + set_header("REQUEST_URI", uri) end def path=(path) - set_header('PATH_INFO', path) + set_header("PATH_INFO", path) end def action=(action_name) @@ -46,24 +46,24 @@ module ActionDispatch end def if_modified_since=(last_modified) - set_header('HTTP_IF_MODIFIED_SINCE', last_modified) + set_header("HTTP_IF_MODIFIED_SINCE", last_modified) end def if_none_match=(etag) - set_header('HTTP_IF_NONE_MATCH', etag) + set_header("HTTP_IF_NONE_MATCH", etag) end def remote_addr=(addr) - set_header('REMOTE_ADDR', addr) + set_header("REMOTE_ADDR", addr) end def user_agent=(user_agent) - set_header('HTTP_USER_AGENT', user_agent) + set_header("HTTP_USER_AGENT", user_agent) end def accept=(mime_types) - delete_header('action_dispatch.request.accepts') - set_header('HTTP_ACCEPT', Array(mime_types).collect(&:to_s).join(",")) + delete_header("action_dispatch.request.accepts") + set_header("HTTP_ACCEPT", Array(mime_types).collect(&:to_s).join(",")) end end end diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb index 9d4b73a43d..5c89f9c75e 100644 --- a/actionpack/lib/action_dispatch/testing/test_response.rb +++ b/actionpack/lib/action_dispatch/testing/test_response.rb @@ -1,3 +1,5 @@ +require "action_dispatch/testing/request_encoder" + module ActionDispatch # Integration test methods such as ActionDispatch::Integration::Session#get # and ActionDispatch::Integration::Session#post return objects of class @@ -10,6 +12,11 @@ module ActionDispatch new response.status, response.headers, response.body end + def initialize(*) # :nodoc: + super + @response_parser = RequestEncoder.parser(content_type) + end + # Was the response successful? alias_method :success?, :successful? @@ -19,8 +26,6 @@ module ActionDispatch # Was there a server-side error? alias_method :error?, :server_error? - attr_writer :response_parser # :nodoc: - def parsed_body @parsed_body ||= @response_parser.call(body) end diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb index 941877d10d..ee6dabd133 100644 --- a/actionpack/lib/action_pack.rb +++ b/actionpack/lib/action_pack.rb @@ -21,4 +21,4 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require 'action_pack/version' +require "action_pack/version" diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 7088cd2760..3d96158431 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,4 +1,4 @@ -require_relative 'gem_version' +require_relative "gem_version" module ActionPack # Returns the version of the currently loaded ActionPack as a <tt>Gem::Version</tt> |