Upgrade to Rails 2.2.0

As a side benefit, fix an (non-user-visible) bug in display_s5().
Also fixed a bug where removing orphaned pages did not expire cached summary pages.
This commit is contained in:
Jacques Distler 2008-10-27 01:47:01 -05:00
parent 39348c65c2
commit 7600aef48b
827 changed files with 123652 additions and 11027 deletions

View file

@ -56,74 +56,24 @@ module ActionController
# # assert that the redirection was to the named route login_url
# assert_redirected_to login_url
#
# # assert that the redirection was to the url for @customer
# assert_redirected_to @customer
#
def assert_redirected_to(options = {}, message=nil)
clean_backtrace do
assert_response(:redirect, message)
return true if options == @response.redirected_to
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
# Support partial arguments for hash redirections
if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
return true if options.all? {|(key, value)| @response.redirected_to[key] == value}
end
redirected_to_after_normalisation = normalize_argument_to_redirection(@response.redirected_to)
options_after_normalisation = normalize_argument_to_redirection(options)
begin
url = {}
original = { :expected => options, :actual => @response.redirected_to.is_a?(Symbol) ? @response.redirected_to : @response.redirected_to.dup }
original.each do |key, value|
if value.is_a?(Symbol)
value = @controller.respond_to?(value, true) ? @controller.send(value) : @controller.send("hash_for_#{value}_url")
end
unless value.is_a?(Hash)
request = case value
when NilClass then nil
when /^\w+:\/\// then recognized_request_for(%r{^(\w+://.*?(/|$|\?))(.*)$} =~ value ? $3 : nil)
else recognized_request_for(value)
end
value = request.path_parameters if request
end
if value.is_a?(Hash) # stringify 2 levels of hash keys
if name = value.delete(:use_route)
route = ActionController::Routing::Routes.named_routes[name]
value.update(route.parameter_shell)
end
value.stringify_keys!
value.values.select { |v| v.is_a?(Hash) }.collect { |v| v.stringify_keys! }
if key == :expected && value['controller'] == @controller.controller_name && original[:actual].is_a?(Hash)
original[:actual].stringify_keys!
value.delete('controller') if original[:actual]['controller'].nil? || original[:actual]['controller'] == value['controller']
end
end
if value.respond_to?(:[]) && value['controller']
value['controller'] = value['controller'].to_s
if key == :actual && value['controller'].first != '/' && !value['controller'].include?('/')
new_controller_path = ActionController::Routing.controller_relative_to(value['controller'], @controller.class.controller_path)
value['controller'] = new_controller_path if value['controller'] != new_controller_path && ActionController::Routing.possible_controllers.include?(new_controller_path) && @response.redirected_to.is_a?(Hash)
end
value['controller'] = value['controller'][1..-1] if value['controller'].first == '/' # strip leading hash
end
url[key] = value
end
@response_diff = url[:actual].diff(url[:expected]) if url[:actual]
msg = build_message(message, "expected a redirect to <?>, found one to <?>, a difference of <?> ", url[:expected], url[:actual], @response_diff)
assert_block(msg) do
url[:expected].keys.all? do |k|
if k == :controller then url[:expected][k] == ActionController::Routing.controller_relative_to(url[:actual][k], @controller.class.controller_path)
else parameterize(url[:expected][k]) == parameterize(url[:actual][k])
end
end
end
rescue ActionController::RoutingError # routing failed us, so match the strings only.
msg = build_message(message, "expected a redirect to <?>, found one to <?>", options, @response.redirect_url)
url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$}
eurl, epath, url, path = [options, @response.redirect_url].collect do |url|
u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url]
[u, (p.first == '/') ? p : '/' + p]
end.flatten
assert_equal(eurl, url, msg) if eurl && url
assert_equal(epath, path, msg) if epath && path
if redirected_to_after_normalisation != options_after_normalisation
flunk "Expected response to be a redirect to <#{options_after_normalisation}> but was a redirect to <#{redirected_to_after_normalisation}>"
end
end
end
@ -137,36 +87,37 @@ module ActionController
#
def assert_template(expected = nil, message=nil)
clean_backtrace do
rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
rendered = @response.rendered_template.to_s
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
assert_block(msg) do
if expected.nil?
!@response.rendered_with_file?
@response.rendered_template.blank?
else
expected == rendered
rendered.to_s.match(expected)
end
end
end
end
private
# Recognizes the route for a given path.
def recognized_request_for(path, request_method = nil)
path = "/#{path}" unless path.first == '/'
# Assume given controller
request = ActionController::TestRequest.new({}, {}, nil)
request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
request.path = path
ActionController::Routing::Routes.recognize(request)
request
end
# Proxy to to_param if the object will respond to it.
def parameterize(value)
value.respond_to?(:to_param) ? value.to_param : value
end
def normalize_argument_to_redirection(fragment)
after_routing = @controller.url_for(fragment)
if after_routing =~ %r{^\w+://.*}
after_routing
else
# FIXME - this should probably get removed.
if after_routing.first != '/'
after_routing = '/' + after_routing
end
@request.protocol + @request.host_with_port + after_routing
end
end
end
end
end

View file

@ -2,7 +2,7 @@ module ActionController
module Assertions
# Suite of assertions to test routes generated by Rails and the handling of requests made to them.
module RoutingAssertions
# Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
# Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
# match +path+. Basically, it asserts that Rails recognizes the route given by +expected_options+.
#
# Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
@ -10,32 +10,32 @@ module ActionController
# and a :method containing the required HTTP verb.
#
# # assert that POSTing to /items will call the create action on ItemsController
# assert_recognizes({:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post})
# assert_recognizes {:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post}
#
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
# to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
# extras argument, appending the query string on the path directly will not work. For example:
# extras argument, appending the query string on the path directly will not work. For example:
#
# # assert that a path of '/items/list/1?view=print' returns the correct options
# assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
# assert_recognizes {:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" }
#
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
#
# ==== Examples
# # Check the default route (i.e., the index action)
# assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
# assert_recognizes {:controller => 'items', :action => 'index'}, 'items'
#
# # Test a specific action
# assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list')
# assert_recognizes {:controller => 'items', :action => 'list'}, 'items/list'
#
# # Test an action with a parameter
# assert_recognizes({:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1')
# assert_recognizes {:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1'
#
# # Test a custom route
# assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1')
# assert_recognizes {:controller => 'items', :action => 'show', :id => '1'}, 'view/item1'
#
# # Check a Simply RESTful generated route
# assert_recognizes(list_items_url, 'items/list')
# assert_recognizes list_items_url, 'items/list'
def assert_recognizes(expected_options, path, extras={}, message=nil)
if path.is_a? Hash
request_method = path[:method]
@ -44,16 +44,16 @@ module ActionController
request_method = nil
end
clean_backtrace do
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
clean_backtrace do
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
request = recognized_request_for(path, request_method)
expected_options = expected_options.clone
extras.each_key { |key| expected_options.delete key } unless extras.nil?
expected_options.stringify_keys!
routing_diff = expected_options.diff(request.path_parameters)
msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
assert_block(msg) { request.path_parameters == expected_options }
end
@ -64,67 +64,67 @@ module ActionController
# a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
#
# The +defaults+ parameter is unused.
#
#
# ==== Examples
# # Asserts that the default action is generated for a route with no action
# assert_generates("/items", :controller => "items", :action => "index")
# assert_generates "/items", :controller => "items", :action => "index"
#
# # Tests that the list action is properly routed
# assert_generates("/items/list", :controller => "items", :action => "list")
# assert_generates "/items/list", :controller => "items", :action => "list"
#
# # Tests the generation of a route with a parameter
# assert_generates("/items/list/1", { :controller => "items", :action => "list", :id => "1" })
# assert_generates "/items/list/1", { :controller => "items", :action => "list", :id => "1" }
#
# # Asserts that the generated route gives us our custom route
# assert_generates "changesets/12", { :controller => 'scm', :action => 'show_diff', :revision => "12" }
def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
clean_backtrace do
clean_backtrace do
expected_path = "/#{expected_path}" unless expected_path[0] == ?/
# Load routes.rb if it hasn't been loaded.
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, defaults)
found_extras = options.reject {|k, v| ! extra_keys.include? k}
msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
assert_block(msg) { found_extras == extras }
msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
expected_path)
assert_block(msg) { expected_path == generated_path }
end
end
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
# <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines +assert_recognizes+
# and +assert_generates+ into one step.
#
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
# +message+ parameter allows you to specify a custom error message to display upon failure.
# +message+ parameter allows you to specify a custom error message to display upon failure.
#
# ==== Examples
# # Assert a basic route: a controller with the default action (index)
# assert_routing('/home', :controller => 'home', :action => 'index')
# assert_routing '/home', :controller => 'home', :action => 'index'
#
# # Test a route generated with a specific controller, action, and parameter (id)
# assert_routing('/entries/show/23', :controller => 'entries', :action => 'show', id => 23)
# assert_routing '/entries/show/23', :controller => 'entries', :action => 'show', id => 23
#
# # Assert a basic route (controller + default action), with an error message if it fails
# assert_routing('/store', { :controller => 'store', :action => 'index' }, {}, {}, 'Route for store index not generated properly')
# assert_routing '/store', { :controller => 'store', :action => 'index' }, {}, {}, 'Route for store index not generated properly'
#
# # Tests a route, providing a defaults hash
# assert_routing 'controller/action/9', {:id => "9", :item => "square"}, {:controller => "controller", :action => "action"}, {}, {:item => "square"}
#
# # Tests a route with a HTTP method
# assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" })
# assert_routing { :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" }
def assert_routing(path, options, defaults={}, extras={}, message=nil)
assert_recognizes(options, path, extras, message)
controller, default_controller = options[:controller], defaults[:controller]
controller, default_controller = options[:controller], defaults[:controller]
if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
options[:controller] = "/#{controller}"
end
assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message)
end

View file

@ -21,10 +21,8 @@ module ActionController
# from the response HTML or elements selected by the enclosing assertion.
#
# In addition to HTML responses, you can make the following assertions:
# * +assert_select_rjs+ - Assertions on HTML content of RJS update and
# insertion operations.
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML,
# for example for dealing with feed item descriptions.
# * +assert_select_rjs+ - Assertions on HTML content of RJS update and insertion operations.
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions.
# * +assert_select_email+ - Assertions on the HTML body of an e-mail.
#
# Also see HTML::Selector to learn how to use selectors.
@ -451,7 +449,13 @@ module ActionController
matches
else
# RJS statement not found.
flunk args.shift || "No RJS statement that replaces or inserts HTML content."
case rjs_type
when :remove, :show, :hide, :toggle
flunk_message = "No RJS statement that #{rjs_type.to_s}s '#{id}' was rendered."
else
flunk_message = "No RJS statement that replaces or inserts HTML content."
end
flunk args.shift || flunk_message
end
end

View file

