aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/abstract_controller/asset_paths.rb2
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb4
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb167
-rw-r--r--actionpack/lib/abstract_controller/logger.rb3
-rw-r--r--actionpack/lib/abstract_controller/railties/routes_helpers.rb4
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb15
-rw-r--r--actionpack/lib/abstract_controller/view_paths.rb2
-rw-r--r--actionpack/lib/action_controller/base.rb2
-rw-r--r--actionpack/lib/action_controller/caching.rb1
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb7
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb53
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb7
-rw-r--r--actionpack/lib/action_controller/metal.rb8
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb53
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb7
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb7
-rw-r--r--actionpack/lib/action_controller/metal/head.rb2
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb17
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb7
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb8
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb11
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb9
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb20
-rw-r--r--actionpack/lib/action_controller/metal/rescue.rb13
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb32
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb3
-rw-r--r--actionpack/lib/action_controller/railtie.rb1
-rw-r--r--actionpack/lib/action_controller/railties/paths.rb7
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb4
-rw-r--r--actionpack/lib/action_controller/test_case.rb34
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/document.rb2
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb2
-rw-r--r--actionpack/lib/action_dispatch.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb42
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb57
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb35
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/closed_error.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb17
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb82
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb78
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb58
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb30
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb52
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb102
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb39
-rw-r--r--actionpack/lib/action_dispatch/middleware/rescue.rb26
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb50
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb177
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb21
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb18
-rw-r--r--actionpack/lib/action_dispatch/routing.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb153
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb10
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb121
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb111
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb9
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb9
-rw-r--r--actionpack/lib/action_pack/version.rb4
-rw-r--r--actionpack/lib/action_view.rb1
-rw-r--r--actionpack/lib/action_view/asset_paths.rb4
-rw-r--r--actionpack/lib/action_view/base.rb8
-rw-r--r--actionpack/lib/action_view/buffers.rb2
-rw-r--r--actionpack/lib/action_view/helpers/active_model_helper.rb5
-rw-r--r--actionpack/lib/action_view/helpers/asset_paths.rb7
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb4
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb3
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb39
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb39
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb70
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb7
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb19
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb8
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/rendering_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/sanitize_helper.rb1
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb15
-rw-r--r--actionpack/lib/action_view/locale/en.yml6
-rw-r--r--actionpack/lib/action_view/lookup_context.rb39
-rw-r--r--actionpack/lib/action_view/renderer/abstract_renderer.rb12
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb45
-rw-r--r--actionpack/lib/action_view/renderer/streaming_template_renderer.rb2
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb31
-rw-r--r--actionpack/lib/action_view/template.rb116
-rw-r--r--actionpack/lib/action_view/template/handlers/builder.rb11
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb26
-rw-r--r--actionpack/lib/action_view/template/resolver.rb1
-rw-r--r--actionpack/lib/action_view/test_case.rb7
-rw-r--r--actionpack/lib/sprockets/assets.rake2
-rw-r--r--actionpack/lib/sprockets/helpers/rails_helper.rb24
-rw-r--r--actionpack/lib/sprockets/railtie.rb2
104 files changed, 1426 insertions, 1037 deletions
diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb
index c2a6809f58..dd5f9a1942 100644
--- a/actionpack/lib/abstract_controller/asset_paths.rb
+++ b/actionpack/lib/abstract_controller/asset_paths.rb
@@ -4,7 +4,7 @@ module AbstractController
included do
config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir,
- :stylesheets_dir, :default_asset_host_protocol
+ :stylesheets_dir, :default_asset_host_protocol, :relative_url_root
end
end
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 14c984e41f..7004e607a1 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -177,7 +177,7 @@ module AbstractController
def prepend_#{filter}_filter(*names, &blk) # def prepend_before_filter(*names, &blk)
_insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after} # options[:if] = (Array.wrap(options[:if]) << "!halted") if false
- set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true))
+ set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
end # end
end # end
@@ -185,7 +185,7 @@ module AbstractController
# for details on the allowed parameters.
def skip_#{filter}_filter(*names, &blk) # def skip_before_filter(*names, &blk)
_insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :#{filter}, name, options)
+ skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :before, name, options)
end # end
end # end
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 10aa34c76b..6a6387632c 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -66,27 +66,40 @@ module AbstractController
# == Inheritance Examples
#
# class BankController < ActionController::Base
- # layout "bank_standard"
+ # # bank.html.erb exists
+ #
+ # class ExchangeController < BankController
+ # # exchange.html.erb exists
+ #
+ # class CurrencyController < BankController
#
# class InformationController < BankController
+ # layout "information"
#
- # class TellerController < BankController
+ # class TellerController < InformationController
# # teller.html.erb exists
#
- # class TillController < TellerController
+ # class EmployeeController < InformationController
+ # # employee.html.erb exists
+ # layout nil
#
# class VaultController < BankController
# layout :access_level_layout
#
- # class EmployeeController < BankController
- # layout nil
+ # class TillController < BankController
+ # layout false
#
- # In these examples:
- # * The InformationController uses the "bank_standard" layout, inherited from BankController.
- # * The TellerController follows convention and uses +app/views/layouts/teller.html.erb+.
- # * The TillController inherits the layout from TellerController and uses +teller.html.erb+ as well.
+ # In these examples, we have three implicit lookup scenrios:
+ # * The BankController uses the "bank" layout.
+ # * The ExchangeController uses the "exchange" layout.
+ # * The CurrencyController inherits the layout from BankController.
+ #
+ # However, when a layout is explicitly set, the explicitly set layout wins:
+ # * The InformationController uses the "information" layout, explicitly set.
+ # * The TellerController also uses the "information" layout, because the parent explicitly set it.
+ # * The EmployeeController uses the "employee" layout, because it set the layout to nil, resetting the parent configuration.
# * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method.
- # * The EmployeeController does not use a layout at all.
+ # * The TillController does not use a layout at all.
#
# == Types of layouts
#
@@ -126,6 +139,22 @@ module AbstractController
# If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>.
# Otherwise, it will be looked up relative to the template root.
#
+ # Setting the layout to nil forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists.
+ # Setting it to nil is useful to re-enable template lookup overriding a previous configuration set in the parent:
+ #
+ # class ApplicationController < ActionController::Base
+ # layout "application"
+ # end
+ #
+ # class PostsController < ApplicationController
+ # # Will use "application" layout
+ # end
+ #
+ # class CommentsController < ApplicationController
+ # # Will search for "comments" layout and fallback "application" layout
+ # layout nil
+ # end
+ #
# == Conditional layouts
#
# If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
@@ -166,13 +195,15 @@ module AbstractController
include Rendering
included do
- class_attribute :_layout_conditions
- remove_possible_method :_layout_conditions
- delegate :_layout_conditions, :to => :'self.class'
+ class_attribute :_layout, :_layout_conditions,
+ :instance_reader => false, :instance_writer => false
+ self._layout = nil
self._layout_conditions = {}
_write_layout_method
end
+ delegate :_layout_conditions, :to => "self.class"
+
module ClassMethods
def inherited(klass)
super
@@ -188,7 +219,7 @@ module AbstractController
#
# ==== Returns
# * <tt> Boolean</tt> - True if the action has a layout, false otherwise.
- def action_has_layout?
+ def conditional_layout?
return unless super
conditions = _layout_conditions
@@ -213,7 +244,7 @@ module AbstractController
# true:: raise an ArgumentError
#
# ==== Parameters
- # * <tt>String, Symbol, false</tt> - The layout to use.
+ # * <tt>layout</tt> - The layout to use.
#
# ==== Options (conditions)
# * :only - A list of actions to apply this layout to.
@@ -224,7 +255,7 @@ module AbstractController
conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
self._layout_conditions = conditions
- @_layout = layout || false # Converts nil to false
+ self._layout = layout
_write_layout_method
end
@@ -244,43 +275,46 @@ module AbstractController
def _write_layout_method
remove_possible_method(:_layout)
- case defined?(@_layout) ? @_layout : nil
- when String
- self.class_eval %{def _layout; #{@_layout.inspect} end}, __FILE__, __LINE__
- when Symbol
- self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
- def _layout
- #{@_layout}.tap do |layout|
+ prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
+ name_clause = if name
+ <<-RUBY
+ lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first || super
+ RUBY
+ end
+
+ layout_definition = case _layout
+ when String
+ _layout.inspect
+ when Symbol
+ <<-RUBY
+ #{_layout}.tap do |layout|
unless layout.is_a?(String) || !layout
- raise ArgumentError, "Your layout method :#{@_layout} returned \#{layout}. It " \
+ raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \
"should have returned a String, false, or nil"
end
end
- end
- ruby_eval
- when Proc
- define_method :_layout_from_proc, &@_layout
- self.class_eval %{def _layout; _layout_from_proc(self) end}, __FILE__, __LINE__
- when false
- self.class_eval %{def _layout; end}, __FILE__, __LINE__
- when true
- raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
- when nil
- if name
- _prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
-
- self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def _layout
- if template_exists?("#{_implied_layout_name}", #{_prefixes.inspect})
- "#{_implied_layout_name}"
- else
- super
- end
- end
RUBY
- end
+ when Proc
+ define_method :_layout_from_proc, &_layout
+ "_layout_from_proc(self)"
+ when false
+ nil
+ when true
+ raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
+ when nil
+ name_clause
end
- self.class_eval { private :_layout }
+
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def _layout
+ if conditional_layout?
+ #{layout_definition}
+ else
+ #{name_clause}
+ end
+ end
+ private :_layout
+ RUBY
end
end
@@ -289,8 +323,7 @@ module AbstractController
if _include_layout?(options)
layout = options.key?(:layout) ? options.delete(:layout) : :default
- value = _layout_for_option(layout)
- options[:layout] = (value =~ /\blayouts/ ? value : "layouts/#{value}") if value
+ options[:layout] = _layout_for_option(layout)
end
end
@@ -305,24 +338,25 @@ module AbstractController
@_action_has_layout
end
+ def conditional_layout?
+ true
+ end
+
private
# This will be overwritten by _write_layout_method
def _layout; end
- # Determine the layout for a given name and details, taking into account
- # the name type.
+ # Determine the layout for a given name, taking into account the name type.
#
# ==== Parameters
# * <tt>name</tt> - The name of the template
- # * <tt>details</tt> - A list of details to restrict
- # the lookup to. By default, layout lookup is limited to the
- # formats specified for the current request.
def _layout_for_option(name)
case name
- when String then name
- when true then _default_layout(true)
- when :default then _default_layout(false)
+ when String then _normalize_layout(name)
+ when Proc then name
+ when true then Proc.new { _default_layout(true) }
+ when :default then Proc.new { _default_layout(false) }
when false, nil then nil
else
raise ArgumentError,
@@ -330,31 +364,32 @@ module AbstractController
end
end
- # Returns the default layout for this controller and a given set of details.
+ def _normalize_layout(value)
+ value.is_a?(String) && value !~ /\blayouts/ ? "layouts/#{value}" : value
+ end
+
+ # Returns the default layout for this controller.
# Optionally raises an exception if the layout could not be found.
#
# ==== Parameters
- # * <tt>details</tt> - A list of details to restrict the search by. This
- # might include details like the format or locale of the template.
- # * <tt>require_layout</tt> - If this is true, raise an ArgumentError
- # with details about the fact that the exception could not be
- # found (defaults to false)
+ # * <tt>require_layout</tt> - If set to true and layout is not found,
+ # an ArgumentError exception is raised (defaults to false)
#
# ==== Returns
# * <tt>template</tt> - The template object for the default layout (or nil)
def _default_layout(require_layout = false)
begin
- layout_name = _layout if action_has_layout?
+ value = _layout if action_has_layout?
rescue NameError => e
raise e, "Could not render layout: #{e.message}"
end
- if require_layout && action_has_layout? && !layout_name
+ if require_layout && action_has_layout? && !value
raise ArgumentError,
"There was no default layout for #{self.class} in #{view_paths.inspect}"
end
- layout_name
+ _normalize_layout(value)
end
def _include_layout?(options)
diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb
index 0b196119f4..a4e31cd2e5 100644
--- a/actionpack/lib/abstract_controller/logger.rb
+++ b/actionpack/lib/abstract_controller/logger.rb
@@ -1,4 +1,3 @@
-require "active_support/core_ext/logger"
require "active_support/benchmarkable"
module AbstractController
@@ -7,7 +6,7 @@ module AbstractController
included do
config_accessor :logger
- extend ActiveSupport::Benchmarkable
+ include ActiveSupport::Benchmarkable
end
end
end
diff --git a/actionpack/lib/abstract_controller/railties/routes_helpers.rb b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
index dec1e9d6d9..6684f46f64 100644
--- a/actionpack/lib/abstract_controller/railties/routes_helpers.rb
+++ b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
@@ -5,8 +5,8 @@ module AbstractController
Module.new do
define_method(:inherited) do |klass|
super(klass)
- if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) }
- klass.send(:include, namespace._railtie.routes.url_helpers)
+ if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) }
+ klass.send(:include, namespace.railtie_routes_url_helpers)
else
klass.send(:include, routes.url_helpers)
end
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 41fdc11196..9019c07bca 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -35,7 +35,7 @@ module AbstractController
include AbstractController::ViewPaths
included do
- config_accessor :protected_instance_variables, :instance_reader => false
+ class_attribute :protected_instance_variables
self.protected_instance_variables = []
end
@@ -59,19 +59,8 @@ module AbstractController
attr_internal_writer :view_context_class
- # Explicitly define protected_instance_variables so it can be
- # inherited and overwritten by other modules if needed.
- def protected_instance_variables
- config.protected_instance_variables
- end
-
def view_context_class
- @_view_context_class || self.class.view_context_class
- end
-
- def initialize(*)
- @_view_context_class = nil
- super
+ @_view_context_class ||= self.class.view_context_class
end
# An instance of a view class. The default view class is ActionView::Base
diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb
index e8394447a7..96118b940f 100644
--- a/actionpack/lib/abstract_controller/view_paths.rb
+++ b/actionpack/lib/abstract_controller/view_paths.rb
@@ -10,7 +10,7 @@ module AbstractController
self._view_paths.freeze
end
- delegate :find_template, :template_exists?, :view_paths, :formats, :formats=,
+ delegate :template_exists?, :view_paths, :formats, :formats=,
:locale, :locale=, :to => :lookup_context
module ClassMethods
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index da93c988c4..98bfe72fef 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -50,7 +50,7 @@ module ActionController
#
# All request parameters, whether they come from a GET or POST request, or from the URL, 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.
+ # <tt>{ "category" => "All", "limit" => "5" }</tt> in params.
#
# It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
#
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 14137f2886..112573a38d 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -24,7 +24,6 @@ module ActionController #:nodoc:
#
# config.action_controller.cache_store = :memory_store
# config.action_controller.cache_store = :file_store, "/path/to/cache/directory"
- # config.action_controller.cache_store = :drb_store, "druby://localhost:9192"
# config.action_controller.cache_store = :mem_cache_store, "localhost"
# config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new("localhost:11211")
# config.action_controller.cache_store = MyOwnStore.new("parameter")
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index 0031d2701f..3b86a9a93a 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -116,9 +116,8 @@ module ActionController #:nodoc:
def expire_action(options = {})
return unless cache_configured?
- actions = options[:action]
- if actions.is_a?(Array)
- actions.each {|action| expire_action(options.merge(:action => action)) }
+ if options.is_a?(Hash) && options[:action].is_a?(Array)
+ options[:action].each {|action| expire_action(options.merge(:action => action)) }
else
expire_fragment(ActionCachePath.new(self, options, false).path)
end
@@ -175,7 +174,7 @@ module ActionController #:nodoc:
private
def normalize!(path)
path << 'index' if path[-1] == ?/
- path << ".#{extension}" if extension and !path.ends_with?(extension)
+ path << ".#{extension}" if extension and !path.split('?').first.ends_with?(".#{extension}")
URI.parser.unescape(path)
end
end
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
index 496390402b..159f718029 100644
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ b/actionpack/lib/action_controller/caching/pages.rb
@@ -16,7 +16,7 @@ module ActionController #:nodoc:
# caches_page :show, :new
# end
#
- # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>, which match the URLs used
+ # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>, which match the URLs used
# that would normally trigger dynamic page generation. Page caching works by configuring a web server to first check for the
# existence of files on disk, and to serve them directly when found, without passing the request through to Action Pack.
# This is much faster than handling the full dynamic request in the usual way.
@@ -38,23 +38,25 @@ module ActionController #:nodoc:
extend ActiveSupport::Concern
included do
- ##
- # :singleton-method:
# The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
# For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>Rails.root + "/public"</tt>). Changing
# this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
# web server to look in the new location for cached files.
- config_accessor :page_cache_directory
+ class_attribute :page_cache_directory
self.page_cache_directory ||= ''
- ##
- # :singleton-method:
# Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
# order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
# If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
# extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
- config_accessor :page_cache_extension
+ class_attribute :page_cache_extension
self.page_cache_extension ||= '.html'
+
+ # The compression used for gzip. If false (default), the page is not compressed.
+ # If can be a symbol showing the ZLib compression method, for example, :best_compression
+ # or :best_speed or an integer configuring the compression level.
+ class_attribute :page_cache_compression
+ self.page_cache_compression ||= false
end
module ClassMethods
@@ -66,24 +68,31 @@ module ActionController #:nodoc:
instrument_page_cache :expire_page, path do
File.delete(path) if File.exist?(path)
+ File.delete(path + '.gz') if File.exist?(path + '.gz')
end
end
# Manually cache the +content+ in the key determined by +path+. Example:
# cache_page "I'm the cached content", "/lists/show"
- def cache_page(content, path, extension = nil)
+ def cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION)
return unless perform_caching
path = page_cache_path(path, extension)
instrument_page_cache :write_page, path do
FileUtils.makedirs(File.dirname(path))
File.open(path, "wb+") { |f| f.write(content) }
+ if gzip
+ Zlib::GzipWriter.open(path + '.gz', gzip) { |f| f.write(content) }
+ end
end
end
- # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
+ # Caches the +actions+ using the page-caching approach that'll store
+ # the cache in a path within the page_cache_directory that
# matches the triggering url.
#
+ # You can also pass a :gzip option to override the class configuration one.
+ #
# Usage:
#
# # cache the index action
@@ -91,10 +100,28 @@ module ActionController #:nodoc:
#
# # cache the index action except for JSON requests
# caches_page :index, :if => Proc.new { |c| !c.request.format.json? }
+ #
+ # # don't gzip images
+ # caches_page :image, :gzip => false
def caches_page(*actions)
return unless perform_caching
options = actions.extract_options!
- after_filter({:only => actions}.merge(options)) { |c| c.cache_page }
+
+ gzip_level = options.fetch(:gzip, page_cache_compression)
+ gzip_level = case gzip_level
+ when Symbol
+ Zlib.const_get(gzip_level.to_s.upcase)
+ when Fixnum
+ gzip_level
+ when false
+ nil
+ else
+ Zlib::BEST_COMPRESSION
+ end
+
+ after_filter({:only => actions}.merge(options)) do |c|
+ c.cache_page(nil, nil, gzip_level)
+ end
end
private
@@ -122,7 +149,7 @@ module ActionController #:nodoc:
if options.is_a?(Hash)
if options[:action].is_a?(Array)
- options[:action].dup.each do |action|
+ options[:action].each do |action|
self.class.expire_page(url_for(options.merge(:only_path => true, :action => action)))
end
else
@@ -136,7 +163,7 @@ module ActionController #:nodoc:
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used.
# If no options are provided, the url of the current request being handled is used. Example:
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
- def cache_page(content = nil, options = nil)
+ def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION)
return unless self.class.perform_caching && caching_allowed?
path = case options
@@ -152,7 +179,7 @@ module ActionController #:nodoc:
extension = ".#{type_symbol}"
end
- self.class.cache_page(content || response.body, path, extension)
+ self.class.cache_page(content || response.body, path, extension, gzip)
end
end
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 35e29398e6..4c76f4c43b 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -20,15 +20,18 @@ module ActionController
status = payload[:status]
if status.nil? && payload[:exception].present?
- status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil
+ status = Rack::Utils.status_code(ActionDispatch::ExceptionWrapper.new({}, payload[:exception]).status_code)
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration
message << " (#{additions.join(" | ")})" unless additions.blank?
- message << "\n"
info(message)
end
+ def halted_callback(event)
+ info "Filter chain halted as #{event.payload[:filter]} rendered or redirected"
+ end
+
def send_file(event)
message = "Sent file %s"
message << " (%.1fms)"
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 0133b2ecbc..125dbf6bb5 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -182,7 +182,13 @@ module ActionController
end
def response_body=(val)
- body = val.nil? ? nil : (val.respond_to?(:each) ? val : [val])
+ body = if val.is_a?(String)
+ [val]
+ elsif val.nil? || val.respond_to?(:each)
+ val
+ else
+ [val]
+ end
super body
end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index a5e37172c9..1645400693 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -23,8 +23,27 @@ module ActionController
# This will render the show template if the request isn't sending a matching etag or
# If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match.
#
- def fresh_when(options)
- options.assert_valid_keys(:etag, :last_modified, :public)
+ # You can also just pass a record where last_modified will be set by calling updated_at and the etag by passing the object itself. Example:
+ #
+ # def show
+ # @article = Article.find(params[:id])
+ # fresh_when(@article)
+ # end
+ #
+ # When passing a record, you can still set whether the public header:
+ #
+ # def show
+ # @article = Article.find(params[:id])
+ # fresh_when(@article, :public => true)
+ # end
+ def fresh_when(record_or_options, additional_options = {})
+ if record_or_options.is_a? Hash
+ options = record_or_options
+ options.assert_valid_keys(:etag, :last_modified, :public)
+ else
+ record = record_or_options
+ options = { :etag => record, :last_modified => record.try(:updated_at) }.merge(additional_options)
+ end
response.etag = options[:etag] if options[:etag]
response.last_modified = options[:last_modified] if options[:last_modified]
@@ -55,8 +74,34 @@ module ActionController
# end
# end
# end
- def stale?(options)
- fresh_when(options)
+ #
+ # You can also just pass a record where last_modified will be set by calling updated_at and the etag by passing the object itself. Example:
+ #
+ # def show
+ # @article = Article.find(params[:id])
+ #
+ # if stale?(@article)
+ # @statistics = @article.really_expensive_call
+ # respond_to do |format|
+ # # all the supported formats
+ # end
+ # end
+ # end
+ #
+ # When passing a record, you can still set whether the public header:
+ #
+ # def show
+ # @article = Article.find(params[:id])
+ #
+ # if stale?(@article, :public => true)
+ # @statistics = @article.really_expensive_call
+ # respond_to do |format|
+ # # all the supported formats
+ # end
+ # end
+ # end
+ def stale?(record_or_options, additional_options = {})
+ fresh_when(record_or_options, additional_options)
!request.fresh?(response)
end
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 5e077dd7bd..30ddf6c16e 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/file/path'
require 'action_controller/metal/exceptions'
module ActionController #:nodoc:
@@ -34,7 +33,7 @@ module ActionController #:nodoc:
# If no content type is registered for the extension, default type 'application/octet-stream' will be used.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
- # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
+ # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
# the URL, which is necessary for i18n filenames on certain browsers
# (setting <tt>:filename</tt> overrides this option).
@@ -92,7 +91,7 @@ module ActionController #:nodoc:
# If no content type is registered for the extension, default type 'application/octet-stream' will be used.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
- # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
+ # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
#
# Generic data download:
#
@@ -115,7 +114,7 @@ module ActionController #:nodoc:
private
def send_file_headers!(options)
type_provided = options.has_key?(:type)
-
+
options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
[:type, :disposition].each do |arg|
raise ArgumentError, ":#{arg} option required" if options[arg].nil?
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index ed693c5967..0fd42f9d8a 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -24,12 +24,15 @@ module ActionController
# * <tt>only</tt> - The callback should be run only for this action
# * <tt>except<tt> - The callback should be run for all actions except this action
def force_ssl(options = {})
+ host = options.delete(:host)
before_filter(options) do
if !request.ssl? && !Rails.env.development?
- redirect_to :protocol => 'https://', :status => :moved_permanently
+ redirect_options = {:protocol => 'https://', :status => :moved_permanently}
+ redirect_options.merge!(:host => host) if host
+ redirect_to redirect_options
end
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 8abcad55a2..a618533d09 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -9,6 +9,8 @@ module ActionController
#
# head :created, :location => person_path(@person)
#
+ # head :created, :location => @person
+ #
# It can also be used to return exceptional conditions:
#
# return head(:method_not_allowed) unless request.post?
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index bd515bba82..50d7aac300 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -56,7 +56,7 @@ module ActionController
include AbstractController::Helpers
included do
- config_accessor :helpers_path, :include_all_helpers
+ class_attribute :helpers_path, :include_all_helpers
self.helpers_path ||= []
self.include_all_helpers = true
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 264806cd36..594ba7a7b8 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -145,7 +145,7 @@ module ActionController
end
def encode_credentials(user_name, password)
- "Basic #{ActiveSupport::Base64.encode64s("#{user_name}:#{password}")}"
+ "Basic #{ActiveSupport::Base64.strict_encode64("#{user_name}:#{password}")}"
end
def authentication_request(controller, realm)
@@ -192,12 +192,15 @@ module ActionController
return false unless password
method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
- uri = credentials[:uri][0,1] == '/' ? request.fullpath : request.url
+ uri = credentials[:uri][0,1] == '/' ? request.original_fullpath : request.original_url
- [true, false].any? do |password_is_ha1|
- expected = expected_response(method, uri, credentials, password, password_is_ha1)
- expected == credentials[:response]
- end
+ [true, false].any? do |trailing_question_mark|
+ [true, false].any? do |password_is_ha1|
+ _uri = trailing_question_mark ? uri + "?" : uri
+ expected = expected_response(method, _uri, credentials, password, password_is_ha1)
+ expected == credentials[:response]
+ end
+ end
end
end
@@ -286,7 +289,7 @@ module ActionController
t = time.to_i
hashed = [t, secret_key]
digest = ::Digest::MD5.hexdigest(hashed.join(":"))
- ActiveSupport::Base64.encode64("#{t}:#{digest}").gsub("\n", '')
+ ActiveSupport::Base64.strict_encode64("#{t}:#{digest}")
end
# Might want a shorter timeout depending on whether the request
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 777a0ab343..640ebf5f00 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -64,7 +64,12 @@ module ActionController
end
end
- protected
+ private
+
+ # A hook invoked everytime a before callback is halted.
+ def halted_callback_hook(filter)
+ ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
+ end
# A hook which allows you to clean up any time taken into account in
# views wrongly, like database querying time.
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index f10287afb4..ca383be76b 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -42,8 +42,8 @@ module ActionController #:nodoc:
def respond_to(*mimes)
options = mimes.extract_options!
- only_actions = Array(options.delete(:only))
- except_actions = Array(options.delete(:except))
+ only_actions = Array(options.delete(:only)).map(&:to_s)
+ except_actions = Array(options.delete(:except)).map(&:to_s)
new = mimes_for_respond_to.dup
mimes.each do |mime|
@@ -182,7 +182,7 @@ module ActionController #:nodoc:
#
# def index
# @people = Person.all
- # respond_with(@person)
+ # respond_with(@people)
# end
# end
#
@@ -245,7 +245,7 @@ module ActionController #:nodoc:
# current action.
#
def collect_mimes_from_class_level #:nodoc:
- action = action_name.to_sym
+ action = action_name.to_s
self.class.mimes_for_respond_to.keys.select do |mime|
config = self.class.mimes_for_respond_to[mime]
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index e0d8e1c992..5c28a8074f 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -43,6 +43,11 @@ module ActionController
# wrap_parameters :person, :include => [:username, :password]
# end
#
+ # On ActiveRecord models with no +:include+ or +:exclude+ option set,
+ # if attr_accessible is set on that model, it will only wrap the accessible
+ # parameters, else it will only wrap the parameters returned by the class
+ # method attribute_names.
+ #
# If you're going to pass the parameters to an +ActiveModel+ object (such as
# +User.new(params[:user])+), you might consider passing the model class to
# the method instead. The +ParamsWrapper+ will actually try to determine the
@@ -141,7 +146,7 @@ module ActionController
# try to find Foo::Bar::User, Foo::User and finally User.
def _default_wrap_model #:nodoc:
return nil if self.anonymous?
- model_name = self.name.sub(/Controller$/, '').singularize
+ model_name = self.name.sub(/Controller$/, '').classify
begin
if model_klass = model_name.safe_constantize
@@ -162,7 +167,9 @@ module ActionController
unless options[:include] || options[:exclude]
model ||= _default_wrap_model
- if model.respond_to?(:attribute_names) && model.attribute_names.present?
+ if model.respond_to?(:accessible_attributes) && model.accessible_attributes.present?
+ options[:include] = model.accessible_attributes.to_a
+ elsif model.respond_to?(:attribute_names) && model.attribute_names.present?
options[:include] = model.attribute_names
end
end
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index f2dfb3833b..b07742e0e1 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -18,7 +18,7 @@ module ActionController
#
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
- # * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) - Is passed straight through as the target for redirection.
+ # * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) or a protocol relative reference (like <tt>//</tt>) - Is passed straight through as the target for redirection.
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
# * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
# * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
@@ -54,8 +54,8 @@ module ActionController
# redirect_to post_url(@post), :status => 301, :flash => { :updated_post_id => @post.id }
# redirect_to { :action=>'atom' }, :alert => "Something serious happened"
#
- # When using <tt>redirect_to :back</tt>, if there is no referrer, RedirectBackError will be raised. You may specify some fallback
- # behavior for this case by rescuing RedirectBackError.
+ # When using <tt>redirect_to :back</tt>, if there is no referrer, ActionController::RedirectBackError will be raised. You may specify some fallback
+ # behavior for this case by rescuing ActionController::RedirectBackError.
def redirect_to(options = {}, response_status = {}) #:doc:
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
@@ -81,7 +81,8 @@ module ActionController
# The scheme name consist of a letter followed by any combination of
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
# characters; and is terminated by a colon (":").
- when %r{^\w[\w+.-]*:.*}
+ # The protocol relative scheme starts with a double slash "//"
+ when %r{^(\w[\w+.-]*:|//).*}
options
when String
request.protocol + request.host_with_port + options
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 0ad9dbeda9..6e9ce450ac 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/blank'
+require 'set'
module ActionController
# See <tt>Renderers.add</tt>
@@ -12,16 +13,13 @@ module ActionController
included do
class_attribute :_renderers
- self._renderers = {}.freeze
+ self._renderers = Set.new.freeze
end
module ClassMethods
def use_renderers(*args)
- new = _renderers.dup
- args.each do |key|
- new[key] = RENDERERS[key]
- end
- self._renderers = new.freeze
+ renderers = _renderers + args
+ self._renderers = renderers.freeze
end
alias use_renderer use_renderers
end
@@ -31,10 +29,10 @@ module ActionController
end
def _handle_render_options(options)
- _renderers.each do |name, value|
- if options.key?(name.to_sym)
+ _renderers.each do |name|
+ if options.key?(name)
_process_options(options)
- return send("_render_option_#{name}", options.delete(name.to_sym), options)
+ return send("_render_option_#{name}", options.delete(name), options)
end
end
nil
@@ -42,7 +40,7 @@ module ActionController
# Hash of available renderers, mapping a renderer name to its proc.
# Default keys are :json, :js, :xml.
- RENDERERS = {}
+ RENDERERS = Set.new
# Adds a new renderer to call within controller actions.
# A renderer is invoked by passing its name as an option to
@@ -79,7 +77,7 @@ module ActionController
# <tt>ActionController::MimeResponds#respond_with</tt>
def self.add(key, &block)
define_method("_render_option_#{key}", &block)
- RENDERERS[key] = block
+ RENDERERS << key.to_sym
end
module All
diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb
index eb037aa1b0..68cc9a9c9b 100644
--- a/actionpack/lib/action_controller/metal/rescue.rb
+++ b/actionpack/lib/action_controller/metal/rescue.rb
@@ -1,4 +1,7 @@
module ActionController #:nodoc:
+ # This module is responsible to provide `rescue_from` helpers
+ # to controllers and configure when detailed exceptions must be
+ # shown.
module Rescue
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
@@ -12,10 +15,20 @@ module ActionController #:nodoc:
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
+ # false, but someone may set it to `request.local?` so local
+ # requests in production still shows the detailed exception pages.
+ def show_detailed_exceptions?
+ false
+ end
+
private
def process_action(*args)
super
rescue Exception => exception
+ request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions?
rescue_with_handler(exception) || raise(exception)
end
end
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index 3794e277f6..9500a349cb 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -84,8 +84,8 @@ module ActionController #:nodoc:
#
# === Custom options
#
- # <code>respond_with</code> also allow you to pass options that are forwarded
- # to the underlying render call. Those options are only applied success
+ # <code>respond_with</code> also allows you to pass options that are forwarded
+ # to the underlying render call. Those options are only applied for success
# scenarios. For instance, you can do the following in the create method above:
#
# def create
@@ -95,7 +95,7 @@ module ActionController #:nodoc:
# respond_with(@project, @task, :status => 201)
# end
#
- # This will return status 201 if the task was saved with success. If not,
+ # This will return status 201 if the task was saved successfully. If not,
# it will simply ignore the given options and return status 422 and the
# resource errors. To customize the failure scenario, you can pass a
# a block to <code>respond_with</code>:
@@ -202,10 +202,8 @@ module ActionController #:nodoc:
display resource
elsif post?
display resource, :status => :created, :location => api_location
- elsif has_empty_resource_definition?
- display empty_resource, :status => :ok
else
- head :ok
+ head :no_content
end
end
@@ -224,7 +222,7 @@ module ActionController #:nodoc:
alias :navigation_location :resource_location
alias :api_location :resource_location
- # If a given response block was given, use it, otherwise call render on
+ # If a response block was given, use it, otherwise call render on
# controller.
#
def default_render
@@ -253,7 +251,7 @@ module ActionController #:nodoc:
end
def display_errors
- controller.render format => resource.errors, :status => :unprocessable_entity
+ controller.render format => resource_errors, :status => :unprocessable_entity
end
# Check whether the resource has errors.
@@ -269,22 +267,12 @@ module ActionController #:nodoc:
@action ||= ACTIONS_FOR_VERBS[request.request_method_symbol]
end
- # Check whether resource needs a specific definition of empty resource to be valid
- #
- def has_empty_resource_definition?
- respond_to?("empty_#{format}_resource")
+ def resource_errors
+ respond_to?("#{format}_resource_errors") ? send("#{format}_resource_errors") : resource.errors
end
- # Delegate to proper empty resource method
- #
- def empty_resource
- send("empty_#{format}_resource")
- end
-
- # Return a valid empty JSON resource
- #
- def empty_json_resource
- "{}"
+ def json_resource_errors
+ {:errors => resource.errors}
end
end
end
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 5fe5334458..0e46402962 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/file/path'
require 'rack/chunked'
module ActionController #:nodoc:
@@ -195,7 +194,7 @@ module ActionController #:nodoc:
# ==== Passenger
#
# To be described.
- #
+ #
module Streaming
extend ActiveSupport::Concern
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index f0c29825ba..7af256fd99 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -29,6 +29,7 @@ module ActionController
# make sure readers methods get compiled
options.asset_path ||= app.config.asset_path
options.asset_host ||= app.config.asset_host
+ options.relative_url_root ||= app.config.relative_url_root
ActiveSupport.on_load(:action_controller) do
include app.routes.mounted_helpers
diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb
index 699c44c62c..bbe63149ad 100644
--- a/actionpack/lib/action_controller/railties/paths.rb
+++ b/actionpack/lib/action_controller/railties/paths.rb
@@ -6,13 +6,14 @@ module ActionController
define_method(:inherited) do |klass|
super(klass)
- if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) }
- paths = namespace._railtie.paths["app/helpers"].existent
+ if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_helpers_paths) }
+ paths = namespace.railtie_helpers_paths
else
- paths = app.config.helpers_paths
+ paths = app.helpers_paths
end
klass.helpers_path = paths
+
if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
klass.helper :all
end
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 2036442cfe..9c38ff44d8 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -14,9 +14,9 @@ module ActionController
# <% end %> </div>
#
# # controller
- # def destroy
+ # def update
# post = Post.find(params[:id])
- # post.destroy
+ # post.update_attributes(params[:post])
#
# redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
# end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 6913c1ef4a..7470897659 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -86,6 +86,21 @@ module ActionController
end
end
when Hash
+ if expected_layout = options[:layout]
+ msg = build_message(message,
+ "expecting layout <?> but action rendered <?>",
+ expected_layout, @layouts.keys)
+
+ case expected_layout
+ when String
+ assert(@layouts.keys.include?(expected_layout), msg)
+ when Regexp
+ assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg)
+ when nil
+ assert(@layouts.empty?, msg)
+ end
+ end
+
if expected_partial = options[:partial]
if expected_locals = options[:locals]
actual_locals = @locals[expected_partial.to_s.sub(/^_/,'')]
@@ -98,19 +113,6 @@ module ActionController
"expecting ? to be rendered ? time(s) but rendered ? time(s)",
expected_partial, expected_count, actual_count)
assert(actual_count == expected_count.to_i, msg)
- elsif options.key?(:layout)
- msg = build_message(message,
- "expecting layout <?> but action rendered <?>",
- expected_layout, @layouts.keys)
-
- case layout = options[:layout]
- when String
- assert(@layouts.include?(expected_layout), msg)
- when Regexp
- assert(@layouts.any? {|l| l =~ layout }, msg)
- when nil
- assert(@layouts.empty?, msg)
- end
else
msg = build_message(message,
"expecting partial <?> but action rendered <?>",
@@ -296,11 +298,11 @@ module ActionController
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
# assert flash.empty? # makes sure that there's nothing in the flash
#
- # For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
+ # For historic reasons, the assigns hash uses string-based keys. So <tt>assigns[:person]</tt> won't work, but <tt>assigns["person"]</tt> will. To
# appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
- # So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
+ # So <tt>assigns(:person)</tt> will work just like <tt>assigns["person"]</tt>, but again, <tt>assigns[:person]</tt> will not work.
#
- # On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
+ # On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
#
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
# action call which can then be asserted against.
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
index 7fa3aead82..386820300a 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
@@ -4,7 +4,7 @@ require 'html/selector'
require 'html/sanitizer'
module HTML #:nodoc:
- # A top-level HTMl document. You give it a body of text, and it will parse that
+ # A top-level HTML document. You give it a body of text, and it will parse that
# text into a tree of nodes.
class Document #:nodoc:
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
index c252e01cf5..8ac8d34430 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
@@ -23,7 +23,7 @@ module HTML #:nodoc:
# Create a new Tokenizer for the given text.
def initialize(text)
- text.encode! if text.encoding_aware?
+ text.encode!
@scanner = StringScanner.new(text)
@position = 0
@line = 0
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 7f972fc281..070ffbdceb 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -29,6 +29,7 @@ $:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include
require 'active_support'
require 'active_support/dependencies/autoload'
+require 'active_support/core_ext/module/attribute_accessors'
require 'action_pack'
require 'active_model'
@@ -47,20 +48,22 @@ module ActionDispatch
end
autoload_under 'middleware' do
+ autoload :RequestId
autoload :BestStandardsSupport
autoload :Callbacks
autoload :Cookies
+ autoload :DebugExceptions
+ autoload :ExceptionWrapper
autoload :Flash
autoload :Head
autoload :ParamsParser
+ autoload :PublicExceptions
autoload :Reloader
autoload :RemoteIp
- autoload :Rescue
autoload :ShowExceptions
autoload :Static
end
- autoload :ClosedError, 'action_dispatch/middleware/closed_error'
autoload :MiddlewareStack, 'action_dispatch/middleware/stack'
autoload :Routing
@@ -82,8 +85,11 @@ module ActionDispatch
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 :Assertions
autoload :Integration
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index aaed0d750f..bea62b94d2 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -4,14 +4,18 @@ 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
+
def if_modified_since
- if since = env['HTTP_IF_MODIFIED_SINCE']
+ if since = env[HTTP_IF_MODIFIED_SINCE]
Time.rfc2822(since) rescue nil
end
end
def if_none_match
- env['HTTP_IF_NONE_MATCH']
+ env[HTTP_IF_NONE_MATCH]
end
def not_modified?(modified_at)
@@ -43,31 +47,35 @@ module ActionDispatch
alias :etag? :etag
def last_modified
- if last = headers['Last-Modified']
+ if last = headers[LAST_MODIFIED]
Time.httpdate(last)
end
end
def last_modified?
- headers.include?('Last-Modified')
+ headers.include?(LAST_MODIFIED)
end
def last_modified=(utc_time)
- headers['Last-Modified'] = utc_time.httpdate
+ headers[LAST_MODIFIED] = utc_time.httpdate
end
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
- @etag = self["ETag"] = %("#{Digest::MD5.hexdigest(key)}")
+ @etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}")
end
private
+ LAST_MODIFIED = "Last-Modified".freeze
+ ETAG = "ETag".freeze
+ CACHE_CONTROL = "Cache-Control".freeze
+
def prepare_cache_control!
@cache_control = {}
- @etag = self["ETag"]
+ @etag = self[ETAG]
- if cache_control = self["Cache-Control"]
+ if cache_control = self[CACHE_CONTROL]
cache_control.split(/,\s*/).each do |segment|
first, last = segment.split("=")
@cache_control[first.to_sym] = last || true
@@ -81,28 +89,32 @@ module ActionDispatch
end
end
- DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
+ DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
+ NO_CACHE = "no-cache".freeze
+ PUBLIC = "public".freeze
+ PRIVATE = "private".freeze
+ MUST_REVALIDATE = "must-revalidate".freeze
def set_conditional_cache_control!
- return if self["Cache-Control"].present?
+ return if self[CACHE_CONTROL].present?
control = @cache_control
if control.empty?
- headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
+ headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL
elsif control[:no_cache]
- headers["Cache-Control"] = "no-cache"
+ headers[CACHE_CONTROL] = NO_CACHE
else
extras = control[:extras]
max_age = control[:max_age]
options = []
options << "max-age=#{max_age.to_i}" if max_age
- options << (control[:public] ? "public" : "private")
- options << "must-revalidate" if control[:must_revalidate]
+ options << (control[:public] ? PUBLIC : PRIVATE)
+ options << MUST_REVALIDATE if control[:must_revalidate]
options.concat(extras) if extras
- headers["Cache-Control"] = options.join(", ")
+ headers[CACHE_CONTROL] = options.join(", ")
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 505d5560b1..040b51e040 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -1,5 +1,3 @@
-require 'active_support/memoizable'
-
module ActionDispatch
module Http
class Headers < ::Hash
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 8a9f9c4315..25affb9f50 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -103,7 +103,7 @@ module Mime
SET << Mime.const_get(symbol.to_s.upcase)
([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup
- ([symbol.to_s] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext] = SET.last }
+ ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last }
end
def parse(accept_header)
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
index 1480e8f77c..490b46c990 100644
--- a/actionpack/lib/action_dispatch/http/parameter_filter.rb
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -20,6 +20,8 @@ module ActionDispatch
@filters.present?
end
+ FILTERED = '[FILTERED]'.freeze
+
def compiled_filter
@compiled_filter ||= begin
regexps, blocks = compile_filter
@@ -29,7 +31,7 @@ module ActionDispatch
original_params.each do |key, value|
if regexps.find { |r| key =~ r }
- value = '[FILTERED]'
+ value = FILTERED
elsif value.is_a?(Hash)
value = filter(value)
elsif value.is_a?(Array)
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index ef5d207b26..d9b63faf5e 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -41,8 +41,6 @@ module ActionDispatch
# you'll get a weird error down the road, but our form handling
# should really prevent that from happening
def encode_params(params)
- return params unless "ruby".encoding_aware?
-
if params.is_a?(String)
return params.force_encoding("UTF-8").encode!
elsif !params.is_a?(Hash)
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 37d0a3e0b8..0a0ebe7fad 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -35,14 +35,6 @@ module ActionDispatch
METHOD
end
- def self.new(env)
- if request = env["action_dispatch.request"] && request.instance_of?(self)
- return request
- end
-
- super
- end
-
def key?(key)
@env.key?(key)
end
@@ -94,31 +86,31 @@ module ActionDispatch
end
# Is this a GET (or HEAD) request?
- # Equivalent to <tt>request.request_method == :get</tt>.
+ # Equivalent to <tt>request.request_method_symbol == :get</tt>.
def get?
HTTP_METHOD_LOOKUP[request_method] == :get
end
# Is this a POST request?
- # Equivalent to <tt>request.request_method == :post</tt>.
+ # Equivalent to <tt>request.request_method_symbol == :post</tt>.
def post?
HTTP_METHOD_LOOKUP[request_method] == :post
end
# Is this a PUT request?
- # Equivalent to <tt>request.request_method == :put</tt>.
+ # Equivalent to <tt>request.request_method_symbol == :put</tt>.
def put?
HTTP_METHOD_LOOKUP[request_method] == :put
end
# Is this a DELETE request?
- # Equivalent to <tt>request.request_method == :delete</tt>.
+ # Equivalent to <tt>request.request_method_symbol == :delete</tt>.
def delete?
HTTP_METHOD_LOOKUP[request_method] == :delete
end
# Is this a HEAD request?
- # Equivalent to <tt>request.method == :head</tt>.
+ # Equivalent to <tt>request.method_symbol == :head</tt>.
def head?
HTTP_METHOD_LOOKUP[method] == :head
end
@@ -130,10 +122,18 @@ module ActionDispatch
Http::Headers.new(@env)
end
+ def original_fullpath
+ @original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath)
+ end
+
def fullpath
@fullpath ||= super
end
+ def original_url
+ base_url + original_fullpath
+ end
+
def media_type
content_mime_type.to_s
end
@@ -155,28 +155,21 @@ module ActionDispatch
@ip ||= super
end
- # Which IP addresses are "trusted proxies" that can be stripped from
- # the right-hand-side of X-Forwarded-For.
- #
- # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces.
- TRUSTED_PROXIES = %r{
- ^127\.0\.0\.1$ | # localhost
- ^(10 | # private IP 10.x.x.x
- 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
- 192\.168 # private IP 192.168.x.x
- )\.
- }x
-
- # Determines originating IP address. REMOTE_ADDR is the standard
- # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
- # HTTP_X_FORWARDED_FOR are set by proxies so check for these if
- # REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
- # delimited list in the case of multiple chained proxies; the last
- # address which is not trusted is the originating IP.
+ # Originating IP address, usually set by the RemoteIp middleware.
def remote_ip
@remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
end
+ # Returns the unique request id, which is based off either the X-Request-Id header that can
+ # be generated by a firewall, load balancer, or web server or by the RequestId middleware
+ # (which sets the action_dispatch.request_id environment variable).
+ #
+ # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
+ # This relies on the rack variable set by the ActionDispatch::RequestId middleware.
+ def uuid
+ @uuid ||= env["action_dispatch.request_id"]
+ end
+
# Returns the lowercase name of the HTTP server software.
def server_software
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
@@ -196,7 +189,7 @@ module ActionDispatch
# variable is already set, wrap it in a StringIO.
def body
if raw_post = @env['RAW_POST_DATA']
- raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
+ raw_post.force_encoding(Encoding::BINARY)
StringIO.new(raw_post)
else
@env['rack.input']
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index f1e85559a3..84732085f0 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -53,8 +53,10 @@ module ActionDispatch # :nodoc:
# information.
attr_accessor :charset, :content_type
- CONTENT_TYPE = "Content-Type"
-
+ CONTENT_TYPE = "Content-Type".freeze
+ SET_COOKIE = "Set-Cookie".freeze
+ LOCATION = "Location".freeze
+
cattr_accessor(:default_charset) { "utf-8" }
include Rack::Response::Helpers
@@ -66,10 +68,10 @@ module ActionDispatch # :nodoc:
@sending_file = false
@blank = false
- if content_type = self["Content-Type"]
+ if content_type = self[CONTENT_TYPE]
type, charset = content_type.split(/;\s*charset=/)
@content_type = Mime::Type.lookup(type)
- @charset = charset || "UTF-8"
+ @charset = charset || self.class.default_charset
end
prepare_cache_control!
@@ -109,9 +111,9 @@ module ActionDispatch # :nodoc:
end
def body
- str = ''
- each { |part| str << part.to_s }
- str
+ strings = []
+ each { |part| strings << part.to_s }
+ strings.join
end
EMPTY = " "
@@ -119,14 +121,7 @@ module ActionDispatch # :nodoc:
def body=(body)
@blank = true if body == EMPTY
- # Explicitly check for strings. This is *wrong* theoretically
- # but if we don't check this, the performance on string bodies
- # is bad on Ruby 1.8 (because strings responds to each then).
- @body = if body.respond_to?(:to_str) || !body.respond_to?(:each)
- [body]
- else
- body
- end
+ @body = body.respond_to?(:each) ? body : [body]
end
def body_parts
@@ -142,12 +137,12 @@ module ActionDispatch # :nodoc:
end
def location
- headers['Location']
+ headers[LOCATION]
end
alias_method :redirect_url, :location
def location=(url)
- headers['Location'] = url
+ headers[LOCATION] = url
end
def close
@@ -158,10 +153,10 @@ module ActionDispatch # :nodoc:
assign_default_content_type_and_charset!
handle_conditional_get!
- @header["Set-Cookie"] = @header["Set-Cookie"].join("\n") if @header["Set-Cookie"].respond_to?(:join)
+ @header[SET_COOKIE] = @header[SET_COOKIE].join("\n") if @header[SET_COOKIE].respond_to?(:join)
if [204, 304].include?(@status)
- @header.delete "Content-Type"
+ @header.delete CONTENT_TYPE
[@status, @header, []]
else
[@status, @header, self]
@@ -175,7 +170,7 @@ module ActionDispatch # :nodoc:
# assert_equal 'AuthorOfNewPage', r.cookies['author']
def cookies
cookies = {}
- if header = self["Set-Cookie"]
+ if header = self[SET_COOKIE]
header = header.split("\n") if header.respond_to?(:to_str)
header.each do |cookie|
if pair = cookie.split(';').first
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 94fa747a79..5ab99d1061 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -22,8 +22,8 @@ module ActionDispatch
private
def encode_filename(filename)
- # Encode the filename in the utf8 encoding, unless it is nil or we're in 1.8
- if "ruby".encoding_aware? && filename
+ # Encode the filename in the utf8 encoding, unless it is nil
+ if filename
filename.force_encoding("UTF-8").encode!
else
filename
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 170c68f3e0..64459836b5 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -45,7 +45,7 @@ module ActionDispatch
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
rewritten_url << "?#{params.to_query}" unless params.empty?
- rewritten_url << "##{Journey::Router::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor]
+ rewritten_url << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
rewritten_url
end
@@ -64,13 +64,13 @@ module ActionDispatch
end
def host_or_subdomain_and_domain(options)
- return options[:host] if options[:subdomain].nil? && options[:domain].nil?
+ return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?)
tld_length = options[:tld_length] || @@tld_length
host = ""
unless options[:subdomain] == false
- host << (options[:subdomain] || extract_subdomain(options[:host], tld_length))
+ host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)).to_param
host << "."
end
host << (options[:domain] || extract_domain(options[:host], tld_length))
diff --git a/actionpack/lib/action_dispatch/middleware/closed_error.rb b/actionpack/lib/action_dispatch/middleware/closed_error.rb
deleted file mode 100644
index 0a4db47f4b..0000000000
--- a/actionpack/lib/action_dispatch/middleware/closed_error.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module ActionDispatch
- class ClosedError < StandardError #:nodoc:
- def initialize(kind)
- super "Cannot modify #{kind} because it was closed. This means it was already streamed back to the client or converted to HTTP headers."
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 8c4615c0c1..39ff58a447 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -121,10 +121,6 @@ module ActionDispatch
@cookies = {}
end
- attr_reader :closed
- alias :closed? :closed
- def close!; @closed = true end
-
def each(&block)
@cookies.each(&block)
end
@@ -165,7 +161,6 @@ module ActionDispatch
# Sets the cookie named +name+. The second argument may be the very cookie
# value, or a hash of options as documented above.
def []=(key, options)
- raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
value = options[:value]
@@ -174,7 +169,7 @@ module ActionDispatch
options = { :value => value }
end
- value = @cookies[key.to_s] = value
+ @cookies[key.to_s] = value
handle_options(options)
@@ -243,10 +238,13 @@ module ActionDispatch
@delete_cookies.clear
end
+ mattr_accessor :always_write_cookie
+ self.always_write_cookie = false
+
private
def write_cookie?(cookie)
- @secure || !cookie[:secure] || defined?(Rails.env) && Rails.env.development?
+ @secure || !cookie[:secure] || always_write_cookie
end
end
@@ -256,7 +254,6 @@ module ActionDispatch
end
def []=(key, options)
- raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
else
@@ -295,7 +292,6 @@ module ActionDispatch
end
def []=(key, options)
- raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
options[:value] = @verifier.generate(options[:value])
@@ -349,9 +345,6 @@ module ActionDispatch
end
[status, headers, body]
- ensure
- cookie_jar = ActionDispatch::Request.new(env).cookie_jar unless cookie_jar
- cookie_jar.close!
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
new file mode 100644
index 0000000000..b903f98761
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -0,0 +1,82 @@
+require 'action_dispatch/http/request'
+require 'action_dispatch/middleware/exception_wrapper'
+
+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.join(File.dirname(__FILE__), 'templates')
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ begin
+ response = @app.call(env)
+
+ if response[1]['X-Cascade'] == 'pass'
+ body = response[2]
+ body.close if body.respond_to?(:close)
+ raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
+ end
+ rescue Exception => exception
+ raise exception if env['action_dispatch.show_exceptions'] == false
+ end
+
+ exception ? render_exception(env, exception) : response
+ end
+
+ private
+
+ def render_exception(env, exception)
+ wrapper = ExceptionWrapper.new(env, exception)
+ log_error(env, wrapper)
+
+ if env['action_dispatch.show_detailed_exceptions']
+ template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
+ :request => Request.new(env),
+ :exception => wrapper.exception,
+ :application_trace => wrapper.application_trace,
+ :framework_trace => wrapper.framework_trace,
+ :full_trace => wrapper.full_trace
+ )
+
+ file = "rescues/#{wrapper.rescue_template}"
+ body = template.render(:template => file, :layout => 'rescues/layout')
+ render(wrapper.status_code, body)
+ else
+ raise exception
+ end
+ end
+
+ def render(status, body)
+ [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ end
+
+ def log_error(env, wrapper)
+ logger = logger(env)
+ return unless logger
+
+ exception = wrapper.exception
+
+ trace = wrapper.application_trace
+ trace = wrapper.framework_trace if trace.empty?
+
+ ActiveSupport::Deprecation.silence do
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << trace.join("\n ")
+ logger.fatal("#{message}\n\n")
+ end
+ end
+
+ def logger(env)
+ env['action_dispatch.logger'] || stderr_logger
+ end
+
+ def stderr_logger
+ @stderr_logger ||= ActiveSupport::Logger.new($stderr)
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
new file mode 100644
index 0000000000..c0532c80c4
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -0,0 +1,78 @@
+require 'action_controller/metal/exceptions'
+require 'active_support/core_ext/exception'
+
+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::NotImplemented' => :not_implemented,
+ 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
+ )
+
+ cattr_accessor :rescue_templates
+ @@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'
+ )
+
+ attr_reader :env, :exception
+
+ def initialize(env, exception)
+ @env = env
+ @exception = original_exception(exception)
+ end
+
+ def rescue_template
+ @@rescue_templates[@exception.class.name]
+ end
+
+ def status_code
+ Rack::Utils.status_code(@@rescue_responses[@exception.class.name])
+ end
+
+ def application_trace
+ clean_backtrace(:silent)
+ end
+
+ def framework_trace
+ clean_backtrace(:noise)
+ end
+
+ def full_trace
+ clean_backtrace(:all)
+ end
+
+ private
+
+ def original_exception(exception)
+ if registered_original_exception?(exception)
+ exception.original_exception
+ else
+ exception
+ end
+ end
+
+ def registered_original_exception?(exception)
+ exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
+ end
+
+ def clean_backtrace(*args)
+ if backtrace_cleaner
+ backtrace_cleaner.clean(@exception.backtrace, *args)
+ else
+ @exception.backtrace
+ end
+ end
+
+ def backtrace_cleaner
+ @backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 2adbce031b..cff0877030 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -70,11 +70,15 @@ module ActionDispatch
end
end
+ # Implementation detail: please do not change the signature of the
+ # FlashHash class. Doing that will likely affect all Rails apps in
+ # production as the FlashHash currently stored in their sessions will
+ # become invalid.
class FlashHash
include Enumerable
def initialize #:nodoc:
- @used = Set.new
+ @discard = Set.new
@closed = false
@flashes = {}
@now = nil
@@ -89,8 +93,7 @@ module ActionDispatch
end
def []=(k, v) #:nodoc:
- raise ClosedError, :flash if closed?
- keep(k)
+ @discard.delete k
@flashes[k] = v
end
@@ -99,7 +102,7 @@ module ActionDispatch
end
def update(h) #:nodoc:
- h.keys.each { |k| keep(k) }
+ @discard.subtract h.keys
@flashes.update h
self
end
@@ -113,6 +116,7 @@ module ActionDispatch
end
def delete(key)
+ @discard.delete key
@flashes.delete key
self
end
@@ -126,6 +130,7 @@ module ActionDispatch
end
def clear
+ @discard.clear
@flashes.clear
end
@@ -136,7 +141,7 @@ module ActionDispatch
alias :merge! :update
def replace(h) #:nodoc:
- @used = Set.new
+ @discard.clear
@flashes.replace h
self
end
@@ -155,16 +160,13 @@ module ActionDispatch
@now ||= FlashNow.new(self)
end
- attr_reader :closed
- alias :closed? :closed
- def close!; @closed = true; end
-
# Keeps either the entire current flash or a specific flash entry available for the next action:
#
# flash.keep # keeps the entire flash
# flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
def keep(k = nil)
- use(k, false)
+ @discard.subtract Array(k || keys)
+ k ? self[k] : self
end
# Marks the entire flash or a single flash entry to be discarded by the end of the current action:
@@ -172,24 +174,16 @@ module ActionDispatch
# flash.discard # discard the entire flash at the end of the current action
# flash.discard(:warning) # discard only the "warning" entry at the end of the current action
def discard(k = nil)
- use(k)
+ @discard.merge Array(k || keys)
+ k ? self[k] : self
end
# Mark for removal entries that were kept, and delete unkept ones.
#
# This method is called automatically by filters, so you generally don't need to care about it.
def sweep #:nodoc:
- keys.each do |k|
- unless @used.include?(k)
- @used << k
- else
- delete(k)
- @used.delete(k)
- end
- end
-
- # clean up after keys that could have been left over by calling reject! or shift on the flash
- (@used - keys).each{ |k| @used.delete(k) }
+ @discard.each { |k| @flashes.delete k }
+ @discard.replace @flashes.keys
end
# Convenience accessor for flash[:alert]
@@ -213,22 +207,9 @@ module ActionDispatch
end
protected
-
- def now_is_loaded?
- !!@now
- end
-
- # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
- # use() # marks the entire flash as used
- # use('msg') # marks the "msg" entry as used
- # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
- # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
- # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
- # if no key is passed.
- def use(key = nil, used = true)
- Array(key || keys).each { |k| used ? @used << k : @used.delete(k) }
- return key ? self[key] : self
- end
+ def now_is_loaded?
+ @now
+ end
end
def initialize(app)
@@ -254,7 +235,6 @@ module ActionDispatch
end
env[KEY] = new_hash
- new_hash.close!
end
if session.key?('flash') && session['flash'].empty?
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index d4208ca96e..1cb803ffb9 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -52,14 +52,9 @@ module ActionDispatch
false
end
rescue Exception => e # YAML, XML or Ruby code block errors
- logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
+ logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
- raise
- { "body" => request.raw_post,
- "content_type" => request.content_mime_type,
- "content_length" => request.content_length,
- "exception" => "#{e.message} (#{e.class})",
- "backtrace" => e.backtrace }
+ raise e
end
def content_type_from_legacy_post_data_format_header(env)
@@ -73,8 +68,8 @@ module ActionDispatch
nil
end
- def logger
- defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
+ def logger(env)
+ env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
new file mode 100644
index 0000000000..85b8d178bf
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -0,0 +1,30 @@
+module ActionDispatch
+ # A simple Rack application that renders exceptions in the given public path.
+ class PublicExceptions
+ attr_accessor :public_path
+
+ def initialize(public_path)
+ @public_path = public_path
+ end
+
+ def call(env)
+ status = env["PATH_INFO"][1..-1]
+ locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
+ path = "#{public_path}/#{status}.html"
+
+ if locale_path && File.exist?(locale_path)
+ render(status, File.read(locale_path))
+ elsif File.exist?(path)
+ render(status, File.read(path))
+ else
+ [404, { "X-Cascade" => "pass" }, []]
+ end
+ end
+
+ private
+
+ def render(status, body)
+ [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index 29289a76b4..4f48f1c974 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -43,34 +43,58 @@ module ActionDispatch
# Execute all prepare callbacks.
def self.prepare!
- new(nil).run_callbacks :prepare
+ new(nil).prepare!
end
# Execute all cleanup callbacks.
def self.cleanup!
- new(nil).run_callbacks :cleanup
+ new(nil).cleanup!
end
- def initialize(app)
+ def initialize(app, condition=nil)
@app = app
- end
-
- module CleanupOnClose
- def close
- super if defined?(super)
- ensure
- ActionDispatch::Reloader.cleanup!
- end
+ @condition = condition || lambda { true }
+ @validated = true
end
def call(env)
- run_callbacks :prepare
+ @validated = @condition.call
+ prepare!
response = @app.call(env)
- response[2].extend(CleanupOnClose)
+ response[2].extend(module_hook)
response
rescue Exception
- run_callbacks :cleanup
+ cleanup!
raise
end
+
+ def prepare! #:nodoc:
+ run_callbacks :prepare if validated?
+ end
+
+ def cleanup! #:nodoc:
+ run_callbacks :cleanup if validated?
+ ensure
+ @validated = true
+ end
+
+ private
+
+ def validated? #:nodoc:
+ @validated
+ end
+
+ def module_hook #:nodoc:
+ middleware = self
+ Module.new do
+ define_method :close do
+ begin
+ super() if defined?(super)
+ ensure
+ middleware.cleanup!
+ end
+ end
+ end
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index c7d710b98e..66ece60860 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -2,50 +2,80 @@ module ActionDispatch
class RemoteIp
class IpSpoofAttackError < StandardError ; end
- class RemoteIpGetter
- def initialize(env, check_ip_spoofing, trusted_proxies)
- @env = env
- @check_ip_spoofing = check_ip_spoofing
- @trusted_proxies = trusted_proxies
+ # IP addresses that are "trusted proxies" that can be stripped from
+ # the comma-delimited list in the X-Forwarded-For header. See also:
+ # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
+ TRUSTED_PROXIES = %r{
+ ^127\.0\.0\.1$ | # localhost
+ ^(10 | # private IP 10.x.x.x
+ 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
+ 192\.168 # private IP 192.168.x.x
+ )\.
+ }x
+
+ attr_reader :check_ip, :proxies
+
+ def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
+ @app = app
+ @check_ip = check_ip_spoofing
+ if custom_proxies
+ custom_regexp = Regexp.new(custom_proxies)
+ @proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp)
+ else
+ @proxies = TRUSTED_PROXIES
end
+ end
- def remote_addrs
- @remote_addrs ||= begin
- list = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : []
- list.reject { |addr| addr =~ @trusted_proxies }
- end
+ def call(env)
+ env["action_dispatch.remote_ip"] = GetIp.new(env, self)
+ @app.call(env)
+ end
+
+ class GetIp
+ def initialize(env, middleware)
+ @env = env
+ @middleware = middleware
+ @calculated_ip = false
end
- def to_s
- return remote_addrs.first if remote_addrs.any?
-
- forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : []
-
- if client_ip = @env['HTTP_CLIENT_IP']
- if @check_ip_spoofing && !forwarded_ips.include?(client_ip)
- # We don't know which came from the proxy, and which from the user
- raise IpSpoofAttackError, "IP spoofing attack?!" \
- "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
- "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
- end
- return client_ip
+ # Determines originating IP address. REMOTE_ADDR is the standard
+ # but will be wrong if the user is behind a proxy. Proxies will set
+ # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those.
+ # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of
+ # multiple chained proxies. The last address which is not a known proxy
+ # will be the originating IP.
+ def calculate_ip
+ client_ip = @env['HTTP_CLIENT_IP']
+ forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR')
+ remote_addrs = ips_from('REMOTE_ADDR')
+
+ check_ip = client_ip && @middleware.check_ip
+ if check_ip && !forwarded_ips.include?(client_ip)
+ # We don't know which came from the proxy, and which from the user
+ raise IpSpoofAttackError, "IP spoofing attack?!" \
+ "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
+ "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
end
- return forwarded_ips.reject { |ip| ip =~ @trusted_proxies }.last || @env["REMOTE_ADDR"]
+ not_proxy = client_ip || forwarded_ips.last || remote_addrs.first
+
+ # Return first REMOTE_ADDR if there are no other options
+ not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first
end
- end
- def initialize(app, check_ip_spoofing = true, trusted_proxies = nil)
- @app = app
- @check_ip_spoofing = check_ip_spoofing
- regex = '(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)'
- regex << "|(#{trusted_proxies})" if trusted_proxies
- @trusted_proxies = Regexp.new(regex, "i")
- end
+ def to_s
+ return @ip if @calculated_ip
+ @calculated_ip = true
+ @ip = calculate_ip
+ end
- def call(env)
- env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies)
- @app.call(env)
+ protected
+
+ def ips_from(header, allow_proxies = false)
+ ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
+ allow_proxies ? ips : ips.reject{|ip| ip =~ @middleware.proxies }
+ end
end
+
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
new file mode 100644
index 0000000000..d5a0b80fd5
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -0,0 +1,39 @@
+require 'securerandom'
+require 'active_support/core_ext/string/access'
+require 'active_support/core_ext/object/blank'
+
+module ActionDispatch
+ # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
+ # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
+ #
+ # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated
+ # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
+ # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
+ #
+ # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
+ # from multiple pieces of the stack.
+ class RequestId
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
+ status, headers, body = @app.call(env)
+
+ headers["X-Request-Id"] = env["action_dispatch.request_id"]
+ [ status, headers, body ]
+ end
+
+ private
+ def external_request_id(env)
+ if request_id = env["HTTP_X_REQUEST_ID"].presence
+ request_id.gsub(/[^\w\-]/, "").first(255)
+ end
+ end
+
+ def internal_request_id
+ SecureRandom.uuid
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/rescue.rb b/actionpack/lib/action_dispatch/middleware/rescue.rb
deleted file mode 100644
index aee672112c..0000000000
--- a/actionpack/lib/action_dispatch/middleware/rescue.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module ActionDispatch
- class Rescue
- def initialize(app, rescuers = {}, &block)
- @app, @rescuers = app, {}
- rescuers.each { |exception, rescuer| rescue_from(exception, rescuer) }
- instance_eval(&block) if block_given?
- end
-
- def call(env)
- @app.call(env)
- rescue Exception => exception
- if rescuer = @rescuers[exception.class.name]
- env['action_dispatch.rescue.exception'] = exception
- rescuer.call(env)
- else
- raise exception
- end
- end
-
- protected
- def rescue_from(exception, rescuer)
- exception = exception.class.name if exception.is_a?(Exception)
- @rescuers[exception.to_s] = rescuer
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 6bcf099d2c..6a8e690d18 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -30,7 +30,7 @@ module ActionDispatch
def generate_sid
sid = SecureRandom.hex(16)
- sid.encode!('UTF-8') if sid.respond_to?(:encode!)
+ sid.encode!('UTF-8')
sid
end
@@ -74,10 +74,6 @@ module ActionDispatch
class AbstractStore < Rack::Session::Abstract::ID
include Compatibility
include StaleSessionCheck
-
- def destroy_session(env, sid, options)
- raise '#destroy_session needs to be implemented.'
- 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
new file mode 100644
index 0000000000..d3b6fd12fa
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -0,0 +1,50 @@
+require 'action_dispatch/middleware/session/abstract_store'
+require 'rack/session/memcache'
+
+module ActionDispatch
+ module Session
+ # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
+ # if you don't store critical data in your sessions and you don't need them to live for extended periods
+ # of time.
+ class CacheStore < AbstractStore
+ # Create a new store. The cache to use can be passed in the <tt>:cache</tt> option. If it is
+ # not specified, <tt>Rails.cache</tt> will be used.
+ def initialize(app, options = {})
+ @cache = options[:cache] || Rails.cache
+ options[:expire_after] ||= @cache.options[:expires_in]
+ super
+ end
+
+ # Get a session from the cache.
+ def get_session(env, sid)
+ sid ||= generate_sid
+ session = @cache.read(cache_key(sid))
+ session ||= {}
+ [sid, session]
+ end
+
+ # Set a session in the cache.
+ def set_session(env, sid, session, options)
+ key = cache_key(sid)
+ if session
+ @cache.write(key, session, :expires_in => options[:expire_after])
+ else
+ @cache.delete(key)
+ end
+ sid
+ end
+
+ # Remove a session from the cache.
+ def destroy_session(env, sid, options)
+ @cache.delete(cache_key(sid))
+ generate_sid
+ end
+
+ private
+ # Turn the session id into a cache key.
+ def cache_key(sid)
+ "_session_id:#{sid}"
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 2fa68c64c5..3d06214bf1 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -1,172 +1,63 @@
-require 'active_support/core_ext/exception'
-require 'action_controller/metal/exceptions'
-require 'active_support/notifications'
require 'action_dispatch/http/request'
+require 'action_dispatch/middleware/exception_wrapper'
+require 'active_support/deprecation'
module ActionDispatch
- # This middleware rescues any exception returned by the application and renders
- # nice exception pages if it's being rescued locally.
+ # This middleware rescues any exception returned by the application
+ # and calls an exceptions app that will wrap it in a format for the end user.
+ #
+ # The exceptions app should be passed as parameter on initialization
+ # of ShowExceptions. Everytime there is an exception, ShowExceptions will
+ # store the exception in env["action_dispatch.exception"], rewrite the
+ # PATH_INFO to the exception status code and call the rack app.
+ #
+ # If the application returns a "X-Cascade" pass response, this middleware
+ # will send an empty response as result with the correct status code.
+ # If any exception happens inside the exceptions app, this middleware
+ # catches the exceptions and returns a FAILSAFE_RESPONSE.
class ShowExceptions
- RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
-
- cattr_accessor :rescue_responses
- @@rescue_responses = Hash.new(:internal_server_error)
- @@rescue_responses.update({
- 'ActionController::RoutingError' => :not_found,
- 'AbstractController::ActionNotFound' => :not_found,
- 'ActiveRecord::RecordNotFound' => :not_found,
- 'ActiveRecord::StaleObjectError' => :conflict,
- 'ActiveRecord::RecordInvalid' => :unprocessable_entity,
- 'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
- 'ActionController::MethodNotAllowed' => :method_not_allowed,
- 'ActionController::NotImplemented' => :not_implemented,
- 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
- })
-
- cattr_accessor :rescue_templates
- @@rescue_templates = Hash.new('diagnostics')
- @@rescue_templates.update({
- 'ActionView::MissingTemplate' => 'missing_template',
- 'ActionController::RoutingError' => 'routing_error',
- 'AbstractController::ActionNotFound' => 'unknown_action',
- 'ActionView::Template::Error' => 'template_error'
- })
-
FAILSAFE_RESPONSE = [500, {'Content-Type' => 'text/html'},
["<html><body><h1>500 Internal Server Error</h1>" <<
"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 " <<
"went wrong.</body></html>"]]
- def initialize(app, consider_all_requests_local = false)
+ def initialize(app, exceptions_app)
@app = app
- @consider_all_requests_local = consider_all_requests_local
+ @exceptions_app = exceptions_app
end
def call(env)
begin
- status, headers, body = @app.call(env)
- exception = nil
-
- # Only this middleware cares about RoutingError. So, let's just raise
- # it here.
- if headers['X-Cascade'] == 'pass'
- raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
- end
+ response = @app.call(env)
rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false
end
- exception ? render_exception(env, exception) : [status, headers, body]
+ response || render_exception(env, exception)
end
private
- def render_exception(env, exception)
- log_error(exception)
- exception = original_exception(exception)
-
- request = Request.new(env)
- if @consider_all_requests_local || request.local?
- rescue_action_locally(request, exception)
- else
- rescue_action_in_public(exception)
- end
- rescue Exception => failsafe_error
- $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
- FAILSAFE_RESPONSE
- end
-
- # Render detailed diagnostics for unhandled exceptions rescued from
- # a controller action.
- def rescue_action_locally(request, exception)
- template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
- :request => request,
- :exception => exception,
- :application_trace => application_trace(exception),
- :framework_trace => framework_trace(exception),
- :full_trace => full_trace(exception)
- )
- file = "rescues/#{@@rescue_templates[exception.class.name]}"
- body = template.render(:template => file, :layout => 'rescues/layout')
- render(status_code(exception), body)
- end
- # Attempts to render a static error page based on the
- # <tt>status_code</tt> thrown, or just return headers if no such file
- # exists. At first, it will try to render a localized static page.
- # For example, if a 500 error is being handled Rails and locale is :da,
- # it will first attempt to render the file at <tt>public/500.da.html</tt>
- # then attempt to render <tt>public/500.html</tt>. If none of them exist,
- # the body of the response will be left empty.
- def rescue_action_in_public(exception)
- status = status_code(exception)
- locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
- path = "#{public_path}/#{status}.html"
-
- if locale_path && File.exist?(locale_path)
- render(status, File.read(locale_path))
- elsif File.exist?(path)
- render(status, File.read(path))
- else
- render(status, '')
- end
- end
-
- def status_code(exception)
- Rack::Utils.status_code(@@rescue_responses[exception.class.name])
- end
-
- def render(status, body)
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
- end
-
- def public_path
- defined?(Rails.public_path) ? Rails.public_path : 'public_path'
- end
-
- def log_error(exception)
- return unless logger
-
- ActiveSupport::Deprecation.silence do
- message = "\n#{exception.class} (#{exception.message}):\n"
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
- message << " " << application_trace(exception).join("\n ")
- logger.fatal("#{message}\n\n")
- end
- end
-
- def application_trace(exception)
- clean_backtrace(exception, :silent)
- end
-
- def framework_trace(exception)
- clean_backtrace(exception, :noise)
- end
-
- def full_trace(exception)
- clean_backtrace(exception, :all)
- end
-
- def clean_backtrace(exception, *args)
- defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
- Rails.backtrace_cleaner.clean(exception.backtrace, *args) :
- exception.backtrace
- end
-
- def logger
- defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
- end
+ # Define this method because some plugins were monkey patching it.
+ # Remove this after 3.2 is out with the other deprecations in this class.
+ def status_code(*)
+ end
- def original_exception(exception)
- if registered_original_exception?(exception)
- exception.original_exception
- else
- exception
- end
+ def render_exception(env, exception)
+ wrapper = ExceptionWrapper.new(env, exception)
+ status = wrapper.status_code
+ env["action_dispatch.exception"] = wrapper.exception
+ env["PATH_INFO"] = "/#{status}"
+ response = @exceptions_app.call(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 registered_original_exception?(exception)
- exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
+ 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/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
index 6e71fd7ddc..1a308707d1 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
@@ -16,6 +16,7 @@
background-color: #eee;
padding: 10px;
font-size: 11px;
+ white-space: pre-wrap;
}
a { color: #000; }
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
index ccfa858cce..f06c07daa5 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
@@ -1,10 +1,15 @@
<h1>Routing Error</h1>
<p><pre><%=h @exception.message %></pre></p>
-<% unless @exception.failures.empty? %><p>
- <h2>Failure reasons:</h2>
- <ol>
- <% @exception.failures.each do |route, reason| %>
- <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
- <% end %>
- </ol>
-</p><% end %>
+<% unless @exception.failures.empty? %>
+ <p>
+ <h2>Failure reasons:</h2>
+ <ol>
+ <% @exception.failures.each do |route, reason| %>
+ <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
+ <% end %>
+ </ol>
+ </p>
+<% end %>
+<p>
+ Try running <code>rake routes</code> for more information on available routes.
+</p> \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 1af89858d1..46c06386d8 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -9,11 +9,27 @@ module ActionDispatch
config.action_dispatch.best_standards_support = true
config.action_dispatch.tld_length = 1
config.action_dispatch.ignore_accept_header = false
- config.action_dispatch.rack_cache = {:metastore => "rails:/", :entitystore => "rails:/", :verbose => true}
+ config.action_dispatch.rescue_templates = { }
+ config.action_dispatch.rescue_responses = { }
+
+ config.action_dispatch.rack_cache = {
+ :metastore => "rails:/",
+ :entitystore => "rails:/",
+ :verbose => true
+ }
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
+ ActionDispatch::Response.default_charset = app.config.encoding
+
+ ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
+ ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
+
+ config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
+ ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
+
+ ActionDispatch.test_app = app
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 1dcd83ceb5..fa300b4a16 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -277,7 +277,6 @@ module ActionDispatch
#
module Routing
autoload :Mapper, 'action_dispatch/routing/mapper'
- autoload :Route, 'action_dispatch/routing/route'
autoload :RouteSet, 'action_dispatch/routing/route_set'
autoload :RoutesProxy, 'action_dispatch/routing/routes_proxy'
autoload :UrlFor, 'action_dispatch/routing/url_for'
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index cd59b13c42..db1e3198b3 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -16,7 +16,7 @@ module ActionDispatch
end
end
- attr_reader :app
+ attr_reader :app, :constraints
def initialize(app, constraints, request)
@app, @constraints, @request = app, constraints, request
@@ -49,7 +49,7 @@ module ActionDispatch
class Mapping #:nodoc:
IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- SHORTHAND_REGEX = %r{^/[\w/]+$}
+ SHORTHAND_REGEX = %r{/[\w/]+$}
WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
def initialize(set, scope, path, options)
@@ -70,7 +70,7 @@ module ActionDispatch
if using_match_shorthand?(path_without_format, @options)
to_shorthand = @options[:to].blank?
- @options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1')
+ @options[:to] ||= path_without_format.gsub(/\(.*\)/, "")[1..-1].sub(%r{/([^/]*)$}, '#\1')
end
@options.merge!(default_controller_and_action(to_shorthand))
@@ -90,7 +90,7 @@ module ActionDispatch
# match "account/overview"
def using_match_shorthand?(path, options)
- path && options.except(:via, :anchor, :to, :as).empty? && path =~ SHORTHAND_REGEX
+ path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX
end
def normalize_path(path)
@@ -285,7 +285,7 @@ module ActionDispatch
# A pattern can also point to a +Rack+ endpoint i.e. anything that
# responds to +call+:
#
- # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon" }
+ # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon"] }
# match 'photos/:id' => PhotoRackApp
# # Yes, controller actions are just rack endpoints
# match 'photos/:id' => PhotosController.action(:show)
@@ -374,10 +374,6 @@ module ActionDispatch
# # Matches any request starting with 'path'
# match 'path' => 'c#a', :anchor => false
def match(path, options=nil)
- mapping = Mapping.new(@set, @scope, path, options || {})
- app, conditions, requirements, defaults, as, anchor = mapping.to_route
- @set.add_route(app, conditions, requirements, defaults, as, anchor)
- self
end
# Mount a Rack-based application to be used within the application.
@@ -696,7 +692,7 @@ module ActionDispatch
# Allows you to constrain the nested routes based on a set of rules.
# For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
#
- # constraints(:id => /\d+\.\d+) do
+ # constraints(:id => /\d+\.\d+/) do
# resources :posts
# end
#
@@ -706,7 +702,7 @@ module ActionDispatch
# You may use this to also restrict other parameters:
#
# resources :posts do
- # constraints(:post_id => /\d+\.\d+) do
+ # constraints(:post_id => /\d+\.\d+/) do
# resources :comments
# end
# end
@@ -735,7 +731,7 @@ module ActionDispatch
# if the user should be given access to that route, or +false+ if the user should not.
#
# class Iphone
- # def self.matches(request)
+ # def self.matches?(request)
# request.env["HTTP_USER_AGENT"] =~ /iPhone/
# end
# end
@@ -867,8 +863,6 @@ module ActionDispatch
CANONICAL_ACTIONS = %w(index create new show update destroy)
class Resource #:nodoc:
- DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit]
-
attr_reader :controller, :path, :options
def initialize(entities, options = {})
@@ -880,7 +874,7 @@ module ActionDispatch
end
def default_actions
- self.class::DEFAULT_ACTIONS
+ [:index, :create, :new, :show, :update, :destroy, :edit]
end
def actions
@@ -934,16 +928,17 @@ module ActionDispatch
end
class SingletonResource < Resource #:nodoc:
- DEFAULT_ACTIONS = [:show, :create, :update, :destroy, :new, :edit]
-
def initialize(entities, options)
super
-
@as = nil
@controller = (options[:controller] || plural).to_s
@as = options[:as]
end
+ def default_actions
+ [:show, :create, :update, :destroy, :new, :edit]
+ end
+
def plural
@plural ||= name.to_s.pluralize
end
@@ -991,7 +986,7 @@ module ActionDispatch
return self
end
- resource_scope(SingletonResource.new(resources.pop, options)) do
+ resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
yield if block_given?
collection do
@@ -1023,6 +1018,7 @@ module ActionDispatch
# creates seven different routes in your application, all mapping to
# the +Photos+ controller:
#
+ # GET /photos
# GET /photos/new
# POST /photos
# GET /photos/:id
@@ -1038,6 +1034,7 @@ module ActionDispatch
#
# This generates the following comments routes:
#
+ # GET /photos/:photo_id/comments
# GET /photos/:photo_id/comments/new
# POST /photos/:photo_id/comments
# GET /photos/:photo_id/comments/:id
@@ -1049,13 +1046,20 @@ module ActionDispatch
# Takes same options as <tt>Base#match</tt> as well as:
#
# [:path_names]
- # Allows you to change the paths of the seven default actions.
- # Paths not specified are not changed.
+ # Allows you to change the segment component of the +edit+ and +new+ actions.
+ # Actions not specified are not changed.
#
# resources :posts, :path_names => { :new => "brand_new" }
#
# The above example will now change /posts/new to /posts/brand_new
#
+ # [:path]
+ # Allows you to change the path prefix for the resource.
+ #
+ # resources :posts, :path => 'postings'
+ #
+ # The resource and all segments will now route to /postings instead of /posts
+ #
# [:only]
# Only generate routes for the given actions.
#
@@ -1120,7 +1124,7 @@ module ActionDispatch
return self
end
- resource_scope(Resource.new(resources.pop, options)) do
+ resource_scope(:resources, Resource.new(resources.pop, options)) do
yield if block_given?
collection do
@@ -1243,32 +1247,44 @@ module ActionDispatch
parent_resource.instance_of?(Resource) && @scope[:shallow]
end
- def match(*args)
- options = args.extract_options!.dup
- options[:anchor] = true unless options.key?(:anchor)
-
- if args.length > 1
- args.each { |path| match(path, options.dup) }
- return self
+ def match(path, *rest)
+ if rest.empty? && Hash === path
+ options = path
+ path, to = options.find { |name, value| name.is_a?(String) }
+ options[:to] = to
+ options.delete(path)
+ paths = [path]
+ else
+ options = rest.pop || {}
+ paths = [path] + rest
end
- on = options.delete(:on)
- if VALID_ON_OPTIONS.include?(on)
- args.push(options)
- return send(on){ match(*args) }
- elsif on
+ options[:anchor] = true unless options.key?(:anchor)
+
+ if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
end
- if @scope[:scope_level] == :resources
- args.push(options)
- return nested { match(*args) }
- elsif @scope[:scope_level] == :resource
- args.push(options)
- return member { match(*args) }
+ paths.each { |_path| decomposed_match(_path, options.dup) }
+ self
+ end
+
+ def decomposed_match(path, options) # :nodoc:
+ if on = options.delete(:on)
+ send(on) { decomposed_match(path, options) }
+ else
+ case @scope[:scope_level]
+ when :resources
+ nested { decomposed_match(path, options) }
+ when :resource
+ member { decomposed_match(path, options) }
+ else
+ add_route(path, options)
+ end
end
+ end
- action = args.first
+ def add_route(action, options) # :nodoc:
path = path_for_action(action, options.delete(:path))
if action.to_s =~ /^[\w\/]+$/
@@ -1277,13 +1293,15 @@ module ActionDispatch
action = nil
end
- if options.key?(:as) && !options[:as]
+ if !options.fetch(:as, true)
options.delete(:as)
else
options[:as] = name_for_action(options[:as], action)
end
- super(path, options)
+ mapping = Mapping.new(@set, @scope, path, options)
+ app, conditions, requirements, defaults, as, anchor = mapping.to_route
+ @set.add_route(app, conditions, requirements, defaults, as, anchor)
end
def root(options={})
@@ -1339,7 +1357,7 @@ module ActionDispatch
end
def scope_action_options? #:nodoc:
- @scope[:options].is_a?(Hash) && (@scope[:options][:only] || @scope[:options][:except])
+ @scope[:options] && (@scope[:options][:only] || @scope[:options][:except])
end
def scope_action_options #:nodoc:
@@ -1347,11 +1365,11 @@ module ActionDispatch
end
def resource_scope? #:nodoc:
- @scope[:scope_level].in?([:resource, :resources])
+ [:resource, :resources].include? @scope[:scope_level]
end
def resource_method_scope? #:nodoc:
- @scope[:scope_level].in?([:collection, :member, :new])
+ [:collection, :member, :new].include? @scope[:scope_level]
end
def with_exclusive_scope
@@ -1376,8 +1394,8 @@ module ActionDispatch
@scope[:scope_level_resource] = old_resource
end
- def resource_scope(resource) #:nodoc:
- with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do
+ def resource_scope(kind, resource) #:nodoc:
+ with_scope_level(kind, resource) do
scope(parent_resource.resource_scope) do
yield
end
@@ -1385,10 +1403,12 @@ module ActionDispatch
end
def nested_options #:nodoc:
- {}.tap do |options|
- options[:as] = parent_resource.member_name
- options[:constraints] = { "#{parent_resource.singular}_id".to_sym => id_constraint } if id_constraint?
- end
+ options = { :as => parent_resource.member_name }
+ options[:constraints] = {
+ :"#{parent_resource.singular}_id" => id_constraint
+ } if id_constraint?
+
+ options
end
def id_constraint? #:nodoc:
@@ -1419,8 +1439,7 @@ module ActionDispatch
end
def action_path(name, path = nil) #:nodoc:
- # Ruby 1.8 can't transform empty strings to symbols
- name = name.to_sym if name.is_a?(String) && !name.empty?
+ name = name.to_sym if name.is_a?(String)
path || @scope[:path_names][name] || name.to_s
end
@@ -1459,22 +1478,17 @@ module ActionDispatch
[name_prefix, member_name, prefix]
end
- candidate = name.select(&:present?).join("_").presence
- candidate unless as.nil? && @set.routes.find { |r| r.name == candidate }
- end
- end
-
- module Shorthand #:nodoc:
- def match(path, *rest)
- if rest.empty? && Hash === path
- options = path
- path, to = options.find { |name, value| name.is_a?(String) }
- options.merge!(:to => to).delete(path)
- super(path, options)
- else
- super
+ if candidate = name.select(&:present?).join("_").presence
+ # If a name was not explicitly given, we check if it is valid
+ # and return nil in case it isn't. Otherwise, we pass the invalid name
+ # forward so the underlying router engine treats it and raises an exception.
+ if as.nil?
+ candidate unless @set.routes.find { |r| r.name == candidate } || candidate !~ /\A[_a-z]/i
+ else
+ candidate
+ end
+ end
end
- end
end
def initialize(set) #:nodoc:
@@ -1487,7 +1501,6 @@ module ActionDispatch
include Redirection
include Scoping
include Resources
- include Shorthand
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index e989a38d8b..013cf93dbc 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -165,7 +165,7 @@ module ActionDispatch
if parent.is_a?(Symbol) || parent.is_a?(String)
parent
else
- ActiveModel::Naming.route_key(parent).singularize
+ ActiveModel::Naming.singular_route_key(parent)
end
end
else
@@ -176,9 +176,11 @@ module ActionDispatch
if record.is_a?(Symbol) || record.is_a?(String)
route << record
elsif record
- route << ActiveModel::Naming.route_key(record)
- route = [route.join("_").singularize] if inflection == :singular
- route << "index" if ActiveModel::Naming.uncountable?(record) && inflection == :plural
+ if inflection == :singular
+ route << ActiveModel::Naming.singular_route_key(record)
+ else
+ route << ActiveModel::Naming.route_key(record)
+ end
else
raise ArgumentError, "Nil location provided. Can't build URI."
end
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 804991ad5f..617b24b46a 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -2,6 +2,54 @@ require 'action_dispatch/http/request'
module ActionDispatch
module Routing
+ class Redirect # :nodoc:
+ attr_reader :status, :block
+
+ def initialize(status, block)
+ @status = status
+ @block = block
+ end
+
+ def call(env)
+ req = Request.new(env)
+
+ uri = URI.parse(path(req.symbolized_path_parameters, req))
+ uri.scheme ||= req.scheme
+ uri.host ||= req.host
+ uri.port ||= req.port unless req.standard_port?
+
+ body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
+
+ headers = {
+ 'Location' => uri.to_s,
+ 'Content-Type' => 'text/html',
+ 'Content-Length' => body.length.to_s
+ }
+
+ [ status, headers, [body] ]
+ end
+
+ def path(params, request)
+ block.call params, request
+ end
+ end
+
+ class OptionRedirect < Redirect # :nodoc:
+ alias :options :block
+
+ def path(params, request)
+ url_options = {
+ :protocol => request.protocol,
+ :host => request.host,
+ :port => request.optional_port,
+ :path => request.path,
+ :params => request.query_parameters
+ }.merge options
+
+ ActionDispatch::Http::URL.url_for url_options
+ end
+ end
+
module Redirection
# Redirect any path to another path:
@@ -40,71 +88,18 @@ module ActionDispatch
options = args.last.is_a?(Hash) ? args.pop : {}
status = options.delete(:status) || 301
- path = args.shift
+ return OptionRedirect.new(status, options) if options.any?
- path_proc = if path.is_a?(String)
- proc { |params| (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params) }
- elsif options.any?
- options_proc(options)
- elsif path.respond_to?(:call)
- proc { |params, request| path.call(params, request) }
- elsif block
- block
- else
- raise ArgumentError, "redirection argument not supported"
- end
-
- redirection_proc(status, path_proc)
- end
+ path = args.shift
- private
-
- def options_proc(options)
- proc do |params, request|
- path = if options[:path].nil?
- request.path
- elsif params.empty? || !options[:path].match(/%\{\w*\}/)
- options.delete(:path)
- else
- (options.delete(:path) % params)
- end
-
- default_options = {
- :protocol => request.protocol,
- :host => request.host,
- :port => request.optional_port,
- :path => path,
- :params => request.query_parameters
- }
-
- ActionDispatch::Http::URL.url_for(options.reverse_merge(default_options))
- end
- end
-
- def redirection_proc(status, path_proc)
- lambda do |env|
- req = Request.new(env)
-
- params = [req.symbolized_path_parameters]
- params << req if path_proc.arity > 1
-
- uri = URI.parse(path_proc.call(*params))
- uri.scheme ||= req.scheme
- uri.host ||= req.host
- uri.port ||= req.port unless req.standard_port?
-
- body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
-
- headers = {
- 'Location' => uri.to_s,
- 'Content-Type' => 'text/html',
- 'Content-Length' => body.length.to_s
- }
-
- [ status, headers, [body] ]
- end
- end
+ block = lambda { |params, request|
+ (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params)
+ } if String === path
+ block = path if path.respond_to? :call
+ raise ArgumentError, "redirection argument not supported" unless block
+ Redirect.new status, block
+ end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index e921269331..10b3e212e6 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,4 +1,4 @@
-require 'journey/router'
+require 'journey'
require 'forwardable'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/to_query'
@@ -37,7 +37,7 @@ module ActionDispatch
# If this is a default_controller (i.e. a controller specified by the user)
# we should raise an error in case it's not found, because it usually means
- # an user error. However, if the controller was retrieved through a dynamic
+ # a user error. However, if the controller was retrieved through a dynamic
# segment, as in :controller(/:action), we should simply return nil and
# delegate the control back to Rack cascade. Besides, if this is not a default
# controller, it means we should respect the @scope[:module] parameter.
@@ -83,7 +83,9 @@ module ActionDispatch
attr_reader :routes, :helpers, :module
def initialize
- clear!
+ @routes = {}
+ @helpers = []
+ @module = Module.new
end
def helper_names
@@ -91,12 +93,8 @@ module ActionDispatch
end
def clear!
- @routes = {}
- @helpers = []
-
- @module ||= Module.new do
- instance_methods.each { |selector| remove_method(selector) }
- end
+ @routes.clear
+ @helpers.clear
end
def add(name, route)
@@ -125,19 +123,9 @@ module ActionDispatch
routes.length
end
- def reset!
- old_routes = routes.dup
- clear!
- old_routes.each do |name, route|
- add(name, route)
- end
- end
-
- def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
- reset! if regenerate
- Array(destinations).each do |dest|
- dest.__send__(:include, @module)
- end
+ def install(destinations = [ActionController::Base, ActionView::Base])
+ helper = @module
+ destinations.each { |d| d.module_eval { include helper } }
end
private
@@ -160,22 +148,23 @@ module ActionDispatch
def define_hash_access(route, name, kind, options)
selector = hash_access_name(name, kind)
- # We use module_eval to avoid leaks
- @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
- remove_possible_method :#{selector}
- def #{selector}(*args)
- options = args.extract_options!
- result = #{options.inspect}
+ @module.module_eval do
+ remove_possible_method selector
+
+ define_method(selector) do |*args|
+ inner_options = args.extract_options!
+ result = options.dup
if args.any?
result[:_positional_args] = args
- result[:_positional_keys] = #{route.segment_keys.inspect}
+ result[:_positional_keys] = route.segment_keys
end
- result.merge(options)
+ result.merge(inner_options)
end
- protected :#{selector}
- END_EVAL
+
+ protected selector
+ end
helpers << selector
end
@@ -287,9 +276,9 @@ module ActionDispatch
@prepend.each { |blk| eval_block(blk) }
end
- def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
- Array(destinations).each { |d| d.module_eval { include Helpers } }
- named_routes.install(destinations, regenerate_code)
+ def install_helpers(destinations)
+ destinations.each { |d| d.module_eval { include Helpers } }
+ named_routes.install(destinations)
end
module MountedHelpers
@@ -317,32 +306,29 @@ module ActionDispatch
end
def url_helpers
- @url_helpers ||= begin
- routes = self
+ routes = self
- helpers = Module.new do
- extend ActiveSupport::Concern
- include UrlFor
+ @url_helpers ||= Module.new {
+ extend ActiveSupport::Concern
+ include UrlFor
- @_routes = routes
- class << self
- delegate :url_for, :to => '@_routes'
- end
- extend routes.named_routes.module
-
- # ROUTES TODO: install_helpers isn't great... can we make a module with the stuff that
- # we can include?
- # Yes plz - JP
- included do
- routes.install_helpers(self)
- singleton_class.send(:redefine_method, :_routes) { routes }
- end
+ @_routes = routes
+ def self.url_for(options)
+ @_routes.url_for options
+ end
+
+ extend routes.named_routes.module
- define_method(:_routes) { @_routes || routes }
+ # ROUTES TODO: install_helpers isn't great... can we make a module with the stuff that
+ # we can include?
+ # Yes plz - JP
+ included do
+ routes.install_helpers([self])
+ singleton_class.send(:redefine_method, :_routes) { routes }
end
- helpers
- end
+ define_method(:_routes) { @_routes || routes }
+ }
end
def empty?
@@ -356,7 +342,7 @@ module ActionDispatch
conditions = build_conditions(conditions, valid_conditions, path.names.map { |x| x.to_sym })
route = @set.add_route(app, path, conditions, defaults, name)
- named_routes[name] = route if name
+ named_routes[name] = route if name && !named_routes[name]
route
end
@@ -394,10 +380,9 @@ module ActionDispatch
if name == :controller
value
elsif value.is_a?(Array)
- value.map { |v| Journey::Router::Utils.escape_uri(v.to_param) }.join('/')
- else
- return nil unless param = value.to_param
- param.split('/').map { |v| Journey::Router::Utils.escape_uri(v) }.join("/")
+ value.map { |v| v.to_param }.join('/')
+ elsif param = value.to_param
+ param
end
end
@@ -558,7 +543,7 @@ module ActionDispatch
path_addition, params = generate(path_options, path_segments || {})
path << path_addition
- ActionDispatch::Http::URL.url_for(options.merge({
+ ActionDispatch::Http::URL.url_for(options.merge!({
:path => path,
:params => params,
:user => user,
@@ -585,7 +570,7 @@ module ActionDispatch
@router.recognize(req) do |route, matches, params|
params.each do |key, value|
if value.is_a?(String)
- value = value.dup.force_encoding(Encoding::BINARY) if value.encoding_aware?
+ value = value.dup.force_encoding(Encoding::BINARY)
params[key] = URI.parser.unescape(value)
end
end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 8fc8dc191b..6c2a98ab15 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -42,7 +42,7 @@ module ActionDispatch
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
- # :host => 'www.example.com') # Changed this.
+ # :host => 'www.example.com')
# # => "http://www.example.com/users/new?message=Welcome%21"
#
# By default, all controllers and views have access to a special version of url_for,
@@ -52,7 +52,7 @@ module ActionDispatch
#
# For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for.
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for'
- # in full. However, mailers don't have hostname information, and what's why you'll still
+ # in full. However, mailers don't have hostname information, and that's why you'll still
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
#
#
@@ -90,8 +90,7 @@ module ActionDispatch
if respond_to?(:class_attribute)
class_attribute :default_url_options
else
- mattr_accessor :default_url_options
- remove_method :default_url_options
+ mattr_writer :default_url_options
end
self.default_url_options = {}
@@ -145,7 +144,7 @@ module ActionDispatch
when String
options
when nil, Hash
- _routes.url_for((options || {}).reverse_merge(url_options).symbolize_keys)
+ _routes.url_for((options || {}).symbolize_keys.reverse_merge!(url_options))
else
polymorphic_url(options)
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 0f1bb9f260..26db8662a8 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -463,9 +463,12 @@ module ActionDispatch
@@app = nil
def self.app
- # DEPRECATE Rails application fallback
- # This should be set by the initializer
- @@app || (defined?(Rails.application) && Rails.application) || nil
+ if !@@app && !ActionDispatch.test_app
+ ActiveSupport::Deprecation.warn "Rails application fallback is deprecated " \
+ "and no longer works, please set ActionDispatch.test_app", caller
+ end
+
+ @@app || ActionDispatch.test_app
end
def self.app=(app)
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index add6b56425..94cbc8e478 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,7 +1,7 @@
module ActionPack
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 2
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index d7229419a9..c24109a547 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -33,6 +33,7 @@ module ActionView
autoload :AssetPaths
autoload :Base
autoload :Context
+ autoload :CompiledTemplates, "action_view/context"
autoload :Helpers
autoload :LookupContext
autoload :PathSet
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
index 1d16e34df6..aa5db0d7bc 100644
--- a/actionpack/lib/action_view/asset_paths.rb
+++ b/actionpack/lib/action_view/asset_paths.rb
@@ -103,8 +103,8 @@ module ActionView
if host.respond_to?(:call)
args = [source]
arity = arity_of(host)
- if arity > 1 && !has_request?
- invalid_asset_host!("Remove the second argument to your asset_host Proc if you do not need the request.")
+ if (arity > 1 || arity < -2) && !has_request?
+ invalid_asset_host!("Remove the second argument to your asset_host Proc if you do not need the request, or make it optional.")
end
args << current_request if (arity > 1 || arity < 0) && has_request?
host.call(*args)
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 36c49d9c91..66a5d59857 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -9,8 +9,8 @@ require 'active_support/core_ext/module/deprecation'
module ActionView #:nodoc:
# = Action View Base
#
- # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
- # (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> (or <tt>.rxml</tt>) extension then Jim Weirich's Builder::XmlMarkup library is used.
+ # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERb
+ # (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> extension then Jim Weirich's Builder::XmlMarkup library is used.
#
# == ERB
#
@@ -94,10 +94,10 @@ module ActionView #:nodoc:
#
# Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
#
- # xml.div {
+ # xml.div do
# xml.h1(@person.name)
# xml.p(@person.bio)
- # }
+ # end
#
# would produce something like:
#
diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb
index be7f65c2ce..2372d3c433 100644
--- a/actionpack/lib/action_view/buffers.rb
+++ b/actionpack/lib/action_view/buffers.rb
@@ -4,7 +4,7 @@ module ActionView
class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
def initialize(*)
super
- encode! if encoding_aware?
+ encode!
end
def <<(value)
diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
index 96c3eec337..56f15604a6 100644
--- a/actionpack/lib/action_view/helpers/active_model_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -1,4 +1,3 @@
-require 'action_view/helpers/form_helper'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/object/blank'
@@ -47,9 +46,5 @@ module ActionView
options['type'] != 'hidden'
end
end
-
- class InstanceTag
- include ActiveModelInstanceTag
- end
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb
deleted file mode 100644
index fae2e4fc1c..0000000000
--- a/actionpack/lib/action_view/helpers/asset_paths.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-ActiveSupport::Deprecation.warn "ActionView::Helpers::AssetPaths is deprecated. Please use ActionView::AssetPaths instead."
-
-module ActionView
- module Helpers
- AssetPaths = ::ActionView::AssetPaths
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 7d01e5ddb8..653e12c7d8 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -228,23 +228,19 @@ module ActionView
)
end
- # Web browsers cache favicons. If you just throw a <tt>favicon.ico</tt> into the document
- # root of your application and it changes later, clients that have it in their cache
- # won't see the update. Using this helper prevents that because it appends an asset ID:
- #
# <%= favicon_link_tag %>
#
# generates
#
- # <link href="/favicon.ico?4649789979" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ # <link href="/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
#
# You may specify a different file in the first argument:
#
- # <%= favicon_link_tag 'favicon.ico' %>
+ # <%= favicon_link_tag '/myicon.ico' %>
#
# That's passed to +path_to_image+ as is, so it gives
#
- # <link href="/images/favicon.ico?4649789979" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ # <link href="/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
#
# The helper accepts an additional options hash where you can override "rel" and "type".
#
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
index 09700bd0c5..d9f1f88ade 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
@@ -119,10 +119,10 @@ module ActionView
# # <script type="text/javascript" src="/elsewhere/cools.js?1423139606"></script>
#
# javascript_include_tag "http://www.example.com/xmlhr"
- # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js?1284139606"></script>
+ # # => <script type="text/javascript" src="http://www.example.com/xmlhr"></script>
#
# javascript_include_tag "http://www.example.com/xmlhr.js"
- # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js?1284139606"></script>
+ # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js"></script>
#
# javascript_include_tag :defaults
# # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
index 343153c8c5..41958c6559 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
@@ -68,6 +68,9 @@ module ActionView
# Returns a stylesheet link tag for the sources specified as arguments. If
# you don't specify an extension, <tt>.css</tt> will be appended automatically.
# You can modify the link attributes by passing a hash as the last argument.
+ # For historical reasons, the 'media' attribute will always be present and defaults
+ # to "screen", so you must explicitely set it to "all" for the stylesheet(s) to
+ # apply to all media types.
#
# ==== Examples
# stylesheet_link_tag "style" # =>
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
index 39c37b25dc..73824dc1f8 100644
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -32,7 +32,7 @@ module ActionView
# app/views/posts/index.atom.builder:
# atom_feed do |feed|
# feed.title("My great blog!")
- # feed.updated(@posts.first.created_at)
+ # feed.updated(@posts[0].created_at) if @posts.length > 0
#
# @posts.each do |post|
# feed.entry(post) do |entry|
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index f81ce3e31c..850dd5f448 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -54,7 +54,7 @@ module ActionView
output_safe = output_buffer.html_safe?
fragment = output_buffer.slice!(pos..-1)
if output_safe
- self.output_buffer = output_buffer.html_safe
+ self.output_buffer = output_buffer.class.new(output_buffer)
end
controller.write_fragment(name, fragment, options)
end
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 8abd85c3a3..17bbfe2efd 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -81,8 +81,8 @@ module ActionView
# <%# This is the layout %>
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
# <head>
- # <title>My Website</title>
- # <%= yield :script %>
+ # <title>My Website</title>
+ # <%= yield :script %>
# </head>
# <body>
# <%= yield %>
@@ -110,7 +110,7 @@ module ActionView
# That will place +script+ tags for your default set of JavaScript files on the page;
# this technique is useful if you'll only be using these scripts in a few views.
#
- # Note that content_for concatenates the blocks it is given for a particular
+ # Note that content_for concatenates (default) the blocks it is given for a particular
# identifier in order. For example:
#
# <% content_for :navigation do %>
@@ -127,16 +127,37 @@ module ActionView
#
# <ul><%= content_for :navigation %></ul>
#
+ # If the flush parameter is true content_for replaces the blocks it is given. For example:
+ #
+ # <% content_for :navigation do %>
+ # <li><%= link_to 'Home', :action => 'index' %></li>
+ # <% end %>
+ #
+ # <%# Add some other content, or use a different template: %>
+ #
+ # <% content_for :navigation, true do %>
+ # <li><%= link_to 'Login', :action => 'login' %></li>
+ # <% end %>
+ #
+ # Then, in another template or layout, this code would render only the last link:
+ #
+ # <ul><%= content_for :navigation %></ul>
+ #
# Lastly, simple content can be passed as a parameter:
#
# <% content_for :script, javascript_include_tag(:defaults) %>
#
# WARNING: content_for is ignored in caches. So you shouldn't use it
# for elements that will be fragment cached.
- def content_for(name, content = nil, &block)
+ def content_for(name, content = nil, flush = false, &block)
if content || block_given?
- content = capture(&block) if block_given?
- @view_flow.append(name, content) if content
+ if block_given?
+ flush = content if content
+ content = capture(&block)
+ end
+ if content
+ flush ? @view_flow.set(name, content) : @view_flow.append(name, content)
+ end
nil
else
@view_flow.get(name)
@@ -164,8 +185,8 @@ module ActionView
# <%# This is the layout %>
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
# <head>
- # <title>My Website</title>
- # <%= yield :script %>
+ # <title>My Website</title>
+ # <%= yield :script %>
# </head>
# <body class="<%= content_for?(:right_col) ? 'one-column' : 'two-column' %>">
# <%= yield %>
@@ -181,7 +202,7 @@ module ActionView
def with_output_buffer(buf = nil) #:nodoc:
unless buf
buf = ActionView::OutputBuffer.new
- buf.force_encoding(output_buffer.encoding) if output_buffer.respond_to?(:encoding) && buf.respond_to?(:force_encoding)
+ buf.force_encoding(output_buffer.encoding) if output_buffer
end
self.output_buffer, old_buffer = buf, output_buffer
yield
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index e850c258ce..2806348337 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -422,7 +422,7 @@ module ActionView
end
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
- # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
+ # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'second' by default.
#
# ==== Examples
@@ -448,7 +448,7 @@ module ActionView
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
# Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute
- # selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
+ # selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
#
# ==== Examples
@@ -473,7 +473,7 @@ module ActionView
end
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
- # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
+ # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
#
# ==== Examples
@@ -736,7 +736,7 @@ module ActionView
if @options[:use_hidden] || @options[:discard_day]
build_hidden(:day, day)
else
- build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false)
+ build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false, :use_two_digit_numbers => @options[:use_two_digit_numbers])
end
end
@@ -765,11 +765,16 @@ module ActionView
if @options[:use_hidden] || @options[:discard_year]
build_hidden(:year, val)
else
- options = {}
- options[:start] = @options[:start_year] || middle_year - 5
- options[:end] = @options[:end_year] || middle_year + 5
- options[:step] = options[:start] < options[:end] ? 1 : -1
- options[:leading_zeros] = false
+ options = {}
+ options[:start] = @options[:start_year] || middle_year - 5
+ options[:end] = @options[:end_year] || middle_year + 5
+ options[:step] = options[:start] < options[:end] ? 1 : -1
+ options[:leading_zeros] = false
+ options[:max_years_allowed] = @options[:max_years_allowed] || 1000
+
+ if (options[:end] - options[:start]).abs > options[:max_years_allowed]
+ raise ArgumentError, "There're too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter"
+ end
build_options_and_select(:year, val, options)
end
@@ -817,6 +822,8 @@ module ActionView
def month_name(number)
if @options[:use_month_numbers]
number
+ elsif @options[:use_two_digit_numbers]
+ sprintf "%02d", number
elsif @options[:add_month_numbers]
"#{number} - #{month_names[number]}"
else
@@ -852,7 +859,7 @@ module ActionView
start = options.delete(:start) || 0
stop = options.delete(:end) || 59
step = options.delete(:step) || 1
- options.reverse_merge!({:leading_zeros => true, :ampm => false})
+ options.reverse_merge!({:leading_zeros => true, :ampm => false, :use_two_digit_numbers => false})
leading_zeros = options.delete(:leading_zeros)
select_options = []
@@ -860,7 +867,8 @@ module ActionView
value = leading_zeros ? sprintf("%02d", i) : i
tag_options = { :value => value }
tag_options[:selected] = "selected" if selected == i
- text = options[:ampm] ? AMPM_TRANSLATION[i] : value
+ text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
+ text = options[:ampm] ? AMPM_TRANSLATION[i] : text
select_options << content_tag(:option, text, tag_options)
end
(select_options.join("\n") + "\n").html_safe
@@ -939,8 +947,9 @@ module ActionView
# and join them with their appropriate separators.
def build_selects_from_types(order)
select = ''
+ first_visible = order.find { |type| !@options[:"discard_#{type}"] }
order.reverse.each do |type|
- separator = separator(type) unless type == order.first # don't add on last field
+ separator = separator(type) unless type == first_visible # don't add before first visible field
select.insert(0, separator.to_s + send("select_#{type}").to_s)
end
select.html_safe
@@ -965,7 +974,7 @@ module ActionView
end
end
- class InstanceTag #:nodoc:
+ module DateHelperInstanceTag
def to_date_select_tag(options = {}, html_options = {})
datetime_selector(options, html_options).select_date.html_safe
end
@@ -1021,6 +1030,10 @@ module ActionView
end
end
+ class InstanceTag #:nodoc:
+ include DateHelperInstanceTag
+ end
+
class FormBuilder
def date_select(method, options = {}, html_options = {})
@template.date_select(@object_name, method, objectify_options(options), html_options)
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index f22c466666..5a301ca9b1 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -2,9 +2,9 @@ require 'cgi'
require 'action_view/helpers/date_helper'
require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
+require 'action_view/helpers/active_model_helper'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/module/method_names'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/array/extract_options'
@@ -158,6 +158,9 @@ module ActionView
# * <tt>:url</tt> - The URL the form is submitted to. It takes the same
# fields you pass to +url_for+ or +link_to+. In particular you may pass
# here a named route directly as well. Defaults to the current action.
+ # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
+ # id attributes on form elements. The namespace attribute will be prefixed
+ # with underscore on the generated HTML id.
# * <tt>:html</tt> - Optional HTML attributes for the form tag.
#
# Also note that +form_for+ doesn't create an exclusive scope. It's still
@@ -384,8 +387,8 @@ module ActionView
as = options[:as]
action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
options[:html].reverse_merge!(
- :class => as ? "#{as}_#{action}" : dom_class(object, action),
- :id => as ? "#{as}_#{action}" : dom_id(object, action),
+ :class => as ? "#{action}_#{as}" : dom_class(object, action),
+ :id => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
:method => method
)
@@ -649,7 +652,7 @@ module ActionView
# # => <label for="post_privacy_public">Public Post</label>
#
# label(:post, :terms) do
- # 'Accept <a href="/terms">Terms</a>.'
+ # 'Accept <a href="/terms">Terms</a>.'.html_safe
# end
def label(object_name, method, content_or_options = nil, options = nil, &block)
content_is_options = content_or_options.is_a?(Hash)
@@ -738,7 +741,7 @@ module ActionView
# # => <input type="file" id="user_avatar" name="user[avatar]" />
#
# file_field(:post, :attached, :accept => 'text/html')
- # # => <input type="file" id="post_attached" name="post[attached]" />
+ # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
#
# file_field(:attachment, :file, :class => 'file_input')
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
@@ -960,7 +963,7 @@ module ActionView
end
class InstanceTag
- include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper
+ include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
attr_reader :object, :method_name, :object_name
@@ -971,6 +974,7 @@ module ActionView
def initialize(object_name, method_name, template_object, object = nil)
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
@template_object = template_object
+
@object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
@object = retrieve_object(object)
@auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
@@ -989,14 +993,23 @@ module ActionView
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
+ options.delete("namespace")
options["for"] ||= name_and_id["id"]
if block_given?
- label_tag(name_and_id["id"], options, &block)
+ @template_object.label_tag(name_and_id["id"], options, &block)
else
content = if text.blank?
+ object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name
- I18n.t("helpers.label.#{object_name}.#{method_and_value}", :default => "").presence
+
+ if object.respond_to?(:to_model)
+ key = object.class.model_name.i18n_key
+ i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
+ end
+
+ i18n_default ||= ""
+ I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
else
text.to_s
end
@@ -1027,6 +1040,8 @@ module ActionView
def to_number_field_tag(field_type, options = {})
options = options.stringify_keys
+ options['size'] ||= nil
+
if range = options.delete("in") || options.delete("within")
options.update("min" => range.min, "max" => range.max)
end
@@ -1185,6 +1200,7 @@ module ActionView
options["name"] ||= tag_name + (options['multiple'] ? '[]' : '')
options["id"] = options.fetch("id"){ tag_id }
end
+ options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
end
def tag_name
@@ -1215,7 +1231,7 @@ module ActionView
class FormBuilder
# The methods which wrap a form helper call.
class_attribute :field_helpers
- self.field_helpers = FormHelper.instance_method_names - %w(form_for convert_to_model)
+ self.field_helpers = FormHelper.instance_methods - [:form_for, :convert_to_model]
attr_accessor :object_name, :object, :options
@@ -1243,7 +1259,7 @@ module ActionView
@nested_child_index = {}
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
@parent_builder = options[:parent_builder]
- @default_options = @options ? @options.slice(:index) : {}
+ @default_options = @options ? @options.slice(:index, :namespace) : {}
if @object_name.to_s.match(/\[\]$/)
if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
@auto_index = object.to_param
@@ -1270,6 +1286,7 @@ module ActionView
fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
fields_options[:builder] ||= options[:builder]
fields_options[:parent_builder] = self
+ fields_options[:namespace] = fields_options[:parent_builder].options[:namespace]
case record_name
when String, Symbol
@@ -1347,6 +1364,39 @@ module ActionView
@template.submit_tag(value, options)
end
+ # Add the submit button for the given form. When no value is given, it checks
+ # if the object is a new resource or not to create the proper label:
+ #
+ # <%= form_for @post do |f| %>
+ # <%= f.button %>
+ # <% end %>
+ #
+ # In the example above, if @post is a new record, it will use "Create Post" as
+ # submit button label, otherwise, it uses "Update Post".
+ #
+ # Those labels can be customized using I18n, under the helpers.submit key and accept
+ # the %{model} as translation interpolation:
+ #
+ # en:
+ # helpers:
+ # button:
+ # create: "Create a %{model}"
+ # update: "Confirm changes to %{model}"
+ #
+ # It also searches for a key specific for the given object:
+ #
+ # en:
+ # helpers:
+ # button:
+ # post:
+ # create: "Add %{model}"
+ #
+ def button(value=nil, options={})
+ value, options = nil, value if value.is_a?(Hash)
+ value ||= submit_default_value
+ @template.button_tag(value, options)
+ end
+
def emitted_hidden_id?
@emitted_hidden_id ||= nil
end
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index c677257d60..3ee0d8ebc5 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -579,7 +579,12 @@ module ActionView
def to_select_tag(choices, options, html_options)
selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
- if !choices.empty? && choices.try(:first).try(:second).respond_to?(:each)
+ # Grouped choices look like this:
+ #
+ # [nil, []]
+ # { nil => [] }
+ #
+ if !choices.empty? && choices.first.respond_to?(:last) && Array === choices.first.last
option_tags = grouped_options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
else
option_tags = options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 13b9dc8553..bb753ae27a 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -393,21 +393,17 @@ module ActionView
# submit_tag "Save edits", :disabled => true
# # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
#
- #
# submit_tag "Complete sale", :disable_with => "Please wait..."
- # # => <input name="commit" data-disable-with="Please wait..."
- # # type="submit" value="Complete sale" />
+ # # => <input name="commit" data-disable-with="Please wait..." type="submit" value="Complete sale" />
#
# submit_tag nil, :class => "form_submit"
# # => <input class="form_submit" name="commit" type="submit" />
#
# submit_tag "Edit", :disable_with => "Editing...", :class => "edit_button"
- # # => <input class="edit_button" data-disable_with="Editing..."
- # # name="commit" type="submit" value="Edit" />
+ # # => <input class="edit_button" data-disable_with="Editing..." name="commit" type="submit" value="Edit" />
#
# submit_tag "Save", :confirm => "Are you sure?"
- # # => <input name='commit' type='submit' value='Save'
- # data-confirm="Are you sure?" />
+ # # => <input name='commit' type='submit' value='Save' data-confirm="Are you sure?" />
#
def submit_tag(value = "Save changes", options = {})
options = options.stringify_keys
@@ -451,12 +447,11 @@ module ActionView
# content_tag(:strong, 'Ask me!')
# end
# # => <button name="button" type="button">
- # <strong>Ask me!</strong>
- # </button>
+ # # <strong>Ask me!</strong>
+ # # </button>
#
# button_tag "Checkout", :disable_with => "Please wait..."
- # # => <button data-disable-with="Please wait..." name="button"
- # type="submit">Checkout</button>
+ # # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button>
#
def button_tag(content_or_options = nil, options = nil, &block)
options = content_or_options if block_given? && content_or_options.is_a?(Hash)
@@ -582,7 +577,7 @@ module ActionView
#
# ==== Examples
# number_field_tag 'quantity', nil, :in => 1...10
- # => <input id="quantity" name="quantity" min="1" max="9" />
+ # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
def number_field_tag(name, value = nil, options = {})
options = options.stringify_keys
options["type"] ||= "number"
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 1adcd716f8..309923490c 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -14,11 +14,7 @@ module ActionView
"'" => "\\'"
}
- if "ruby".encoding_aware?
- JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '&#x2028;'
- else
- JS_ESCAPE_MAP["\342\200\250"] = '&#x2028;'
- end
+ JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '&#x2028;'
# Escapes carriage returns and single and double quotes for JavaScript segments.
#
@@ -94,7 +90,7 @@ module ActionView
# If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+. Once all
# the JavaScript is set, the helper appends "; return false;".
#
- # The +href+ attribute of the tag is set to "#" unles +html_options+ has one.
+ # The +href+ attribute of the tag is set to "#" unless +html_options+ has one.
#
# link_to_function "Greeting", "alert('Hello world!')", :class => "nav_link"
# # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a>
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index ec6c2c8db3..1c0fdb8ff9 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -1,7 +1,6 @@
# encoding: utf-8
require 'active_support/core_ext/big_decimal/conversions'
-require 'active_support/core_ext/float/rounding'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
@@ -69,7 +68,7 @@ module ActionView
number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
else
number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
- number.slice!(0, 1) if number.starts_with?('-')
+ number.slice!(0, 1) if number.starts_with?(delimiter) && !delimiter.blank?
end
str = []
@@ -100,10 +99,10 @@ module ActionView
# number_to_currency(1234567890.50) # => $1,234,567,890.50
# number_to_currency(1234567890.506) # => $1,234,567,890.51
# number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506
- # number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,506 €
+ # number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,51 €
#
# number_to_currency(-1234567890.50, :negative_format => "(%u%n)")
- # # => ($1,234,567,890.51)
+ # # => ($1,234,567,890.50)
# number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "")
# # => &pound;1234567890,50
# number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
@@ -193,7 +192,8 @@ module ActionView
# number_with_delimiter(12345678) # => 12,345,678
# number_with_delimiter(12345678.05) # => 12,345,678.05
# number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678
- # number_with_delimiter(12345678, :separator => ",") # => 12,345,678
+ # number_with_delimiter(12345678, :delimiter => ",") # => 12,345,678
+ # number_with_delimiter(12345678.05, :separator => " ") # => 12,345,678 05
# number_with_delimiter(12345678.05, :locale => :fr) # => 12 345 678,05
# number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",")
# # => 98 765 432,98
diff --git a/actionpack/lib/action_view/helpers/rendering_helper.rb b/actionpack/lib/action_view/helpers/rendering_helper.rb
index 47efdded42..626e1a1ab7 100644
--- a/actionpack/lib/action_view/helpers/rendering_helper.rb
+++ b/actionpack/lib/action_view/helpers/rendering_helper.rb
@@ -8,7 +8,7 @@ module ActionView
module RenderingHelper
# Returns the result of a render that's dictated by the options hash. The primary options are:
#
- # * <tt>:partial</tt> - See ActionView::Partials.
+ # * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt>.
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
# * <tt>:text</tt> - Renders the text passed in out.
@@ -87,4 +87,4 @@ module ActionView
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb
index bcc8f6fbcb..7768c8c151 100644
--- a/actionpack/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/object/try'
require 'action_controller/vendor/html-scanner'
-require 'action_view/helpers/tag_helper'
module ActionView
# = Action View Sanitize Helpers
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 21074efe86..209360ee82 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/filters'
-require 'action_view/helpers/tag_helper'
module ActionView
# = Action View Text Helpers
@@ -31,6 +30,7 @@ module ActionView
extend ActiveSupport::Concern
include SanitizeHelper
+ include TagHelper
# The preferred method of outputting text in your views is to use the
# <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
# do not operate as expected in an eRuby code block. If you absolutely must
@@ -120,7 +120,7 @@ module ActionView
text
else
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
- text.gsub(/(#{match})(?!(?:[^<]*?)(?:["'])[^<>]*>)/i, options[:highlighter])
+ text.gsub(/(#{match})(?![^<]*?>)/i, options[:highlighter])
end.html_safe
end
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index be64dc823e..cc74eff53a 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -45,11 +45,18 @@ module ActionView
# you know what kind of output to expect when you call translate in a template.
def translate(key, options = {})
options.merge!(:rescue_format => :html) unless options.key?(:rescue_format)
- translation = I18n.translate(scope_key_by_partial(key), options)
- if html_safe_translation_key?(key) && translation.respond_to?(:html_safe)
- translation.html_safe
+ if html_safe_translation_key?(key)
+ html_safe_options = options.dup
+ options.except(*I18n::RESERVED_KEYS).each do |name, value|
+ unless name == :count && value.is_a?(Numeric)
+ html_safe_options[name] = ERB::Util.html_escape(value.to_s)
+ end
+ end
+ translation = I18n.translate(scope_key_by_partial(key), html_safe_options)
+
+ translation.respond_to?(:html_safe) ? translation.html_safe : translation
else
- translation
+ I18n.translate(scope_key_by_partial(key), options)
end
end
alias :t :translate
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index eb816b9446..f2a83b92a9 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -152,3 +152,9 @@
update: 'Update %{model}'
submit: 'Save %{model}'
+ # Default translation keys for button FormHelper
+ button:
+ create: 'Create %{model}'
+ update: 'Update %{model}'
+ submit: 'Save %{model}'
+
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index fa4bf70f77..3f07314dda 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -20,7 +20,7 @@ module ActionView
def self.register_detail(name, options = {}, &block)
self.registered_details << name
- initialize = registered_details.map { |n| "self.#{n} = details[:#{n}]" }
+ initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }
Accessors.send :define_method, :"default_#{name}", &block
Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
@@ -29,7 +29,7 @@ module ActionView
end
def #{name}=(value)
- value = Array.wrap(value.presence || default_#{name})
+ value = value.present? ? Array.wrap(value) : default_#{name}
_set_detail(:#{name}, value) if value != @details[:#{name}]
end
@@ -44,7 +44,7 @@ module ActionView
module Accessors #:nodoc:
end
- register_detail(:locale) { [I18n.locale, I18n.default_locale] }
+ register_detail(:locale) { [I18n.locale, I18n.default_locale].uniq }
register_detail(:formats) { Mime::SET.symbols }
register_detail(:handlers){ Template::Handlers.extensions }
@@ -56,7 +56,11 @@ module ActionView
@details_keys = Hash.new
def self.get(details)
- @details_keys[details.freeze] ||= new
+ @details_keys[details] ||= new
+ end
+
+ def self.clear
+ @details_keys.clear
end
def initialize
@@ -85,9 +89,9 @@ module ActionView
protected
def _set_detail(key, value)
+ @details = @details.dup if @details_key
@details_key = nil
- @details = @details.dup if @details.frozen?
- @details[key] = value.freeze
+ @details[key] = value
end
end
@@ -147,27 +151,17 @@ module ActionView
# as well as incorrectly putting part of the path in the template
# name instead of the prefix.
def normalize_name(name, prefixes) #:nodoc:
- name = name.to_s.sub(handlers_regexp) do |match|
- ActiveSupport::Deprecation.warn "Passing a template handler in the template name is deprecated. " \
- "You can simply remove the handler name or pass render :handlers => [:#{match[1..-1]}] instead.", caller
- ""
- end
+ prefixes = nil if prefixes.blank?
+ parts = name.to_s.split('/')
+ name = parts.pop
- parts = name.split('/')
- name = parts.pop
+ return name, prefixes || [""] if parts.empty?
- prefixes = if prefixes.blank?
- [parts.join('/')]
- else
- prefixes.map { |prefix| [prefix, *parts].compact.join('/') }
- end
+ parts = parts.join('/')
+ prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
return name, prefixes
end
-
- def handlers_regexp #:nodoc:
- @@handlers_regexp ||= /\.(?:#{default_handlers.join('|')})$/
- end
end
include Accessors
@@ -227,7 +221,6 @@ module ActionView
end
# A method which only uses the first format in the formats array for layout lookup.
- # This method plays straight with instance variables for performance reasons.
def with_layout_format
if formats.size == 1
yield
diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb
index c0936441ac..5a611e9f63 100644
--- a/actionpack/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb
@@ -22,18 +22,6 @@ module ActionView
details
end
- def extract_format(value, details)
- if value.is_a?(String) && value.sub!(formats_regexp, "")
- ActiveSupport::Deprecation.warn "Passing the format in the template name is deprecated. " \
- "Please pass render with :formats => [:#{$1}] instead.", caller
- details[:formats] ||= [$1.to_sym]
- end
- end
-
- def formats_regexp
- @@formats_regexp ||= /\.(#{Mime::SET.symbols.join('|')})$/
- end
-
def instrument(name, options={})
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield }
end
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index e808fa3415..e231aade01 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -27,7 +27,7 @@ module ActionView
#
# == The :as and :object options
#
- # By default <tt>ActionView::Partials::PartialRenderer</tt> doesn't have any local variables.
+ # By default <tt>ActionView::PartialRenderer</tt> doesn't have any local variables.
# The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
#
# <%= render :partial => "account", :object => @buyer %>
@@ -82,16 +82,18 @@ module ActionView
#
# This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
#
- # == Rendering objects with the RecordIdentifier
+ # == Rendering objects that respond to `to_partial_path`
#
- # Instead of explicitly naming the location of a partial, you can also let the RecordIdentifier do the work if
- # you're following its conventions for RecordIdentifier#partial_path. Examples:
+ # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
+ # and pick the proper path by checking `to_proper_path` method. If the object passed to render is a collection,
+ # all objects must return the same path.
#
- # # @account is an Account instance, so it uses the RecordIdentifier to replace
+ # # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
# # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
# <%= render :partial => @account %>
#
- # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
+ # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
+ # # that's why we can replace:
# # <%= render :partial => "posts/post", :collection => @posts %>
# <%= render :partial => @posts %>
#
@@ -106,13 +108,14 @@ module ActionView
# # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %>
# <%= render "account", :account => @buyer %>
#
- # # @account is an Account instance, so it uses the RecordIdentifier to replace
- # # <%= render :partial => "accounts/account", :locals => { :account => @account } %>
- # <%= render(@account) %>
+ # # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
+ # # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
+ # <%= render @account %>
#
- # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
+ # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
+ # # that's why we can replace:
# # <%= render :partial => "posts/post", :collection => @posts %>
- # <%= render(@posts) %>
+ # <%= render @posts %>
#
# == Rendering partials with layouts
#
@@ -205,7 +208,7 @@ module ActionView
# Deadline: <%= user.deadline %>
# <%- end -%>
# <% end %>
- class PartialRenderer < AbstractRenderer #:nodoc:
+ class PartialRenderer < AbstractRenderer
PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} }
def initialize(*)
@@ -297,7 +300,6 @@ module ActionView
"and is followed by any combinations of letters, numbers, or underscores.")
end
- extract_format(@path, @details)
self
end
@@ -366,27 +368,26 @@ module ActionView
path = if object.respond_to?(:to_partial_path)
object.to_partial_path
else
- ActiveSupport::Deprecation.warn "ActiveModel-compatible objects whose classes return a #model_name that responds to #partial_path are deprecated. Please respond to #to_partial_path directly instead."
- object.class.model_name.partial_path
+ raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
end
- @partial_names[path] ||= path.dup.tap do |object_path|
- merge_prefix_into_object_path(@context_prefix, object_path)
- end
+ @partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
end
def merge_prefix_into_object_path(prefix, object_path)
if prefix.include?(?/) && object_path.include?(?/)
- overlap = []
+ prefixes = []
prefix_array = File.dirname(prefix).split('/')
object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
prefix_array.each_with_index do |dir, index|
- overlap << dir if dir == object_path_array[index]
+ break if dir == object_path_array[index]
+ prefixes << dir
end
- object_path.gsub!(/^#{overlap.join('/')}\//,'')
- object_path.insert(0, "#{File.dirname(prefix)}/")
+ (prefixes << object_path).join("/")
+ else
+ object_path
end
end
diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
index 1ccf5a8ddb..4b66f66e2d 100644
--- a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
@@ -1,7 +1,7 @@
# 1.9 ships with Fibers but we need to require the extra
# methods explicitly. We only load those extra methods if
# Fiber is available in the first place.
-require 'fiber' if defined?(Fiber)
+require 'fiber'
module ActionView
# == TODO
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
index ac91d333ba..06148ccc98 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -6,7 +6,6 @@ module ActionView
def render(context, options)
@view = context
@details = extract_details(options)
- extract_format(options[:file] || options[:template], @details)
template = determine_template(options)
freeze_formats(template.formats, true)
render_template(template, options[:layout], options[:locals])
@@ -26,6 +25,8 @@ module ActionView
elsif options.key?(:template)
options[:template].respond_to?(:render) ?
options[:template] : find_template(options[:template], options[:prefixes], false, keys, @details)
+ else
+ raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option."
end
end
@@ -58,14 +59,28 @@ module ActionView
# context object. If no layout is found, it checks if at least a layout with
# the given name exists across all details before raising the error.
def find_layout(layout, keys)
- begin
- with_layout_format do
- layout =~ /^\// ?
- with_fallbacks { find_template(layout, nil, false, keys, @details) } : find_template(layout, nil, false, keys, @details)
+ with_layout_format { resolve_layout(layout, keys) }
+ end
+
+ def resolve_layout(layout, keys)
+ case layout
+ when String
+ begin
+ if layout =~ /^\//
+ with_fallbacks { find_template(layout, nil, false, keys, @details) }
+ else
+ find_template(layout, nil, false, keys, @details)
+ end
+ rescue ActionView::MissingTemplate
+ all_details = @details.merge(:formats => @lookup_context.default_formats)
+ raise unless template_exists?(layout, nil, false, keys, all_details)
end
- rescue ActionView::MissingTemplate
- all_details = @details.merge(:formats => @lookup_context.default_formats)
- raise unless template_exists?(layout, nil, false, keys, all_details)
+ when Proc
+ resolve_layout(layout.call, keys)
+ when FalseClass
+ nil
+ else
+ layout
end
end
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 10797c010f..2d9fc3df7a 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -173,6 +173,50 @@ module ActionView
@inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier
end
+ # This method is responsible for properly setting the encoding of the
+ # source. Until this point, we assume that the source is BINARY data.
+ # If no additional information is supplied, we assume the encoding is
+ # the same as <tt>Encoding.default_external</tt>.
+ #
+ # The user can also specify the encoding via a comment on the first
+ # line of the template (# encoding: NAME-OF-ENCODING). This will work
+ # with any template engine, as we process out the encoding comment
+ # before passing the source on to the template engine, leaving a
+ # blank line in its stead.
+ def encode!
+ return unless source.encoding == Encoding::BINARY
+
+ # Look for # encoding: *. If we find one, we'll encode the
+ # String in that encoding, otherwise, we'll use the
+ # default external encoding.
+ if source.sub!(/\A#{ENCODING_FLAG}/, '')
+ encoding = magic_encoding = $1
+ else
+ encoding = Encoding.default_external
+ end
+
+ # Tag the source with the default external encoding
+ # or the encoding specified in the file
+ source.force_encoding(encoding)
+
+ # If the user didn't specify an encoding, and the handler
+ # handles encodings, we simply pass the String as is to
+ # the handler (with the default_external tag)
+ if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
+ source
+ # Otherwise, if the String is valid in the encoding,
+ # encode immediately to default_internal. This means
+ # that if a handler doesn't handle encodings, it will
+ # always get Strings in the default_internal
+ elsif source.valid_encoding?
+ source.encode!
+ # Otherwise, since the String is invalid in the encoding
+ # specified, raise an exception
+ else
+ raise WrongEncodingError.new(source, encoding)
+ end
+ end
+
protected
# Compile a template. This method ensures a template is compiled
@@ -195,15 +239,7 @@ module ActionView
end
# Among other things, this method is responsible for properly setting
- # the encoding of the source. Until this point, we assume that the
- # source is BINARY data. If no additional information is supplied,
- # we assume the encoding is the same as <tt>Encoding.default_external</tt>.
- #
- # The user can also specify the encoding via a comment on the first
- # line of the template (# encoding: NAME-OF-ENCODING). This will work
- # with any template engine, as we process out the encoding comment
- # before passing the source on to the template engine, leaving a
- # blank line in its stead.
+ # the encoding of the compiled template.
#
# If the template engine handles encodings, we send the encoded
# String to the engine without further processing. This allows
@@ -215,40 +251,8 @@ module ActionView
# In general, this means that templates will be UTF-8 inside of Rails,
# regardless of the original source encoding.
def compile(view, mod) #:nodoc:
+ encode!
method_name = self.method_name
-
- if source.encoding_aware?
- # Look for # encoding: *. If we find one, we'll encode the
- # String in that encoding, otherwise, we'll use the
- # default external encoding.
- if source.sub!(/\A#{ENCODING_FLAG}/, '')
- encoding = magic_encoding = $1
- else
- encoding = Encoding.default_external
- end
-
- # Tag the source with the default external encoding
- # or the encoding specified in the file
- source.force_encoding(encoding)
-
- # If the user didn't specify an encoding, and the handler
- # handles encodings, we simply pass the String as is to
- # the handler (with the default_external tag)
- if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
- source
- # Otherwise, if the String is valid in the encoding,
- # encode immediately to default_internal. This means
- # that if a handler doesn't handle encodings, it will
- # always get Strings in the default_internal
- elsif source.valid_encoding?
- source.encode!
- # Otherwise, since the String is invalid in the encoding
- # specified, raise an exception
- else
- raise WrongEncodingError.new(source, encoding)
- end
- end
-
code = @handler.call(self)
# Make sure that the resulting String to be evalled is in the
@@ -261,20 +265,18 @@ module ActionView
end
end_src
- if source.encoding_aware?
- # Make sure the source is in the encoding of the returned code
- source.force_encoding(code.encoding)
+ # Make sure the source is in the encoding of the returned code
+ source.force_encoding(code.encoding)
- # In case we get back a String from a handler that is not in
- # BINARY or the default_internal, encode it to the default_internal
- source.encode!
+ # In case we get back a String from a handler that is not in
+ # BINARY or the default_internal, encode it to the default_internal
+ source.encode!
- # Now, validate that the source we got back from the template
- # handler is valid in the default_internal. This is for handlers
- # that handle encoding but screw up
- unless source.valid_encoding?
- raise WrongEncodingError.new(@source, Encoding.default_internal)
- end
+ # Now, validate that the source we got back from the template
+ # handler is valid in the default_internal. This is for handlers
+ # that handle encoding but screw up
+ unless source.valid_encoding?
+ raise WrongEncodingError.new(@source, Encoding.default_internal)
end
begin
@@ -297,7 +299,11 @@ module ActionView
raise e
else
assigns = view.respond_to?(:assigns) ? view.assigns : {}
- template = @virtual_path ? refresh(view) : self
+ template = self
+ unless template.source
+ template = refresh(view)
+ template.encode!
+ end
raise Template::Error.new(template, assigns, e)
end
end
diff --git a/actionpack/lib/action_view/template/handlers/builder.rb b/actionpack/lib/action_view/template/handlers/builder.rb
index 2c52cfd90e..34397c3bcf 100644
--- a/actionpack/lib/action_view/template/handlers/builder.rb
+++ b/actionpack/lib/action_view/template/handlers/builder.rb
@@ -6,12 +6,21 @@ module ActionView
self.default_format = Mime::XML
def call(template)
- require 'builder'
+ require_engine
"xml = ::Builder::XmlMarkup.new(:indent => 2);" +
"self.output_buffer = xml.target!;" +
template.source +
";xml.target!;"
end
+
+ protected
+
+ def require_engine
+ @required ||= begin
+ require "builder"
+ true
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index 77720e2bc8..323df67c97 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -1,5 +1,5 @@
require 'action_dispatch/http/mime_type'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/class/attribute'
require 'erubis'
module ActionView
@@ -67,23 +67,19 @@ module ActionView
end
def call(template)
- if template.source.encoding_aware?
- # First, convert to BINARY, so in case the encoding is
- # wrong, we can still find an encoding tag
- # (<%# encoding %>) inside the String using a regular
- # expression
- template_source = template.source.dup.force_encoding("BINARY")
+ # First, convert to BINARY, so in case the encoding is
+ # wrong, we can still find an encoding tag
+ # (<%# encoding %>) inside the String using a regular
+ # expression
+ template_source = template.source.dup.force_encoding("BINARY")
- erb = template_source.gsub(ENCODING_TAG, '')
- encoding = $2
+ erb = template_source.gsub(ENCODING_TAG, '')
+ encoding = $2
- erb.force_encoding valid_encoding(template.source.dup, encoding)
+ erb.force_encoding valid_encoding(template.source.dup, encoding)
- # Always make sure we return a String in the default_internal
- erb.encode!
- else
- erb = template.source.dup
- end
+ # Always make sure we return a String in the default_internal
+ erb.encode!
self.class.erb_implementation.new(
erb,
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index f855ea257c..7fa86866a7 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -1,6 +1,5 @@
require "pathname"
require "active_support/core_ext/class"
-require "active_support/core_ext/io"
require "action_view/template"
module ActionView
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 1db079d0e3..9ebe498192 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -50,7 +50,12 @@ module ActionView
module ClassMethods
def tests(helper_class)
- self.helper_class = helper_class
+ case helper_class
+ when String, Symbol
+ self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize
+ when Module
+ self.helper_class = helper_class
+ end
end
def determine_default_helper_class(name)
diff --git a/actionpack/lib/sprockets/assets.rake b/actionpack/lib/sprockets/assets.rake
index a5145080c2..a61a121d55 100644
--- a/actionpack/lib/sprockets/assets.rake
+++ b/actionpack/lib/sprockets/assets.rake
@@ -6,7 +6,7 @@ namespace :assets do
groups = ENV['RAILS_GROUPS'] || 'assets'
args = [$0, task,"RAILS_ENV=#{env}","RAILS_GROUPS=#{groups}"]
args << "--trace" if Rake.application.options.trace
- ruby *args
+ ruby(*args)
end
# We are currently running with no explicit bundler group
diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb
index f866bc626e..c34b3f6f26 100644
--- a/actionpack/lib/sprockets/helpers/rails_helper.rb
+++ b/actionpack/lib/sprockets/helpers/rails_helper.rb
@@ -26,10 +26,10 @@ module Sprockets
sources.collect do |source|
if debug && asset = asset_paths.asset_for(source, 'js')
asset.to_a.map { |dep|
- super(dep.to_s, { :src => asset_path(dep, :ext => 'js', :body => true, :digest => digest) }.merge!(options))
+ super(dep.pathname.to_s, { :src => path_to_asset(dep, :ext => 'js', :body => true, :digest => digest) }.merge!(options))
}
else
- super(source.to_s, { :src => asset_path(source, :ext => 'js', :body => body, :digest => digest) }.merge!(options))
+ super(source.to_s, { :src => path_to_asset(source, :ext => 'js', :body => body, :digest => digest) }.merge!(options))
end
end.join("\n").html_safe
end
@@ -43,10 +43,10 @@ module Sprockets
sources.collect do |source|
if debug && asset = asset_paths.asset_for(source, 'css')
asset.to_a.map { |dep|
- super(dep.to_s, { :href => asset_path(dep, :ext => 'css', :body => true, :protocol => :request, :digest => digest) }.merge!(options))
+ super(dep.pathname.to_s, { :href => path_to_asset(dep, :ext => 'css', :body => true, :protocol => :request, :digest => digest) }.merge!(options))
}
else
- super(source.to_s, { :href => asset_path(source, :ext => 'css', :body => body, :protocol => :request, :digest => digest) }.merge!(options))
+ super(source.to_s, { :href => path_to_asset(source, :ext => 'css', :body => body, :protocol => :request, :digest => digest) }.merge!(options))
end
end.join("\n").html_safe
end
@@ -56,19 +56,20 @@ module Sprockets
path = asset_paths.compute_public_path(source, asset_prefix, options.merge(:body => true))
options[:body] ? "#{path}?body=1" : path
end
+ alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route
def image_path(source)
- asset_path(source)
+ path_to_asset(source)
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
def javascript_path(source)
- asset_path(source)
+ path_to_asset(source)
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with an javascript_path named route
def stylesheet_path(source)
- asset_path(source)
+ path_to_asset(source)
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with an stylesheet_path named route
@@ -113,16 +114,13 @@ module Sprockets
class AssetNotPrecompiledError < StandardError; end
- # Return the filesystem path for the source
- def compute_source_path(source, ext)
- asset_for(source, ext)
- end
-
def asset_for(source, ext)
source = source.to_s
return nil if is_uri?(source)
source = rewrite_extension(source, nil, ext)
asset_environment[source]
+ rescue Sprockets::FileOutsidePaths
+ nil
end
def digest_for(logical_path)
@@ -152,7 +150,7 @@ module Sprockets
end
def rewrite_extension(source, dir, ext)
- if ext && File.extname(source).empty?
+ if ext && File.extname(source) != ".#{ext}"
"#{source}.#{ext}"
else
source
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
index 3d330bd91a..a7eb03acaf 100644
--- a/actionpack/lib/sprockets/railtie.rb
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -10,8 +10,6 @@ module Sprockets
# TODO: Get rid of config.assets.enabled
class Railtie < ::Rails::Railtie
- config.action_controller.default_asset_host_protocol = :relative
-
rake_tasks do
load "sprockets/assets.rake"
end