aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r--actionpack/lib/action_controller/base.rb28
-rw-r--r--actionpack/lib/action_controller/caching.rb1
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb31
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb67
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb7
-rw-r--r--actionpack/lib/action_controller/deprecated.rb6
-rw-r--r--actionpack/lib/action_controller/deprecated/integration_test.rb3
-rw-r--r--actionpack/lib/action_controller/deprecated/performance_test.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb11
-rw-r--r--actionpack/lib/action_controller/metal.rb10
-rw-r--r--actionpack/lib/action_controller/metal/compatibility.rb58
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb65
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb61
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb13
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb27
-rw-r--r--actionpack/lib/action_controller/metal/head.rb25
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb16
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb117
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb2
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb7
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb219
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb58
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb28
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb32
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb10
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb25
-rw-r--r--actionpack/lib/action_controller/metal/rescue.rb13
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb55
-rw-r--r--actionpack/lib/action_controller/metal/session_management.rb9
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb13
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb40
-rw-r--r--actionpack/lib/action_controller/railtie.rb31
-rw-r--r--actionpack/lib/action_controller/railties/helpers.rb22
-rw-r--r--actionpack/lib/action_controller/railties/paths.rb24
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb22
-rw-r--r--actionpack/lib/action_controller/test_case.rb238
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/document.rb2
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/node.rb2
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb17
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb2
40 files changed, 888 insertions, 531 deletions
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index ce56d8bc71..71425cd542 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:
#
@@ -63,7 +63,7 @@ module ActionController
#
# == Sessions
#
- # Sessions allows you to store objects in between requests. This is useful for objects that are not yet ready to be persisted,
+ # Sessions allow you to store objects in between requests. This is useful for objects that are not yet ready to be persisted,
# such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such
# as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely
# they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at.
@@ -116,12 +116,12 @@ module ActionController
#
# Title: <%= @post.title %>
#
- # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates
+ # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates
# will use the manual rendering methods:
#
# def search
# @results = Search.find(params[:query])
- # case @results
+ # case @results.count
# when 0 then render :action => "no_results"
# when 1 then render :action => "show"
# when 2..10 then render :action => "show_many"
@@ -133,7 +133,7 @@ module ActionController
# == Redirects
#
# Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to the
- # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're
+ # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're
# going to reuse (and redirect to) a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
#
# def create
@@ -171,6 +171,16 @@ module ActionController
class Base < Metal
abstract!
+ # Shortcut helper that returns all the ActionController::Base modules except the ones passed in the argument:
+ #
+ # class MetalController
+ # ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
+ # include left
+ # end
+ # end
+ #
+ # This gives better control over what you want to exclude and makes it easier
+ # to create a bare controller class, instead of listing the modules required manually.
def self.without_modules(*modules)
modules = modules.map do |m|
m.is_a?(Symbol) ? ActionController.const_get(m) : m
@@ -192,7 +202,6 @@ module ActionController
Renderers::All,
ConditionalGet,
RackDelegation,
- SessionManagement,
Caching,
MimeResponds,
ImplicitRender,
@@ -228,8 +237,11 @@ module ActionController
include mod
end
- # Rails 2.x compatibility
- include ActionController::Compatibility
+ # Define some internal variables that should not be propagated to the view.
+ self.protected_instance_variables = [
+ :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
+ :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout
+ ]
ActiveSupport.run_load_hooks(:action_controller, self)
end
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..0238135bc1 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -40,14 +40,15 @@ module ActionController #:nodoc:
#
# You can modify the default action cache path by passing a
# <tt>:cache_path</tt> option. This will be passed directly to
- # <tt>ActionCachePath.path_for</tt>. This is handy for actions with
+ # <tt>ActionCachePath.new</tt>. This is handy for actions with
# multiple possible routes that should be cached differently. If a
# block is given, it is called with the current controller instance.
#
# And you can also use <tt>:if</tt> (or <tt>:unless</tt>) to pass a
# proc that specifies when the action should be cached.
#
- # Finally, if you are using memcached, you can also pass <tt>:expires_in</tt>.
+ # As of Rails 3.0, you can also pass <tt>:expires_in</tt> with a time
+ # interval (in seconds) to schedule expiration of the cached item.
#
# The following example depicts some of the points made above:
#
@@ -56,14 +57,14 @@ module ActionController #:nodoc:
#
# caches_page :public
#
- # caches_action :index, :if => proc do
+ # caches_action :index, :if => Proc.new do
# !request.format.json? # cache if is not a JSON request
# end
#
# caches_action :show, :cache_path => { :project => 1 },
# :expires_in => 1.hour
#
- # caches_action :feed, :cache_path => proc do
+ # caches_action :feed, :cache_path => Proc.new do
# if params[:user_id]
# user_list_url(params[:user_id, params[:id])
# else
@@ -102,8 +103,10 @@ module ActionController #:nodoc:
end
def _save_fragment(name, options)
- content = response_body
- content = content.join if content.is_a?(Array)
+ content = ""
+ response_body.each do |parts|
+ content << parts
+ end
if caching_allowed?
write_fragment(name, content, options)
@@ -116,9 +119,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
@@ -131,6 +133,8 @@ module ActionController #:nodoc:
end
def filter(controller)
+ cache_layout = @cache_layout.respond_to?(:call) ? @cache_layout.call(controller) : @cache_layout
+
path_options = if @cache_path.respond_to?(:call)
controller.instance_exec(controller, &@cache_path)
else
@@ -142,13 +146,13 @@ module ActionController #:nodoc:
body = controller.read_fragment(cache_path.path, @store_options)
unless body
- controller.action_has_layout = false unless @cache_layout
+ controller.action_has_layout = false unless cache_layout
yield
controller.action_has_layout = true
body = controller._save_fragment(cache_path.path, @store_options)
end
- body = controller.render_to_string(:text => body, :layout => true) unless @cache_layout
+ body = controller.render_to_string(:text => body, :layout => true) unless cache_layout
controller.response_body = body
controller.content_type = Mime[cache_path.extension || :html]
@@ -168,14 +172,15 @@ module ActionController #:nodoc:
options.reverse_merge!(:format => @extension) if options.is_a?(Hash)
end
- path = controller.url_for(options).split(%r{://}).last
+ path = controller.url_for(options).split('://', 2).last
@path = normalize!(path)
end
private
def normalize!(path)
+ ext = URI.parser.escape(extension) if extension
path << 'index' if path[-1] == ?/
- path << ".#{extension}" if extension and !path.ends_with?(extension)
+ path << ".#{ext}" if extension and !path.split('?', 2).first.ends_with?(".#{ext}")
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..dd4eddbe9a 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,27 +38,30 @@ 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
- # Expires the page that was cached with the +path+ as a key. Example:
+ # Expires the page that was cached with the +path+ as a key.
+ #
# expire_page "/lists/show"
def expire_page(path)
return unless perform_caching
@@ -66,35 +69,59 @@ 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:
+ # Manually cache the +content+ in the key determined by +path+.
+ #
# 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.
#
- # Usage:
+ # You can also pass a :gzip option to override the class configuration one.
#
# # cache the index action
# caches_page :index
#
# # cache the index action except for JSON requests
- # caches_page :index, :if => Proc.new { |c| !c.request.format.json? }
+ # caches_page :index, :if => Proc.new { !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
@@ -115,14 +142,15 @@ module ActionController #:nodoc:
end
end
- # Expires the page that was cached with the +options+ as a key. Example:
+ # Expires the page that was cached with the +options+ as a key.
+ #
# expire_page :controller => "lists", :action => "show"
def expire_page(options = {})
return unless self.class.perform_caching
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
@@ -134,9 +162,10 @@ module ActionController #:nodoc:
end
# 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:
+ # If no options are provided, the url of the current request being handled is used.
+ #
# 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 +181,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/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
index 938a6ae81c..cc1fa23935 100644
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ b/actionpack/lib/action_controller/caching/sweeping.rb
@@ -54,6 +54,11 @@ module ActionController #:nodoc:
class Sweeper < ActiveRecord::Observer #:nodoc:
attr_accessor :controller
+ def initialize(*args)
+ super
+ @controller = nil
+ end
+
def before(controller)
self.controller = controller
callback(:before) if controller.perform_caching
@@ -88,7 +93,7 @@ module ActionController #:nodoc:
end
def method_missing(method, *arguments, &block)
- return if @controller.nil?
+ return super unless @controller
@controller.__send__(method, *arguments, &block)
end
end
diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb
index aa0cfc9395..2405bebb97 100644
--- a/actionpack/lib/action_controller/deprecated.rb
+++ b/actionpack/lib/action_controller/deprecated.rb
@@ -1,3 +1,7 @@
ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
-ActionController::Routing = ActionDispatch::Routing \ No newline at end of file
+ActionController::Routing = ActionDispatch::Routing
+
+ActiveSupport::Deprecation.warn 'ActionController::AbstractRequest and ActionController::Request are deprecated and will be removed, use ActionDispatch::Request instead.'
+ActiveSupport::Deprecation.warn 'ActionController::AbstractResponse and ActionController::Response are deprecated and will be removed, use ActionDispatch::Response instead.'
+ActiveSupport::Deprecation.warn 'ActionController::Routing is deprecated and will be removed, use ActionDispatch::Routing instead.' \ No newline at end of file
diff --git a/actionpack/lib/action_controller/deprecated/integration_test.rb b/actionpack/lib/action_controller/deprecated/integration_test.rb
index 86336b6bc4..54eae48f47 100644
--- a/actionpack/lib/action_controller/deprecated/integration_test.rb
+++ b/actionpack/lib/action_controller/deprecated/integration_test.rb
@@ -1,2 +1,5 @@
ActionController::Integration = ActionDispatch::Integration
ActionController::IntegrationTest = ActionDispatch::IntegrationTest
+
+ActiveSupport::Deprecation.warn 'ActionController::Integration is deprecated and will be removed, use ActionDispatch::Integration instead.'
+ActiveSupport::Deprecation.warn 'ActionController::IntegrationTest is deprecated and will be removed, use ActionDispatch::IntegrationTest instead.'
diff --git a/actionpack/lib/action_controller/deprecated/performance_test.rb b/actionpack/lib/action_controller/deprecated/performance_test.rb
index fcf47d31a7..c7ba5a2fe7 100644
--- a/actionpack/lib/action_controller/deprecated/performance_test.rb
+++ b/actionpack/lib/action_controller/deprecated/performance_test.rb
@@ -1 +1,3 @@
ActionController::PerformanceTest = ActionDispatch::PerformanceTest
+
+ActiveSupport::Deprecation.warn 'ActionController::PerformanceTest is deprecated and will be removed, use ActionDispatch::PerformanceTest instead.'
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 35e29398e6..0fb419f941 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -20,19 +20,20 @@ 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 = 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)"
- info(message % [event.payload[:path], event.duration])
+ info("Sent file %s (%.1fms)" % [event.payload[:path], event.duration])
end
def redirect_to(event)
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 0133b2ecbc..92433ab462 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -181,9 +181,13 @@ module ActionController
@_status = Rack::Utils.status_code(status)
end
- def response_body=(val)
- body = val.nil? ? nil : (val.respond_to?(:each) ? val : [val])
- super body
+ def response_body=(body)
+ body = [body] unless body.nil? || body.respond_to?(:each)
+ super
+ end
+
+ def performed?
+ !!response_body
end
def dispatch(name, request) #:nodoc:
diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb
deleted file mode 100644
index 05dca445a4..0000000000
--- a/actionpack/lib/action_controller/metal/compatibility.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-module ActionController
- module Compatibility
- extend ActiveSupport::Concern
-
- class ::ActionController::ActionControllerError < StandardError #:nodoc:
- end
-
- # Temporary hax
- included do
- ::ActionController::UnknownAction = ::AbstractController::ActionNotFound
- ::ActionController::DoubleRenderError = ::AbstractController::DoubleRenderError
-
- # ROUTES TODO: This should be handled by a middleware and route generation
- # should be able to handle SCRIPT_NAME
- self.config.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT']
-
- class << self
- delegate :default_charset=, :to => "ActionDispatch::Response"
- end
-
- self.protected_instance_variables = %w(
- @_status @_headers @_params @_env @_response @_request
- @_view_runtime @_stream @_url_options @_action_has_layout
- )
-
- def rescue_action(env)
- raise env["action_dispatch.rescue.exception"]
- end
- end
-
- # For old tests
- def initialize_template_class(*) end
- def assign_shortcuts(*) end
-
- def _normalize_options(options)
- options[:text] = nil if options.delete(:nothing) == true
- options[:text] = " " if options.key?(:text) && options[:text].nil?
- super
- end
-
- def render_to_body(options)
- options[:template].sub!(/^\//, '') if options.key?(:template)
- super || " "
- end
-
- def _handle_method_missing
- method_missing(@_action_name.to_sym)
- end
-
- def method_for_action(action_name)
- super || (respond_to?(:method_missing) && "_handle_method_missing")
- end
-
- def performed?
- response_body
- end
- end
-end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index a5e37172c9..2193dde667 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,26 +74,58 @@ 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
# Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a <tt>private</tt> instruction, so that
# intermediate caches must not cache the response.
#
- # Examples:
# expires_in 20.minutes
# expires_in 3.hours, :public => true
- # expires_in 3.hours, 'max-stale' => 5.hours, :public => true
+ # expires_in 3.hours, :public => true, :must_revalidate => true
#
# This method will overwrite an existing Cache-Control header.
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
+ #
+ # The method will also ensure a HTTP Date header for client compatibility.
def expires_in(seconds, options = {}) #:doc:
- response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public))
+ response.cache_control.merge!(
+ :max_age => seconds,
+ :public => options.delete(:public),
+ :must_revalidate => options.delete(:must_revalidate)
+ )
options.delete(:private)
response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
+ response.date = Time.now unless response.date?
end
# Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should occur by the browser or
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 5e077dd7bd..379ff97048 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:
@@ -9,15 +8,13 @@ module ActionController #:nodoc:
include ActionController::Rendering
- DEFAULT_SEND_FILE_OPTIONS = {
- :type => 'application/octet-stream'.freeze,
- :disposition => 'attachment'.freeze,
- }.freeze
+ DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc:
+ DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc:
protected
# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
# via the Rack::Sendfile middleware. The header to use is set via
- # config.action_dispatch.x_sendfile_header.
+ # +config.action_dispatch.x_sendfile_header+.
# Your server can also configure this for you by setting the X-Sendfile-Type header.
#
# Be careful to sanitize the path parameter if it is coming from a web
@@ -34,7 +31,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).
@@ -75,7 +72,27 @@ module ActionController #:nodoc:
self.status = options[:status] || 200
self.content_type = options[:content_type] if options.key?(:content_type)
- self.response_body = File.open(path, "rb")
+ self.response_body = FileBody.new(path)
+ end
+
+ # Avoid having to pass an open file handle as the response body.
+ # Rack::Sendfile will usually intercepts the response and just uses
+ # the path directly, so no reason to open the file.
+ class FileBody #:nodoc:
+ attr_reader :to_path
+
+ def initialize(path)
+ @to_path = path
+ end
+
+ # Stream the file's contents if Rack::Sendfile isn't present.
+ def each
+ File.open(to_path, 'rb') do |file|
+ while chunk = file.read(16384)
+ yield chunk
+ end
+ end
+ end
end
# Sends the given binary data to the browser. This method is similar to
@@ -92,7 +109,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:
#
@@ -108,23 +125,16 @@ module ActionController #:nodoc:
#
# See +send_file+ for more information on HTTP Content-* headers and caching.
def send_data(data, options = {}) #:doc:
- send_file_headers! options.dup
+ send_file_headers! options
render options.slice(:status, :content_type).merge(:text => data)
end
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?
- end
- disposition = options[:disposition]
- disposition += %(; filename="#{options[:filename]}") if options[:filename]
-
- content_type = options[:type]
+ content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE)
+ raise ArgumentError, ":type option required" if content_type.nil?
if content_type.is_a?(Symbol)
extension = Mime[content_type]
@@ -133,15 +143,18 @@ module ActionController #:nodoc:
else
if !type_provided && options[:filename]
# If type wasn't provided, try guessing from file extension.
- content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.tr('.','')) || content_type
+ content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type
end
self.content_type = content_type
end
- headers.merge!(
- 'Content-Disposition' => disposition,
- 'Content-Transfer-Encoding' => 'binary'
- )
+ disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
+ unless disposition.nil?
+ disposition += %(; filename="#{options[:filename]}") if options[:filename]
+ headers['Content-Disposition'] = disposition
+ end
+
+ headers['Content-Transfer-Encoding'] = 'binary'
response.sending_file = true
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index 07024d0a9a..8fd8f4797c 100644
--- a/actionpack/lib/action_controller/metal/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -2,6 +2,9 @@ module ActionController
class ActionControllerError < StandardError #:nodoc:
end
+ class BadRequest < ActionControllerError #:nodoc:
+ end
+
class RenderError < ActionControllerError #:nodoc:
end
@@ -14,8 +17,6 @@ module ActionController
end
class MethodNotAllowed < ActionControllerError #:nodoc:
- attr_reader :allowed_methods
-
def initialize(*allowed_methods)
super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
end
@@ -30,9 +31,6 @@ module ActionController
class MissingFile < ActionControllerError #:nodoc:
end
- class RenderError < ActionControllerError #:nodoc:
- end
-
class SessionOverflowError < ActionControllerError #:nodoc:
DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
@@ -43,4 +41,7 @@ module ActionController
class UnknownHttpMethod < ActionControllerError #:nodoc:
end
-end \ No newline at end of file
+
+ class UnknownFormat < ActionControllerError #:nodoc:
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index ed693c5967..ac12cbb625 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -18,18 +18,37 @@ module ActionController
# Force the request to this particular controller or specified actions to be
# under HTTPS protocol.
#
- # Note that this method will not be effective on development environment.
+ # If you need to disable this for any reason (e.g. development) then you can use
+ # an +:if+ or +:unless+ condition.
+ #
+ # class AccountsController < ApplicationController
+ # force_ssl :if => :ssl_configured?
+ #
+ # def ssl_configured?
+ # !Rails.env.development?
+ # end
+ # end
#
# ==== Options
+ # * <tt>host</tt> - Redirect to a different host name
# * <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
+ # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
+ # will be called only when it returns a true value.
+ # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
+ # will be called only when it returns a false value.
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
+ unless request.ssl?
+ redirect_options = {:protocol => 'https://', :status => :moved_permanently}
+ redirect_options.merge!(:host => host) if host
+ redirect_options.merge!(:params => request.query_parameters)
+ flash.keep if respond_to?(:flash)
+ 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..2fcd933d32 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?
@@ -18,6 +20,7 @@ module ActionController
options, status = status, nil if status.is_a?(Hash)
status ||= options.delete(:status) || :ok
location = options.delete(:location)
+ content_type = options.delete(:content_type)
options.each do |key, value|
headers[key.to_s.dasherize.split('-').each { |v| v[0] = v[0].chr.upcase }.join('-')] = value.to_s
@@ -25,8 +28,28 @@ module ActionController
self.status = status
self.location = url_for(location) if location
- self.content_type = Mime[formats.first] if formats
+
+ if include_content_headers?(self.status)
+ self.content_type = content_type || (Mime[formats.first] if formats)
+ else
+ headers.delete('Content-Type')
+ headers.delete('Content-Length')
+ end
+
self.response_body = " "
end
+
+ private
+ # :nodoc:
+ def include_content_headers?(status)
+ case status
+ when 100..199
+ false
+ when 204, 205, 304
+ false
+ else
+ true
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 2df0e9422c..86d061e3b7 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute'
module ActionController
@@ -7,14 +6,16 @@ module ActionController
# by default.
#
# In addition to using the standard template helpers provided, creating custom helpers to
- # extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will
- # include a helper whose name matches that of the controller, e.g., <tt>MyController</tt> will automatically
- # include <tt>MyHelper</tt>.
+ # extract complicated logic or reusable functionality is strongly encouraged. By default, each controller
+ # will include all helpers.
+ #
+ # In previous versions of \Rails the controller will include a helper whose
+ # name matches that of the controller, e.g., <tt>MyController</tt> will automatically
+ # include <tt>MyHelper</tt>. To return old behavior set +config.action_controller.include_all_helpers+ to +false+.
#
# Additional helpers can be specified using the +helper+ class method in ActionController::Base or any
# controller which inherits from it.
#
- # ==== Examples
# The +to_s+ method from the \Time class can be wrapped in a helper method to display a custom message if
# a \Time object is blank:
#
@@ -50,10 +51,11 @@ module ActionController
module Helpers
extend ActiveSupport::Concern
+ class << self; attr_accessor :helpers_path; end
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
@@ -91,7 +93,7 @@ module ActionController
def all_helpers_from_path(path)
helpers = []
- Array.wrap(path).each do |_path|
+ Array(path).each do |_path|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 7420a5e7e9..57bb0e2a32 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -1,9 +1,10 @@
-require 'active_support/base64'
+require 'base64'
require 'active_support/core_ext/object/blank'
module ActionController
+ # Makes it dead easy to do HTTP Basic, Digest and Token authentication.
module HttpAuthentication
- # Makes it dead easy to do HTTP \Basic and \Digest authentication.
+ # Makes it dead easy to do HTTP \Basic authentication.
#
# === Simple \Basic example
#
@@ -60,47 +61,6 @@ module ActionController
#
# assert_equal 200, status
# end
- #
- # === Simple \Digest example
- #
- # require 'digest/md5'
- # class PostsController < ApplicationController
- # REALM = "SuperSecret"
- # USERS = {"dhh" => "secret", #plain text password
- # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password
- #
- # before_filter :authenticate, :except => [:index]
- #
- # def index
- # render :text => "Everyone can see me!"
- # end
- #
- # def edit
- # render :text => "I'm only accessible if you know the password"
- # end
- #
- # private
- # def authenticate
- # authenticate_or_request_with_http_digest(REALM) do |username|
- # USERS[username]
- # end
- # end
- # end
- #
- # === Notes
- #
- # The +authenticate_or_request_with_http_digest+ block must return the user's password
- # or the ha1 digest hash so the framework can appropriately hash to check the user's
- # credentials. Returning +nil+ will cause authentication to fail.
- #
- # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
- # the password file or database is compromised, the attacker would be able to use the ha1 hash to
- # authenticate as the user at this +realm+, but would not have the user's password to try using at
- # other sites.
- #
- # In rare instances, web servers or front proxies strip authorization headers before
- # they reach your application. You can debug this situation by logging all environment
- # variables, and check for HTTP_AUTHORIZATION, amongst others.
module Basic
extend self
@@ -141,11 +101,11 @@ module ActionController
end
def decode_credentials(request)
- ActiveSupport::Base64.decode64(request.authorization.split(' ', 2).last || '')
+ ::Base64.decode64(request.authorization.split(' ', 2).last || '')
end
def encode_credentials(user_name, password)
- "Basic #{ActiveSupport::Base64.encode64("#{user_name}:#{password}")}"
+ "Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}"
end
def authentication_request(controller, realm)
@@ -155,6 +115,48 @@ module ActionController
end
end
+ # Makes it dead easy to do HTTP \Digest authentication.
+ #
+ # === Simple \Digest example
+ #
+ # require 'digest/md5'
+ # class PostsController < ApplicationController
+ # REALM = "SuperSecret"
+ # USERS = {"dhh" => "secret", #plain text password
+ # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
+ #
+ # before_filter :authenticate, :except => [:index]
+ #
+ # def index
+ # render :text => "Everyone can see me!"
+ # end
+ #
+ # def edit
+ # render :text => "I'm only accessible if you know the password"
+ # end
+ #
+ # private
+ # def authenticate
+ # authenticate_or_request_with_http_digest(REALM) do |username|
+ # USERS[username]
+ # end
+ # end
+ # end
+ #
+ # === Notes
+ #
+ # The +authenticate_or_request_with_http_digest+ block must return the user's password
+ # or the ha1 digest hash so the framework can appropriately hash to check the user's
+ # credentials. Returning +nil+ will cause authentication to fail.
+ #
+ # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
+ # the password file or database is compromised, the attacker would be able to use the ha1 hash to
+ # authenticate as the user at this +realm+, but would not have the user's password to try using at
+ # other sites.
+ #
+ # In rare instances, web servers or front proxies strip authorization headers before
+ # they reach your application. You can debug this situation by logging all environment
+ # variables, and check for HTTP_AUTHORIZATION, amongst others.
module Digest
extend self
@@ -192,12 +194,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
@@ -226,7 +231,7 @@ module ActionController
def decode_credentials(header)
Hash[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair|
key, value = pair.split('=', 2)
- [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')]
+ [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').delete('\'')]
end]
end
@@ -260,7 +265,7 @@ module ActionController
# The quality of the implementation depends on a good choice.
# A nonce might, for example, be constructed as the base 64 encoding of
#
- # => time-stamp H(time-stamp ":" ETag ":" private-key)
+ # time-stamp H(time-stamp ":" ETag ":" private-key)
#
# where time-stamp is a server-generated time or other non-repeating value,
# ETag is the value of the HTTP ETag header associated with the requested entity,
@@ -276,7 +281,7 @@ module ActionController
#
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
- # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
+ # POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
# of this document.
#
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
@@ -286,16 +291,16 @@ module ActionController
t = time.to_i
hashed = [t, secret_key]
digest = ::Digest::MD5.hexdigest(hashed.join(":"))
- ActiveSupport::Base64.encode64("#{t}:#{digest}").gsub("\n", '')
+ ::Base64.strict_encode64("#{t}:#{digest}")
end
# Might want a shorter timeout depending on whether the request
- # is a PUT or POST, and if client is browser or web service.
+ # is a PATCH, PUT, or POST, and if client is browser or web service.
# Can be much shorter if the Stale directive is implemented. This would
# allow a user to use new nonce without prompting user again for their
# username and password.
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
- t = ActiveSupport::Base64.decode64(value).split(":").first.to_i
+ t = ::Base64.decode64(value).split(":").first.to_i
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
end
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index e8e465d3ba..ae04b53825 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -2,7 +2,7 @@ module ActionController
module ImplicitRender
def send_action(method, *args)
ret = super
- default_render unless response_body
+ default_render unless performed?
ret
end
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..0b800c3c62 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -6,8 +6,6 @@ module ActionController #:nodoc:
module MimeResponds
extend ActiveSupport::Concern
- include ActionController::ImplicitRender
-
included do
class_attribute :responder, :mimes_for_respond_to
self.responder = ActionController::Responder
@@ -18,8 +16,6 @@ module ActionController #:nodoc:
# Defines mime types that are rendered by default when invoking
# <tt>respond_with</tt>.
#
- # Examples:
- #
# respond_to :html, :xml, :json
#
# Specifies that all actions in the controller respond to requests
@@ -42,8 +38,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|
@@ -58,7 +54,7 @@ module ActionController #:nodoc:
# Clear all mime types in <tt>respond_to</tt>.
#
def clear_respond_to
- self.mimes_for_respond_to = ActiveSupport::OrderedHash.new.freeze
+ self.mimes_for_respond_to = Hash.new.freeze
end
end
@@ -76,7 +72,7 @@ module ActionController #:nodoc:
#
# respond_to do |format|
# format.html
- # format.xml { render :xml => @people.to_xml }
+ # format.xml { render :xml => @people }
# end
# end
#
@@ -182,34 +178,120 @@ module ActionController #:nodoc:
#
# def index
# @people = Person.all
- # respond_with(@person)
+ # respond_with(@people)
# end
# end
#
# Be sure to check respond_with and respond_to documentation for more examples.
- #
def respond_to(*mimes, &block)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
- if response = retrieve_response_from_mimes(mimes, &block)
- response.call(nil)
+ if collector = retrieve_collector_from_mimes(mimes, &block)
+ response = collector.response
+ response ? response.call : render({})
end
end
- # respond_with wraps a resource around a responder for default representation.
- # First it invokes respond_to, if a response cannot be found (ie. no block
- # for the request was given and template was not available), it instantiates
- # an ActionController::Responder with the controller and resource.
+ # For a given controller action, respond_with generates an appropriate
+ # response based on the mime-type requested by the client.
#
- # ==== Example
+ # If the method is called with just a resource, as in this example -
#
- # def index
- # @users = User.all
- # respond_with(@users)
+ # class PeopleController < ApplicationController
+ # respond_to :html, :xml, :json
+ #
+ # def index
+ # @people = Person.all
+ # respond_with @people
+ # end
+ # end
+ #
+ # then the mime-type of the response is typically selected based on the
+ # request's Accept header and the set of available formats declared
+ # by previous calls to the controller's class method +respond_to+. Alternatively
+ # the mime-type can be selected by explicitly setting <tt>request.format</tt> in
+ # the controller.
+ #
+ # If an acceptable format is not identified, the application returns a
+ # '406 - not acceptable' status. Otherwise, the default response is to render
+ # a template named after the current action and the selected format,
+ # e.g. <tt>index.html.erb</tt>. If no template is available, the behavior
+ # depends on the selected format:
+ #
+ # * for an html response - if the request method is +get+, an exception
+ # is raised but for other requests such as +post+ the response
+ # depends on whether the resource has any validation errors (i.e.
+ # assuming that an attempt has been made to save the resource,
+ # e.g. by a +create+ action) -
+ # 1. If there are no errors, i.e. the resource
+ # was saved successfully, the response +redirect+'s to the resource
+ # i.e. its +show+ action.
+ # 2. If there are validation errors, the response
+ # renders a default action, which is <tt>:new</tt> for a
+ # +post+ request or <tt>:edit</tt> for +put+.
+ # Thus an example like this -
+ #
+ # respond_to :html, :xml
+ #
+ # def create
+ # @user = User.new(params[:user])
+ # flash[:notice] = 'User was successfully created.' if @user.save
+ # respond_with(@user)
+ # end
+ #
+ # is equivalent, in the absence of <tt>create.html.erb</tt>, to -
+ #
+ # def create
+ # @user = User.new(params[:user])
+ # respond_to do |format|
+ # if @user.save
+ # flash[:notice] = 'User was successfully created.'
+ # format.html { redirect_to(@user) }
+ # format.xml { render :xml => @user }
+ # else
+ # format.html { render :action => "new" }
+ # format.xml { render :xml => @user }
+ # end
+ # end
+ # end
+ #
+ # * for a javascript request - if the template isn't found, an exception is
+ # raised.
+ # * for other requests - i.e. data formats such as xml, json, csv etc, if
+ # the resource passed to +respond_with+ responds to <code>to_<format></code>,
+ # the method attempts to render the resource in the requested format
+ # directly, e.g. for an xml request, the response is equivalent to calling
+ # <code>render :xml => resource</code>.
+ #
+ # === Nested resources
+ #
+ # As outlined above, the +resources+ argument passed to +respond_with+
+ # can play two roles. It can be used to generate the redirect url
+ # for successful html requests (e.g. for +create+ actions when
+ # no template exists), while for formats other than html and javascript
+ # it is the object that gets rendered, by being converted directly to the
+ # required format (again assuming no template exists).
+ #
+ # For redirecting successful html requests, +respond_with+ also supports
+ # the use of nested resources, which are supplied in the same way as
+ # in <code>form_for</code> and <code>polymorphic_url</code>. For example -
+ #
+ # def create
+ # @project = Project.find(params[:project_id])
+ # @task = @project.comments.build(params[:task])
+ # flash[:notice] = 'Task was successfully created.' if @task.save
+ # respond_with(@project, @task)
# end
#
- # It also accepts a block to be given. It's used to overwrite a default
- # response:
+ # This would cause +respond_with+ to redirect to <code>project_task_url</code>
+ # instead of <code>task_url</code>. For request formats other than html or
+ # javascript, if multiple resources are passed in this way, it is the last
+ # one specified that is rendered.
+ #
+ # === Customizing response behavior
+ #
+ # Like +respond_to+, +respond_with+ may also be called with a block that
+ # can be used to overwrite any of the default responses, e.g. -
#
# def create
# @user = User.new(params[:user])
@@ -220,21 +302,31 @@ module ActionController #:nodoc:
# end
# end
#
- # All options given to respond_with are sent to the underlying responder,
- # except for the option :responder itself. Since the responder interface
- # is quite simple (it just needs to respond to call), you can even give
- # a proc to it.
- #
- # In order to use respond_with, first you need to declare the formats your
- # controller responds to in the class level with a call to <tt>respond_to</tt>.
- #
+ # The argument passed to the block is an ActionController::MimeResponds::Collector
+ # object which stores the responses for the formats defined within the
+ # block. Note that formats with responses defined explicitly in this way
+ # do not have to first be declared using the class method +respond_to+.
+ #
+ # Also, a hash passed to +respond_with+ immediately after the specified
+ # resource(s) is interpreted as a set of options relevant to all
+ # formats. Any option accepted by +render+ can be used, e.g.
+ # respond_with @people, :status => 200
+ # However, note that these options are ignored after an unsuccessful attempt
+ # to save a resource, e.g. when automatically rendering <tt>:new</tt>
+ # after a post request.
+ #
+ # Two additional options are relevant specifically to +respond_with+ -
+ # 1. <tt>:location</tt> - overwrites the default redirect location used after
+ # a successful html +post+ request.
+ # 2. <tt>:action</tt> - overwrites the default render action used after an
+ # unsuccessful html +post+ request.
def respond_with(*resources, &block)
raise "In order to use respond_with, first you need to declare the formats your " <<
"controller responds to in the class level" if self.class.mimes_for_respond_to.empty?
- if response = retrieve_response_from_mimes(&block)
+ if collector = retrieve_collector_from_mimes(&block)
options = resources.size == 1 ? {} : resources.extract_options!
- options.merge!(:default_response => response)
+ options[:default_response] = collector.response
(options.delete(:responder) || self.class.responder).call(self, resources, options)
end
end
@@ -243,9 +335,8 @@ module ActionController #:nodoc:
# Collect mimes declared in the class method respond_to valid for the
# 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]
@@ -260,30 +351,56 @@ module ActionController #:nodoc:
end
end
- # Collects mimes and return the response for the negotiated format. Returns
- # nil if :not_acceptable was sent to the client.
+ # Returns a Collector object containing the appropriate mime-type response
+ # for the current request, based on the available responses defined by a block.
+ # In typical usage this is the block passed to +respond_with+ or +respond_to+.
#
- def retrieve_response_from_mimes(mimes=nil, &block) #:nodoc:
+ # Sends :not_acceptable to the client and returns nil if no suitable format
+ # is available.
+ def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc:
mimes ||= collect_mimes_from_class_level
- collector = Collector.new(mimes) { |options| default_render(options || {}) }
+ collector = Collector.new(mimes)
block.call(collector) if block_given?
+ format = collector.negotiate_format(request)
- if format = request.negotiate_mime(collector.order)
+ if format
self.content_type ||= format.to_s
- lookup_context.freeze_formats([format.to_sym])
- collector.response_for(format)
+ lookup_context.formats = [format.to_sym]
+ lookup_context.rendered_format = lookup_context.formats.first
+ collector
else
- head :not_acceptable
- nil
+ raise ActionController::UnknownFormat
end
end
- class Collector #:nodoc:
+ # A container for responses available from the current controller for
+ # requests for different mime-types sent to a particular action.
+ #
+ # The public controller methods +respond_with+ and +respond_to+ may be called
+ # with a block that is used to define responses to different mime-types, e.g.
+ # for +respond_to+ :
+ #
+ # respond_to do |format|
+ # format.html
+ # format.xml { render :xml => @people }
+ # end
+ #
+ # In this usage, the argument passed to the block (+format+ above) is an
+ # instance of the ActionController::MimeResponds::Collector class. This
+ # object serves as a container in which available responses can be stored by
+ # calling any of the dynamically generated, mime-type-specific methods such
+ # as +html+, +xml+ etc on the Collector. Each response is represented by a
+ # corresponding block if present.
+ #
+ # A subsequent call to #negotiate_format(request) will enable the Collector
+ # to determine which specific mime-type it should respond with for the current
+ # request, with this response then being accessible by calling #response.
+ class Collector
include AbstractController::Collector
- attr_accessor :order
+ attr_accessor :order, :format
- def initialize(mimes, &block)
- @order, @responses, @default_response = [], {}, block
+ def initialize(mimes)
+ @order, @responses = [], {}
mimes.each { |mime| send(mime) }
end
@@ -302,8 +419,12 @@ module ActionController #:nodoc:
@responses[mime_type] ||= block
end
- def response_for(mime)
- @responses[mime] || @responses[Mime::ALL] || @default_response
+ def response
+ @responses[format] || @responses[Mime::ALL]
+ end
+
+ def negotiate_format(request)
+ @format = request.negotiate_mime(order)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index dfc4e3d72c..aa67fa7f23 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -1,36 +1,34 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/except'
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/module/anonymous'
require 'action_dispatch/http/mime_types'
module ActionController
- # Wraps parameters hash into nested hash. This will allow client to submit
- # POST request without having to specify a root element in it.
+ # Wraps the parameters hash into a nested hash. This will allow clients to submit
+ # POST requests without having to specify any root elements.
#
- # By default this functionality won't be enabled. You can enable
- # it globally by setting +ActionController::Base.wrap_parameters+:
- #
- # ActionController::Base.wrap_parameters = [:json]
+ # This functionality is enabled in +config/initializers/wrap_parameters.rb+
+ # and can be customized. If you are upgrading to \Rails 3.1, this file will
+ # need to be created for the functionality to be enabled.
#
# You could also turn it on per controller by setting the format array to
- # non-empty array:
+ # a non-empty array:
#
# class UsersController < ApplicationController
# wrap_parameters :format => [:json, :xml]
# end
#
- # If you enable +ParamsWrapper+ for +:json+ format. Instead of having to
+ # If you enable +ParamsWrapper+ for +:json+ format, instead of having to
# send JSON parameters like this:
#
# {"user": {"name": "Konata"}}
#
- # You can now just send a parameters like this:
+ # You can send parameters like this:
#
# {"name": "Konata"}
#
- # And it will be wrapped into a nested hash with the key name matching
+ # And it will be wrapped into a nested hash with the key name matching the
# controller's name. For example, if you're posting to +UsersController+,
# your new +params+ hash will look like this:
#
@@ -44,8 +42,13 @@ 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
+ # <tt>User.new(params[:user])</tt>), you might consider passing the model class to
# the method instead. The +ParamsWrapper+ will actually try to determine the
# list of attribute names from the model and only wrap those attributes:
#
@@ -63,7 +66,7 @@ module ActionController
# class Admin::UsersController < ApplicationController
# end
#
- # will try to check if +Admin::User+ or +User+ model exists, and use it to
+ # will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to
# determine the wrapper key respectively. If both models don't exist,
# it will then fallback to use +user+ as the key.
module ParamsWrapper
@@ -82,7 +85,7 @@ module ActionController
#
# ==== Examples
# wrap_parameters :format => :xml
- # # enables the parmeter wrapper for XML format
+ # # enables the parameter wrapper for XML format
#
# wrap_parameters :person
# # wraps parameters into +params[:person]+ hash
@@ -121,8 +124,6 @@ module ActionController
_set_wrapper_defaults(_wrapper_options.slice(:format).merge(options), model)
end
- alias :wrap_parameters= :wrap_parameters
-
# Sets the default wrapper key or model which will be used to determine
# wrapper key and attribute names. Will be called automatically when the
# module is inherited.
@@ -144,19 +145,16 @@ 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
- model_klass = model_name.constantize
- rescue NameError, ArgumentError => e
- if e.message =~ /is not missing constant|uninitialized constant #{model_name}/
+ if model_klass = model_name.safe_constantize
+ model_klass
+ else
namespaces = model_name.split("::")
namespaces.delete_at(-2)
break if namespaces.last == model_name
model_name = namespaces.join("::")
- else
- raise
end
end until model_klass
@@ -168,7 +166,10 @@ module ActionController
unless options[:include] || options[:exclude]
model ||= _default_wrap_model
- if model.respond_to?(:attribute_names) && model.attribute_names.present?
+ role = options.fetch(:as, :default)
+ if model.respond_to?(:accessible_attributes) && model.accessible_attributes(role).present?
+ options[:include] = model.accessible_attributes(role).to_a
+ elsif model.respond_to?(:attribute_names) && model.attribute_names.present?
options[:include] = model.attribute_names
end
end
@@ -179,9 +180,9 @@ module ActionController
controller_name.singularize
end
- options[:include] = Array.wrap(options[:include]).collect(&:to_s) if options[:include]
- options[:exclude] = Array.wrap(options[:exclude]).collect(&:to_s) if options[:exclude]
- options[:format] = Array.wrap(options[:format])
+ options[:include] = Array(options[:include]).collect(&:to_s) if options[:include]
+ options[:exclude] = Array(options[:exclude]).collect(&:to_s) if options[:exclude]
+ options[:format] = Array(options[:format])
self._wrapper_options = options
end
@@ -192,7 +193,8 @@ module ActionController
def process_action(*args)
if _wrapper_enabled?
wrapped_hash = _wrap_parameters request.request_parameters
- wrapped_filtered_hash = _wrap_parameters request.filtered_parameters
+ wrapped_keys = request.request_parameters.keys
+ wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
# This will make the wrapped hash accessible from controller and view
request.parameters.merge! wrapped_hash
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index dee7eb1ec8..ee0e69d87c 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -18,13 +18,12 @@ 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.
# Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
#
- # Examples:
# redirect_to :action => "show", :id => 5
# redirect_to post
# redirect_to "http://www.rubyonrails.org"
@@ -35,7 +34,6 @@ module ActionController
#
# The redirection happens as a "302 Moved" header unless otherwise specified.
#
- # Examples:
# redirect_to post_url(@post), :status => :found
# redirect_to :action=>'atom', :status => :moved_permanently
# redirect_to post_url(@post), :status => 301
@@ -45,20 +43,29 @@ module ActionController
# integer, or a symbol representing the downcased, underscored and symbolized description.
# Note that the status code must be a 3xx HTTP code, or redirection will not occur.
#
- # It is also possible to assign a flash message as part of the redirection. There are two special accessors for commonly used the flash names
+ # If you are using XHR requests other than GET or POST and redirecting after the
+ # request then some browsers will follow the redirect using the original request
+ # method. This may lead to undesirable behavior such as a double DELETE. To work
+ # around this you can return a <tt>303 See Other</tt> status code which will be
+ # followed using a GET request.
+ #
+ # redirect_to posts_url, :status => :see_other
+ # redirect_to :action => 'index', :status => 303
+ #
+ # It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
#
- # Examples:
# redirect_to post_url(@post), :alert => "Watch it, mister!"
# redirect_to post_url(@post), :status=> :found, :notice => "Pay attention to the road"
# redirect_to post_url(@post), :status => 301, :flash => { :updated_post_id => @post.id }
# redirect_to { :action=>'atom' }, :alert => "Something serious happened"
#
- # 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!") if options.nil?
+ raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
+ logger.debug { "Redirected by #{caller(1).first rescue "unknown"}" } if logger
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(options)
@@ -81,7 +88,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
@@ -92,7 +100,7 @@ module ActionController
_compute_redirect_to_location options.call
else
url_for(options)
- end.gsub(/[\r\n]/, '')
+ end.delete("\0\r\n")
end
end
end
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 0ad9dbeda9..1927c8bdc7 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
@@ -51,7 +49,6 @@ module ActionController
# is the value paired with its key and the second is the remaining
# hash of options passed to +render+.
#
- # === Example
# Create a csv renderer:
#
# ActionController::Renderers.add :csv do |obj, options|
@@ -79,7 +76,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
@@ -93,9 +90,14 @@ module ActionController
add :json do |json, options|
json = json.to_json(options) unless json.kind_of?(String)
- json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
- self.content_type ||= Mime::JSON
- json
+
+ if options[:callback].present?
+ self.content_type ||= Mime::JS
+ "#{options[:callback]}(#{json})"
+ else
+ self.content_type ||= Mime::JSON
+ json
+ end
end
add :js do |js, options|
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 70fd79bb8b..c5e7d4e357 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -14,7 +14,7 @@ module ActionController
def render(*args) #:nodoc:
raise ::AbstractController::DoubleRenderError if response_body
super
- self.content_type ||= Mime[formats.first].to_s
+ self.content_type ||= Mime[lookup_context.rendered_format].to_s
response_body
end
@@ -29,6 +29,10 @@ module ActionController
self.response_body = nil
end
+ def render_to_body(*)
+ super || " "
+ end
+
private
# Normalize arguments by catching blocks and setting them on :update.
@@ -44,6 +48,10 @@ module ActionController
options[:text] = options[:text].to_text
end
+ if options.delete(:nothing) || (options.key?(:text) && options[:text].nil?)
+ options[:text] = " "
+ end
+
if options[:status]
options[:status] = Rack::Utils.status_code(options[:status])
end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 4d016271ea..95b0e99ed5 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -17,7 +17,6 @@ module ActionController #:nodoc:
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
# which checks the token and resets the session if it doesn't match what was expected.
# A call to this method is generated for new \Rails applications by default.
- # You can customize the error message by editing public/422.html.
#
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
# value of this token must be added to every layout that renders forms by including
@@ -37,6 +36,10 @@ module ActionController #:nodoc:
config_accessor :request_forgery_protection_token
self.request_forgery_protection_token ||= :authenticity_token
+ # Controls how unverified request will be handled
+ config_accessor :request_forgery_protection_method
+ self.request_forgery_protection_method ||= :reset_session
+
# Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
config_accessor :allow_forgery_protection
self.allow_forgery_protection = true if allow_forgery_protection.nil?
@@ -48,8 +51,6 @@ module ActionController #:nodoc:
module ClassMethods
# Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
#
- # Example:
- #
# class FooController < ApplicationController
# protect_from_forgery :except => :index
#
@@ -64,8 +65,10 @@ module ActionController #:nodoc:
# Valid Options:
#
# * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
+ # * <tt>:with</tt> - Set the method to handle unverified request. Valid values: <tt>:exception</tt> and <tt>:reset_session</tt> (default).
def protect_from_forgery(options = {})
self.request_forgery_protection_token ||= :authenticity_token
+ self.request_forgery_protection_method = options.delete(:with) if options.key?(:with)
prepend_before_filter :verify_authenticity_token, options
end
end
@@ -74,15 +77,25 @@ module ActionController #:nodoc:
# The actual before_filter that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
unless verified_request?
- logger.debug "WARNING: Can't verify CSRF token authenticity" if logger
+ logger.warn "Can't verify CSRF token authenticity" if logger
handle_unverified_request
end
end
# This is the method that defines the application behavior when a request is found to be unverified.
- # By default, \Rails resets the session when it finds an unverified request.
+ # By default, \Rails uses <tt>request_forgery_protection_method</tt> when it finds an unverified request:
+ #
+ # * <tt>:reset_session</tt> - Resets the session.
+ # * <tt>:exception</tt>: - Raises ActionController::InvalidAuthenticityToken exception.
def handle_unverified_request
- reset_session
+ case request_forgery_protection_method
+ when :exception
+ raise ActionController::InvalidAuthenticityToken
+ when :reset_session
+ reset_session
+ else
+ raise ArgumentError, 'Invalid request forgery protection method, use :exception or :reset_session'
+ end
end
# Returns true or false if a request is verified. Checks:
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..83407846dc 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -53,7 +53,7 @@ module ActionController #:nodoc:
# end
# end
#
- # The same happens for PUT and DELETE requests.
+ # The same happens for PATCH/PUT and DELETE requests.
#
# === Nested resources
#
@@ -63,7 +63,7 @@ module ActionController #:nodoc:
#
# def create
# @project = Project.find(params[:project_id])
- # @task = @project.comments.build(params[:task])
+ # @task = @project.tasks.build(params[:task])
# flash[:notice] = 'Task was successfully created.' if @task.save
# respond_with(@project, @task)
# end
@@ -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>:
@@ -116,8 +116,9 @@ module ActionController #:nodoc:
class Responder
attr_reader :controller, :request, :format, :resource, :resources, :options
- ACTIONS_FOR_VERBS = {
+ DEFAULT_ACTIONS_FOR_VERBS = {
:post => :new,
+ :patch => :edit,
:put => :edit
}
@@ -133,7 +134,7 @@ module ActionController #:nodoc:
end
delegate :head, :render, :redirect_to, :to => :controller
- delegate :get?, :post?, :put?, :delete?, :to => :request
+ delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request
# Undefine :to_json and :to_yaml since it's defined on Object
undef_method(:to_json) if method_defined?(:to_json)
@@ -172,7 +173,7 @@ module ActionController #:nodoc:
# responds to :to_format and display it.
#
def to_format
- if get? || !has_errors?
+ if get? || !has_errors? || response_overridden?
default_render
else
display_errors
@@ -202,10 +203,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,11 +223,15 @@ 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
- @default_response.call(options)
+ if @default_response
+ @default_response.call(options)
+ else
+ controller.default_render(options)
+ end
end
# Display is just a shortcut to render a resource with the current format.
@@ -253,7 +256,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.
@@ -262,29 +265,23 @@ module ActionController #:nodoc:
resource.respond_to?(:errors) && !resource.errors.empty?
end
- # By default, render the <code>:edit</code> action for HTML requests with failure, unless
- # the verb is POST.
+ # By default, render the <code>:edit</code> action for HTML requests with errors, unless
+ # the verb was POST.
#
def default_action
- @action ||= ACTIONS_FOR_VERBS[request.request_method_symbol]
+ @action ||= DEFAULT_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", true) ? send("#{format}_resource_errors") : resource.errors
end
- # Delegate to proper empty resource method
- #
- def empty_resource
- send("empty_#{format}_resource")
+ def json_resource_errors
+ {:errors => resource.errors}
end
- # Return a valid empty JSON resource
- #
- def empty_json_resource
- "{}"
+ def response_overridden?
+ @default_response.present?
end
end
end
diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb
deleted file mode 100644
index 91d89ff9a4..0000000000
--- a/actionpack/lib/action_controller/metal/session_management.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module ActionController #:nodoc:
- module SessionManagement #:nodoc:
- extend ActiveSupport::Concern
-
- module ClassMethods
-
- end
- end
-end
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 5fe5334458..eeb37db2e7 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:
@@ -140,17 +139,17 @@ module ActionController #:nodoc:
# session or flash after the template starts rendering will not propagate
# to the client.
#
- # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+
+ # If you try to modify cookies, session or flash, an <tt>ActionDispatch::ClosedError</tt>
# will be raised, showing those objects are closed for modification.
#
# == Middlewares
#
# Middlewares that need to manipulate the body won't work with streaming.
# You should disable those middlewares whenever streaming in development
- # or production. For instance, +Rack::Bug+ won't work when streaming as it
+ # or production. For instance, <tt>Rack::Bug</tt> won't work when streaming as it
# needs to inject contents in the HTML body.
#
- # Also +Rack::Cache+ won't work with streaming as it does not support
+ # Also <tt>Rack::Cache</tt> won't work with streaming as it does not support
# streaming bodies yet. Whenever streaming Cache-Control is automatically
# set to "no-cache".
#
@@ -163,7 +162,7 @@ module ActionController #:nodoc:
# Currently, when an exception happens in development or production, Rails
# will automatically stream to the client:
#
- # "><script type="text/javascript">window.location = "/500.html"</script></html>
+ # "><script>window.location = "/500.html"</script></html>
#
# The first two characters (">) are required in case the exception happens
# while rendering attributes for a given tag. You can check the real cause
@@ -195,7 +194,7 @@ module ActionController #:nodoc:
# ==== Passenger
#
# To be described.
- #
+ #
module Streaming
extend ActiveSupport::Concern
@@ -217,7 +216,7 @@ module ActionController #:nodoc:
end
end
- # Call render_to_body if we are streaming instead of usual +render+.
+ # Call render_body if we are streaming instead of usual +render+.
def _render_template(options) #:nodoc:
if options.delete(:stream)
Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 08132b1900..e28c05cc2d 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -1,25 +1,22 @@
-# Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
-# the <tt>_routes</tt> method. Otherwise, an exception will be raised.
-#
-# In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
-# url options like the +host+. In order to do so, this module requires the host class
-# to implement +env+ and +request+, which need to be a Rack-compatible.
-#
-# Example:
-#
-# class RootUrl
-# include ActionController::UrlFor
-# include Rails.application.routes.url_helpers
-#
-# delegate :env, :request, :to => :controller
-#
-# def initialize(controller)
-# @controller = controller
-# @url = root_path # named route from the application.
-# end
-# end
-# =>
module ActionController
+ # Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
+ # the <tt>_routes</tt> method. Otherwise, an exception will be raised.
+ #
+ # In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
+ # url options like the +host+. In order to do so, this module requires the host class
+ # to implement +env+ and +request+, which need to be a Rack-compatible.
+ #
+ # class RootUrl
+ # include ActionController::UrlFor
+ # include Rails.application.routes.url_helpers
+ #
+ # delegate :env, :request, :to => :controller
+ #
+ # def initialize(controller)
+ # @controller = controller
+ # @url = root_path # named route from the application.
+ # end
+ # end
module UrlFor
extend ActiveSupport::Concern
@@ -42,6 +39,5 @@ module ActionController
@_url_options
end
end
-
end
end
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index f0c29825ba..851a2c4aee 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -3,38 +3,49 @@ require "action_controller"
require "action_dispatch/railtie"
require "action_view/railtie"
require "abstract_controller/railties/routes_helpers"
-require "action_controller/railties/paths"
+require "action_controller/railties/helpers"
module ActionController
- class Railtie < Rails::Railtie
+ class Railtie < Rails::Railtie #:nodoc:
config.action_controller = ActiveSupport::OrderedOptions.new
- initializer "action_controller.logger" do
- ActiveSupport.on_load(:action_controller) { self.logger ||= Rails.logger }
+ initializer "action_controller.assets_config", :group => :all do |app|
+ app.config.action_controller.assets_dir ||= app.config.paths["public"].first
end
- initializer "action_controller.initialize_framework_caches" do
- ActiveSupport.on_load(:action_controller) { self.cache_store ||= RAILS_CACHE }
+ initializer "action_controller.set_helpers_path" do |app|
+ ActionController::Helpers.helpers_path = app.helpers_paths
end
initializer "action_controller.set_configs" do |app|
paths = app.config.paths
options = app.config.action_controller
- options.assets_dir ||= paths["public"].first
+ options.logger ||= Rails.logger
+ options.cache_store ||= Rails.cache
+
options.javascripts_dir ||= paths["public/javascripts"].first
options.stylesheets_dir ||= paths["public/stylesheets"].first
options.page_cache_directory ||= paths["public"].first
- # make sure readers methods get compiled
+ # Ensure 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
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
- extend ::ActionController::Railties::Paths.with(app)
- options.each { |k,v| send("#{k}=", v) }
+ extend ::ActionController::Railties::Helpers
+
+ options.each do |k,v|
+ k = "#{k}="
+ if respond_to?(k)
+ send(k, v)
+ elsif !Base.respond_to?(k)
+ raise "Invalid option key: #{k}"
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/railties/helpers.rb b/actionpack/lib/action_controller/railties/helpers.rb
new file mode 100644
index 0000000000..3985c6b273
--- /dev/null
+++ b/actionpack/lib/action_controller/railties/helpers.rb
@@ -0,0 +1,22 @@
+module ActionController
+ module Railties
+ module Helpers
+ def inherited(klass)
+ super
+ return unless klass.respond_to?(:helpers_path=)
+
+ if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_helpers_paths) }
+ paths = namespace.railtie_helpers_paths
+ else
+ paths = ActionController::Helpers.helpers_path
+ end
+
+ klass.helpers_path = paths
+
+ if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
+ klass.helper :all
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb
deleted file mode 100644
index 699c44c62c..0000000000
--- a/actionpack/lib/action_controller/railties/paths.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-module ActionController
- module Railties
- module Paths
- def self.with(app)
- Module.new do
- define_method(:inherited) do |klass|
- super(klass)
-
- if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) }
- paths = namespace._railtie.paths["app/helpers"].existent
- else
- paths = app.config.helpers_paths
- end
-
- klass.helpers_path = paths
- if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
- klass.helper :all
- end
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index c11d676c5e..16a5decc62 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -2,8 +2,8 @@ require 'active_support/core_ext/module'
module ActionController
# The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
- # Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate
- # the view actions to a higher logical level. Example:
+ # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to
+ # a higher logical level.
#
# # routes
# resources :posts
@@ -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
@@ -30,7 +30,7 @@ module ActionController
JOIN = '_'.freeze
NEW = 'new'.freeze
- # The DOM class convention is to use the singular form of an object or class. Examples:
+ # The DOM class convention is to use the singular form of an object or class.
#
# dom_class(post) # => "post"
# dom_class(Person) # => "person"
@@ -45,7 +45,7 @@ module ActionController
end
# The DOM id convention is to use the singular form of an object or class with the id following an underscore.
- # If no id is found, prefix with "new_" instead. Examples:
+ # If no id is found, prefix with "new_" instead.
#
# dom_id(Post.find(45)) # => "post_45"
# dom_id(Post.new) # => "new_post"
@@ -53,6 +53,7 @@ module ActionController
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
#
# dom_id(Post.find(45), :edit) # => "edit_post_45"
+ # dom_id(Post.new, :custom) # => "custom_post"
def dom_id(record, prefix = nil)
if record_id = record_key_for_dom_id(record)
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
@@ -67,19 +68,14 @@ module ActionController
# This can be overwritten to customize the default generated string representation if desired.
# If you need to read back a key from a dom_id in order to query for the underlying database record,
# you should write a helper like 'person_record_from_dom_id' that will extract the key either based
- # on the default implementation (which just joins all key attributes with '-') or on your own
+ # on the default implementation (which just joins all key attributes with '_') or on your own
# overwritten version of the method. By default, this implementation passes the key string through a
# method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to
# make sure yourself that your dom ids are valid, in case you overwrite this method.
def record_key_for_dom_id(record)
record = record.to_model if record.respond_to?(:to_model)
key = record.to_key
- key ? sanitize_dom_id(key.join('_')) : key
- end
-
- # Replaces characters that are invalid in HTML DOM ids with valid ones.
- def sanitize_dom_id(candidate_id)
- candidate_id # TODO implement conversion to valid DOM id values
+ key ? key.join('_') : key
end
end
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index c8cf04bb69..028a8d3fba 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -20,20 +20,25 @@ module ActionController
ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
path = payload[:layout]
- @layouts[path] += 1
+ if path
+ @layouts[path] += 1
+ if path =~ /^layouts\/(.*)/
+ @layouts[$1] += 1
+ end
+ end
end
ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
path = payload[:virtual_path]
next unless path
partial = path =~ /^.*\/_[^\/]*$/
+
if partial
@partials[path] += 1
@partials[path.split("/").last] += 1
- @templates[path] += 1
- else
- @templates[path] += 1
end
+
+ @templates[path] += 1
end
end
@@ -51,11 +56,21 @@ module ActionController
# Asserts that the request was rendered with the appropriate template file or partials.
#
- # ==== Examples
- #
# # assert that the "new" view template was rendered
# assert_template "new"
#
+ # # assert that the exact template "admin/posts/new" was rendered
+ # assert_template %r{\Aadmin/posts/new\Z}
+ #
+ # # assert that the layout 'admin' was rendered
+ # assert_template :layout => 'admin'
+ # assert_template :layout => 'layouts/admin'
+ # assert_template :layout => :admin
+ #
+ # # assert that no layout was rendered
+ # assert_template :layout => nil
+ # assert_template :layout => false
+ #
# # assert that the "_customer" partial was rendered twice
# assert_template :partial => '_customer', :count => 2
#
@@ -67,25 +82,40 @@ module ActionController
#
# # assert that the "_customer" partial was rendered with a specific object
# assert_template :partial => '_customer', :locals => { :customer => @customer }
- #
def assert_template(options = {}, message = nil)
- validate_request!
+ # Force body to be read in case the
+ # template is being streamed
+ response.body
case options
- when NilClass, String, Symbol
+ when NilClass, String, Symbol, Regexp
options = options.to_s if Symbol === options
rendered = @templates
- msg = build_message(message,
- "expecting <?> but rendering with <?>",
- options, rendered.keys.join(', '))
- assert_block(msg) do
- if options.nil?
- @templates.blank?
- else
+ msg = message || sprintf("expecting <%s> but rendering with <%s>",
+ options.inspect, rendered.keys)
+ matches_template =
+ if options
rendered.any? { |t,num| t.match(options) }
+ else
+ @templates.blank?
end
- end
+ assert matches_template, msg
when Hash
+ if options.key?(:layout)
+ expected_layout = options[:layout]
+ msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
+ expected_layout, @layouts.keys)
+
+ case expected_layout
+ when String, Symbol
+ assert_includes @layouts.keys, expected_layout.to_s, msg
+ when Regexp
+ assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg)
+ when nil, false
+ assert(@layouts.empty?, msg)
+ end
+ end
+
if expected_partial = options[:partial]
if expected_locals = options[:locals]
actual_locals = @locals[expected_partial.to_s.sub(/^_/,'')]
@@ -94,33 +124,20 @@ module ActionController
end
elsif expected_count = options[:count]
actual_count = @partials[expected_partial]
- msg = build_message(message,
- "expecting ? to be rendered ? time(s) but rendered ? time(s)",
+ msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s 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 <?>",
+ msg = message || sprintf("expecting partial <%s> but action rendered <%s>",
options[:partial], @partials.keys)
- assert(@partials.include?(expected_partial), msg)
+ assert_includes @partials, expected_partial, msg
end
- else
+ elsif options.key?(:partial)
assert @partials.empty?,
"Expected no partials to be rendered"
end
+ else
+ raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil"
end
end
end
@@ -135,9 +152,6 @@ module ActionController
class Result < ::Array #:nodoc:
def to_s() join '/' end
- def self.new_escaped(strings)
- new strings.collect {|str| uri_parser.unescape str}
- end
end
def assign_parameters(routes, controller_path, action, parameters = {})
@@ -145,17 +159,23 @@ module ActionController
extra_keys = routes.extra_keys(parameters)
non_path_parameters = get? ? query_parameters : request_parameters
parameters.each do |key, value|
- if value.is_a? Fixnum
- value = value.to_s
- elsif value.is_a? Array
- value = Result.new(value.map { |v| v.is_a?(String) ? v.dup : v })
- elsif value.is_a? String
+ if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?))
+ value = value.map{ |v| v.duplicable? ? v.dup : v }
+ elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? })
+ value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }]
+ elsif value.frozen? && value.duplicable?
value = value.dup
end
if extra_keys.include?(key.to_sym)
non_path_parameters[key] = value
else
+ if value.is_a?(Array)
+ value = Result.new(value.map(&:to_param))
+ else
+ value = value.to_param
+ end
+
path_parameters[key.to_s] = value
end
end
@@ -180,7 +200,7 @@ module ActionController
@env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
@symbolized_path_params = nil
@method = @request_method = nil
- @fullpath = @ip = @remote_ip = nil
+ @fullpath = @ip = @remote_ip = @protocol = nil
@env['action_dispatch.request.query_parameters'] = {}
@set_cookies ||= {}
@set_cookies.update(Hash[cookie_jar.instance_variable_get("@set_cookies").map{ |k,o| [k,o[:value]] }])
@@ -229,7 +249,7 @@ module ActionController
# == Basic example
#
# Functional tests are written as follows:
- # 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
+ # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
# an HTTP request.
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
# the controller's HTTP response, the database contents, etc.
@@ -250,6 +270,13 @@ module ActionController
# end
# end
#
+ # You can also send a real document in the simulated HTTP request.
+ #
+ # def test_create
+ # json = {:book => { :title => "Love Hina" }}.to_json
+ # post :create, json
+ # end
+ #
# == Special instance variables
#
# ActionController::TestCase will also automatically provide the following instance
@@ -296,11 +323,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.
@@ -320,10 +347,15 @@ module ActionController
# == \Testing named routes
#
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
- # Example:
#
# assert_redirected_to page_url(:title => 'foo')
class TestCase < ActiveSupport::TestCase
+
+ # Use AS::TestCase for the base class when describing a model
+ register_spec_type(self) do |desc|
+ desc < ActionController::Base
+ end
+
module Behavior
extend ActiveSupport::Concern
include ActionDispatch::TestProcess
@@ -333,9 +365,20 @@ module ActionController
module ClassMethods
# Sets the controller class name. Useful if the name can't be inferred from test class.
- # Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>.
+ # Normalizes +controller_class+ before using.
+ #
+ # tests WidgetController
+ # tests :widget
+ # tests 'widget'
def tests(controller_class)
- self.controller_class = controller_class
+ case controller_class
+ when String, Symbol
+ self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize
+ when Class
+ self.controller_class = controller_class
+ else
+ raise ArgumentError, "controller class must be a String, Symbol, or Class"
+ end
end
def controller_class=(new_class)
@@ -352,9 +395,7 @@ module ActionController
end
def determine_default_controller_class(name)
- name.sub(/Test$/, '').constantize
- rescue NameError
- nil
+ name.sub(/Test$/, '').safe_constantize
end
def prepare_controller_class(new_class)
@@ -364,28 +405,33 @@ module ActionController
end
# Executes a request simulating GET HTTP method and set/volley the response
- def get(action, parameters = nil, session = nil, flash = nil)
- process(action, parameters, session, flash, "GET")
+ def get(action, *args)
+ process(action, "GET", *args)
end
# Executes a request simulating POST HTTP method and set/volley the response
- def post(action, parameters = nil, session = nil, flash = nil)
- process(action, parameters, session, flash, "POST")
+ def post(action, *args)
+ process(action, "POST", *args)
+ end
+
+ # Executes a request simulating PATCH HTTP method and set/volley the response
+ def patch(action, *args)
+ process(action, "PATCH", *args)
end
# Executes a request simulating PUT HTTP method and set/volley the response
- def put(action, parameters = nil, session = nil, flash = nil)
- process(action, parameters, session, flash, "PUT")
+ def put(action, *args)
+ process(action, "PUT", *args)
end
# Executes a request simulating DELETE HTTP method and set/volley the response
- def delete(action, parameters = nil, session = nil, flash = nil)
- process(action, parameters, session, flash, "DELETE")
+ def delete(action, *args)
+ process(action, "DELETE", *args)
end
# Executes a request simulating HEAD HTTP method and set/volley the response
def head(action, parameters = nil, session = nil, flash = nil)
- process(action, parameters, session, flash, "HEAD")
+ process(action, "HEAD", parameters, session, flash)
end
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@@ -401,9 +447,7 @@ module ActionController
def paramify_values(hash_or_array_or_value)
case hash_or_array_or_value
when Hash
- hash_or_array_or_value.each do |key, value|
- hash_or_array_or_value[key] = paramify_values(value)
- end
+ Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }]
when Array
hash_or_array_or_value.map {|i| paramify_values(i)}
when Rack::Test::UploadedFile
@@ -413,19 +457,20 @@ module ActionController
end
end
- def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
- # Ensure that numbers and symbols passed as params are converted to
- # proper params, as is the case when engaging rack.
- paramify_values(parameters)
+ def process(action, http_method = 'GET', *args)
+ check_required_ivars
+ http_method, args = handle_old_process_api(http_method, args)
- # Sanity check for required instance variables so we can give an
- # understandable error message.
- %w(@routes @controller @request @response).each do |iv_name|
- if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
- raise "#{iv_name} is nil: make sure you set it in your test's setup method."
- end
+ if args.first.is_a?(String) && http_method != 'HEAD'
+ @request.env['RAW_POST_DATA'] = args.shift
end
+ parameters, session, flash = args
+
+ # Ensure that numbers and symbols passed as params are converted to
+ # proper params, as is the case when engaging rack.
+ parameters = paramify_values(parameters) if html_format?(parameters)
+
@request.recycle!
@response.recycle!
@controller.response_body = nil
@@ -437,20 +482,18 @@ module ActionController
parameters ||= {}
controller_class_name = @controller.class.anonymous? ?
- "anonymous_controller" :
+ "anonymous" :
@controller.class.name.underscore.sub(/_controller$/, '')
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
- @request.session = ActionController::TestSession.new(session) if session
+ @request.session.update(session) if session
@request.session["flash"] = @request.flash.update(flash || {})
- @request.session["flash"].sweep
@controller.request = @request
- @controller.params.merge!(parameters)
build_request_uri(action, parameters)
@controller.class.class_eval { include Testing }
- @controller.recycle!
+ @controller.recycle!
@controller.process_with_new_base_test(@request, @response)
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
@request.session.delete('flash') if @request.session['flash'].blank?
@@ -473,11 +516,6 @@ module ActionController
end
end
- # Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
- def rescue_action_in_public!
- @request.remote_addr = '208.77.188.166' # example.com
- end
-
included do
include ActionController::TemplateAssertions
include ActionDispatch::Assertions
@@ -486,6 +524,26 @@ module ActionController
end
private
+ def check_required_ivars
+ # Sanity check for required instance variables so we can give an
+ # understandable error message.
+ [:@routes, :@controller, :@request, :@response].each do |iv_name|
+ if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
+ raise "#{iv_name} is nil: make sure you set it in your test's setup method."
+ end
+ end
+ end
+
+ def handle_old_process_api(http_method, args)
+ # 4.0: Remove this method.
+ if http_method.is_a?(Hash)
+ ActiveSupport::Deprecation.warn("TestCase#process now expects the HTTP method as second argument: process(action, http_method, params, session, flash)")
+ args.unshift(http_method)
+ http_method = args.last.is_a?(String) ? args.last : "GET"
+ end
+
+ [http_method, args]
+ end
def build_request_uri(action, parameters)
unless @request.env["PATH_INFO"]
@@ -503,6 +561,12 @@ module ActionController
@request.env["QUERY_STRING"] = query_string || ""
end
end
+
+ def html_format?(parameters)
+ return true unless parameters.is_a?(Hash)
+ format = Mime[parameters[:format]]
+ format.nil? || format.html?
+ end
end
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
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/node.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
index 22b3243104..4e1f016431 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
@@ -156,7 +156,7 @@ module HTML #:nodoc:
end
closing = ( scanner.scan(/\//) ? :close : nil )
- return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:-]+/)
+ return Text.new(parent, line, pos, content) unless name = scanner.scan(/[^\s!>\/]+/)
name.downcase!
unless closing
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
index eaefdc0f15..6b269e7a31 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
@@ -1,9 +1,12 @@
require 'set'
+require 'cgi'
require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/class/attribute_accessors'
module HTML
class Sanitizer
def sanitize(text, options = {})
+ validate_options(options)
return text unless sanitizeable?(text)
tokenize(text, options).join
end
@@ -26,6 +29,16 @@ module HTML
def process_node(node, result, options)
result << node.to_s
end
+
+ def validate_options(options)
+ if options[:tags] && !options[:tags].is_a?(Enumerable)
+ raise ArgumentError, "You should pass :tags as an Enumerable"
+ end
+
+ if options[:attributes] && !options[:attributes].is_a?(Enumerable)
+ raise ArgumentError, "You should pass :attributes as an Enumerable"
+ end
+ end
end
class FullSanitizer < Sanitizer
@@ -87,7 +100,7 @@ module HTML
self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto
feed svn urn aim rsync tag ssh sftp rtsp afs))
- # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept.
+ # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept.
self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse
border-color border-left-color border-right-color border-top-color clear color cursor direction display
elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height
@@ -170,7 +183,7 @@ module HTML
def contains_bad_protocols?(attr_name, value)
uri_attributes.include?(attr_name) &&
- (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase))
+ (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
end
end
end
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