@ -252,7 +252,7 @@ module ActionController #:nodoc:
#
# def do_something
# redirect_to(:action => "elsewhere") and return if monkeys.nil?
# render :action => "overthere" # won't be called unless monkeys is nil
# render :action => "overthere" # won't be called if monkeys is nil
# end
#
class Base
@ -260,10 +260,11 @@ module ActionController #:nodoc:
include StatusCodes
cattr_reader :protected_instance_variables
# Controller specific instance variables which will not be accessible inside views.
@@protected_view_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller
@action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params
@_flash @_response)
@@protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller
@action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params
@_flash @_response)
# Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
# and images to a dedicated asset server away from the main web server. Example:
@ -283,10 +284,9 @@ module ActionController #:nodoc:
@@debug_routes = true
cattr_accessor :debug_routes
# Indicates to Mongrel or Webrick whether to allow concurrent action
# processing. Your controller actions and any other code they call must
# also behave well when called from concurrent threads. Turned off by
# default.
# Indicates whether to allow concurrent action processing. Your
# controller actions and any other code they call must also behave well
# when called from concurrent threads. Turned off by default.
@@allow_concurrency = false
cattr_accessor :allow_concurrency
@ -347,10 +347,29 @@ module ActionController #:nodoc:
cattr_accessor :optimise_named_routes
self.optimise_named_routes = true
# Indicates whether the response format should be determined by examining the Accept HTTP header,
# or by using the simpler params + ajax rules.
#
# If this is set to +true+ (the default) then +respond_to+ and +Request#format+ will take the Accept
# header into account. If it is set to false then the request format will be determined solely
# by examining params[:format]. If params format is missing, the format will be either HTML or
# Javascript depending on whether the request is an AJAX request.
cattr_accessor :use_accept_header
self.use_accept_header = true
# Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode.
class_inheritable_accessor :allow_forgery_protection
self.allow_forgery_protection = true
# If you are deploying to a subdirectory, you will need to set
# <tt>config.action_controller.relative_url_root</tt>
# This defaults to ENV['RAILS_RELATIVE_URL_ROOT']
cattr_writer :relative_url_root
def self.relative_url_root
@@relative_url_root || ENV['RAILS_RELATIVE_URL_ROOT']
end
# Holds the request object that's primarily used to get environment variables through access like
# <tt>request.env["REQUEST_URI"]</tt>.
attr_internal :request
@ -373,16 +392,9 @@ module ActionController #:nodoc:
# directive. Values should always be specified as strings.
attr_internal :headers
# Holds the hash of variables that are passed on to the template class to be made available to the view. This hash
# is generated by taking a snapshot of all the instance variables in the current scope just before a template is rendered.
attr_accessor :assigns
# Returns the name of the action this controller is processing.
attr_accessor :action_name
# Templates that are exempt from layouts
@@exempt_from_layout = Set.new([/\.rjs$/])
class << self
# Factory for the standard create, process loop where the controller is discarded after processing.
def process(request, response) #:nodoc:
@ -408,28 +420,27 @@ module ActionController #:nodoc:
# By default, all methods defined in ActionController::Base and included modules are hidden.
# More methods can be hidden using <tt>hide_actions</tt>.
def hidden_actions
unless read_inheritable_attribute(:hidden_actions)
write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map(&:to_s))
end
read_inheritable_attribute(:hidden_actions)
read_inheritable_attribute(:hidden_actions) || write_inheritable_attribute(:hidden_actions, [])
end
# Hide each of the given methods from being callable as actions.
def hide_action(*names)
write_inheritable_attribute(:hidden_actions, hidden_actions | names.map(&:to_s))
write_inheritable_attribute(:hidden_actions, hidden_actions | names.map { |name| name.to_s })
end
## View load paths determine the bases from which template references can be made. So a call to
## render("test/template") will be looked up in the view load paths array and the closest match will be
## returned.
# View load paths determine the bases from which template references can be made. So a call to
# render("test/template") will be looked up in the view load paths array and the closest match will be
# returned.
def view_paths
@view_paths || superclass.view_paths
if defined? @view_paths
@view_paths
else
superclass.view_paths
end
end
def view_paths=(value)
@view_paths = value
ActionView::TemplateFinder.process_view_paths(value)
@view_paths = ActionView::Base.process_view_paths(value) if value
end
# Adds a view_path to the front of the view_paths array.
@ -440,9 +451,8 @@ module ActionController #:nodoc:
# ArticleController.prepend_view_path(["views/default", "views/custom"])
#
def prepend_view_path(path)
@view_paths = superclass.view_paths.dup if @view_paths.nil?
view_paths.unshift(*path)
ActionView::TemplateFinder.process_view_paths(path)
@view_paths = superclass.view_paths.dup if !defined?(@view_paths) || @view_paths.nil?
@view_paths.unshift(*path)
end
# Adds a view_path to the end of the view_paths array.
@ -454,8 +464,7 @@ module ActionController #:nodoc:
#
def append_view_path(path)
@view_paths = superclass.view_paths.dup if @view_paths.nil?
view_paths.push(*path)
ActionView::TemplateFinder.process_view_paths(path)
@view_paths.push(*path)
end
# Replace sensitive parameter data from the request log.
@ -507,38 +516,34 @@ module ActionController #:nodoc:
protected :filter_parameters
end
# Don't render layouts for templates with the given extensions.
def exempt_from_layout(*extensions)
regexps = extensions.collect do |extension|
extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
end
@@exempt_from_layout.merge regexps
end
delegate :exempt_from_layout, :to => 'ActionView::Base'
end
public
# Extracts the action_name from the request parameters and performs that action.
def process(request, response, method = :perform_action, *arguments) #:nodoc:
response.request = request
initialize_template_class(response)
assign_shortcuts(request, response)
initialize_current_url
assign_names
forget_variables_added_to_assigns
log_processing
send(method, *arguments)
assign_default_content_type_and_charset
response.request = request
response.prepare! unless component_request?
response
send_response
ensure
process_cleanup
end
# Returns a URL that has been rewritten according to the options hash and the defined Routes.
# (For doing a complete redirect, use redirect_to).
def send_response
response.prepare! unless component_request?
response
end
# Returns a URL that has been rewritten according to the options hash and the defined routes.
# (For doing a complete redirect, use +redirect_to+).
#
# <tt>url_for</tt> is used to:
#
@ -578,7 +583,15 @@ module ActionController #:nodoc:
# missing values in the current request's parameters. Routes attempts to guess when a value should and should not be
# taken from the defaults. There are a few simple rules on how this is performed:
#
# * If the controller name begins with a slash, no defaults are used: <tt>url_for :controller => '/home'</tt>
# * If the controller name begins with a slash no defaults are used:
#
# url_for :controller => '/home'
#
# In particular, a leading slash ensures no namespace is assumed. Thus,
# while <tt>url_for :controller => 'users'</tt> may resolve to
# <tt>Admin::UsersController</tt> if the current controller lives under
# that module, <tt>url_for :controller => '/users'</tt> ensures you link
# to <tt>::UsersController</tt> no matter what.
# * If the controller changes, the action will default to index unless provided
#
# The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the
@ -648,11 +661,11 @@ module ActionController #:nodoc:
# View load paths for controller.
def view_paths
@template.finder.view_paths
@template.view_paths
end
def view_paths=(value)
@template.finder.view_paths = value # Mutex needed
@template.view_paths = ActionView::Base.process_view_paths(value)
end
# Adds a view_path to the front of the view_paths array.
@ -662,7 +675,7 @@ module ActionController #:nodoc:
# self.prepend_view_path(["views/default", "views/custom"])
#
def prepend_view_path(path)
@template.finder.prepend_view_path(path) # Mutex needed
@template.view_paths.unshift(*path)
end
# Adds a view_path to the end of the view_paths array.
@ -672,7 +685,7 @@ module ActionController #:nodoc:
# self.append_view_path(["views/default", "views/custom"])
#
def append_view_path(path)
@template.finder.append_view_path(path) # Mutex needed
@template.view_paths.push(*path)
end
protected
@ -713,6 +726,9 @@ module ActionController #:nodoc:
# # builds the complete response.
# render :partial => "person", :collection => @winners
#
# # Renders a collection of partials but with a custom local variable name
# render :partial => "admin_person", :collection => @winners, :as => :person
#
# # Renders the same collection of partials, but also renders the
# # person_divider partial between each person partial.
# render :partial => "person", :collection => @winners, :spacer_template => "person_divider"
@ -760,9 +776,6 @@ module ActionController #:nodoc:
# render :file => "/path/to/some/template.erb", :layout => true, :status => 404
# render :file => "c:/path/to/some/template.erb", :layout => true, :status => 404
#
# # Renders a template relative to the template root and chooses the proper file extension
# render :file => "some/template", :use_full_path => true
#
# === Rendering text
#
# Rendering of text is usually used for tests or for rendering prepared content, such as a cache. By default, text
@ -842,7 +855,7 @@ module ActionController #:nodoc:
raise DoubleRenderError, "Can only render or redirect once per action" if performed?
if options.nil?
return render_for_file(default_template_name, nil, true)
return render(:file => default_template_name, :layout => true)
elsif !extra_options.is_a?(Hash)
raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}"
else
@ -853,6 +866,9 @@ module ActionController #:nodoc:
end
end
response.layout = layout = pick_layout(options)
logger.info("Rendering template within #{layout}") if logger && layout
if content_type = options[:content_type]
response.content_type = content_type.to_s
end
@ -862,27 +878,21 @@ module ActionController #:nodoc:
end
if options.has_key?(:text)
render_for_text(options[:text], options[:status])
text = layout ? @template.render(options.merge(:text => options[:text], :layout => layout)) : options[:text]
render_for_text(text, options[:status])
else
if file = options[:file]
render_for_file(file, options[:status], options[:use_full_path], options[:locals] || {})
render_for_file(file, options[:status], layout, options[:locals] || {})
elsif template = options[:template]
render_for_file(template, options[:status], true, options[:locals] || {})
render_for_file(template, options[:status], layout, options[:locals] || {})
elsif inline = options[:inline]
add_variables_to_assigns
tmpl = ActionView::InlineTemplate.new(@template, options[:inline], options[:locals], options[:type])
render_for_text(@template.render_template(tmpl), options[:status])
render_for_text(@template.render(options.merge(:layout => layout)), options[:status])
elsif action_name = options[:action]
template = default_template_name(action_name.to_s)
if options[:layout] && !template_exempt_from_layout?(template)
render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true)
else
render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true)
end
render_for_file(default_template_name(action_name.to_s), options[:status], layout)
elsif xml = options[:xml]
response.content_type ||= Mime::XML
@ -894,36 +904,26 @@ module ActionController #:nodoc:
response.content_type ||= Mime::JSON
render_for_text(json, options[:status])
elsif partial = options[:partial]
partial = default_template_name if partial == true
add_variables_to_assigns
if collection = options[:collection]
render_for_text(
@template.send!(:render_partial_collection, partial, collection,
options[:spacer_template], options[:locals]), options[:status]
)
elsif options[:partial]
options[:partial] = default_template_name if options[:partial] == true
if layout
render_for_text(@template.render(:text => @template.render(options), :layout => layout), options[:status])
else
render_for_text(
@template.send!(:render_partial, partial,
ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]), options[:status]
)
render_for_text(@template.render(options), options[:status])
end
elsif options[:update]
add_variables_to_assigns
@template.send! :evaluate_assigns
@template.send(:_evaluate_assigns_and_ivars)
generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
response.content_type = Mime::JS
render_for_text(generator.to_s, options[:status])
elsif options[:nothing]
# Safari doesn't pass the headers of the return if the response is zero length
render_for_text(" ", options[:status])
render_for_text(nil, options[:status])
else
render_for_file(default_template_name, options[:status], true)
render_for_file(default_template_name, options[:status], layout)
end
end
end
@ -934,7 +934,6 @@ module ActionController #:nodoc:
render(options, &block)
ensure
erase_render_results
forget_variables_added_to_assigns
reset_variables_added_to_assigns
end
@ -966,7 +965,6 @@ module ActionController #:nodoc:
render :nothing => true, :status => status
end
# Clears the rendered results, allowing for another render to be performed.
def erase_render_results #:nodoc:
response.body = nil
@ -1051,26 +1049,73 @@ module ActionController #:nodoc:
status = 302
end
response.redirected_to= options
logger.info("Redirected to #{options}") if logger && logger.info?
case options
when %r{^\w+://.*}
raise DoubleRenderError if performed?
logger.info("Redirected to #{options}") if logger && logger.info?
response.redirect(options, interpret_status(status))
response.redirected_to = options
@performed_redirect = true
redirect_to_full_url(options, status)
when String
redirect_to(request.protocol + request.host_with_port + options, :status=>status)
redirect_to_full_url(request.protocol + request.host_with_port + options, status)
when :back
request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"], :status=>status) : raise(RedirectBackError)
when Hash
redirect_to(url_for(options), :status=>status)
response.redirected_to = options
if referer = request.headers["Referer"]
redirect_to(referer, :status=>status)
else
raise RedirectBackError
end
else
redirect_to(url_for(options), :status=>status)
redirect_to_full_url(url_for(options), status)
end
end
def redirect_to_full_url(url, status)
raise DoubleRenderError if performed?
response.redirect(url, interpret_status(status))
@performed_redirect = true
end
# Sets the etag and/or last_modified on the response and checks it against
# the client request. If the request doesn't match the options provided, the
# request is considered stale and should be generated from scratch. Otherwise,
# it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent.
#
# Example:
#
# def show
# @article = Article.find(params[:id])
#
# if stale?(:etag => @article, :last_modified => @article.created_at.utc)
# @statistics = @article.really_expensive_call
# respond_to do |format|
# # all the supported formats
# end
# end
# end
def stale?(options)
fresh_when(options)
!request.fresh?(response)
end
# Sets the etag, last_modified, or both on the response and renders a
# "304 Not Modified" response if the request is already fresh.
#
# Example:
#
# def show
# @article = Article.find(params[:id])
# fresh_when(:etag => @article, :last_modified => @article.created_at.utc)
# end
#
# This will render the show template if the request isn't sending a matching etag or
# If-Modified-Since header and just a "304 Not Modified" response if there's a match.
def fresh_when(options)
options.assert_valid_keys(:etag, :last_modified)
response.etag = options[:etag] if options[:etag]
response.last_modified = options[:last_modified] if options[:last_modified]
if request.fresh?(response)
head :not_modified
end
end
@ -1106,10 +1151,9 @@ module ActionController #:nodoc:
private
def render_for_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc:
add_variables_to_assigns
def render_for_file(template_path, status = nil, layout = nil, locals = {}) #:nodoc:
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
render_for_text(@template.render_file(template_path, use_full_path, locals), status)
render_for_text @template.render(:file => template_path, :locals => locals, :layout => layout), status
end
def render_for_text(text = nil, status = nil, append_response = false) #:nodoc:
@ -1121,13 +1165,17 @@ module ActionController #:nodoc:
response.body ||= ''
response.body << text.to_s
else
response.body = text.is_a?(Proc) ? text : text.to_s
response.body = case text
when Proc then text
when nil then " " # Safari doesn't pass the headers of the return if the response is zero length
else text.to_s
end
end
end
def initialize_template_class(response)
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
response.template.extend self.class.master_helper_module
response.template.helpers.send :include, self.class.master_helper_module
response.redirected_to = nil
@performed_render = @performed_redirect = false
end
@ -1140,7 +1188,6 @@ module ActionController #:nodoc:
@_session = @_response.session
@template = @_response.template
@assigns = @_response.template.assigns
@_headers = @_response.headers
end
@ -1162,16 +1209,16 @@ module ActionController #:nodoc:
end
def perform_action
if self.class.action_methods.include?(action_name)
if action_methods.include?(action_name)
send(action_name)
default_render unless performed?
elsif respond_to? :method_missing
method_missing action_name
default_render unless performed?
elsif template_exists? && template_public?
elsif template_exists?
default_render
else
raise UnknownAction, "No action responded to #{action_name}", caller
raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller
end
end
@ -1184,43 +1231,30 @@ module ActionController #:nodoc:
end
def assign_default_content_type_and_charset
response.content_type ||= Mime::HTML
response.charset ||= self.class.default_charset unless sending_file?
end
def sending_file?
response.headers["Content-Transfer-Encoding"] == "binary"
response.assign_default_content_type_and_charset!
end
deprecate :assign_default_content_type_and_charset => :'response.assign_default_content_type_and_charset!'
def action_methods
self.class.action_methods
end
def self.action_methods
@action_methods ||= Set.new(public_instance_methods.map(&:to_s)) - hidden_actions
end
def add_variables_to_assigns
unless @variables_added
add_instance_variables_to_assigns
@variables_added = true
end
end
def forget_variables_added_to_assigns
@variables_added = nil
@action_methods ||=
# All public instance methods of this class, including ancestors
public_instance_methods(true).map { |m| m.to_s }.to_set -
# Except for public instance methods of Base and its ancestors
Base.public_instance_methods(true).map { |m| m.to_s } +
# Be sure to include shadowed public instance methods of this class
public_instance_methods(false).map { |m| m.to_s } -
# And always exclude explicitly hidden actions
hidden_actions
end
def reset_variables_added_to_assigns
@template.instance_variable_set("@assigns_added", nil)
end
def add_instance_variables_to_assigns
(instance_variable_names - @@protected_view_variables).each do |var|
@assigns[var[1..-1]] = instance_variable_get(var)
end
end
def request_origin
# this *needs* to be cached!
# otherwise you'd get different results if calling it more than once
@ -1236,17 +1270,9 @@ module ActionController #:nodoc:
end
def template_exists?(template_name = default_template_name)
@template.finder.file_exists?(template_name)
end
def template_public?(template_name = default_template_name)
@template.file_public?(template_name)
end
def template_exempt_from_layout?(template_name = default_template_name)
extension = @template && @template.finder.pick_template_extension(template_name)
name_with_extension = !template_name.include?('.') && extension ? "#{template_name}.#{extension}" : template_name
@@exempt_from_layout.any? { |ext| name_with_extension =~ ext }
@template.send(:_pick_template, template_name) ? true : false
rescue ActionView::MissingTemplate
false
end
def default_template_name(action_name = self.action_name)
@ -1256,7 +1282,7 @@ module ActionController #:nodoc:
action_name = strip_out_controller(action_name)
end
end
"#{self.class.controller_path}/#{action_name}"
"#{self.controller_path}/#{action_name}"
end
def strip_out_controller(path)
@ -1264,7 +1290,7 @@ module ActionController #:nodoc:
end
def template_path_includes_controller?(path)
self.class.controller_path.split('/')[-1] == path.split('/')[0]
self.controller_path.split('/')[-1] == path.split('/')[0]
end
def process_cleanup

View file

@ -24,7 +24,7 @@ module ActionController #:nodoc:
if logger && logger.level == log_level
result = nil
seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
logger.add(log_level, "#{title} (#{('%.1f' % (seconds * 1000))}ms)")
result
else
yield
@ -42,53 +42,66 @@ module ActionController #:nodoc:
protected
def render_with_benchmark(options = nil, extra_options = {}, &block)
unless logger
render_without_benchmark(options, extra_options, &block)
else
db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
if logger
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
db_runtime = ActiveRecord::Base.connection.reset_runtime
end
render_output = nil
@rendering_runtime = Benchmark::realtime{ render_output = render_without_benchmark(options, extra_options, &block) }
@view_runtime = Benchmark::realtime { render_output = render_without_benchmark(options, extra_options, &block) }
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
@db_rt_before_render = db_runtime
@db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
@rendering_runtime -= @db_rt_after_render
@view_runtime -= @db_rt_after_render
end
render_output
else
render_without_benchmark(options, extra_options, &block)
end
end
private
def perform_action_with_benchmark
unless logger
perform_action_without_benchmark
else
runtime = [ Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001 ].max
if logger
seconds = [ Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001 ].max
logging_view = defined?(@view_runtime)
logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
log_message = "Completed in #{sprintf("%.0f", seconds * 1000)}ms"
if logging_view || logging_active_record
log_message << " ("
log_message << view_runtime if logging_view
if logging_active_record
log_message << ", " if logging_view
log_message << active_record_runtime + ")"
else
")"
end
end
log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
log_message << rendering_runtime(runtime) if defined?(@rendering_runtime)
log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
log_message << " | #{headers["Status"]}"
log_message << " [#{complete_request_uri rescue "unknown"}]"
logger.info(log_message)
response.headers["X-Runtime"] = sprintf("%.5f", runtime)
response.headers["X-Runtime"] = "#{sprintf("%.0f", seconds * 1000)}ms"
else
perform_action_without_benchmark
end
end
def rendering_runtime(runtime)
percentage = @rendering_runtime * 100 / runtime
" | Rendering: %.5f (%d%%)" % [@rendering_runtime, percentage.to_i]
def view_runtime
"View: %.0f" % (@view_runtime * 1000)
end
def active_record_runtime(runtime)
db_runtime = ActiveRecord::Base.connection.reset_runtime
db_runtime += @db_rt_before_render if @db_rt_before_render
db_runtime += @db_rt_after_render if @db_rt_after_render
db_percentage = db_runtime * 100 / runtime
" | DB: %.5f (%d%%)" % [db_runtime, db_percentage.to_i]
def active_record_runtime
db_runtime = ActiveRecord::Base.connection.reset_runtime
db_runtime += @db_rt_before_render if @db_rt_before_render
db_runtime += @db_rt_after_render if @db_rt_after_render
"DB: %.0f" % (db_runtime * 1000)
end
end
end

View file

@ -27,19 +27,23 @@ module ActionController #:nodoc:
# You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. 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 :if to pass a Proc that specifies when the action should be cached.
# And you can also use :if (or :unless) to pass a Proc that specifies when the action should be cached.
#
# Finally, if you are using memcached, you can also pass :expires_in.
#
# class ListsController < ApplicationController
# before_filter :authenticate, :except => :public
# caches_page :public
# caches_action :index, :if => Proc.new { |c| !c.request.format.json? } # cache if is not a JSON request
# caches_action :show, :cache_path => { :project => 1 }
# caches_action :show, :cache_path => { :project => 1 }, :expires_in => 1.hour
# caches_action :feed, :cache_path => Proc.new { |controller|
# controller.params[:user_id] ?
# controller.send(:user_list_url, c.params[:user_id], c.params[:id]) :
# controller.send(:list_url, c.params[:id]) }
# controller.send(:user_list_url, controller.params[:user_id], controller.params[:id]) :
# controller.send(:list_url, controller.params[:id]) }
# end
#
# If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information.
#
module Actions
def self.included(base) #:nodoc:
base.extend(ClassMethods)
@ -54,7 +58,10 @@ module ActionController #:nodoc:
def caches_action(*actions)
return unless cache_configured?
options = actions.extract_options!
around_filter(ActionCacheFilter.new(:cache_path => options.delete(:cache_path)), {:only => actions}.merge(options))
filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) }
cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options)
around_filter(cache_filter, filter_options)
end
end
@ -64,10 +71,10 @@ module ActionController #:nodoc:
if options[:action].is_a?(Array)
options[:action].dup.each do |action|
expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action })))
expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action }), false))
end
else
expire_fragment(ActionCachePath.path_for(self, options))
expire_fragment(ActionCachePath.path_for(self, options, false))
end
end
@ -77,11 +84,13 @@ module ActionController #:nodoc:
end
def before(controller)
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options))
if cache = controller.read_fragment(cache_path.path)
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path)))
if cache = controller.read_fragment(cache_path.path, @options[:store_options])
controller.rendered_action_cache = true
set_content_type!(controller, cache_path.extension)
controller.send!(:render_for_text, cache)
options = { :text => cache }
options.merge!(:layout => true) if cache_layout?
controller.__send__(:render, options)
false
else
controller.action_cache_path = cache_path
@ -90,7 +99,8 @@ module ActionController #:nodoc:
def after(controller)
return if controller.rendered_action_cache || !caching_allowed(controller)
controller.write_fragment(controller.action_cache_path.path, controller.response.body)
action_content = cache_layout? ? content_for_layout(controller) : controller.response.body
controller.write_fragment(controller.action_cache_path.path, action_content, @options[:store_options])
end
private
@ -105,22 +115,38 @@ module ActionController #:nodoc:
def caching_allowed(controller)
controller.request.get? && controller.response.headers['Status'].to_i == 200
end
def cache_layout?
@options[:layout] == false
end
def content_for_layout(controller)
controller.response.layout && controller.response.template.instance_variable_get('@cached_content_for_layout')
end
end
class ActionCachePath
attr_reader :path, :extension
class << self
def path_for(controller, options)
new(controller, options).path
def path_for(controller, options, infer_extension=true)
new(controller, options, infer_extension).path
end
end
def initialize(controller, options = {})
@extension = extract_extension(controller.request.path)
# When true, infer_extension will look up the cache path extension from the request's path & format.
# This is desirable when reading and writing the cache, but not when expiring the cache - expire_action should expire the same files regardless of the request format.
def initialize(controller, options = {}, infer_extension=true)
if infer_extension and options.is_a? Hash
request_extension = extract_extension(controller.request)
options = options.reverse_merge(:format => request_extension)
end
path = controller.url_for(options).split('://').last
normalize!(path)
add_extension!(path, @extension)
if infer_extension
@extension = request_extension
add_extension!(path, @extension)
end
@path = URI.unescape(path)
end
@ -130,13 +156,19 @@ module ActionController #:nodoc:
end
def add_extension!(path, extension)
path << ".#{extension}" if extension
path << ".#{extension}" if extension and !path.ends_with?(extension)
end
def extract_extension(file_path)
def extract_extension(request)
# Don't want just what comes after the last '.' to accommodate multi part extensions
# such as tar.gz.
file_path[/^[^.]+\.(.+)$/, 1]
extension = request.path[/^[^.]+\.(.+)$/, 1]
# If there's no extension in the path, check request.format
if extension.nil?
extension = request.cache_format
end
extension
end
end
end

View file

@ -2,7 +2,7 @@ module ActionController #:nodoc:
module Caching
# Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when
# certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple
# parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like:
# parties. The caching is done using the cache helper available in the Action View. A template with caching might look something like:
#
# <b>Hello <%= @name %></b>
# <% cache do %>
@ -26,32 +26,6 @@ module ActionController #:nodoc:
#
# expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
module Fragments
def self.included(base) #:nodoc:
base.class_eval do
class << self
def fragment_cache_store=(store_option) #:nodoc:
ActiveSupport::Deprecation.warn('The fragment_cache_store= method is now use cache_store=')
self.cache_store = store_option
end
def fragment_cache_store #:nodoc:
ActiveSupport::Deprecation.warn('The fragment_cache_store method is now use cache_store')
cache_store
end
end
def fragment_cache_store=(store_option) #:nodoc:
ActiveSupport::Deprecation.warn('The fragment_cache_store= method is now use cache_store=')
self.cache_store = store_option
end
def fragment_cache_store #:nodoc:
ActiveSupport::Deprecation.warn('The fragment_cache_store method is now use cache_store')
cache_store
end
end
end
# Given a key (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,
# writing, or expiring a cached fragment. If the key is a hash, the generated key is the return
# value of url_for on that hash (without the protocol). All keys are prefixed with "views/" and uses
@ -60,17 +34,17 @@ module ActionController #:nodoc:
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
end
def fragment_for(block, name = {}, options = nil) #:nodoc:
unless perform_caching then block.call; return end
buffer = yield
if cache = read_fragment(name, options)
buffer.concat(cache)
def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
if perform_caching
if cache = read_fragment(name, options)
buffer.concat(cache)
else
pos = buffer.length
block.call
write_fragment(name, buffer[pos..-1], options)
end
else
pos = buffer.length
block.call
write_fragment(name, buffer[pos..-1], options)
end
end

View file

@ -83,13 +83,13 @@ module ActionController #:nodoc:
controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
send!(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
send!(action_callback_method_name) if respond_to?(action_callback_method_name, true)
__send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
__send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
end
def method_missing(method, *arguments)
return if @controller.nil?
@controller.send!(method, *arguments)
@controller.__send__(method, *arguments)
end
end
end

View file

@ -6,28 +6,8 @@ class CGI #:nodoc:
# * Expose the CGI instance to session stores.
# * Don't require 'digest/md5' whenever a new session id is generated.
class Session #:nodoc:
begin
require 'securerandom'
# Generate a 32-character unique id using SecureRandom.
# This is used to generate session ids but may be reused elsewhere.
def self.generate_unique_id(constant = nil)
SecureRandom.hex(16)
end
rescue LoadError
# Generate an 32-character unique id based on a hash of the current time,
# a random number, the process id, and a constant string. This is used
# to generate session ids but may be reused elsewhere.
def self.generate_unique_id(constant = 'foobar')
md5 = Digest::MD5.new
now = Time.now
md5 << now.to_s
md5 << String(now.usec)
md5 << String(rand(0))
md5 << String($$)
md5 << constant
md5.hexdigest
end
def self.generate_unique_id(constant = nil)
ActiveSupport::SecureRandom.hex(16)
end
# Make the CGI instance available to session stores.

View file

@ -42,13 +42,14 @@ module ActionController #:nodoc:
:prefix => "ruby_sess.", # prefix session file names
:session_path => "/", # available to all paths in app
:session_key => "_session_id",
:cookie_only => true
} unless const_defined?(:DEFAULT_SESSION_OPTIONS)
:cookie_only => true,
:session_http_only=> true
}
def initialize(cgi, session_options = {})
@cgi = cgi
@session_options = session_options
@env = @cgi.send!(:env_table)
@env = @cgi.__send__(:env_table)
super()
end
@ -61,53 +62,14 @@ module ActionController #:nodoc:
end
end
# The request body is an IO input stream. If the RAW_POST_DATA environment
# variable is already set, wrap it in a StringIO.
def body
if raw_post = env['RAW_POST_DATA']
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
StringIO.new(raw_post)
else
@cgi.stdinput
end
end
def query_parameters
@query_parameters ||= self.class.parse_query_parameters(query_string)
end
def request_parameters
@request_parameters ||= parse_formatted_request_parameters
def body_stream #:nodoc:
@cgi.stdinput
end
def cookies
@cgi.cookies.freeze
end
def host_with_port_without_standard_port_handling
if forwarded = env["HTTP_X_FORWARDED_HOST"]
forwarded.split(/,\s?/).last
elsif http_host = env['HTTP_HOST']
http_host
elsif server_name = env['SERVER_NAME']
server_name
else
"#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
end
end
def host
host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
end
def port
if host_with_port_without_standard_port_handling =~ /:(\d+)$/
$1.to_i
else
standard_port
end
end
def session
unless defined?(@session)
if @session_options == false
@ -146,7 +108,7 @@ module ActionController #:nodoc:
end
def method_missing(method_id, *arguments)
@cgi.send!(method_id, *arguments) rescue super
@cgi.__send__(method_id, *arguments) rescue super
end
private
@ -203,7 +165,7 @@ end_msg
begin
output.write(@cgi.header(@headers))
if @cgi.send!(:env_table)['REQUEST_METHOD'] == 'HEAD'
if @cgi.__send__(:env_table)['REQUEST_METHOD'] == 'HEAD'
return
elsif @body.respond_to?(:call)
# Flush the output now in case the @body Proc uses

View file

@ -38,6 +38,7 @@ module ActionController #:nodoc:
def self.included(base) #:nodoc:
base.class_eval do
include InstanceMethods
include ActiveSupport::Deprecation
extend ClassMethods
helper HelperMethods
@ -64,7 +65,7 @@ module ActionController #:nodoc:
module HelperMethods
def render_component(options)
@controller.send!(:render_component_as_string, options)
@controller.__send__(:render_component_as_string, options)
end
end
@ -82,6 +83,7 @@ module ActionController #:nodoc:
render_for_text(component_response(options, true).body, response.headers["Status"])
end
end
deprecate :render_component => "Please install render_component plugin from http://github.com/rails/render_component/tree/master"
# Returns the component response as a string
def render_component_as_string(options) #:doc:
@ -95,6 +97,7 @@ module ActionController #:nodoc:
end
end
end
deprecate :render_component_as_string => "Please install render_component plugin from http://github.com/rails/render_component/tree/master"
def flash_with_components(refresh = false) #:nodoc:
if !defined?(@_flash) || refresh

View file

@ -22,6 +22,16 @@ module ActionController #:nodoc:
#
# cookies.delete :user_name
#
# Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
#
# cookies[:key] = {
# :value => 'a yummy cookie',
# :expires => 1.year.from_now,
# :domain => 'domain.com'
# }
#
# cookies.delete(:key, :domain => 'domain.com')
#
# The option symbols for setting cookies are:
#
# * <tt>:value</tt> - The cookie's value or list of values (as an array).

View file

@ -22,11 +22,12 @@ module ActionController
end
if defined?(ActiveRecord)
after_dispatch :checkin_connections
before_dispatch { ActiveRecord::Base.verify_active_connections! }
to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers }
end
after_dispatch :flush_logger if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush)
after_dispatch :flush_logger if Base.logger && Base.logger.respond_to?(:flush)
end
# Backward-compatible class method takes CGI-specific args. Deprecated
@ -38,7 +39,7 @@ module ActionController
# Add a preparation callback. Preparation callbacks are run before every
# request in development mode, and before the first request in production
# mode.
#
#
# An optional identifier may be supplied for the callback. If provided,
# to_prepare may be called again with the same identifier to replace the
# existing callback. Passing an identifier is a suggested practice if the
@ -46,7 +47,7 @@ module ActionController
def to_prepare(identifier = nil, &block)
@prepare_dispatch_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new
callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier)
@prepare_dispatch_callbacks | callback
@prepare_dispatch_callbacks.replace_or_append!(callback)
end
# If the block raises, send status code as a last-ditch response.
@ -96,19 +97,27 @@ module ActionController
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
def initialize(output, request = nil, response = nil)
def initialize(output = $stdout, request = nil, response = nil)
@output, @request, @response = output, request, response
end
def dispatch_unlocked
begin
run_callbacks :before_dispatch
handle_request
rescue Exception => exception
failsafe_rescue exception
ensure
run_callbacks :after_dispatch, :enumerator => :reverse_each
end
end
def dispatch
@@guard.synchronize do
begin
run_callbacks :before_dispatch
handle_request
rescue Exception => exception
failsafe_rescue exception
ensure
run_callbacks :after_dispatch, :enumerator => :reverse_each
if ActionController::Base.allow_concurrency
dispatch_unlocked
else
@@guard.synchronize do
dispatch_unlocked
end
end
end
@ -123,12 +132,19 @@ module ActionController
failsafe_rescue exception
end
def call(env)
@request = RackRequest.new(env)
@response = RackResponse.new(@request)
dispatch
end
def reload_application
# Run prepare callbacks before every request in development mode
run_callbacks :prepare_dispatch
Routing::Routes.reload
ActionView::TemplateFinder.reload! unless ActionView::Base.cache_template_loading
ActionController::Base.view_paths.reload!
ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear
end
# Cleanup the application by clearing out loaded classes so they can
@ -140,7 +156,22 @@ module ActionController
end
def flush_logger
RAILS_DEFAULT_LOGGER.flush
Base.logger.flush
end
def mark_as_test_request!
@test_request = true
self
end
def test_request?
@test_request
end
def checkin_connections
# Don't return connection (and peform implicit rollback) if this request is a part of integration test
return if test_request?
ActiveRecord::Base.clear_active_connections!
end
protected

View file

@ -94,7 +94,7 @@ module ActionController #:nodoc:
map! do |filter|
if filters.include?(filter)
new_filter = filter.dup
new_filter.options.merge!(options)
new_filter.update_options!(options)
new_filter
else
filter
@ -104,16 +104,34 @@ module ActionController #:nodoc:
end
class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
def initialize(kind, method, options = {})
super
update_options! options
end
# override these to return true in appropriate subclass
def before?
self.class == BeforeFilter
false
end
def after?
self.class == AfterFilter
false
end
def around?
self.class == AroundFilter
false
end
# Make sets of strings from :only/:except options
def update_options!(other)
if other
convert_only_and_except_options_to_sets_of_strings(other)
if other[:skip]
convert_only_and_except_options_to_sets_of_strings(other[:skip])
end
end
options.update(other)
end
private
@ -127,9 +145,9 @@ module ActionController #:nodoc:
def included_in_action?(controller, options)
if options[:only]
Array(options[:only]).map(&:to_s).include?(controller.action_name)
options[:only].include?(controller.action_name)
elsif options[:except]
!Array(options[:except]).map(&:to_s).include?(controller.action_name)
!options[:except].include?(controller.action_name)
else
true
end
@ -138,6 +156,14 @@ module ActionController #:nodoc:
def should_run_callback?(controller)
should_not_skip?(controller) && included_in_action?(controller, options) && super
end
def convert_only_and_except_options_to_sets_of_strings(opts)
[:only, :except].each do |key|
if values = opts[key]
opts[key] = Array(values).map(&:to_s).to_set
end
end
end
end
class AroundFilter < Filter #:nodoc:
@ -145,6 +171,10 @@ module ActionController #:nodoc:
:around
end
def around?
true
end
def call(controller, &block)
if should_run_callback?(controller)
method = filter_responds_to_before_and_after? ? around_proc : self.method
@ -169,8 +199,8 @@ module ActionController #:nodoc:
Proc.new do |controller, action|
method.before(controller)
if controller.send!(:performed?)
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
if controller.__send__(:performed?)
controller.__send__(:halt_filter_chain, method, :rendered_or_redirected)
else
begin
action.call
@ -187,10 +217,14 @@ module ActionController #:nodoc:
:before
end
def before?
true
end
def call(controller, &block)
super
if controller.send!(:performed?)
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
if controller.__send__(:performed?)
controller.__send__(:halt_filter_chain, method, :rendered_or_redirected)
end
end
end
@ -199,6 +233,10 @@ module ActionController #:nodoc:
def type
:after
end
def after?
true
end
end
# Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do

View file

@ -1,31 +1,33 @@
require 'active_support/memoizable'
module ActionController
module Http
class Headers < ::Hash
def initialize(constructor = {})
if constructor.is_a?(Hash)
extend ActiveSupport::Memoizable
def initialize(*args)
if args.size == 1 && args[0].is_a?(Hash)
super()
update(constructor)
update(args[0])
else
super(constructor)
super
end
end
def [](header_name)
if include?(header_name)
super
super
else
super(normalize_header(header_name))
super(env_name(header_name))
end
end
private
# Takes an HTTP header name and returns it in the
# format
def normalize_header(header_name)
# Converts a HTTP header name to an environment variable name.
def env_name(header_name)
"HTTP_#{header_name.upcase.gsub(/-/, '_')}"
end
memoize :env_name
end
end
end
end

View file

@ -204,8 +204,8 @@ module ActionController #:nodoc:
begin
child.master_helper_module = Module.new
child.master_helper_module.send! :include, master_helper_module
child.send! :default_helper_module!
child.master_helper_module.__send__ :include, master_helper_module
child.__send__ :default_helper_module!
rescue MissingSourceFile => e
raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
end

View file

@ -117,7 +117,7 @@ module ActionController
def authentication_request(controller, realm)
controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
controller.send! :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
end
end
end

View file

@ -1,9 +1,10 @@
require 'stringio'
require 'uri'
require 'active_support/test_case'
require 'action_controller/dispatcher'
require 'action_controller/test_process'
require 'stringio'
require 'uri'
module ActionController
module Integration #:nodoc:
# An integration Session instance represents a set of requests and responses
@ -100,7 +101,7 @@ module ActionController
@https = flag
end
# Return +true+ if the session is mimicing a secure HTTPS request.
# Return +true+ if the session is mimicking a secure HTTPS request.
#
# if session.https?
# ...
@ -164,11 +165,19 @@ module ActionController
status/100 == 3
end
# Performs a GET request with the given parameters. The parameters may
# be +nil+, a Hash, or a string that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# The headers should be a hash. The keys will automatically be upcased, with the
# prefix 'HTTP_' added if needed.
# Performs a GET request with the given parameters.
#
# - +path+: The URI (as a String) on which you want to perform a GET request.
# - +parameters+: The HTTP parameters that you want to pass. This may be +nil+,
# a Hash, or a String that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
# automatically be upcased, with the prefix 'HTTP_' added if needed.
#
# This method returns an AbstractResponse object, which one can use to inspect
# the details of the response. Furthermore, if this method was called from an
# ActionController::IntegrationTest object, then that object's <tt>@response</tt>
# instance variable will point to the same response object.
#
# You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
# +put+, +delete+, and +head+.
@ -219,21 +228,6 @@ module ActionController
end
private
class StubCGI < CGI #:nodoc:
attr_accessor :stdinput, :stdoutput, :env_table
def initialize(env, stdinput = nil)
self.env_table = env
self.stdoutput = StringIO.new
super
stdinput.set_encoding(Encoding::BINARY) if stdinput.respond_to?(:set_encoding)
stdinput.force_encoding(Encoding::BINARY) if stdinput.respond_to?(:force_encoding)
@stdinput = stdinput.is_a?(IO) ? stdinput : StringIO.new(stdinput || '')
end
end
# Tailors the session based on the given URI, setting the HTTPS value
# and the hostname.
def interpret_uri(path)
@ -281,9 +275,8 @@ module ActionController
ActionController::Base.clear_last_instantiation!
cgi = StubCGI.new(env, data)
ActionController::Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
@result = cgi.stdoutput.string
env['rack.input'] = data.is_a?(IO) ? data : StringIO.new(data || '')
@status, @headers, result_body = ActionController::Dispatcher.new.mark_as_test_request!.call(env)
@request_count += 1
@controller = ActionController::Base.last_instantiation
@ -297,7 +290,29 @@ module ActionController
@html_document = nil
parse_result
# Inject status back in for backwords compatibility with CGI
@headers['Status'] = @status
@status, @status_message = @status.split(/ /)
@status = @status.to_i
cgi_headers = Hash.new { |h,k| h[k] = [] }
@headers.each do |key, value|
cgi_headers[key.downcase] << value
end
cgi_headers['set-cookie'] = cgi_headers['set-cookie'].first
@headers = cgi_headers
@response.headers['cookie'] ||= []
(@headers['set-cookie'] || []).each do |cookie|
name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
@cookies[name] = value
# Fake CGI cookie header
# DEPRECATE: Use response.headers["Set-Cookie"] instead
@response.headers['cookie'] << CGI::Cookie::new("name" => name, "value" => value)
end
return status
rescue MultiPartNeededException
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
@ -305,26 +320,6 @@ module ActionController
return status
end
# Parses the result of the response and extracts the various values,
# like cookies, status, headers, etc.
def parse_result
response_headers, result_body = @result.split(/\r\n\r\n/, 2)
@headers = Hash.new { |h,k| h[k] = [] }
response_headers.to_s.each_line do |line|
key, value = line.strip.split(/:\s*/, 2)
@headers[key.downcase] << value
end
(@headers['set-cookie'] || [] ).each do |string|
name, value = string.match(/^([^=]*)=([^;]*);/)[1,2]
@cookies[name] = value
end
@status, @status_message = @headers["status"].first.to_s.split(/ /)
@status = @status.to_i
end
# Encode the cookies hash in a format suitable for passing to a
# request.
def encode_cookies
@ -335,13 +330,15 @@ module ActionController
# Get a temporary URL writer object
def generic_url_rewriter
cgi = StubCGI.new('REQUEST_METHOD' => "GET",
'QUERY_STRING' => "",
"REQUEST_URI" => "/",
"HTTP_HOST" => host,
"SERVER_PORT" => https? ? "443" : "80",
"HTTPS" => https? ? "on" : "off")
ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {})
env = {
'REQUEST_METHOD' => "GET",
'QUERY_STRING' => "",
"REQUEST_URI" => "/",
"HTTP_HOST" => host,
"SERVER_PORT" => https? ? "443" : "80",
"HTTPS" => https? ? "on" : "off"
}
ActionController::UrlRewriter.new(ActionController::RackRequest.new(env), {})
end
def name_with_prefix(prefix, name)
@ -442,12 +439,12 @@ EOF
end
%w(get post put head delete cookies assigns
xml_http_request get_via_redirect post_via_redirect).each do |method|
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless @integration_session
# reset the html_document variable, but only for new get/post calls
@html_document = nil unless %w(cookies assigns).include?(method)
returning @integration_session.send!(method, *args) do
returning @integration_session.__send__(method, *args) do
copy_session_variables!
end
end
@ -472,12 +469,12 @@ EOF
self.class.fixture_table_names.each do |table_name|
name = table_name.tr(".", "_")
next unless respond_to?(name)
extras.send!(:define_method, name) { |*args| delegate.send(name, *args) }
extras.__send__(:define_method, name) { |*args| delegate.send(name, *args) }
end
end
# delegate add_assertion to the test case
extras.send!(:define_method, :add_assertion) { test_result.add_assertion }
extras.__send__(:define_method, :add_assertion) { test_result.add_assertion }
session.extend(extras)
session.delegate = self
session.test_result = @_result
@ -491,14 +488,14 @@ EOF
def copy_session_variables! #:nodoc:
return unless @integration_session
%w(controller response request).each do |var|
instance_variable_set("@#{var}", @integration_session.send!(var))
instance_variable_set("@#{var}", @integration_session.__send__(var))
end
end
# Delegate unhandled messages to the current session instance.
def method_missing(sym, *args, &block)
reset! unless @integration_session
returning @integration_session.send!(sym, *args, &block) do
returning @integration_session.__send__(sym, *args, &block) do
copy_session_variables!
end
end
@ -580,7 +577,7 @@ EOF
# end
# end
# end
class IntegrationTest < Test::Unit::TestCase
class IntegrationTest < ActiveSupport::TestCase
include Integration::Runner
# Work around a bug in test/unit caused by the default test being named

View file

@ -3,11 +3,6 @@ module ActionController #:nodoc:
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
# NOTE: Can't use alias_method_chain here because +render_without_layout+ is already
# defined as a publicly exposed method
alias_method :render_with_no_layout, :render
alias_method :render, :render_with_a_layout
class << self
alias_method_chain :inherited, :layout
end
@ -169,17 +164,17 @@ module ActionController #:nodoc:
# performance and have access to them as any normal template would.
def layout(template_name, conditions = {}, auto = false)
add_layout_conditions(conditions)
write_inheritable_attribute "layout", template_name
write_inheritable_attribute "auto_layout", auto
write_inheritable_attribute(:layout, template_name)
write_inheritable_attribute(:auto_layout, auto)
end
def layout_conditions #:nodoc:
@layout_conditions ||= read_inheritable_attribute("layout_conditions")
@layout_conditions ||= read_inheritable_attribute(:layout_conditions)
end
def default_layout(format) #:nodoc:
layout = read_inheritable_attribute("layout")
return layout unless read_inheritable_attribute("auto_layout")
layout = read_inheritable_attribute(:layout)
return layout unless read_inheritable_attribute(:auto_layout)
@default_layout ||= {}
@default_layout[format] ||= default_layout_with_format(format, layout)
@default_layout[format]
@ -199,7 +194,7 @@ module ActionController #:nodoc:
end
def add_layout_conditions(conditions)
write_inheritable_hash "layout_conditions", normalize_conditions(conditions)
write_inheritable_hash(:layout_conditions, normalize_conditions(conditions))
end
def normalize_conditions(conditions)
@ -221,10 +216,10 @@ module ActionController #:nodoc:
# object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
# weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
def active_layout(passed_layout = nil)
layout = passed_layout || self.class.default_layout(response.template.template_format)
layout = passed_layout || self.class.default_layout(default_template_format)
active_layout = case layout
when String then layout
when Symbol then send!(layout)
when Symbol then __send__(layout)
when Proc then layout.call(self)
end
@ -240,51 +235,24 @@ module ActionController #:nodoc:
end
end
protected
def render_with_a_layout(options = nil, extra_options = {}, &block) #:nodoc:
template_with_options = options.is_a?(Hash)
if (layout = pick_layout(template_with_options, options)) && apply_layout?(template_with_options, options)
options = options.merge :layout => false if template_with_options
logger.info("Rendering template within #{layout}") if logger
content_for_layout = render_with_no_layout(options, extra_options, &block)
erase_render_results
add_variables_to_assigns
@template.instance_variable_set("@content_for_layout", content_for_layout)
response.layout = layout
status = template_with_options ? options[:status] : nil
render_for_text(@template.render_file(layout, true), status)
else
render_with_no_layout(options, extra_options, &block)
end
end
private
def apply_layout?(template_with_options, options)
return false if options == :update
template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout?
end
def candidate_for_layout?(options)
(options.has_key?(:layout) && options[:layout] != false) ||
options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? &&
!template_exempt_from_layout?(options[:template] || default_template_name(options[:action]))
options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty? &&
!@template.__send__(:_exempt_from_layout?, options[:template] || default_template_name(options[:action]))
end
def pick_layout(template_with_options, options)
if template_with_options
case layout = options[:layout]
when FalseClass
nil
when NilClass, TrueClass
active_layout if action_has_layout?
else
active_layout(layout)
def pick_layout(options)
if options.has_key?(:layout)
case layout = options.delete(:layout)
when FalseClass
nil
when NilClass, TrueClass
active_layout if action_has_layout? && !@template.__send__(:_exempt_from_layout?, default_template_name)
else
active_layout(layout)
end
else
active_layout if action_has_layout?
active_layout if action_has_layout? && candidate_for_layout?(options)
end
end
@ -304,7 +272,13 @@ module ActionController #:nodoc:
end
def layout_directory?(layout_name)
@template.finder.find_template_extension_from_handler(File.join('layouts', layout_name))
@template.__send__(:_pick_template, "#{File.join('layouts', layout_name)}.#{@template.template_format}") ? true : false
rescue ActionView::MissingTemplate
false
end
def default_template_format
response.template.template_format
end
end
end

View file

@ -114,7 +114,11 @@ module ActionController #:nodoc:
@request = controller.request
@response = controller.response
@mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
if ActionController::Base.use_accept_header
@mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
else
@mime_type_priority = [@request.format]
end
@order = []
@responses = {}

View file

@ -1,3 +1,5 @@
require 'set'
module Mime
SET = []
EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
@ -72,57 +74,61 @@ module Mime
end
def parse(accept_header)
# keep track of creation order to keep the subsequent sort stable
list = []
accept_header.split(/,/).each_with_index do |header, index|
params, q = header.split(/;\s*q=/)
if params
params.strip!
list << AcceptItem.new(index, params, q) unless params.empty?
end
end
list.sort!
# Take care of the broken text/xml entry by renaming or deleting it
text_xml = list.index("text/xml")
app_xml = list.index(Mime::XML.to_s)
if text_xml && app_xml
# set the q value to the max of the two
list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
# make sure app_xml is ahead of text_xml in the list
if app_xml > text_xml
list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
app_xml, text_xml = text_xml, app_xml
end
# delete text_xml from the list
list.delete_at(text_xml)
elsif text_xml
list[text_xml].name = Mime::XML.to_s
end
# Look for more specific XML-based types and sort them ahead of app/xml
if app_xml
idx = app_xml
app_xml_type = list[app_xml]
while(idx < list.length)
type = list[idx]
break if type.q < app_xml_type.q
if type.name =~ /\+xml$/
list[app_xml], list[idx] = list[idx], list[app_xml]
app_xml = idx
if accept_header !~ /,/
[Mime::Type.lookup(accept_header)]
else
# keep track of creation order to keep the subsequent sort stable
list = []
accept_header.split(/,/).each_with_index do |header, index|
params, q = header.split(/;\s*q=/)
if params
params.strip!
list << AcceptItem.new(index, params, q) unless params.empty?
end
idx += 1
end
end
list.sort!
list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
list
# Take care of the broken text/xml entry by renaming or deleting it
text_xml = list.index("text/xml")
app_xml = list.index(Mime::XML.to_s)
if text_xml && app_xml
# set the q value to the max of the two
list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
# make sure app_xml is ahead of text_xml in the list
if app_xml > text_xml
list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
app_xml, text_xml = text_xml, app_xml
end
# delete text_xml from the list
list.delete_at(text_xml)
elsif text_xml
list[text_xml].name = Mime::XML.to_s
end
# Look for more specific XML-based types and sort them ahead of app/xml
if app_xml
idx = app_xml
app_xml_type = list[app_xml]
while(idx < list.length)
type = list[idx]
break if type.q < app_xml_type.q
if type.name =~ /\+xml$/
list[app_xml], list[idx] = list[idx], list[app_xml]
app_xml = idx
end
idx += 1
end
end
list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
list
end
end
end

View file

@ -17,4 +17,5 @@ Mime::Type.register "multipart/form-data", :multipart_form
Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
# http://www.ietf.org/rfc/rfc4627.txt
Mime::Type.register "application/json", :json, %w( text/x-json )
# http://www.json.org/JSONRequest.html
Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )

View file

@ -0,0 +1,16 @@
require 'action_controller/integration'
require 'active_support/testing/performance'
require 'active_support/testing/default'
module ActionController
# An integration test that runs a code profiler on your test methods.
# Profiling output for combinations of each test method, measurement, and
# output format are written to your tmp/performance directory.
#
# By default, process_time is measured and both flat and graph_html output
# formats are written, so you'll have two output files per test method.
class PerformanceTest < ActionController::IntegrationTest
include ActiveSupport::Testing::Performance
include ActiveSupport::Testing::Default
end
end

View file

@ -102,7 +102,13 @@ module ActionController
args << format if format
named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
send!(named_route, *args)
url_options = options.except(:action, :routing_type, :format)
unless url_options.empty?
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
end
__send__(named_route, *args)
end
# Returns the path component of a URL for the given record. It uses
@ -114,19 +120,19 @@ module ActionController
%w(edit new formatted).each do |action|
module_eval <<-EOT, __FILE__, __LINE__
def #{action}_polymorphic_url(record_or_hash)
polymorphic_url(record_or_hash, :action => "#{action}")
def #{action}_polymorphic_url(record_or_hash, options = {})
polymorphic_url(record_or_hash, options.merge(:action => "#{action}"))
end
def #{action}_polymorphic_path(record_or_hash)
polymorphic_url(record_or_hash, :action => "#{action}", :routing_type => :path)
def #{action}_polymorphic_path(record_or_hash, options = {})
polymorphic_url(record_or_hash, options.merge(:action => "#{action}", :routing_type => :path))
end
EOT
end
private
def action_prefix(options)
options[:action] ? "#{options[:action]}_" : ""
options[:action] ? "#{options[:action]}_" : options[:format] ? "formatted_" : ""
end
def routing_type(options)
@ -143,7 +149,7 @@ module ActionController
if parent.is_a?(Symbol) || parent.is_a?(String)
string << "#{parent}_"
else
string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
string << "#{RecordIdentifier.__send__("singular_class_name", parent)}_"
end
end
end
@ -151,7 +157,7 @@ module ActionController
if record.is_a?(Symbol) || record.is_a?(String)
route << "#{record}_"
else
route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
route << "#{RecordIdentifier.__send__("#{inflection}_class_name", record)}_"
end
action_prefix(options) + namespace + route + routing_type(options).to_s

View file

@ -0,0 +1,303 @@
require 'action_controller/cgi_ext'
require 'action_controller/session/cookie_store'
module ActionController #:nodoc:
class RackRequest < AbstractRequest #:nodoc:
attr_accessor :session_options
attr_reader :cgi
class SessionFixationAttempt < StandardError #:nodoc:
end
DEFAULT_SESSION_OPTIONS = {
:database_manager => CGI::Session::CookieStore, # store data in cookie
:prefix => "ruby_sess.", # prefix session file names
:session_path => "/", # available to all paths in app
:session_key => "_session_id",
:cookie_only => true,
:session_http_only=> true
}
def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
@session_options = session_options
@env = env
@cgi = CGIWrapper.new(self)
super()
end
%w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
PATH_TRANSLATED REMOTE_HOST
REMOTE_IDENT REMOTE_USER SCRIPT_NAME
SERVER_NAME SERVER_PROTOCOL
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
define_method(env.sub(/^HTTP_/n, '').downcase) do
@env[env]
end
end
def query_string
qs = super
if !qs.blank?
qs
else
@env['QUERY_STRING']
end
end
def body_stream #:nodoc:
@env['rack.input']
end
def key?(key)
@env.key?(key)
end
def cookies
return {} unless @env["HTTP_COOKIE"]
unless @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
@env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
@env["rack.request.cookie_hash"] = CGI::Cookie::parse(@env["rack.request.cookie_string"])
end
@env["rack.request.cookie_hash"]
end
def server_port
@env['SERVER_PORT'].to_i
end
def server_software
@env['SERVER_SOFTWARE'].split("/").first
end
def session
unless defined?(@session)
if @session_options == false
@session = Hash.new
else
stale_session_check! do
if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
raise SessionFixationAttempt
end
case value = session_options_with_string_keys['new_session']
when true
@session = new_session
when false
begin
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
# CGI::Session raises ArgumentError if 'new_session' == false
# and no session cookie or query param is present.
rescue ArgumentError
@session = Hash.new
end
when nil
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
else
raise ArgumentError, "Invalid new_session option: #{value}"
end
@session['__valid_session']
end
end
end
@session
end
def reset_session
@session.delete if defined?(@session) && @session.is_a?(CGI::Session)
@session = new_session
end
private
# Delete an old session if it exists then create a new one.
def new_session
if @session_options == false
Hash.new
else
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
end
end
def cookie_only?
session_options_with_string_keys['cookie_only']
end
def stale_session_check!
yield
rescue ArgumentError => argument_error
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
begin
# Note that the regexp does not allow $1 to end with a ':'
$1.constantize
rescue LoadError, NameError => const_error
raise ActionController::SessionRestoreError, <<-end_msg
Session contains objects whose class definition isn\'t available.
Remember to require the classes for all objects kept in the session.
(Original exception: #{const_error.message} [#{const_error.class}])
end_msg
end
retry
else
raise
end
end
def session_options_with_string_keys
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
end
end
class RackResponse < AbstractResponse #:nodoc:
def initialize(request)
@cgi = request.cgi
@writer = lambda { |x| @body << x }
@block = nil
super()
end
# Retrieve status from instance variable if has already been delete
def status
@status || super
end
def out(output = $stdout, &block)
# Nasty hack because CGI sessions are closed after the normal
# prepare! statement
set_cookies!
@block = block
@status = headers.delete("Status")
if [204, 304].include?(status.to_i)
headers.delete("Content-Type")
[status, headers.to_hash, []]
else
[status, headers.to_hash, self]
end
end
alias to_a out
def each(&callback)
if @body.respond_to?(:call)
@writer = lambda { |x| callback.call(x) }
@body.call(self, self)
elsif @body.is_a?(String)
@body.each_line(&callback)
else
@body.each(&callback)
end
@writer = callback
@block.call(self) if @block
end
def write(str)
@writer.call str.to_s
str
end
def close
@body.close if @body.respond_to?(:close)
end
def empty?
@block == nil && @body.empty?
end
def prepare!
super
convert_language!
convert_expires!
set_status!
# set_cookies!
end
private
def convert_language!
headers["Content-Language"] = headers.delete("language") if headers["language"]
end
def convert_expires!
headers["Expires"] = headers.delete("") if headers["expires"]
end
def convert_content_type!
super
headers['Content-Type'] = headers.delete('type') || "text/html"
headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset']
end
def set_content_length!
super
headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"]
end
def set_status!
self.status ||= "200 OK"
end
def set_cookies!
# Convert 'cookie' header to 'Set-Cookie' headers.
# Because Set-Cookie header can appear more the once in the response body,
# we store it in a line break separated string that will be translated to
# multiple Set-Cookie header by the handler.
if cookie = headers.delete('cookie')
cookies = []
case cookie
when Array then cookie.each { |c| cookies << c.to_s }
when Hash then cookie.each { |_, c| cookies << c.to_s }
else cookies << cookie.to_s
end
@cgi.output_cookies.each { |c| cookies << c.to_s } if @cgi.output_cookies
headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact
end
end
end
class CGIWrapper < ::CGI
attr_reader :output_cookies
def initialize(request, *args)
@request = request
@args = *args
@input = request.body
super *args
end
def params
@params ||= @request.params
end
def cookies
@request.cookies
end
def query_string
@request.query_string
end
# Used to wrap the normal args variable used inside CGI.
def args
@args
end
# Used to wrap the normal env_table variable used inside CGI.
def env_table
@request.env
end
# Used to wrap the normal stdinput variable used inside CGI.
def stdinput
@input
end
end
end

View file

@ -2,33 +2,37 @@ require 'tempfile'
require 'stringio'
require 'strscan'
module ActionController
# HTTP methods which are accepted by default.
ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options ))
require 'active_support/memoizable'
module ActionController
# CgiRequest and TestRequest provide concrete implementations.
class AbstractRequest
cattr_accessor :relative_url_root
remove_method :relative_url_root
extend ActiveSupport::Memoizable
def self.relative_url_root=(*args)
ActiveSupport::Deprecation.warn(
"ActionController::AbstractRequest.relative_url_root= has been renamed." +
"You can now set it with config.action_controller.relative_url_root=", caller)
end
HTTP_METHODS = %w(get head put post delete options)
HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
# The hash of environment variables for this request,
# such as { 'RAILS_ENV' => 'production' }.
attr_reader :env
# The true HTTP request method as a lowercase symbol, such as <tt>:get</tt>.
# The true HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
# UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
def request_method
@request_method ||= begin
method = ((@env['REQUEST_METHOD'] == 'POST' && !parameters[:_method].blank?) ? parameters[:_method].to_s : @env['REQUEST_METHOD']).downcase
if ACCEPTED_HTTP_METHODS.include?(method)
method.to_sym
else
raise UnknownHttpMethod, "#{method}, accepted HTTP methods are #{ACCEPTED_HTTP_METHODS.to_a.to_sentence}"
end
end
end
method = @env['REQUEST_METHOD']
method = parameters[:_method] if method == 'POST' && !parameters[:_method].blank?
# The HTTP request method as a lowercase symbol, such as <tt>:get</tt>.
HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
end
memoize :request_method
# The HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
# Note, HEAD is returned as <tt>:get</tt> since the two are functionally
# equivalent from the application's perspective.
def method
@ -55,57 +59,107 @@ module ActionController
request_method == :delete
end
# Is this a HEAD request? <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
# so check the HTTP method directly.
# Is this a HEAD request? Since <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
# this \method checks the actual HTTP \method directly.
def head?
request_method == :head
end
# Provides acccess to the request's HTTP headers, for example:
# request.headers["Content-Type"] # => "text/plain"
# Provides access to the request's HTTP headers, for example:
#
# request.headers["Content-Type"] # => "text/plain"
def headers
@headers ||= ActionController::Http::Headers.new(@env)
ActionController::Http::Headers.new(@env)
end
memoize :headers
# Returns the content length of the request as an integer.
def content_length
@content_length ||= env['CONTENT_LENGTH'].to_i
@env['CONTENT_LENGTH'].to_i
end
memoize :content_length
# The MIME type of the HTTP request, such as Mime::XML.
#
# For backward compatibility, the post format is extracted from the
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_type
@content_type ||= Mime::Type.lookup(content_type_without_parameters)
Mime::Type.lookup(content_type_without_parameters)
end
memoize :content_type
# Returns the accepted MIME type for the request
# Returns the accepted MIME type for the request.
def accepts
@accepts ||=
if @env['HTTP_ACCEPT'].to_s.strip.empty?
[ content_type, Mime::ALL ].compact # make sure content_type being nil is not included
else
Mime::Type.parse(@env['HTTP_ACCEPT'])
end
header = @env['HTTP_ACCEPT'].to_s.strip
if header.empty?
[content_type, Mime::ALL].compact
else
Mime::Type.parse(header)
end
end
memoize :accepts
def if_modified_since
if since = env['HTTP_IF_MODIFIED_SINCE']
Time.rfc2822(since) rescue nil
end
end
memoize :if_modified_since
def if_none_match
env['HTTP_IF_NONE_MATCH']
end
# Returns the Mime type for the format used in the request. If there is no format available, the first of the
# accept types will be used. Examples:
def not_modified?(modified_at)
if_modified_since && modified_at && if_modified_since >= modified_at
end
def etag_matches?(etag)
if_none_match && if_none_match == etag
end
# Check response freshness (Last-Modified and ETag) against request
# If-Modified-Since and If-None-Match conditions. If both headers are
# supplied, both must match, or the request is not considered fresh.
def fresh?(response)
case
when if_modified_since && if_none_match
not_modified?(response.last_modified) && etag_matches?(response.etag)
when if_modified_since
not_modified?(response.last_modified)
when if_none_match
etag_matches?(response.etag)
else
false
end
end
# Returns the Mime type for the \format used in the request.
#
# GET /posts/5.xml | request.format => Mime::XML
# GET /posts/5.xhtml | request.format => Mime::HTML
# GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers)
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
def format
@format ||= parameters[:format] ? Mime::Type.lookup_by_extension(parameters[:format]) : accepts.first
@format ||=
if parameters[:format]
Mime::Type.lookup_by_extension(parameters[:format])
elsif ActionController::Base.use_accept_header
accepts.first
elsif xhr?
Mime::Type.lookup_by_extension("js")
else
Mime::Type.lookup_by_extension("html")
end
end
# Sets the format by string extension, which can be used to force custom formats that are not controlled by the extension.
# Example:
# Sets the \format by string extension, which can be used to force custom formats
# that are not controlled by the extension.
#
# class ApplicationController < ActionController::Base
# before_filter :adjust_format_for_iphone
#
#
# private
# def adjust_format_for_iphone
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
@ -116,6 +170,25 @@ module ActionController
@format = Mime::Type.lookup_by_extension(parameters[:format])
end
# Returns a symbolized version of the <tt>:format</tt> parameter of the request.
# If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
# otherwise.
def template_format
parameter_format = parameters[:format]
if parameter_format
parameter_format
elsif xhr?
:js
else
:html
end
end
def cache_format
parameters[:format]
end
# Returns true if the request's "X-Requested-With" header contains
# "XMLHttpRequest". (The Prototype Javascript library sends this header with
# every Ajax request.)
@ -128,7 +201,7 @@ module ActionController
# the right-hand-side of X-Forwarded-For
TRUSTED_PROXIES = /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
# Determine originating IP address. REMOTE_ADDR is the standard
# Determines originating IP address. REMOTE_ADDR is the standard
# but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
# HTTP_X_FORWARDED_FOR are set by proxies so check for these if
# REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
@ -166,44 +239,65 @@ EOM
@env['REMOTE_ADDR']
end
memoize :remote_ip
# Returns the lowercase name of the HTTP server software.
def server_software
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
end
memoize :server_software
# Returns the complete URL used for this request
# Returns the complete URL used for this request.
def url
protocol + host_with_port + request_uri
end
memoize :url
# Return 'https://' if this is an SSL request and 'http://' otherwise.
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
def protocol
ssl? ? 'https://' : 'http://'
end
memoize :protocol
# Is this an SSL request?
def ssl?
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
end
# Returns the host for this request, such as example.com.
def host
# Returns the \host for this request, such as "example.com".
def raw_host_with_port
if forwarded = env["HTTP_X_FORWARDED_HOST"]
forwarded.split(/,\s?/).last
else
env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
end
end
# Returns a host:port string for this request, such as example.com or
# example.com:8080.
def host_with_port
@host_with_port ||= host + port_string
# Returns the host for this request, such as example.com.
def host
raw_host_with_port.sub(/:\d+$/, '')
end
memoize :host
# Returns a \host:\port string for this request, such as "example.com" or
# "example.com:8080".
def host_with_port
"#{host}#{port_string}"
end
memoize :host_with_port
# Returns the port number of this request as an integer.
def port
@port_as_int ||= @env['SERVER_PORT'].to_i
if raw_host_with_port =~ /:(\d+)$/
$1.to_i
else
standard_port
end
end
memoize :port
# Returns the standard port number for this request's protocol
# Returns the standard \port number for this request's protocol.
def standard_port
case protocol
when 'https://' then 443
@ -211,13 +305,13 @@ EOM
end
end
# Returns a port suffix like ":8080" if the port number of this request
# is not the default HTTP port 80 or HTTPS port 443.
# Returns a \port suffix like ":8080" if the \port number of this request
# is not the default HTTP \port 80 or HTTPS \port 443.
def port_string
(port == standard_port) ? '' : ":#{port}"
port == standard_port ? '' : ":#{port}"
end
# Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify
# Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
def domain(tld_length = 1)
return nil unless named_host?(host)
@ -225,8 +319,9 @@ EOM
host.split('.').last(1 + tld_length).join('.')
end
# Returns all the subdomains as an array, so ["dev", "www"] would be returned for "dev.www.rubyonrails.org".
# You can specify a different <tt>tld_length</tt>, such as 2 to catch ["www"] instead of ["www", "rubyonrails"]
# Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
# such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
# in "www.rubyonrails.co.uk".
def subdomains(tld_length = 1)
return [] unless named_host?(host)
@ -234,7 +329,7 @@ EOM
parts[0..-(tld_length+2)]
end
# Return the query string, accounting for server idiosyncracies.
# Returns the query string, accounting for server idiosyncrasies.
def query_string
if uri = @env['REQUEST_URI']
uri.split('?', 2)[1] || ''
@ -242,8 +337,9 @@ EOM
@env['QUERY_STRING'] || ''
end
end
memoize :query_string
# Return the request URI, accounting for server idiosyncracies.
# Returns the request URI, accounting for server idiosyncrasies.
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
def request_uri
if uri = @env['REQUEST_URI']
@ -251,48 +347,36 @@ EOM
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
else
# Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
uri = @env['PATH_INFO']
uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil?
unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
uri << '?' << env_qs
uri = @env['PATH_INFO'].to_s
if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
uri = uri.sub(/#{script_filename}\//, '')
end
if uri.nil?
env_qs = @env['QUERY_STRING'].to_s
uri += "?#{env_qs}" unless env_qs.empty?
if uri.blank?
@env.delete('REQUEST_URI')
uri
else
@env['REQUEST_URI'] = uri
end
end
end
memoize :request_uri
# Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
# Returns the interpreted \path to requested resource after all the installation
# directory of this application was taken into account.
def path
path = (uri = request_uri) ? uri.split('?').first.to_s : ''
# Cut off the path to the installation directory if given
path.sub!(%r/^#{relative_url_root}/, '')
path || ''
end
# Returns the path minus the web server relative installation directory.
# This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
# It can be automatically extracted for Apache setups. If the server is not
# Apache, this method returns an empty string.
def relative_url_root
@@relative_url_root ||= case
when @env["RAILS_RELATIVE_URL_ROOT"]
@env["RAILS_RELATIVE_URL_ROOT"]
when server_software == 'apache'
@env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '')
else
''
end
path.sub!(%r/^#{ActionController::Base.relative_url_root}/, '')
path || ''
end
memoize :path
# Read the request body. This is useful for web services that need to
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
unless env.include? 'RAW_POST_DATA'
@ -302,7 +386,7 @@ EOM
env['RAW_POST_DATA']
end
# Returns both GET and POST parameters in a single hash.
# Returns both GET and POST \parameters in a single hash.
def parameters
@parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
end
@ -312,34 +396,56 @@ EOM
@symbolized_path_parameters = @parameters = nil
end
# The same as <tt>path_parameters</tt> with explicitly symbolized keys
def symbolized_path_parameters
# The same as <tt>path_parameters</tt> with explicitly symbolized keys.
def symbolized_path_parameters
@symbolized_path_parameters ||= path_parameters.symbolize_keys
end
# Returns a hash with the parameters used to form the path of the request.
# Returned hash keys are strings. See <tt>symbolized_path_parameters</tt> for symbolized keys.
#
# Example:
# Returns a hash with the \parameters used to form the \path of the request.
# Returned hash keys are strings:
#
# {'action' => 'my_action', 'controller' => 'my_controller'}
#
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
def path_parameters
@path_parameters ||= {}
end
# The request body is an IO input stream. If the RAW_POST_DATA environment
# variable is already set, wrap it in a StringIO.
def body
if raw_post = env['RAW_POST_DATA']
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
StringIO.new(raw_post)
else
body_stream
end
end
def remote_addr
@env['REMOTE_ADDR']
end
def referrer
@env['HTTP_REFERER']
end
alias referer referrer
def query_parameters
@query_parameters ||= self.class.parse_query_parameters(query_string)
end
def request_parameters
@request_parameters ||= parse_formatted_request_parameters
end
#--
# Must be implemented in the concrete request
#++
# The request body is an IO input stream.
def body
end
def query_parameters #:nodoc:
end
def request_parameters #:nodoc:
def body_stream #:nodoc:
end
def cookies #:nodoc:
@ -366,8 +472,9 @@ EOM
# The raw content type string with its parameters stripped off.
def content_type_without_parameters
@content_type_without_parameters ||= self.class.extract_content_type_without_parameters(content_type_with_parameters)
self.class.extract_content_type_without_parameters(content_type_with_parameters)
end
memoize :content_type_without_parameters
private
def content_type_from_legacy_post_data_format_header

View file

@ -17,7 +17,7 @@ module ActionController #:nodoc:
# forged link from another site, is done by embedding a token based on the session (which an attacker wouldn't know) in all
# forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only
# HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
# scheme there anyway). Also, GET requests are not protected as these should be indempotent anyway.
# scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway.
#
# This is turned on with the <tt>protect_from_forgery</tt> method, which will check the token and raise an
# ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the error message in

View file

@ -41,10 +41,9 @@ module ActionController #:nodoc:
base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE)
base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES
base.class_inheritable_array :rescue_handlers
base.rescue_handlers = []
base.extend(ClassMethods)
base.send :include, ActiveSupport::Rescuable
base.class_eval do
alias_method_chain :perform_action, :rescue
end
@ -54,78 +53,12 @@ module ActionController #:nodoc:
def process_with_exception(request, response, exception) #:nodoc:
new.process(request, response, :rescue_action, exception)
end
# Rescue exceptions raised in controller actions.
#
# <tt>rescue_from</tt> receives a series of exception classes or class
# names, and a trailing <tt>:with</tt> option with the name of a method
# or a Proc object to be called to handle them. Alternatively a block can
# be given.
#
# Handlers that take one argument will be called with the exception, so
# that the exception can be inspected when dealing with it.
#
# Handlers are inherited. They are searched from right to left, from
# bottom to top, and up the hierarchy. The handler of the first class for
# which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if
# any.
#
# class ApplicationController < ActionController::Base
# rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
# rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
#
# rescue_from 'MyAppError::Base' do |exception|
# render :xml => exception, :status => 500
# end
#
# protected
# def deny_access
# ...
# end
#
# def show_errors(exception)
# exception.record.new_record? ? ...
# end
# end
def rescue_from(*klasses, &block)
options = klasses.extract_options!
unless options.has_key?(:with)
block_given? ? options[:with] = block : raise(ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument.")
end
klasses.each do |klass|
key = if klass.is_a?(Class) && klass <= Exception
klass.name
elsif klass.is_a?(String)
klass
else
raise(ArgumentError, "#{klass} is neither an Exception nor a String")
end
# Order is important, we put the pair at the end. When dealing with an
# exception we will follow the documented order going from right to left.
rescue_handlers << [key, options[:with]]
end
end
end
protected
# Exception handler called when the performance of an action raises an exception.
def rescue_action(exception)
log_error(exception) if logger
erase_results if performed?
# Let the exception alter the response if it wants.
# For example, MethodNotAllowed sets the Allow header.
if exception.respond_to?(:handle_response!)
exception.handle_response!(response)
end
if consider_all_requests_local || local_request?
rescue_action_locally(exception)
else
rescue_action_in_public(exception)
end
rescue_with_handler(exception) || rescue_action_without_handler(exception)
end
# Overwrite to implement custom logging of errors. By default logs as fatal.
@ -144,7 +77,7 @@ module ActionController #:nodoc:
end
# Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
# default will call render_optional_error_file. Override this method to provide more user friendly error messages.s
# default will call render_optional_error_file. Override this method to provide more user friendly error messages.
def rescue_action_in_public(exception) #:doc:
render_optional_error_file response_code_for_rescue(exception)
end
@ -173,26 +106,28 @@ module ActionController #:nodoc:
# Render detailed diagnostics for unhandled exceptions rescued from
# a controller action.
def rescue_action_locally(exception)
add_variables_to_assigns
@template.instance_variable_set("@exception", exception)
@template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
@template.send!(:assign_variables_from_controller)
@template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
@template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception)))
response.content_type = Mime::HTML
render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
end
# Tries to rescue the exception by looking up and calling a registered handler.
def rescue_action_with_handler(exception)
if handler = handler_for_rescue(exception)
if handler.arity != 0
handler.call(exception)
else
handler.call
end
true # don't rely on the return value of the handler
def rescue_action_without_handler(exception)
log_error(exception) if logger
erase_results if performed?
# Let the exception alter the response if it wants.
# For example, MethodNotAllowed sets the Allow header.
if exception.respond_to?(:handle_response!)
exception.handle_response!(response)
end
if consider_all_requests_local || local_request?
rescue_action_locally(exception)
else
rescue_action_in_public(exception)
end
end
@ -200,7 +135,7 @@ module ActionController #:nodoc:
def perform_action_with_rescue #:nodoc:
perform_action_without_rescue
rescue Exception => exception
rescue_action_with_handler(exception) || rescue_action(exception)
rescue_action(exception)
end
def rescues_path(template_name)
@ -215,36 +150,6 @@ module ActionController #:nodoc:
rescue_responses[exception.class.name]
end
def handler_for_rescue(exception)
# We go from right to left because pairs are pushed onto rescue_handlers
# as rescue_from declarations are found.
_, handler = *rescue_handlers.reverse.detect do |klass_name, handler|
# The purpose of allowing strings in rescue_from is to support the
# declaration of handler associations for exception classes whose
# definition is yet unknown.
#
# Since this loop needs the constants it would be inconsistent to
# assume they should exist at this point. An early raised exception
# could trigger some other handler and the array could include
# precisely a string whose corresponding constant has not yet been
# seen. This is why we are tolerant to unknown constants.
#
# Note that this tolerance only matters if the exception was given as
# a string, otherwise a NameError will be raised by the interpreter
# itself when rescue_from CONSTANT is executed.
klass = self.class.const_get(klass_name) rescue nil
klass ||= klass_name.constantize rescue nil
exception.is_a?(klass) if klass
end
case handler
when Symbol
method(handler)
when Proc
handler.bind(self)
end
end
def clean_backtrace(exception)
if backtrace = exception.backtrace
if defined?(RAILS_ROOT)

View file

@ -1,23 +1,23 @@
module ActionController
# == Overview
#
# ActionController::Resources are a way of defining RESTful resources. A RESTful resource, in basic terms,
# ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
# is something that can be pointed at and it will respond with a representation of the data requested.
# In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
# requests XML data.
#
# RESTful design is based on the assumption that there are four generic verbs that a user of an
# application can request from a resource (the noun).
# application can request from a \resource (the noun).
#
# Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
# \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
# denotes the type of action that should take place.
#
# === The Different Methods and their Usage
#
# +GET+ Requests for a resource, no saving or editing of a resource should occur in a GET request
# +POST+ Creation of resources
# +PUT+ Editing of attributes on a resource
# +DELETE+ Deletion of a resource
# * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
# * POST - Creation of \resources.
# * PUT - Editing of attributes on a \resource.
# * DELETE - Deletion of a \resource.
#
# === Examples
#
@ -72,7 +72,7 @@ module ActionController
end
def conditions
@conditions = @options[:conditions] || {}
@conditions ||= @options[:conditions] || {}
end
def path
@ -80,21 +80,29 @@ module ActionController
end
def new_path
new_action = self.options[:path_names][:new] if self.options[:path_names]
new_action = self.options[:path_names][:new] if self.options[:path_names]
new_action ||= Base.resources_path_names[:new]
@new_path ||= "#{path}/#{new_action}"
@new_path ||= "#{path}/#{new_action}"
end
def shallow_path_prefix
@shallow_path_prefix ||= "#{path_prefix unless @options[:shallow]}"
end
def member_path
@member_path ||= "#{path}/:id"
@member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
end
def nesting_path_prefix
@nesting_path_prefix ||= "#{path}/:#{singular}_id"
@nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
end
def shallow_name_prefix
@shallow_name_prefix ||= "#{name_prefix unless @options[:shallow]}"
end
def nesting_name_prefix
"#{name_prefix}#{singular}_"
"#{shallow_name_prefix}#{singular}_"
end
def action_separator
@ -141,12 +149,14 @@ module ActionController
super
end
alias_method :shallow_path_prefix, :path_prefix
alias_method :shallow_name_prefix, :name_prefix
alias_method :member_path, :path
alias_method :nesting_path_prefix, :path
end
# Creates named routes for implementing verb-oriented controllers
# for a collection resource.
# for a collection \resource.
#
# For example:
#
@ -238,23 +248,24 @@ module ActionController
#
# The +resources+ method accepts the following options to customize the resulting routes:
# * <tt>:collection</tt> - Add named routes for other actions that operate on the collection.
# Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>
# or <tt>:any</tt> if the method does not matter. These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
# Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>,
# an array of any of the previous, or <tt>:any</tt> if the method does not matter.
# These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
# * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member.
# * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new resource action.
# * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
# * <tt>:controller</tt> - Specify the controller name for the routes.
# * <tt>:singular</tt> - Specify the singular name used in the member routes.
# * <tt>:requirements</tt> - Set custom routing parameter requirements.
# * <tt>:conditions</tt> - Specify custom routing recognition conditions. Resources sets the <tt>:method</tt> value for the method-specific routes.
# * <tt>:as</tt> - Specify a different resource name to use in the URL path. For example:
# * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
# * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
# # products_path == '/productos'
# map.resources :products, :as => 'productos' do |product|
# # product_reviews_path(product) == '/productos/1234/comentarios'
# product.resources :product_reviews, :as => 'comentarios'
# end
#
# * <tt>:has_one</tt> - Specify nested resources, this is a shorthand for mapping singleton resources beneath the current.
# * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural resources.
# * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
# * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources.
#
# You may directly specify the routing association with +has_one+ and +has_many+ like:
#
@ -277,18 +288,18 @@ module ActionController
#
# * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables.
#
# Weblog comments usually belong to a post, so you might use resources like:
# Weblog comments usually belong to a post, so you might use +resources+ like:
#
# map.resources :articles
# map.resources :comments, :path_prefix => '/articles/:article_id'
#
# You can nest resources calls to set this automatically:
# You can nest +resources+ calls to set this automatically:
#
# map.resources :articles do |article|
# article.resources :comments
# end
#
# The comment resources work the same, but must now include a value for <tt>:article_id</tt>.
# The comment \resources work the same, but must now include a value for <tt>:article_id</tt>.
#
# article_comments_url(@article)
# article_comment_url(@article, @comment)
@ -296,23 +307,52 @@ module ActionController
# article_comments_url(:article_id => @article)
# article_comment_url(:article_id => @article, :id => @comment)
#
# If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly:
#
# articles_comments_url(@comment.article_id, @comment)
#
# * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore.
# Use this if you have named routes that may clash.
#
# map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
# map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
#
# You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested resource:
#
# You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource:
#
# map.resources :articles do |article|
# article.resources :comments, :name_prefix => nil
# end
#
# This will yield named resources like so:
#
# end
#
# This will yield named \resources like so:
#
# comments_url(@article)
# comment_url(@article, @comment)
#
# * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member
# (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
#
# The <tt>:shallow</tt> option is inherited by any nested resource(s).
#
# For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
#
# map.resources :users, :shallow => true do |user|
# user.resources :posts do |post|
# post.resources :comments
# end
# end
# # --> GET /users/1/posts (maps to the PostsController#index action as usual)
# # also adds the usual named route called "user_posts"
# # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
# # also adds the named route called "post"
# # --> GET /posts/2/comments (maps to the CommentsController#index action)
# # also adds the named route called "post_comments"
# # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
# # also adds the named route called "comment"
#
# You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like:
#
# map.resources :users, :has_many => { :posts => :comments }, :shallow => true
#
# If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
#
# Examples:
@ -345,28 +385,28 @@ module ActionController
#
# The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
# HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
# <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for resource routes.
# <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes.
def resources(*entities, &block)
options = entities.extract_options!
entities.each { |entity| map_resource(entity, options.dup, &block) }
end
# Creates named routes for implementing verb-oriented controllers for a singleton resource.
# A singleton resource is global to its current context. For unnested singleton resources,
# the resource is global to the current user visiting the application, such as a user's
# /account profile. For nested singleton resources, the resource is global to its parent
# resource, such as a <tt>projects</tt> resource that <tt>has_one :project_manager</tt>.
# The <tt>project_manager</tt> should be mapped as a singleton resource under <tt>projects</tt>:
# Creates named routes for implementing verb-oriented controllers for a singleton \resource.
# A singleton \resource is global to its current context. For unnested singleton \resources,
# the \resource is global to the current user visiting the application, such as a user's
# <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent
# \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>.
# The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>:
#
# map.resources :projects do |project|
# project.resource :project_manager
# end
#
# See map.resources for general conventions. These are the main differences:
# * A singular name is given to map.resource. The default controller name is still taken from the plural name.
# See +resources+ for general conventions. These are the main differences:
# * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name.
# * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option.
# * No default index route is created for the singleton resource controller.
# * When nesting singleton resources, only the singular name is used as the path prefix (example: 'account/messages/1')
# * No default index route is created for the singleton \resource controller.
# * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
#
# For example:
#
@ -438,7 +478,7 @@ module ActionController
map_associations(resource, options)
if block_given?
with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block)
end
end
end
@ -455,30 +495,45 @@ module ActionController
map_associations(resource, options)
if block_given?
with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block)
end
end
end
def map_associations(resource, options)
map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
Array(options[:has_many]).each do |association|
resources(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace])
end
Array(options[:has_one]).each do |association|
resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace])
resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace], :shallow => options[:shallow])
end
end
def map_has_many_associations(resource, associations, options)
case associations
when Hash
associations.each do |association,has_many|
map_has_many_associations(resource, association, options.merge(:has_many => has_many))
end
when Array
associations.each do |association|
map_has_many_associations(resource, association, options)
end
when Symbol, String
resources(associations, :path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], :has_many => options[:has_many])
else
end
end
def map_collection_actions(map, resource)
resource.collection_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
map.named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}.:format", action_options)
[method].flatten.each do |m|
action_options = action_options_for(action, resource, m)
map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
end
end
end
end
@ -491,18 +546,15 @@ module ActionController
index_route_name << "_index"
end
map.named_route(index_route_name, resource.path, index_action_options)
map.named_route("formatted_#{index_route_name}", "#{resource.path}.:format", index_action_options)
map_named_routes(map, index_route_name, resource.path, index_action_options)
create_action_options = action_options_for("create", resource)
map.connect(resource.path, create_action_options)
map.connect("#{resource.path}.:format", create_action_options)
map_unnamed_routes(map, resource.path, create_action_options)
end
def map_default_singleton_actions(map, resource)
create_action_options = action_options_for("create", resource)
map.connect(resource.path, create_action_options)
map.connect("#{resource.path}.:format", create_action_options)
map_unnamed_routes(map, resource.path, create_action_options)
end
def map_new_actions(map, resource)
@ -510,11 +562,9 @@ module ActionController
actions.each do |action|
action_options = action_options_for(action, resource, method)
if action == :new
map.named_route("new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
map.named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}.:format", action_options)
map_named_routes(map, "new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
else
map.named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
map.named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}.:format", action_options)
map_named_routes(map, "#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
end
end
end
@ -523,27 +573,35 @@ module ActionController
def map_member_actions(map, resource)
resource.member_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
[method].flatten.each do |m|
action_options = action_options_for(action, resource, m)
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
action_path ||= Base.resources_path_names[action] || action
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
action_path ||= Base.resources_path_names[action] || action
map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}.:format",action_options)
map_named_routes(map, "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
end
end
end
show_action_options = action_options_for("show", resource)
map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options)
map_named_routes(map, "#{resource.shallow_name_prefix}#{resource.singular}", resource.member_path, show_action_options)
update_action_options = action_options_for("update", resource)
map.connect(resource.member_path, update_action_options)
map.connect("#{resource.member_path}.:format", update_action_options)
map_unnamed_routes(map, resource.member_path, update_action_options)
destroy_action_options = action_options_for("destroy", resource)
map.connect(resource.member_path, destroy_action_options)
map.connect("#{resource.member_path}.:format", destroy_action_options)
map_unnamed_routes(map, resource.member_path, destroy_action_options)
end
def map_unnamed_routes(map, path_without_format, options)
map.connect(path_without_format, options)
map.connect("#{path_without_format}.:format", options)
end
def map_named_routes(map, name, path_without_format, options)
map.named_route(name, path_without_format, options)
map.named_route("formatted_#{name}", "#{path_without_format}.:format", options)
end
def add_conditions_for(conditions, method)
@ -555,6 +613,7 @@ module ActionController
def action_options_for(action, resource, method = nil)
default_options = { :action => action.to_s }
require_id = !resource.kind_of?(SingletonResource)
case default_options[:action]
when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)

View file

@ -1,57 +1,169 @@
require 'digest/md5'
module ActionController
class AbstractResponse #:nodoc:
module ActionController # :nodoc:
# Represents an HTTP response generated by a controller action. One can use an
# ActionController::AbstractResponse object to retrieve the current state of the
# response, or customize the response. An AbstractResponse object can either
# represent a "real" HTTP response (i.e. one that is meant to be sent back to the
# web browser) or a test response (i.e. one that is generated from integration
# tests). See CgiResponse and TestResponse, respectively.
#
# AbstractResponse is mostly a Ruby on Rails framework implement detail, and should
# never be used directly in controllers. Controllers should use the methods defined
# in ActionController::Base instead. For example, if you want to set the HTTP
# response's content MIME type, then use ActionControllerBase#headers instead of
# AbstractResponse#headers.
#
# Nevertheless, integration tests may want to inspect controller responses in more
# detail, and that's when AbstractResponse can be useful for application developers.
# Integration test methods such as ActionController::Integration::Session#get and
# ActionController::Integration::Session#post return objects of type TestResponse
# (which are of course also of type AbstractResponse).
#
# For example, the following demo integration "test" prints the body of the
# controller response to the console:
#
# class DemoControllerTest < ActionController::IntegrationTest
# def test_print_root_path_to_console
# get('/')
# puts @response.body
# end
# end
class AbstractResponse
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
attr_accessor :request
attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
# The body content (e.g. HTML) of the response, as a String.
attr_accessor :body
# The headers of the response, as a Hash. It maps header names to header values.
attr_accessor :headers
attr_accessor :session, :cookies, :assigns, :template, :layout
attr_accessor :redirected_to, :redirected_to_method_params
delegate :default_charset, :to => 'ActionController::Base'
def initialize
@body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
end
def status; headers['Status'] end
def status=(status) headers['Status'] = status end
def location; headers['Location'] end
def location=(url) headers['Location'] = url end
# Sets the HTTP response's content MIME type. For example, in the controller
# you could write this:
#
# response.content_type = "text/plain"
#
# If a character set has been defined for this response (see charset=) then
# the character set information will also be included in the content type
# information.
def content_type=(mime_type)
self.headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type
self.headers["Content-Type"] =
if mime_type =~ /charset/ || (c = charset).nil?
mime_type.to_s
else
"#{mime_type}; charset=#{c}"
end
end
# Returns the response's content MIME type, or nil if content type has been set.
def content_type
content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0]
content_type.blank? ? nil : content_type
end
def charset=(encoding)
self.headers["Content-Type"] = "#{content_type || Mime::HTML}; charset=#{encoding}"
# Set the charset of the Content-Type header. Set to nil to remove it.
# If no content type is set, it defaults to HTML.
def charset=(charset)
headers["Content-Type"] =
if charset
"#{content_type || Mime::HTML}; charset=#{charset}"
else
content_type || Mime::HTML.to_s
end
end
def charset
charset = String(headers["Content-Type"] || headers["type"]).split(";")[1]
charset.blank? ? nil : charset.strip.split("=")[1]
end
def redirect(to_url, response_status)
self.headers["Status"] = response_status
self.headers["Location"] = to_url.gsub(/[\r\n]/, '')
def last_modified
if last = headers['Last-Modified']
Time.httpdate(last)
end
end
self.body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(to_url)}\">redirected</a>.</body></html>"
def last_modified?
headers.include?('Last-Modified')
end
def last_modified=(utc_time)
headers['Last-Modified'] = utc_time.httpdate
end
def etag
headers['ETag']
end
def etag?
headers.include?('ETag')
end
def etag=(etag)
headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
end
def redirect(url, status)
self.status = status
self.location = url.gsub(/[\r\n]/, '')
self.body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>"
end
def sending_file?
headers["Content-Transfer-Encoding"] == "binary"
end
def assign_default_content_type_and_charset!
self.content_type ||= Mime::HTML
self.charset ||= default_charset unless sending_file?
end
def prepare!
assign_default_content_type_and_charset!
handle_conditional_get!
convert_content_type!
set_content_length!
convert_content_type!
end
private
def handle_conditional_get!
if body.is_a?(String) && (headers['Status'] ? headers['Status'][0..2] == '200' : true) && !body.empty?
self.headers['ETag'] ||= %("#{Digest::MD5.hexdigest(body)}")
self.headers['Cache-Control'] = 'private, max-age=0, must-revalidate' if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
def handle_conditional_get!
if etag? || last_modified?
set_conditional_cache_control!
elsif nonempty_ok_response?
self.etag = body
if request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
self.headers['Status'] = '304 Not Modified'
self.body = ''
end
if request && request.etag_matches?(etag)
self.status = '304 Not Modified'
self.body = ''
end
set_conditional_cache_control!
end
end
def nonempty_ok_response?
ok = !status || status[0..2] == '200'
ok && body.is_a?(String) && !body.empty?
end
def set_conditional_cache_control!
if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
headers['Cache-Control'] = 'private, max-age=0, must-revalidate'
end
end
@ -70,7 +182,9 @@ module ActionController
# Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice
# for, say, a 2GB streaming file.
def set_content_length!
self.headers["Content-Length"] = body.size unless body.respond_to?(:call)
unless body.respond_to?(:call) || (status && status[0..2] == '304')
self.headers["Content-Length"] ||= body.size
end
end
end
end
end

View file

@ -201,7 +201,7 @@ module ActionController
# With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
#
# * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>,
# <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
# <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
# <tt>:any</tt> means that any method can access the route.
#
# Example:
@ -213,7 +213,7 @@ module ActionController
#
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
# URL will route to the <tt>show</tt> action.
#
#
# == Reloading routes
#
# You can reload routes if you feel you must:
@ -281,9 +281,9 @@ module ActionController
end
class << self
# Expects an array of controller names as the first argument.
# Executes the passed block with only the named controllers named available.
# This method is used in internal Rails testing.
# Expects an array of controller names as the first argument.
# Executes the passed block with only the named controllers named available.
# This method is used in internal Rails testing.
def with_controllers(names)
prior_controllers = @possible_controllers
use_controllers! names
@ -292,10 +292,10 @@ module ActionController
use_controllers! prior_controllers
end
# Returns an array of paths, cleaned of double-slashes and relative path references.
# * "\\\" and "//" become "\\" or "/".
# * "/foo/bar/../config" becomes "/foo/config".
# The returned array is sorted by length, descending.
# Returns an array of paths, cleaned of double-slashes and relative path references.
# * "\\\" and "//" become "\\" or "/".
# * "/foo/bar/../config" becomes "/foo/config".
# The returned array is sorted by length, descending.
def normalize_paths(paths)
# do the hokey-pokey of path normalization...
paths = paths.collect do |path|
@ -314,7 +314,7 @@ module ActionController
paths = paths.uniq.sort_by { |path| - path.length }
end
# Returns the array of controller names currently available to ActionController::Routing.
# Returns the array of controller names currently available to ActionController::Routing.
def possible_controllers
unless @possible_controllers
@possible_controllers = []
@ -339,28 +339,27 @@ module ActionController
@possible_controllers
end
# Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
# ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
# Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
# ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
def use_controllers!(controller_names)
@possible_controllers = controller_names
end
# Returns a controller path for a new +controller+ based on a +previous+ controller path.
# Handles 4 scenarios:
#
# * stay in the previous controller:
# controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
#
# * stay in the previous namespace:
# controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
#
# * forced move to the root namespace:
# controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
#
# * previous namespace is root:
# controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
#
# Returns a controller path for a new +controller+ based on a +previous+ controller path.
# Handles 4 scenarios:
#
# * stay in the previous controller:
# controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
#
# * stay in the previous namespace:
# controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
#
# * forced move to the root namespace:
# controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
#
# * previous namespace is root:
# controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
#
def controller_relative_to(controller, previous)
if controller.nil? then previous
elsif controller[0] == ?/ then controller[1..-1]
@ -369,12 +368,11 @@ module ActionController
end
end
end
Routes = RouteSet.new
ActiveSupport::Inflector.module_eval do
# Ensures that routes are reloaded when Rails inflections are updated.
# Ensures that routes are reloaded when Rails inflections are updated.
def inflections_with_route_reloading(&block)
returning(inflections_without_route_reloading(&block)) {
ActionController::Routing::Routes.reload! if block_given?

View file

@ -48,14 +48,10 @@ module ActionController
end
when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true)
when /\A\?(.*?)\?/
returning segment = StaticSegment.new($1) do
segment.is_optional = true
end
StaticSegment.new($1, :optional => true)
when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1)
when Regexp.new(separator_pattern) then
returning segment = DividerSegment.new($&) do
segment.is_optional = (optional_separators.include? $&)
end
DividerSegment.new($&, :optional => (optional_separators.include? $&))
end
[segment, $~.post_match]
end
@ -64,18 +60,18 @@ module ActionController
# segments are passed alongside in order to distinguish between default values
# and requirements.
def divide_route_options(segments, options)
options = options.dup
options = options.except(:path_prefix, :name_prefix)
if options[:namespace]
options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
options.delete(:path_prefix)
options.delete(:name_prefix)
end
requirements = (options.delete(:requirements) || {}).dup
defaults = (options.delete(:defaults) || {}).dup
conditions = (options.delete(:conditions) || {}).dup
validate_route_conditions(conditions)
path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
options.each do |key, value|
hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
@ -174,30 +170,32 @@ module ActionController
defaults, requirements, conditions = divide_route_options(segments, options)
requirements = assign_route_options(segments, defaults, requirements)
route = Route.new
# TODO: Segments should be frozen on initialize
segments.each { |segment| segment.freeze }
route.segments = segments
route.requirements = requirements
route.conditions = conditions
if !route.significant_keys.include?(:action) && !route.requirements[:action]
route.requirements[:action] = "index"
route.significant_keys << :action
end
# Routes cannot use the current string interpolation method
# if there are user-supplied <tt>:requirements</tt> as the interpolation
# code won't raise RoutingErrors when generating
if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
route.optimise = false
end
route = Route.new(segments, requirements, conditions)
if !route.significant_keys.include?(:controller)
raise ArgumentError, "Illegal route: the :controller must be specified!"
end
route
route.freeze
end
private
def validate_route_conditions(conditions)
if method = conditions[:method]
[method].flatten.each do |m|
if m == :head
raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
end
unless HTTP_METHODS.include?(m.to_sym)
raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
end
end
end
end
end
end
end

View file

@ -1,14 +1,14 @@
module ActionController
module Routing
# Much of the slow performance from routes comes from the
# Much of the slow performance from routes comes from the
# complexity of expiry, <tt>:requirements</tt> matching, defaults providing
# and figuring out which url pattern to use. With named routes
# we can avoid the expense of finding the right route. So if
# and figuring out which url pattern to use. With named routes
# we can avoid the expense of finding the right route. So if
# they've provided the right number of arguments, and have no
# <tt>:requirements</tt>, we can just build up a string and return it.
#
# To support building optimisations for other common cases, the
# generation code is separated into several classes
#
# To support building optimisations for other common cases, the
# generation code is separated into several classes
module Optimisation
def generate_optimisation_block(route, kind)
return "" unless route.optimise?
@ -20,6 +20,7 @@ module ActionController
class Optimiser
attr_reader :route, :kind
def initialize(route, kind)
@route = route
@kind = kind
@ -53,12 +54,12 @@ module ActionController
# map.person '/people/:id'
#
# If the user calls <tt>person_url(@person)</tt>, we can simply
# return a string like "/people/#{@person.to_param}"
# return a string like "/people/#{@person.to_param}"
# rather than triggering the expensive logic in +url_for+.
class PositionalArguments < Optimiser
def guard_condition
number_of_arguments = route.segment_keys.size
# if they're using foo_url(:id=>2) it's one
# if they're using foo_url(:id=>2) it's one
# argument, but we don't want to generate /foos/id2
if number_of_arguments == 1
"(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)"
@ -76,7 +77,7 @@ module ActionController
elements << '#{request.host_with_port}'
end
elements << '#{request.relative_url_root if request.relative_url_root}'
elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}'
# The last entry in <tt>route.segments</tt> appears to *always* be a
# 'divider segment' for '/' but we have assertions to ensure that
@ -94,23 +95,24 @@ module ActionController
end
# This case is mostly the same as the positional arguments case
# above, but it supports additional query parameters as the last
# above, but it supports additional query parameters as the last
# argument
class PositionalArgumentsWithAdditionalParams < PositionalArguments
def guard_condition
"(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{route.segment_keys.size + 1} && !args.last.has_key?(:anchor) && !args.last.has_key?(:port) && !args.last.has_key?(:host)"
end
# This case uses almost the same code as positional arguments,
# but add an args.last.to_query on the end
# This case uses almost the same code as positional arguments,
# but add a question mark and args.last.to_query on the end,
# unless the last arg is empty
def generation_code
super.insert(-2, '?#{args.last.to_query}')
super.insert(-2, '#{\'?\' + args.last.to_query unless args.last.empty?}')
end
# To avoid generating "http://localhost/?host=foo.example.com" we
# can't use this optimisation on routes without any segments
def applicable?
super && route.segment_keys.size > 0
super && route.segment_keys.size > 0
end
end

View file

@ -51,7 +51,6 @@ module ActionController
# 3) segm test for /users/:id
# (jump to list index = 5)
# 4) full test for /users/:id => here we are!
class RouteSet
def recognize_path(path, environment={})
result = recognize_optimized(path, environment) and return result
@ -68,28 +67,6 @@ module ActionController
end
end
def recognize_optimized(path, env)
write_recognize_optimized
recognize_optimized(path, env)
end
def write_recognize_optimized
tree = segment_tree(routes)
body = generate_code(tree)
instance_eval %{
def recognize_optimized(path, env)
segments = to_plain_segments(path)
index = #{body}
return nil unless index
while index < routes.size
result = routes[index].recognize(path, env) and return result
index += 1
end
nil
end
}, __FILE__, __LINE__
end
def segment_tree(routes)
tree = [0]
@ -153,6 +130,45 @@ module ActionController
segments
end
private
def write_recognize_optimized!
tree = segment_tree(routes)
body = generate_code(tree)
remove_recognize_optimized!
instance_eval %{
def recognize_optimized(path, env)
segments = to_plain_segments(path)
index = #{body}
return nil unless index
while index < routes.size
result = routes[index].recognize(path, env) and return result
index += 1
end
nil
end
}, __FILE__, __LINE__
end
def clear_recognize_optimized!
remove_recognize_optimized!
class << self
def recognize_optimized(path, environment)
write_recognize_optimized!
recognize_optimized(path, environment)
end
end
end
def remove_recognize_optimized!
if respond_to?(:recognize_optimized)
class << self
remove_method :recognize_optimized
end
end
end
end
end
end

View file

@ -3,11 +3,25 @@ module ActionController
class Route #:nodoc:
attr_accessor :segments, :requirements, :conditions, :optimise
def initialize
@segments = []
@requirements = {}
@conditions = {}
@optimise = true
def initialize(segments = [], requirements = {}, conditions = {})
@segments = segments
@requirements = requirements
@conditions = conditions
if !significant_keys.include?(:action) && !requirements[:action]
@requirements[:action] = "index"
@significant_keys << :action
end
# Routes cannot use the current string interpolation method
# if there are user-supplied <tt>:requirements</tt> as the interpolation
# code won't raise RoutingErrors when generating
has_requirements = @segments.detect { |segment| segment.respond_to?(:regexp) && segment.regexp }
if has_requirements || @requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
@optimise = false
else
@optimise = true
end
end
# Indicates whether the routes should be optimised with the string interpolation
@ -22,129 +36,6 @@ module ActionController
end.compact
end
# Write and compile a +generate+ method for this Route.
def write_generation
# Build the main body of the generation
body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
# If we have conditions that must be tested first, nest the body inside an if
body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
args = "options, hash, expire_on = {}"
# Nest the body inside of a def block, and then compile it.
raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
# expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
# are the same as the keys that were recalled from the previous request. Thus,
# we can use the expire_on.keys to determine which keys ought to be used to build
# the query string. (Never use keys from the recalled request when building the
# query string.)
method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend"
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend"
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
raw_method
end
# Build several lines of code that extract values from the options hash. If any
# of the values are missing or rejected then a return will be executed.
def generation_extraction
segments.collect do |segment|
segment.extraction_code
end.compact * "\n"
end
# Produce a condition expression that will check the requirements of this route
# upon generation.
def generation_requirements
requirement_conditions = requirements.collect do |key, req|
if req.is_a? Regexp
value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
"hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
else
"hash[:#{key}] == #{req.inspect}"
end
end
requirement_conditions * ' && ' unless requirement_conditions.empty?
end
def generation_structure
segments.last.string_structure segments[0..-2]
end
# Write and compile a +recognize+ method for this Route.
def write_recognition
# Create an if structure to extract the params from a match if it occurs.
body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
# Build the method declaration and compile it
method_decl = "def recognize(path, env={})\n#{body}\nend"
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
method_decl
end
# Plugins may override this method to add other conditions, like checks on
# host, subdomain, and so forth. Note that changes here only affect route
# recognition, not generation.
def recognition_conditions
result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
result << "conditions[:method] === env[:method]" if conditions[:method]
result
end
# Build the regular expression pattern that will match this route.
def recognition_pattern(wrap = true)
pattern = ''
segments.reverse_each do |segment|
pattern = segment.build_pattern pattern
end
wrap ? ("\\A" + pattern + "\\Z") : pattern
end
# Write the code to extract the parameters from a matched route.
def recognition_extraction
next_capture = 1
extraction = segments.collect do |segment|
x = segment.match_extraction(next_capture)
next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
x
end
extraction.compact
end
# Write the real generation implementation and then resend the message.
def generate(options, hash, expire_on = {})
write_generation
generate options, hash, expire_on
end
def generate_extras(options, hash, expire_on = {})
write_generation
generate_extras options, hash, expire_on
end
# Generate the query string with any extra keys in the hash and append
# it to the given path, returning the new path.
def append_query_string(path, hash, query_keys=nil)
return nil unless path
query_keys ||= extra_keys(hash)
"#{path}#{build_query_string(hash, query_keys)}"
end
# Determine which keys in the given hash are "extra". Extra keys are
# those that were not used to generate a particular route. The extra
# keys also do not include those recalled from the prior request, nor
# do they include any keys that were implied in the route (like a
# <tt>:controller</tt> that is required, but not explicitly used in the
# text of the route.)
def extra_keys(hash, recall={})
(hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
end
# Build a query string from the keys of the given hash. If +only_keys+
# is given (as an array), only the keys indicated will be used to build
# the query string. The query string will correctly build array parameter
@ -161,12 +52,6 @@ module ActionController
elements.empty? ? '' : "?#{elements.sort * '&'}"
end
# Write the real recognition implementation and then resend the message.
def recognize(path, environment={})
write_recognition
recognize path, environment
end
# A route's parameter shell contains parameter values that are not in the
# route's path, but should be placed in the recognized hash.
#
@ -186,7 +71,7 @@ module ActionController
# includes keys that appear inside the path, and keys that have requirements
# placed upon them.
def significant_keys
@significant_keys ||= returning [] do |sk|
@significant_keys ||= returning([]) do |sk|
segments.each { |segment| sk << segment.key if segment.respond_to? :key }
sk.concat requirements.keys
sk.uniq!
@ -209,12 +94,7 @@ module ActionController
end
def matches_controller_and_action?(controller, action)
unless defined? @matching_prepared
@controller_requirement = requirement_for(:controller)
@action_requirement = requirement_for(:action)
@matching_prepared = true
end
prepare_matching!
(@controller_requirement.nil? || @controller_requirement === controller) &&
(@action_requirement.nil? || @action_requirement === action)
end
@ -226,15 +106,150 @@ module ActionController
end
end
protected
def requirement_for(key)
return requirements[key] if requirements.key? key
segments.each do |segment|
return segment.regexp if segment.respond_to?(:key) && segment.key == key
# TODO: Route should be prepared and frozen on initialize
def freeze
unless frozen?
write_generation!
write_recognition!
prepare_matching!
parameter_shell
significant_keys
defaults
to_s
end
nil
super
end
private
def requirement_for(key)
return requirements[key] if requirements.key? key
segments.each do |segment|
return segment.regexp if segment.respond_to?(:key) && segment.key == key
end
nil
end
# Write and compile a +generate+ method for this Route.
def write_generation!
# Build the main body of the generation
body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
# If we have conditions that must be tested first, nest the body inside an if
body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
args = "options, hash, expire_on = {}"
# Nest the body inside of a def block, and then compile it.
raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
# expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
# are the same as the keys that were recalled from the previous request. Thus,
# we can use the expire_on.keys to determine which keys ought to be used to build
# the query string. (Never use keys from the recalled request when building the
# query string.)
method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend"
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend"
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
raw_method
end
# Build several lines of code that extract values from the options hash. If any
# of the values are missing or rejected then a return will be executed.
def generation_extraction
segments.collect do |segment|
segment.extraction_code
end.compact * "\n"
end
# Produce a condition expression that will check the requirements of this route
# upon generation.
def generation_requirements
requirement_conditions = requirements.collect do |key, req|
if req.is_a? Regexp
value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
"hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
else
"hash[:#{key}] == #{req.inspect}"
end
end
requirement_conditions * ' && ' unless requirement_conditions.empty?
end
def generation_structure
segments.last.string_structure segments[0..-2]
end
# Write and compile a +recognize+ method for this Route.
def write_recognition!
# Create an if structure to extract the params from a match if it occurs.
body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
# Build the method declaration and compile it
method_decl = "def recognize(path, env = {})\n#{body}\nend"
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
method_decl
end
# Plugins may override this method to add other conditions, like checks on
# host, subdomain, and so forth. Note that changes here only affect route
# recognition, not generation.
def recognition_conditions
result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
result << "[conditions[:method]].flatten.include?(env[:method])" if conditions[:method]
result
end
# Build the regular expression pattern that will match this route.
def recognition_pattern(wrap = true)
pattern = ''
segments.reverse_each do |segment|
pattern = segment.build_pattern pattern
end
wrap ? ("\\A" + pattern + "\\Z") : pattern
end
# Write the code to extract the parameters from a matched route.
def recognition_extraction
next_capture = 1
extraction = segments.collect do |segment|
x = segment.match_extraction(next_capture)
next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
x
end
extraction.compact
end
# Generate the query string with any extra keys in the hash and append
# it to the given path, returning the new path.
def append_query_string(path, hash, query_keys = nil)
return nil unless path
query_keys ||= extra_keys(hash)
"#{path}#{build_query_string(hash, query_keys)}"
end
# Determine which keys in the given hash are "extra". Extra keys are
# those that were not used to generate a particular route. The extra
# keys also do not include those recalled from the prior request, nor
# do they include any keys that were implied in the route (like a
# <tt>:controller</tt> that is required, but not explicitly used in the
# text of the route.)
def extra_keys(hash, recall = {})
(hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
end
def prepare_matching!
unless defined? @matching_prepared
@controller_requirement = requirement_for(:controller)
@action_requirement = requirement_for(:action)
@matching_prepared = true
end
end
end
end
end

View file

@ -1,6 +1,6 @@
module ActionController
module Routing
class RouteSet #:nodoc:
class RouteSet #:nodoc:
# Mapper instances are used to build routes. The object passed to the draw
# block in config/routes.rb is a Mapper instance.
#
@ -115,7 +115,7 @@ module ActionController
def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
reset! if regenerate
Array(destinations).each do |dest|
dest.send! :include, @module
dest.__send__(:include, @module)
end
end
@ -194,6 +194,8 @@ module ActionController
def initialize
self.routes = []
self.named_routes = NamedRouteCollection.new
clear_recognize_optimized!
end
# Subclasses and plugins may override this method to specify a different
@ -215,7 +217,7 @@ module ActionController
@routes_by_controller = nil
# This will force routing/recognition_optimization.rb
# to refresh optimisations.
@compiled_recognize_optimized = nil
clear_recognize_optimized!
end
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
@ -231,7 +233,6 @@ module ActionController
Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
clear!
load_routes!
install_helpers
end
# reload! will always force a reload whereas load checks the timestamp first
@ -352,7 +353,7 @@ module ActionController
if generate_all
# Used by caching to expire all paths for a resource
return routes.collect do |route|
route.send!(method, options, merged, expire_on)
route.__send__(method, options, merged, expire_on)
end.compact
end
@ -360,7 +361,7 @@ module ActionController
routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]
routes.each do |route|
results = route.send!(method, options, merged, expire_on)
results = route.__send__(method, options, merged, expire_on)
return results if results && (!results.is_a?(Array) || results.first)
end
end
@ -432,4 +433,4 @@ module ActionController
end
end
end
end
end

View file

@ -1,4 +1,3 @@
class Object
def to_param
to_s

View file

@ -2,13 +2,15 @@ module ActionController
module Routing
class Segment #:nodoc:
RESERVED_PCHAR = ':@&=+$,;'
UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
# TODO: Convert :is_optional accessor to read only
attr_accessor :is_optional
alias_method :optional?, :is_optional
def initialize
self.is_optional = false
@is_optional = false
end
def extraction_code
@ -63,12 +65,14 @@ module ActionController
end
class StaticSegment < Segment #:nodoc:
attr_accessor :value, :raw
attr_reader :value, :raw
alias_method :raw?, :raw
def initialize(value = nil)
def initialize(value = nil, options = {})
super()
self.value = value
@value = value
@raw = options[:raw] if options.key?(:raw)
@is_optional = options[:optional] if options.key?(:optional)
end
def interpolation_chunk
@ -97,10 +101,8 @@ module ActionController
end
class DividerSegment < StaticSegment #:nodoc:
def initialize(value = nil)
super(value)
self.raw = true
self.is_optional = true
def initialize(value = nil, options = {})
super(value, {:raw => true, :optional => true}.merge(options))
end
def optionality_implied?
@ -109,13 +111,17 @@ module ActionController
end
class DynamicSegment < Segment #:nodoc:
attr_accessor :key, :default, :regexp
attr_reader :key
# TODO: Convert these accessors to read only
attr_accessor :default, :regexp
def initialize(key = nil, options = {})
super()
self.key = key
self.default = options[:default] if options.key? :default
self.is_optional = true if options[:optional] || options.key?(:default)
@key = key
@default = options[:default] if options.key?(:default)
@regexp = options[:regexp] if options.key?(:regexp)
@is_optional = true if options[:optional] || options.key?(:default)
end
def to_s
@ -130,6 +136,7 @@ module ActionController
def extract_value
"#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
end
def value_check
if default # Then we know it won't be nil
"#{value_regexp.inspect} =~ #{local_name}" if regexp
@ -141,6 +148,7 @@ module ActionController
"#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
end
end
def expiry_statement
"expired, hash = true, options if !expired && expire_on[:#{key}]"
end
@ -152,7 +160,7 @@ module ActionController
s << "\n#{expiry_statement}"
end
def interpolation_chunk(value_code = "#{local_name}")
def interpolation_chunk(value_code = local_name)
"\#{CGI.escape(#{value_code}.to_s)}"
end
@ -175,7 +183,7 @@ module ActionController
end
def regexp_chunk
if regexp
if regexp
if regexp_has_modifiers?
"(#{regexp.to_s})"
else
@ -214,7 +222,6 @@ module ActionController
def regexp_has_modifiers?
regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
end
end
class ControllerSegment < DynamicSegment #:nodoc:
@ -224,9 +231,10 @@ module ActionController
end
# Don't URI.escape the controller name since it may contain slashes.
def interpolation_chunk(value_code = "#{local_name}")
def interpolation_chunk(value_code = local_name)
"\#{#{value_code}.to_s}"
end
# Make sure controller names like Admin/Content are correctly normalized to
# admin/content
def extract_value
@ -243,12 +251,12 @@ module ActionController
end
class PathSegment < DynamicSegment #:nodoc:
def interpolation_chunk(value_code = "#{local_name}")
def interpolation_chunk(value_code = local_name)
"\#{#{value_code}}"
end
def extract_value
"#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| CGI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
"#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| CGI.escape(path_component.to_param) }.to_param #{"|| #{default.inspect}" if default}"
end
def default

View file

@ -70,7 +70,8 @@ class CGI::Session::CookieStore
'path' => options['session_path'],
'domain' => options['session_domain'],
'expires' => options['session_expires'],
'secure' => options['session_secure']
'secure' => options['session_secure'],
'http_only' => options['session_http_only']
}
# Set no_hidden and no_cookies since the session id is unused and we
@ -129,7 +130,7 @@ class CGI::Session::CookieStore
private
# Marshal a session hash into safe cookie data. Include an integrity hash.
def marshal(session)
data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop
data = ActiveSupport::Base64.encode64s(Marshal.dump(session))
"#{data}--#{generate_digest(data)}"
end

View file

@ -1,8 +1,8 @@
#!/usr/local/bin/ruby -w
# This is a really simple session storage daemon, basically just a hash,
#!/usr/bin/env ruby
# This is a really simple session storage daemon, basically just a hash,
# which is enabled for DRb access.
require 'drb'
session_hash = Hash.new
@ -14,13 +14,13 @@ class <<session_hash
super(key, value)
end
end
def [](key)
@mutex.synchronize do
super(key)
end
end
def delete(key)
@mutex.synchronize do
super(key)
@ -29,4 +29,4 @@ class <<session_hash
end
DRb.start_service('druby://127.0.0.1:9192', session_hash)
DRb.thread.join
DRb.thread.join

View file

@ -60,6 +60,10 @@ module ActionController #:nodoc:
# # the session will only work over HTTPS, but only for the foo action
# session :only => :foo, :session_secure => true
#
# # the session by default uses HttpOnly sessions for security reasons.
# # this can be switched off.
# session :only => :foo, :session_http_only => false
#
# # the session will only be disabled for 'foo', and only if it is
# # requested as a web service
# session :off, :only => :foo,
@ -86,14 +90,14 @@ module ActionController #:nodoc:
raise ArgumentError, "only one of either :only or :except are allowed"
end
write_inheritable_array("session_options", [options])
write_inheritable_array(:session_options, [options])
end
# So we can declare session options in the Rails initializer.
alias_method :session=, :session
def cached_session_options #:nodoc:
@session_options ||= read_inheritable_attribute("session_options") || []
@session_options ||= read_inheritable_attribute(:session_options) || []
end
def session_options_for(request, action) #:nodoc:

View file

@ -12,19 +12,21 @@ module ActionController #:nodoc:
X_SENDFILE_HEADER = 'X-Sendfile'.freeze
protected
# Sends the file by streaming it 4096 bytes at a time. This way the
# whole file doesn't need to be read into memory at once. This makes
# it feasible to send even large files.
# Sends the file, by default streaming it 4096 bytes at a time. This way the
# whole file doesn't need to be read into memory at once. This makes it
# feasible to send even large files. You can optionally turn off streaming
# and send the whole file at once.
#
# Be careful to sanitize the path parameter if it coming from a web
# Be careful to sanitize the path parameter if it is coming from a web
# page. <tt>send_file(params[:path])</tt> allows a malicious user to
# download any file on your server.
#
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
# Defaults to <tt>File.basename(path)</tt>.
# * <tt>:type</tt> - specifies an HTTP content type.
# Defaults to 'application/octet-stream'.
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
# * <tt>:length</tt> - used to manually override the length (in bytes) of the content that
# is going to be sent to the client. Defaults to <tt>File.size(path)</tt>.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
# * <tt>:stream</tt> - whether to send the file to the user agent as it is read (+true+)
@ -35,6 +37,12 @@ module ActionController #:nodoc:
# * <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).
# * <tt>:x_sendfile</tt> - uses X-Sendfile to send the file when set to +true+. This is currently
# only available with Lighttpd/Apache2 and specific modules installed and activated. Since this
# uses the web server to send the file, this may lower memory consumption on your server and
# it will not block your application for further requests.
# See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and
# http://tn123.ath.cx/mod_xsendfile/ for details. Defaults to +false+.
#
# The default Content-Type and Content-Disposition headers are
# set to download arbitrary binary files in as many browsers as
@ -99,8 +107,7 @@ module ActionController #:nodoc:
#
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
# * <tt>:type</tt> - specifies an HTTP content type.
# Defaults to 'application/octet-stream'.
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
# * <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'.

View file

@ -6,6 +6,6 @@
</h1>
<pre><%=h @exception.clean_message %></pre>
<%= render_file(@rescues_path + "/_trace.erb", false) %>
<%= render(:file => @rescues_path + "/_trace.erb") %>
<%= render_file(@rescues_path + "/_request_and_response.erb", false) %>
<%= render(:file => @rescues_path + "/_request_and_response.erb") %>

View file

@ -15,7 +15,7 @@
<% @real_exception = @exception
@exception = @exception.original_exception || @exception %>
<%= render_file(@rescues_path + "/_trace.erb", false) %>
<%= render(:file => @rescues_path + "/_trace.erb") %>
<% @exception = @real_exception %>
<%= render_file(@rescues_path + "/_request_and_response.erb", false) %>
<%= render(:file => @rescues_path + "/_request_and_response.erb") %>

View file

@ -15,6 +15,65 @@ module ActionController
end
end
# Superclass for ActionController functional tests. Functional tests allow you to
# test a single controller action per test method. This should not be confused with
# integration tests (see ActionController::IntegrationTest), which are more like
# "stories" that can involve multiple controllers and mutliple actions (i.e. multiple
# different HTTP requests).
#
# == Basic example
#
# Functional tests are written as follows:
# 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
# an HTTP request.
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
# the controller's HTTP response, the database contents, etc.
#
# For example:
#
# class BooksControllerTest < ActionController::TestCase
# def test_create
# # Simulate a POST response with the given HTTP parameters.
# post(:create, :book => { :title => "Love Hina" })
#
# # Assert that the controller tried to redirect us to
# # the created book's URI.
# assert_response :found
#
# # Assert that the controller really put the book in the database.
# assert_not_nil Book.find_by_title("Love Hina")
# end
# end
#
# == Special instance variables
#
# ActionController::TestCase will also automatically provide the following instance
# variables for use in the tests:
#
# <b>@controller</b>::
# The controller instance that will be tested.
# <b>@request</b>::
# An ActionController::TestRequest, representing the current HTTP
# request. You can modify this object before sending the HTTP request. For example,
# you might want to set some session properties before sending a GET request.
# <b>@response</b>::
# An ActionController::TestResponse object, representing the response
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
# after calling +post+. If the various assert methods are not sufficient, then you
# may use this object to inspect the HTTP response in detail.
#
# (Earlier versions of Rails required each functional test to subclass
# Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
#
# == Controller is automatically inferred
#
# ActionController::TestCase will automatically infer the controller under test
# from the test class name. If the controller cannot be inferred from the test
# class name, you can explicity set it with +tests+.
#
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
# tests WidgetController
# end
class TestCase < ActiveSupport::TestCase
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
# (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
@ -25,7 +84,7 @@ module ActionController
module RaiseActionExceptions
attr_accessor :exception
def rescue_action(e)
def rescue_action_without_handler(e)
self.exception = e
if request.remote_addr == "0.0.0.0"
@ -41,6 +100,8 @@ module ActionController
@@controller_class = nil
class << self
# 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>.
def tests(controller_class)
self.controller_class = controller_class
end
@ -73,6 +134,9 @@ module ActionController
@controller = self.class.controller_class.new
@controller.request = @request = TestRequest.new
@response = TestResponse.new
@controller.params = {}
@controller.send(:initialize_current_url)
end
# Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
@ -80,4 +144,4 @@ module ActionController
@request.remote_addr = '208.77.188.166' # example.com
end
end
end
end

View file

@ -3,6 +3,8 @@ require 'action_controller/test_case'
module ActionController #:nodoc:
class Base
attr_reader :assigns
# Process a test request called with a TestRequest object.
def self.process_test(request)
new.process_test(request)
@ -14,7 +16,12 @@ module ActionController #:nodoc:
def process_with_test(*args)
returning process_without_test(*args) do
add_variables_to_assigns
@assigns = {}
(instance_variable_names - @@protected_instance_variables).each do |var|
value = instance_variable_get(var)
@assigns[var[1..-1]] = value
response.template.assigns[var[1..-1]] = value if response
end
end
end
@ -23,7 +30,7 @@ module ActionController #:nodoc:
class TestRequest < AbstractRequest #:nodoc:
attr_accessor :cookies, :session_options
attr_accessor :query_parameters, :request_parameters, :path, :session, :env
attr_accessor :query_parameters, :request_parameters, :path, :session
attr_accessor :host, :user_agent
def initialize(query_parameters = nil, request_parameters = nil, session = nil)
@ -42,7 +49,7 @@ module ActionController #:nodoc:
end
# Wraps raw_post in a StringIO.
def body
def body_stream #:nodoc:
StringIO.new(raw_post)
end
@ -54,7 +61,7 @@ module ActionController #:nodoc:
def port=(number)
@env["SERVER_PORT"] = number.to_i
@port_as_int = nil
port(true)
end
def action=(action_name)
@ -68,6 +75,8 @@ module ActionController #:nodoc:
@env["REQUEST_URI"] = value
@request_uri = nil
@path = nil
request_uri(true)
path(true)
end
def request_uri=(uri)
@ -77,21 +86,26 @@ module ActionController #:nodoc:
def accept=(mime_types)
@env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
accepts(true)
end
def if_modified_since=(last_modified)
@env["HTTP_IF_MODIFIED_SINCE"] = last_modified
end
def if_none_match=(etag)
@env["HTTP_IF_NONE_MATCH"] = etag
end
def remote_addr=(addr)
@env['REMOTE_ADDR'] = addr
end
def remote_addr
@env['REMOTE_ADDR']
end
def request_uri
def request_uri(*args)
@request_uri || super
end
def path
def path(*args)
@path || super
end
@ -113,17 +127,13 @@ module ActionController #:nodoc:
end
end
@parameters = nil # reset TestRequest#parameters to use the new path_parameters
end
end
def recycle!
self.request_parameters = {}
self.query_parameters = {}
self.path_parameters = {}
@request_method, @accepts, @content_type = nil, nil, nil
end
def referer
@env["HTTP_REFERER"]
unmemoize_all
end
private
@ -135,7 +145,7 @@ module ActionController #:nodoc:
@host = "test.host"
@request_uri = "/"
@user_agent = "Rails Testing"
self.remote_addr = "0.0.0.0"
self.remote_addr = "0.0.0.0"
@env["SERVER_PORT"] = 80
@env['REQUEST_METHOD'] = "GET"
end
@ -157,16 +167,16 @@ module ActionController #:nodoc:
module TestResponseBehavior #:nodoc:
# The response code of the request
def response_code
headers['Status'][0,3].to_i rescue 0
status[0,3].to_i rescue 0
end
# Returns a String to ensure compatibility with Net::HTTPResponse
def code
headers['Status'].to_s.split(' ')[0]
status.to_s.split(' ')[0]
end
def message
headers['Status'].to_s.split(' ',2)[1]
status.to_s.split(' ',2)[1]
end
# Was the response successful?
@ -205,24 +215,13 @@ module ActionController #:nodoc:
p.match(redirect_url) != nil
end
# Returns the template path of the file which was used to
# render this response (or nil)
def rendered_file(with_controller=false)
unless template.first_render.nil?
unless with_controller
template.first_render
else
template.first_render.split('/').last || template.first_render
end
end
# Returns the template of the file which was used to
# render this response (or nil)
def rendered_template
template.send(:_first_render)
end
# Was this template rendered by a file?
def rendered_with_file?
!rendered_file.nil?
end
# A shortcut to the flash. Returns an empyt hash if no session flash exists.
# A shortcut to the flash. Returns an empty hash if no session flash exists.
def flash
session['flash'] || {}
end
@ -254,11 +253,11 @@ module ActionController #:nodoc:
# Does the specified template object exist?
def has_template_object?(name=nil)
!template_objects[name].nil?
!template_objects[name].nil?
end
# Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs
#
#
# assert_equal ['AuthorOfNewPage'], r.cookies['author'].value
def cookies
headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash }
@ -277,8 +276,19 @@ module ActionController #:nodoc:
end
end
class TestResponse < AbstractResponse #:nodoc:
# Integration test methods such as ActionController::Integration::Session#get
# and ActionController::Integration::Session#post return objects of class
# TestResponse, which represent the HTTP response results of the requested
# controller actions.
#
# See AbstractResponse for more information on controller response objects.
class TestResponse < AbstractResponse
include TestResponseBehavior
def recycle!
headers.delete('ETag')
headers.delete('Last-Modified')
end
end
class TestSession #:nodoc:
@ -324,7 +334,7 @@ module ActionController #:nodoc:
#
# Usage example, within a functional test:
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
#
#
# Pass a true third parameter to ensure the uploaded file is opened in binary mode (only required for Windows):
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary)
require 'tempfile'
@ -352,13 +362,14 @@ module ActionController #:nodoc:
alias local_path path
def method_missing(method_name, *args, &block) #:nodoc:
@tempfile.send!(method_name, *args, &block)
@tempfile.__send__(method_name, *args, &block)
end
end
module TestProcess
def self.included(base)
# execute the request simulating a specific HTTP method and set/volley the response
# TODO: this should be un-DRY'ed for the sake of API documentation.
%w( get post put delete head ).each do |method|
base.class_eval <<-EOV, __FILE__, __LINE__
def #{method}(action, parameters = nil, session = nil, flash = nil)
@ -380,6 +391,7 @@ module ActionController #:nodoc:
end
@request.recycle!
@response.recycle!
@html_document = nil
@request.env['REQUEST_METHOD'] ||= "GET"
@ -397,32 +409,21 @@ module ActionController #:nodoc:
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@request.env['HTTP_ACCEPT'] = 'text/javascript, text/html, application/xml, text/xml, */*'
returning send!(request_method, action, parameters, session, flash) do
returning __send__(request_method, action, parameters, session, flash) do
@request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT'
end
end
alias xhr :xml_http_request
def follow_redirect
redirected_controller = @response.redirected_to[:controller]
if redirected_controller && redirected_controller != @controller.controller_name
raise "Can't follow redirects outside of current controller (from #{@controller.controller_name} to #{redirected_controller})"
def assigns(key = nil)
if key.nil?
@response.template.assigns
else
@response.template.assigns[key.to_s]
end
get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys)
end
deprecate :follow_redirect => "If you wish to follow redirects, you should use integration tests"
def assigns(key = nil)
if key.nil?
@response.template.assigns
else
@response.template.assigns[key.to_s]
end
end
def session
@response.session
end
@ -441,7 +442,7 @@ module ActionController #:nodoc:
def build_request_uri(action, parameters)
unless @request.env['REQUEST_URI']
options = @controller.send!(:rewrite_options, parameters)
options = @controller.__send__(:rewrite_options, parameters)
options.update(:only_path => true, :action => action)
url = ActionController::UrlRewriter.new(@request, parameters)
@ -463,10 +464,13 @@ module ActionController #:nodoc:
end
def method_missing(selector, *args)
return @controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
return super
if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
@controller.send(selector, *args)
else
super
end
end
# Shortcut for <tt>ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type)</tt>:
#
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
@ -477,7 +481,7 @@ module ActionController #:nodoc:
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
def fixture_file_upload(path, mime_type = nil, binary = false)
ActionController::TestUploadedFile.new(
Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path,
Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path,
mime_type,
binary
)
@ -485,7 +489,7 @@ module ActionController #:nodoc:
# A helper to make it easier to test different route configurations.
# This method temporarily replaces ActionController::Routing::Routes
# with a new RouteSet instance.
# with a new RouteSet instance.
#
# The new instance is yielded to the passed block. Typically the block
# will create some routes using <tt>map.draw { map.connect ... }</tt>:

View file

@ -0,0 +1,13 @@
module ActionController
module Translation
def translate(*args)
I18n.translate *args
end
alias :t :translate
def localize(*args)
I18n.localize *args
end
alias :l :localize
end
end

View file

@ -1,19 +1,96 @@
module ActionController
# Write URLs from arbitrary places in your codebase, such as your mailers.
# In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
# is also possible: an URL can be generated from one of your routing definitions.
# URL generation functionality is centralized in this module.
#
# Example:
# See ActionController::Routing and ActionController::Resources for general
# information about routing and routes.rb.
#
# class MyMailer
# include ActionController::UrlWriter
# default_url_options[:host] = 'www.basecamphq.com'
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
# then ActionController::UrlWriter is what you're looking for. Read on for
# an introduction.
#
# def signup_url(token)
# url_for(:controller => 'signup', action => 'index', :token => token)
# == URL generation from parameters
#
# As you may know, some functions - such as ActionController::Base#url_for
# and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
# of parameters. For example, you've probably had the chance to write code
# like this in one of your views:
#
# <%= link_to('Click here', :controller => 'users',
# :action => 'new', :message => 'Welcome!') %>
#
# #=> Generates a link to: /users/new?message=Welcome%21
#
# link_to, and all other functions that require URL generation functionality,
# actually use ActionController::UrlWriter under the hood. And in particular,
# they use the ActionController::UrlWriter#url_for method. One can generate
# the same path as the above example by using the following code:
#
# include UrlWriter
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
# :only_path => true)
# # => "/users/new?message=Welcome%21"
#
# Notice the <tt>:only_path => true</tt> part. This is because UrlWriter has no
# information about the website hostname that your Rails app is serving. So if you
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
# argument:
#
# include UrlWriter
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
# :host => 'www.example.com') # Changed this.
# # => "http://www.example.com/users/new?message=Welcome%21"
#
# By default, all controllers and views have access to a special version of url_for,
# that already knows what the current hostname is. So if you use url_for in your
# controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
# argument.
#
# For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for.
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for'
# in full. However, mailers don't have hostname information, and what's why you'll still
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
#
#
# == URL generation for named routes
#
# UrlWriter also allows one to access methods that have been auto-generated from
# named routes. For example, suppose that you have a 'users' resource in your
# <b>routes.rb</b>:
#
# map.resources :users
#
# This generates, among other things, the method <tt>users_path</tt>. By default,
# this method is accessible from your controllers, views and mailers. If you need
# to access this auto-generated method from other places (such as a model), then
# you can do that in two ways.
#
# The first way is to include ActionController::UrlWriter in your class:
#
# class User < ActiveRecord::Base
# include ActionController::UrlWriter # !!!
#
# def name=(value)
# write_attribute('name', value)
# write_attribute('base_uri', users_path) # !!!
# end
# end
# end
#
# In addition to providing +url_for+, named routes are also accessible after
# including UrlWriter.
# The second way is to access them through ActionController::UrlWriter.
# The autogenerated named routes methods are available as class methods:
#
# class User < ActiveRecord::Base
# def name=(value)
# write_attribute('name', value)
# path = ActionController::UrlWriter.users_path # !!!
# write_attribute('base_uri', path) # !!!
# end
# end
module UrlWriter
# The default options for urls written by this writer. Typically a <tt>:host</tt>
# pair is provided.
@ -37,7 +114,7 @@ module ActionController
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
# +relative_url_root+ set in ActionController::AbstractRequest.relative_url_root.
# +relative_url_root+ set in ActionController::Base.relative_url_root.
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
#
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
@ -67,7 +144,7 @@ module ActionController
[:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
end
trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
url << ActionController::AbstractRequest.relative_url_root.to_s unless options[:skip_relative_url_root]
url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
generated = Routing::Routes.generate(options, {})
url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
@ -108,7 +185,7 @@ module ActionController
end
path = rewrite_path(options)
rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root]
rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
rewritten_url << "##{options[:anchor]}" if options[:anchor]

View file

@ -150,7 +150,14 @@ module HTML #:nodoc:
end
if scanner.skip(/!\[CDATA\[/)
scanner.scan_until(/\]\]>/)
unless scanner.skip_until(/\]\]>/)
if strict
raise "expected ]]> (got #{scanner.rest.inspect} for #{content})"
else
scanner.skip_until(/\Z/)
end
end
return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
end
@ -265,7 +272,7 @@ module HTML #:nodoc:
# itself.
class CDATA < Text #:nodoc:
def to_s
"<![CDATA[#{super}]>"
"<![CDATA[#{super}]]>"
end
end

View file

@ -64,7 +64,7 @@ module HTML
#
# When using a combination of the above, the element name comes first
# followed by identifier, class names, attributes, pseudo classes and
# negation in any order. Do not seprate these parts with spaces!
# negation in any order. Do not separate these parts with spaces!
# Space separation is used for descendant selectors.
#
# For example:
@ -158,7 +158,7 @@ module HTML
# * <tt>:not(selector)</tt> -- Match the element only if the element does not
# match the simple selector.
#
# As you can see, <tt>:nth-child<tt> pseudo class and its varient can get quite
# As you can see, <tt>:nth-child<tt> pseudo class and its variant can get quite
# tricky and the CSS specification doesn't do a much better job explaining it.
# But after reading the examples and trying a few combinations, it's easy to
# figure out.

View file

@ -80,7 +80,7 @@ module ActionController #:nodoc:
# array (may also be a single value).
def verify(options={})
before_filter :only => options[:only], :except => options[:except] do |c|
c.send! :verify_action, options
c.__send__ :verify_action, options
end
end
end
@ -116,7 +116,7 @@ module ActionController #:nodoc:
end
def apply_redirect_to(redirect_to_option) # :nodoc:
(redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.send!(redirect_to_option) : redirect_to_option
(redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.__send__(redirect_to_option) : redirect_to_option
end
def apply_remaining_actions(options) # :nodoc: