Rails 2.1 RC1
Updated Instiki to Rails 2.1 RC1 (aka 2.0.991).
This commit is contained in:
parent
14afed5893
commit
5292899c9a
971 changed files with 46318 additions and 17450 deletions
|
@ -33,7 +33,13 @@ module ActionController
|
|||
elsif type.is_a?(Symbol) && @response.response_code == ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE[type]
|
||||
assert_block("") { true } # to count the assertion
|
||||
else
|
||||
assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
|
||||
if @response.error?
|
||||
exception = @response.template.instance_variable_get(:@exception)
|
||||
exception_message = exception && exception.message
|
||||
assert_block(build_message(message, "Expected response to be a <?>, but was <?>\n<?>", type, @response.response_code, exception_message.to_s)) { false }
|
||||
else
|
||||
assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -114,6 +114,9 @@ module ActionController
|
|||
#
|
||||
# # 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" })
|
||||
def assert_routing(path, options, defaults={}, extras={}, message=nil)
|
||||
assert_recognizes(options, path, extras, message)
|
||||
|
||||
|
@ -122,7 +125,7 @@ module ActionController
|
|||
options[:controller] = "/#{controller}"
|
||||
end
|
||||
|
||||
assert_generates(path, options, defaults, extras, message)
|
||||
assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -140,4 +143,4 @@ module ActionController
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,11 +21,11 @@ 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
|
||||
# * +assert_select_rjs+ - Assertions on HTML content of RJS update and
|
||||
# insertion operations.
|
||||
# * #assert_select_encoded -- Assertions on HTML encoded inside XML,
|
||||
# * +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.
|
||||
# * +assert_select_email+ - Assertions on the HTML body of an e-mail.
|
||||
#
|
||||
# Also see HTML::Selector to learn how to use selectors.
|
||||
module SelectorAssertions
|
||||
|
@ -136,27 +136,27 @@ module ActionController
|
|||
# === Equality Tests
|
||||
#
|
||||
# The equality test may be one of the following:
|
||||
# * <tt>true</tt> -- Assertion is true if at least one element selected.
|
||||
# * <tt>false</tt> -- Assertion is true if no element selected.
|
||||
# * <tt>String/Regexp</tt> -- Assertion is true if the text value of at least
|
||||
# * <tt>true</tt> - Assertion is true if at least one element selected.
|
||||
# * <tt>false</tt> - Assertion is true if no element selected.
|
||||
# * <tt>String/Regexp</tt> - Assertion is true if the text value of at least
|
||||
# one element matches the string or regular expression.
|
||||
# * <tt>Integer</tt> -- Assertion is true if exactly that number of
|
||||
# * <tt>Integer</tt> - Assertion is true if exactly that number of
|
||||
# elements are selected.
|
||||
# * <tt>Range</tt> -- Assertion is true if the number of selected
|
||||
# * <tt>Range</tt> - Assertion is true if the number of selected
|
||||
# elements fit the range.
|
||||
# If no equality test specified, the assertion is true if at least one
|
||||
# element selected.
|
||||
#
|
||||
# To perform more than one equality tests, use a hash with the following keys:
|
||||
# * <tt>:text</tt> -- Narrow the selection to elements that have this text
|
||||
# * <tt>:text</tt> - Narrow the selection to elements that have this text
|
||||
# value (string or regexp).
|
||||
# * <tt>:html</tt> -- Narrow the selection to elements that have this HTML
|
||||
# * <tt>:html</tt> - Narrow the selection to elements that have this HTML
|
||||
# content (string or regexp).
|
||||
# * <tt>:count</tt> -- Assertion is true if the number of selected elements
|
||||
# * <tt>:count</tt> - Assertion is true if the number of selected elements
|
||||
# is equal to this value.
|
||||
# * <tt>:minimum</tt> -- Assertion is true if the number of selected
|
||||
# * <tt>:minimum</tt> - Assertion is true if the number of selected
|
||||
# elements is at least this value.
|
||||
# * <tt>:maximum</tt> -- Assertion is true if the number of selected
|
||||
# * <tt>:maximum</tt> - Assertion is true if the number of selected
|
||||
# elements is at most this value.
|
||||
#
|
||||
# If the method is called with a block, once all equality tests are
|
||||
|
@ -263,12 +263,15 @@ module ActionController
|
|||
if match_with = equals[:text]
|
||||
matches.delete_if do |match|
|
||||
text = ""
|
||||
text.force_encoding(match_with.encoding) if text.respond_to?(:force_encoding)
|
||||
stack = match.children.reverse
|
||||
while node = stack.pop
|
||||
if node.tag?
|
||||
stack.concat node.children.reverse
|
||||
else
|
||||
text << node.content
|
||||
content = node.content
|
||||
content.force_encoding(match_with.encoding) if content.respond_to?(:force_encoding)
|
||||
text << content
|
||||
end
|
||||
end
|
||||
text.strip! unless NO_STRIP.include?(match.name)
|
||||
|
|
|
@ -5,6 +5,7 @@ require 'action_controller/routing'
|
|||
require 'action_controller/resources'
|
||||
require 'action_controller/url_rewriter'
|
||||
require 'action_controller/status_codes'
|
||||
require 'action_view'
|
||||
require 'drb'
|
||||
require 'set'
|
||||
|
||||
|
@ -15,9 +16,6 @@ module ActionController #:nodoc:
|
|||
class SessionRestoreError < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
class MissingTemplate < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
class RenderError < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
|
@ -161,28 +159,34 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# Hello #{session[:person]}
|
||||
#
|
||||
# For removing objects from the session, you can either assign a single key to nil, like <tt>session[:person] = nil</tt>, or you can
|
||||
# remove the entire session with reset_session.
|
||||
# For removing objects from the session, you can either assign a single key to +nil+:
|
||||
#
|
||||
# Sessions are stored in a browser cookie that's cryptographically signed, but unencrypted, by default. This prevents
|
||||
# the user from tampering with the session but also allows him to see its contents.
|
||||
# # removes :person from session
|
||||
# session[:person] = nil
|
||||
#
|
||||
# Do not put secret information in session!
|
||||
# or you can remove the entire session with +reset_session+.
|
||||
#
|
||||
# Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted.
|
||||
# This prevents the user from tampering with the session but also allows him to see its contents.
|
||||
#
|
||||
# Do not put secret information in cookie-based sessions!
|
||||
#
|
||||
# Other options for session storage are:
|
||||
#
|
||||
# ActiveRecordStore: sessions are stored in your database, which works better than PStore with multiple app servers and,
|
||||
# unlike CookieStore, hides your session contents from the user. To use ActiveRecordStore, set
|
||||
# * ActiveRecordStore - Sessions are stored in your database, which works better than PStore with multiple app servers and,
|
||||
# unlike CookieStore, hides your session contents from the user. To use ActiveRecordStore, set
|
||||
#
|
||||
# config.action_controller.session_store = :active_record_store
|
||||
# config.action_controller.session_store = :active_record_store
|
||||
#
|
||||
# in your <tt>environment.rb</tt> and run <tt>rake db:sessions:create</tt>.
|
||||
# in your <tt>config/environment.rb</tt> and run <tt>rake db:sessions:create</tt>.
|
||||
#
|
||||
# MemCacheStore: sessions are stored as entries in your memcached cache. Set the session store type in <tt>environment.rb</tt>:
|
||||
# * MemCacheStore - Sessions are stored as entries in your memcached cache.
|
||||
# Set the session store type in <tt>config/environment.rb</tt>:
|
||||
#
|
||||
# config.action_controller.session_store = :mem_cache_store
|
||||
# config.action_controller.session_store = :mem_cache_store
|
||||
#
|
||||
# This assumes that memcached has been installed and configured properly. See the MemCacheStore docs for more information.
|
||||
# This assumes that memcached has been installed and configured properly.
|
||||
# See the MemCacheStore docs for more information.
|
||||
#
|
||||
# == Responses
|
||||
#
|
||||
|
@ -255,16 +259,12 @@ module ActionController #:nodoc:
|
|||
DEFAULT_RENDER_STATUS_CODE = "200 OK"
|
||||
|
||||
include StatusCodes
|
||||
|
||||
# Determines whether the view has access to controller internals @request, @response, @session, and @template.
|
||||
# By default, it does.
|
||||
@@view_controller_internals = true
|
||||
cattr_accessor :view_controller_internals
|
||||
|
||||
# Protected instance variable cache
|
||||
@@protected_variables_cache = nil
|
||||
cattr_accessor :protected_variables_cache
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
# 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:
|
||||
# ActionController::Base.asset_host = "http://assets.example.com"
|
||||
|
@ -283,9 +283,10 @@ module ActionController #:nodoc:
|
|||
@@debug_routes = true
|
||||
cattr_accessor :debug_routes
|
||||
|
||||
# Controls whether the application is thread-safe, so multi-threaded servers like WEBrick know whether to apply a mutex
|
||||
# around the performance of each action. Action Pack and Active Record are by default thread-safe, but many applications
|
||||
# may not be. Turned off by default.
|
||||
# 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.
|
||||
@@allow_concurrency = false
|
||||
cattr_accessor :allow_concurrency
|
||||
|
||||
|
@ -315,9 +316,10 @@ module ActionController #:nodoc:
|
|||
# A YAML parser is also available and can be turned on with:
|
||||
#
|
||||
# ActionController::Base.param_parsers[Mime::YAML] = :yaml
|
||||
@@param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
|
||||
@@param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
|
||||
Mime::URL_ENCODED_FORM => :url_encoded_form,
|
||||
Mime::XML => :xml_simple }
|
||||
Mime::XML => :xml_simple,
|
||||
Mime::JSON => :json }
|
||||
cattr_accessor :param_parsers
|
||||
|
||||
# Controls the default charset for all renders.
|
||||
|
@ -328,17 +330,16 @@ module ActionController #:nodoc:
|
|||
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
|
||||
cattr_accessor :logger
|
||||
|
||||
# Determines which template class should be used by ActionController.
|
||||
cattr_accessor :template_class
|
||||
|
||||
# Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates.
|
||||
cattr_accessor :ignore_missing_templates
|
||||
|
||||
# Controls the resource action separator
|
||||
@@resource_action_separator = "/"
|
||||
cattr_accessor :resource_action_separator
|
||||
|
||||
# Sets the token parameter name for RequestForgery. Calling #protect_from_forgery sets it to :authenticity_token by default
|
||||
# Allow to override path names for default resources' actions
|
||||
@@resources_path_names = { :new => 'new', :edit => 'edit' }
|
||||
cattr_accessor :resources_path_names
|
||||
|
||||
# Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+
|
||||
# sets it to <tt>:authenticity_token</tt> by default.
|
||||
cattr_accessor :request_forgery_protection_token
|
||||
|
||||
# Indicates whether or not optimise the generated named
|
||||
|
@ -428,6 +429,7 @@ module ActionController #:nodoc:
|
|||
|
||||
def view_paths=(value)
|
||||
@view_paths = value
|
||||
ActionView::TemplateFinder.process_view_paths(value)
|
||||
end
|
||||
|
||||
# Adds a view_path to the front of the view_paths array.
|
||||
|
@ -440,6 +442,7 @@ module ActionController #:nodoc:
|
|||
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)
|
||||
end
|
||||
|
||||
# Adds a view_path to the end of the view_paths array.
|
||||
|
@ -452,6 +455,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)
|
||||
end
|
||||
|
||||
# Replace sensitive parameter data from the request log.
|
||||
|
@ -534,23 +538,23 @@ module ActionController #:nodoc:
|
|||
|
||||
# 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:
|
||||
#
|
||||
# All keys given to url_for are forwarded to the Route module, save for the following:
|
||||
# * <tt>:anchor</tt> -- specifies the anchor name to be appended to the path. For example,
|
||||
#
|
||||
# All keys given to +url_for+ are forwarded to the Route module, save for the following:
|
||||
# * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path. For example,
|
||||
# <tt>url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments'</tt>
|
||||
# will produce "/posts/show/10#comments".
|
||||
# * <tt>:only_path</tt> -- if true, returns the relative URL (omitting the protocol, host name, and port) (<tt>false</tt> by default)
|
||||
# * <tt>:trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this
|
||||
# * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>false</tt> by default).
|
||||
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this
|
||||
# is currently not recommended since it breaks caching.
|
||||
# * <tt>:host</tt> -- overrides the default (current) host if provided.
|
||||
# * <tt>:protocol</tt> -- overrides the default (current) protocol if provided.
|
||||
# * <tt>:port</tt> -- optionally specify the port to connect to.
|
||||
# * <tt>:user</tt> -- Inline HTTP authentication (only plucked out if :password is also present).
|
||||
# * <tt>:password</tt> -- Inline HTTP authentication (only plucked out if :user is also present).
|
||||
# * <tt>:skip_relative_url_root</tt> -- if true, the url is not constructed using the relative_url_root of the request so the path
|
||||
# will include the web server relative installation directory.
|
||||
# * <tt>:host</tt> - Overrides the default (current) host if provided.
|
||||
# * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
|
||||
# * <tt>:port</tt> - Optionally specify the port to connect to.
|
||||
# * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
|
||||
# * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
|
||||
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the +relative_url_root+
|
||||
# of the request so the path will include the web server relative installation directory.
|
||||
#
|
||||
# The URL is generated from the remaining keys in the hash. A URL contains two key parts: the <base> and a query string.
|
||||
# Routes composes a query string as the key/value pairs not included in the <base>.
|
||||
|
@ -601,7 +605,7 @@ module ActionController #:nodoc:
|
|||
# url_for :controller => 'posts', :action => nil
|
||||
#
|
||||
# If you explicitly want to create a URL that's almost the same as the current URL, you can do so using the
|
||||
# :overwrite_params options. Say for your posts you have different views for showing and printing them.
|
||||
# <tt>:overwrite_params</tt> options. Say for your posts you have different views for showing and printing them.
|
||||
# Then, in the show view, you get the URL for the print view like this
|
||||
#
|
||||
# url_for :overwrite_params => { :action => 'print' }
|
||||
|
@ -642,11 +646,11 @@ module ActionController #:nodoc:
|
|||
|
||||
# View load paths for controller.
|
||||
def view_paths
|
||||
(@template || self.class).view_paths
|
||||
@template.finder.view_paths
|
||||
end
|
||||
|
||||
def view_paths=(value)
|
||||
(@template || self.class).view_paths = value
|
||||
@template.finder.view_paths = value # Mutex needed
|
||||
end
|
||||
|
||||
# Adds a view_path to the front of the view_paths array.
|
||||
|
@ -656,7 +660,7 @@ module ActionController #:nodoc:
|
|||
# self.prepend_view_path(["views/default", "views/custom"])
|
||||
#
|
||||
def prepend_view_path(path)
|
||||
(@template || self.class).prepend_view_path(path)
|
||||
@template.finder.prepend_view_path(path) # Mutex needed
|
||||
end
|
||||
|
||||
# Adds a view_path to the end of the view_paths array.
|
||||
|
@ -666,7 +670,7 @@ module ActionController #:nodoc:
|
|||
# self.append_view_path(["views/default", "views/custom"])
|
||||
#
|
||||
def append_view_path(path)
|
||||
(@template || self.class).append_view_path(path)
|
||||
@template.finder.append_view_path(path) # Mutex needed
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -772,7 +776,7 @@ module ActionController #:nodoc:
|
|||
# # placed in "app/views/layouts/special.r(html|xml)"
|
||||
# render :text => "Hi there!", :layout => "special"
|
||||
#
|
||||
# The :text option can also accept a Proc object, which can be used to manually control the page generation. This should
|
||||
# The <tt>:text</tt> option can also accept a Proc object, which can be used to manually control the page generation. This should
|
||||
# generally be avoided, as it violates the separation between code and content, and because almost everything that can be
|
||||
# done with this method can also be done more cleanly using one of the other rendering methods, most notably templates.
|
||||
#
|
||||
|
@ -826,19 +830,21 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# === Rendering with status and location headers
|
||||
#
|
||||
# All renders take the :status and :location options and turn them into headers. They can even be used together:
|
||||
# All renders take the <tt>:status</tt> and <tt>:location</tt> options and turn them into headers. They can even be used together:
|
||||
#
|
||||
# render :xml => post.to_xml, :status => :created, :location => post_url(post)
|
||||
def render(options = nil, &block) #:doc:
|
||||
def render(options = nil, extra_options = {}, &block) #:doc:
|
||||
raise DoubleRenderError, "Can only render or redirect once per action" if performed?
|
||||
|
||||
if options.nil?
|
||||
return render_for_file(default_template_name, nil, true)
|
||||
elsif !extra_options.is_a?(Hash)
|
||||
raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}"
|
||||
else
|
||||
if options == :update
|
||||
options = { :update => true }
|
||||
options = extra_options.merge({ :update => true })
|
||||
elsif !options.is_a?(Hash)
|
||||
raise RenderError, "You called render with invalid options : #{options}"
|
||||
raise RenderError, "You called render with invalid options : #{options.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -850,8 +856,8 @@ module ActionController #:nodoc:
|
|||
response.headers["Location"] = url_for(location)
|
||||
end
|
||||
|
||||
if text = options[:text]
|
||||
render_for_text(text, options[:status])
|
||||
if options.has_key?(:text)
|
||||
render_for_text(options[:text], options[:status])
|
||||
|
||||
else
|
||||
if file = options[:file]
|
||||
|
@ -862,7 +868,8 @@ module ActionController #:nodoc:
|
|||
|
||||
elsif inline = options[:inline]
|
||||
add_variables_to_assigns
|
||||
render_for_text(@template.render_template(options[:type], inline, nil, options[:locals] || {}), options[:status])
|
||||
tmpl = ActionView::InlineTemplate.new(@template, options[:inline], options[:locals], options[:type])
|
||||
render_for_text(@template.render_template(tmpl), options[:status])
|
||||
|
||||
elsif action_name = options[:action]
|
||||
template = default_template_name(action_name.to_s)
|
||||
|
@ -904,7 +911,7 @@ module ActionController #:nodoc:
|
|||
|
||||
generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
|
||||
response.content_type = Mime::JS
|
||||
render_for_text(generator.to_s)
|
||||
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
|
||||
|
@ -997,7 +1004,7 @@ module ActionController #:nodoc:
|
|||
# As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
|
||||
# urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
|
||||
# by this method.
|
||||
def default_url_options(options) #:doc:
|
||||
def default_url_options(options = nil)
|
||||
end
|
||||
|
||||
# Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
|
||||
|
@ -1029,7 +1036,8 @@ module ActionController #:nodoc:
|
|||
# RedirectBackError will be raised. You may specify some fallback
|
||||
# behavior for this case by rescuing RedirectBackError.
|
||||
def redirect_to(options = {}, response_status = {}) #:doc:
|
||||
|
||||
raise ActionControllerError.new("Cannot redirect to nil!") if options.nil?
|
||||
|
||||
if options.is_a?(Hash) && options[:status]
|
||||
status = options.delete(:status)
|
||||
elsif response_status[:status]
|
||||
|
@ -1095,7 +1103,6 @@ module ActionController #:nodoc:
|
|||
private
|
||||
def render_for_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc:
|
||||
add_variables_to_assigns
|
||||
assert_existence_of_template_file(template_path) if use_full_path
|
||||
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
|
||||
render_for_text(@template.render_file(template_path, use_full_path, locals), status)
|
||||
end
|
||||
|
@ -1114,11 +1121,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def initialize_template_class(response)
|
||||
unless @@template_class
|
||||
raise "You must assign a template class through ActionController.template_class= before processing a request"
|
||||
end
|
||||
|
||||
response.template = ActionView::Base.new(view_paths, {}, self)
|
||||
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
|
||||
response.template.extend self.class.master_helper_module
|
||||
response.redirected_to = nil
|
||||
@performed_render = @performed_redirect = false
|
||||
|
@ -1195,7 +1198,6 @@ module ActionController #:nodoc:
|
|||
def add_variables_to_assigns
|
||||
unless @variables_added
|
||||
add_instance_variables_to_assigns
|
||||
add_class_variables_to_assigns if view_controller_internals
|
||||
@variables_added = true
|
||||
end
|
||||
end
|
||||
|
@ -1209,30 +1211,11 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def add_instance_variables_to_assigns
|
||||
@@protected_variables_cache ||= Set.new(protected_instance_variables)
|
||||
instance_variables.each do |var|
|
||||
next if @@protected_variables_cache.include?(var)
|
||||
(instance_variable_names - @@protected_view_variables).each do |var|
|
||||
@assigns[var[1..-1]] = instance_variable_get(var)
|
||||
end
|
||||
end
|
||||
|
||||
def add_class_variables_to_assigns
|
||||
%w(view_paths logger template_class ignore_missing_templates).each do |cvar|
|
||||
@assigns[cvar] = self.send(cvar)
|
||||
end
|
||||
end
|
||||
|
||||
def protected_instance_variables
|
||||
if view_controller_internals
|
||||
%w(@assigns @performed_redirect @performed_render)
|
||||
else
|
||||
%w(@assigns @performed_redirect @performed_render
|
||||
@_request @request @_response @response @_params @params
|
||||
@_session @session @_cookies @cookies
|
||||
@template @request_origin @parent_controller)
|
||||
end
|
||||
end
|
||||
|
||||
def request_origin
|
||||
# this *needs* to be cached!
|
||||
# otherwise you'd get different results if calling it more than once
|
||||
|
@ -1248,7 +1231,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def template_exists?(template_name = default_template_name)
|
||||
@template.file_exists?(template_name)
|
||||
@template.finder.file_exists?(template_name)
|
||||
end
|
||||
|
||||
def template_public?(template_name = default_template_name)
|
||||
|
@ -1256,20 +1239,11 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def template_exempt_from_layout?(template_name = default_template_name)
|
||||
extension = @template && @template.pick_template_extension(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 }
|
||||
end
|
||||
|
||||
def assert_existence_of_template_file(template_name)
|
||||
unless template_exists?(template_name) || ignore_missing_templates
|
||||
full_template_path = template_name.include?('.') ? template_name : "#{template_name}.#{@template.template_format}.erb"
|
||||
display_paths = view_paths.join(':')
|
||||
template_type = (template_name =~ /layouts/i) ? 'layout' : 'template'
|
||||
raise(MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}")
|
||||
end
|
||||
end
|
||||
|
||||
def default_template_name(action_name = self.action_name)
|
||||
if action_name
|
||||
action_name = action_name.to_s
|
||||
|
|
|
@ -41,14 +41,14 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
protected
|
||||
def render_with_benchmark(options = nil, deprecated_status = nil, &block)
|
||||
def render_with_benchmark(options = nil, extra_options = {}, &block)
|
||||
unless logger
|
||||
render_without_benchmark(options, &block)
|
||||
render_without_benchmark(options, extra_options, &block)
|
||||
else
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
|
||||
render_output = nil
|
||||
@rendering_runtime = Benchmark::measure{ render_output = render_without_benchmark(options, &block) }.real
|
||||
@rendering_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
|
||||
|
|
|
@ -2,6 +2,13 @@ require 'fileutils'
|
|||
require 'uri'
|
||||
require 'set'
|
||||
|
||||
require 'action_controller/caching/pages'
|
||||
require 'action_controller/caching/actions'
|
||||
require 'action_controller/caching/sql_cache'
|
||||
require 'action_controller/caching/sweeping'
|
||||
require 'action_controller/caching/fragments'
|
||||
|
||||
|
||||
module ActionController #:nodoc:
|
||||
# Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
|
||||
# around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment.
|
||||
|
@ -9,675 +16,57 @@ module ActionController #:nodoc:
|
|||
# You can read more about each approach and the sweeping assistance by clicking the modules below.
|
||||
#
|
||||
# Note: To turn off all caching and sweeping, set Base.perform_caching = false.
|
||||
#
|
||||
#
|
||||
# == Caching stores
|
||||
#
|
||||
# All the caching stores from ActiveSupport::Cache is available to be used as backends for Action Controller caching. This setting only
|
||||
# affects action and fragment caching as page caching is always written to disk.
|
||||
#
|
||||
# Configuration examples (MemoryStore is the default):
|
||||
#
|
||||
# ActionController::Base.cache_store = :memory_store
|
||||
# ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
|
||||
# ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"
|
||||
# ActionController::Base.cache_store = :mem_cache_store, "localhost"
|
||||
# ActionController::Base.cache_store = MyOwnStore.new("parameter")
|
||||
module Caching
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
include Pages, Actions, Fragments
|
||||
@@cache_store = nil
|
||||
cattr_reader :cache_store
|
||||
|
||||
if defined? ActiveRecord
|
||||
include Sweeping, SqlCache
|
||||
# Defines the storage option for cached fragments
|
||||
def self.cache_store=(store_option)
|
||||
@@cache_store = ActiveSupport::Cache.lookup_store(store_option)
|
||||
end
|
||||
|
||||
include Pages, Actions, Fragments
|
||||
include Sweeping, SqlCache if defined?(ActiveRecord)
|
||||
|
||||
@@perform_caching = true
|
||||
cattr_accessor :perform_caching
|
||||
|
||||
def self.cache_configured?
|
||||
perform_caching && cache_store
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server
|
||||
# can serve without going through the Action Pack. This can be as much as 100 times faster than going through the process of dynamically
|
||||
# generating the content. Unfortunately, this incredible speed-up is only available to stateless pages where all visitors
|
||||
# are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are a great fit
|
||||
# for this approach, but account-based systems where people log in and manipulate their own data are often less likely candidates.
|
||||
#
|
||||
# Specifying which actions to cache is done through the <tt>caches</tt> class method:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# caches_page :show, :new
|
||||
# end
|
||||
#
|
||||
# This will generate cache files such as weblog/show/5 and weblog/new, which match the URLs used to trigger the dynamic
|
||||
# generation. This is how the web server is able pick up a cache file when it exists and otherwise let the request pass on to
|
||||
# the Action Pack to generate it.
|
||||
#
|
||||
# Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache
|
||||
# is not restored before another hit is made against it. The API for doing so mimics the options from url_for and friends:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# def update
|
||||
# List.update(params[:list][:id], params[:list])
|
||||
# expire_page :action => "show", :id => params[:list][:id]
|
||||
# redirect_to :action => "show", :id => params[:list][:id]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
|
||||
# expired.
|
||||
#
|
||||
# == Setting the cache directory
|
||||
#
|
||||
# The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root".
|
||||
# For Rails, this directory has already been set to RAILS_ROOT + "/public".
|
||||
#
|
||||
# == Setting the cache extension
|
||||
#
|
||||
# By default, the cache extension is .html, which makes it easy for the cached files to be picked up by the web server. If you want
|
||||
# something else, like .php or .shtml, just set Base.page_cache_extension.
|
||||
module Pages
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
@@page_cache_directory = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : ""
|
||||
cattr_accessor :page_cache_directory
|
||||
|
||||
@@page_cache_extension = '.html'
|
||||
cattr_accessor :page_cache_extension
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Expires the page that was cached with the +path+ as a key. Example:
|
||||
# expire_page "/lists/show"
|
||||
def expire_page(path)
|
||||
return unless perform_caching
|
||||
|
||||
benchmark "Expired page: #{page_cache_file(path)}" do
|
||||
File.delete(page_cache_path(path)) if File.exist?(page_cache_path(path))
|
||||
end
|
||||
end
|
||||
|
||||
# Manually cache the +content+ in the key determined by +path+. Example:
|
||||
# cache_page "I'm the cached content", "/lists/show"
|
||||
def cache_page(content, path)
|
||||
return unless perform_caching
|
||||
|
||||
benchmark "Cached page: #{page_cache_file(path)}" do
|
||||
FileUtils.makedirs(File.dirname(page_cache_path(path)))
|
||||
File.open(page_cache_path(path), "wb+") { |f| f.write(content) }
|
||||
end
|
||||
end
|
||||
|
||||
# Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
|
||||
# matches the triggering url.
|
||||
def caches_page(*actions)
|
||||
return unless perform_caching
|
||||
actions = actions.map(&:to_s)
|
||||
after_filter { |c| c.cache_page if actions.include?(c.action_name) }
|
||||
end
|
||||
|
||||
private
|
||||
def page_cache_file(path)
|
||||
name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/'))
|
||||
name << page_cache_extension unless (name.split('/').last || name).include? '.'
|
||||
return name
|
||||
end
|
||||
|
||||
def page_cache_path(path)
|
||||
page_cache_directory + page_cache_file(path)
|
||||
end
|
||||
end
|
||||
|
||||
# Expires the page that was cached with the +options+ as a key. Example:
|
||||
# expire_page :controller => "lists", :action => "show"
|
||||
def expire_page(options = {})
|
||||
return unless perform_caching
|
||||
|
||||
if options.is_a?(Hash)
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action)))
|
||||
end
|
||||
else
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true)))
|
||||
end
|
||||
protected
|
||||
# Convenience accessor
|
||||
def cache(key, options = {}, &block)
|
||||
if cache_configured?
|
||||
cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
|
||||
else
|
||||
self.class.expire_page(options)
|
||||
end
|
||||
end
|
||||
|
||||
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used
|
||||
# If no options are provided, the requested url is used. Example:
|
||||
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
|
||||
def cache_page(content = nil, options = nil)
|
||||
return unless perform_caching && caching_allowed
|
||||
|
||||
path = case options
|
||||
when Hash
|
||||
url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format]))
|
||||
when String
|
||||
options
|
||||
else
|
||||
request.path
|
||||
end
|
||||
|
||||
self.class.cache_page(content || response.body, path)
|
||||
end
|
||||
|
||||
private
|
||||
def caching_allowed
|
||||
request.get? && response.headers['Status'].to_i == 200
|
||||
end
|
||||
end
|
||||
|
||||
# Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching,
|
||||
# every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which
|
||||
# allows for authentication and other restrictions on whether someone is allowed to see the cache. Example:
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# before_filter :authenticate, :except => :public
|
||||
# caches_page :public
|
||||
# caches_action :show, :feed
|
||||
# end
|
||||
#
|
||||
# In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the
|
||||
# show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches.
|
||||
#
|
||||
# Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both
|
||||
# the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named
|
||||
# "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and
|
||||
# "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern.
|
||||
#
|
||||
# Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt>
|
||||
# are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same
|
||||
# as <tt>:action => 'list', :format => :xml</tt>.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# before_filter :authenticate, :except => :public
|
||||
# caches_page :public
|
||||
# caches_action :show, :cache_path => { :project => 1 }
|
||||
# caches_action :show, :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]) }
|
||||
# end
|
||||
module Actions
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
attr_accessor :rendered_action_cache, :action_cache_path
|
||||
alias_method_chain :protected_instance_variables, :action_caching
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Declares that +actions+ should be cached.
|
||||
# See ActionController::Caching::Actions for details.
|
||||
def caches_action(*actions)
|
||||
return unless perform_caching
|
||||
around_filter(ActionCacheFilter.new(*actions))
|
||||
end
|
||||
end
|
||||
|
||||
def protected_instance_variables_with_action_caching
|
||||
protected_instance_variables_without_action_caching + %w(@action_cache_path)
|
||||
end
|
||||
|
||||
def expire_action(options = {})
|
||||
return unless perform_caching
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action })))
|
||||
end
|
||||
else
|
||||
expire_fragment(ActionCachePath.path_for(self, options))
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCacheFilter #:nodoc:
|
||||
def initialize(*actions, &block)
|
||||
@options = actions.extract_options!
|
||||
@actions = Set.new actions
|
||||
end
|
||||
|
||||
def before(controller)
|
||||
return unless @actions.include?(controller.action_name.intern)
|
||||
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options))
|
||||
if cache = controller.read_fragment(cache_path.path)
|
||||
controller.rendered_action_cache = true
|
||||
set_content_type!(controller, cache_path.extension)
|
||||
controller.send!(:render_for_text, cache)
|
||||
false
|
||||
else
|
||||
controller.action_cache_path = cache_path
|
||||
end
|
||||
end
|
||||
|
||||
def after(controller)
|
||||
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache || !caching_allowed(controller)
|
||||
controller.write_fragment(controller.action_cache_path.path, controller.response.body)
|
||||
end
|
||||
|
||||
private
|
||||
def set_content_type!(controller, extension)
|
||||
controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension
|
||||
end
|
||||
|
||||
def path_options_for(controller, options)
|
||||
((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {}
|
||||
end
|
||||
|
||||
def caching_allowed(controller)
|
||||
controller.request.get? && controller.response.headers['Status'].to_i == 200
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCachePath
|
||||
attr_reader :path, :extension
|
||||
|
||||
class << self
|
||||
def path_for(controller, options)
|
||||
new(controller, options).path
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(controller, options = {})
|
||||
@extension = extract_extension(controller.request.path)
|
||||
path = controller.url_for(options).split('://').last
|
||||
normalize!(path)
|
||||
add_extension!(path, @extension)
|
||||
@path = URI.unescape(path)
|
||||
end
|
||||
|
||||
private
|
||||
def normalize!(path)
|
||||
path << 'index' if path[-1] == ?/
|
||||
end
|
||||
|
||||
def add_extension!(path, extension)
|
||||
path << ".#{extension}" if extension
|
||||
end
|
||||
|
||||
def extract_extension(file_path)
|
||||
# Don't want just what comes after the last '.' to accommodate multi part extensions
|
||||
# such as tar.gz.
|
||||
file_path[/^[^.]+\.(.+)$/, 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# 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:
|
||||
#
|
||||
# <b>Hello <%= @name %></b>
|
||||
# <% cache do %>
|
||||
# All the topics in the system:
|
||||
# <%= render :partial => "topic", :collection => Topic.find(:all) %>
|
||||
# <% end %>
|
||||
#
|
||||
# This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would
|
||||
# be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>.
|
||||
#
|
||||
# This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using
|
||||
# <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like:
|
||||
#
|
||||
# <% cache(:action => "list", :action_suffix => "all_topics") do %>
|
||||
#
|
||||
# That would result in a name such as "/topics/list/all_topics", avoiding conflicts with the action cache and with any fragments that use a
|
||||
# different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique
|
||||
# cache names that we can refer to when we need to expire the cache.
|
||||
#
|
||||
# The expiration call for this example is:
|
||||
#
|
||||
# expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
|
||||
#
|
||||
# == Fragment stores
|
||||
#
|
||||
# By default, cached fragments are stored in memory. The available store options are:
|
||||
#
|
||||
# * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and allows all
|
||||
# processes running from the same application directory to access the cached content.
|
||||
# * MemoryStore: Keeps the fragments in memory, which is fine for WEBrick and for FCGI (if you don't care that each FCGI process holds its
|
||||
# own fragment store). It's not suitable for CGI as the process is thrown away at the end of each request. It can potentially also take
|
||||
# up a lot of memory since each process keeps all the caches in memory.
|
||||
# * DRbStore: Keeps the fragments in the memory of a separate, shared DRb process. This works for all environments and only keeps one cache
|
||||
# around for all processes, but requires that you run and manage a separate DRb process.
|
||||
# * MemCacheStore: Works like DRbStore, but uses Danga's MemCache instead.
|
||||
# Requires the ruby-memcache library: gem install ruby-memcache.
|
||||
#
|
||||
# Configuration examples (MemoryStore is the default):
|
||||
#
|
||||
# ActionController::Base.fragment_cache_store = :memory_store
|
||||
# ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"
|
||||
# ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192"
|
||||
# ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost"
|
||||
# ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter")
|
||||
module Fragments
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
@@fragment_cache_store = MemoryStore.new
|
||||
cattr_reader :fragment_cache_store
|
||||
|
||||
# Defines the storage option for cached fragments
|
||||
def self.fragment_cache_store=(store_option)
|
||||
store, *parameters = *([ store_option ].flatten)
|
||||
@@fragment_cache_store = if store.is_a?(Symbol)
|
||||
store_class_name = (store == :drb_store ? "DRbStore" : store.to_s.camelize)
|
||||
store_class = ActionController::Caching::Fragments.const_get(store_class_name)
|
||||
store_class.new(*parameters)
|
||||
else
|
||||
store
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Given a name (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,
|
||||
# writing, or expiring a cached fragment. If the name is a hash, the generated name is the return
|
||||
# value of url_for on that hash (without the protocol).
|
||||
def fragment_cache_key(name)
|
||||
name.is_a?(Hash) ? url_for(name).split("://").last : name
|
||||
end
|
||||
|
||||
# Called by CacheHelper#cache
|
||||
def cache_erb_fragment(block, name = {}, options = nil)
|
||||
unless perform_caching then block.call; return end
|
||||
|
||||
buffer = eval(ActionView::Base.erb_variable, block.binding)
|
||||
|
||||
if cache = read_fragment(name, options)
|
||||
buffer.concat(cache)
|
||||
else
|
||||
pos = buffer.length
|
||||
block.call
|
||||
write_fragment(name, buffer[pos..-1], options)
|
||||
end
|
||||
end
|
||||
|
||||
# Writes <tt>content</tt> to the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
||||
def write_fragment(name, content, options = nil)
|
||||
return unless perform_caching
|
||||
|
||||
key = fragment_cache_key(name)
|
||||
self.class.benchmark "Cached fragment: #{key}" do
|
||||
fragment_cache_store.write(key, content, options)
|
||||
end
|
||||
content
|
||||
end
|
||||
|
||||
# Reads a cached fragment from the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
||||
def read_fragment(name, options = nil)
|
||||
return unless perform_caching
|
||||
|
||||
key = fragment_cache_key(name)
|
||||
self.class.benchmark "Fragment read: #{key}" do
|
||||
fragment_cache_store.read(key, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Name can take one of three forms:
|
||||
# * String: This would normally take the form of a path like "pages/45/notes"
|
||||
# * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
|
||||
# * Regexp: Will destroy all the matched fragments, example:
|
||||
# %r{pages/\d*/notes}
|
||||
# Ensure you do not specify start and finish in the regex (^$) because
|
||||
# the actual filename matched looks like ./cache/filename/path.cache
|
||||
# Regexp expiration is only supported on caches that can iterate over
|
||||
# all keys (unlike memcached).
|
||||
def expire_fragment(name, options = nil)
|
||||
return unless perform_caching
|
||||
|
||||
key = fragment_cache_key(name)
|
||||
|
||||
if key.is_a?(Regexp)
|
||||
self.class.benchmark "Expired fragments matching: #{key.source}" do
|
||||
fragment_cache_store.delete_matched(key, options)
|
||||
end
|
||||
else
|
||||
self.class.benchmark "Expired fragment: #{key}" do
|
||||
fragment_cache_store.delete(key, options)
|
||||
end
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class UnthreadedMemoryStore #:nodoc:
|
||||
def initialize #:nodoc:
|
||||
@data = {}
|
||||
end
|
||||
|
||||
def read(name, options=nil) #:nodoc:
|
||||
@data[name]
|
||||
end
|
||||
|
||||
def write(name, value, options=nil) #:nodoc:
|
||||
@data[name] = value
|
||||
end
|
||||
|
||||
def delete(name, options=nil) #:nodoc:
|
||||
@data.delete(name)
|
||||
end
|
||||
|
||||
def delete_matched(matcher, options=nil) #:nodoc:
|
||||
@data.delete_if { |k,v| k =~ matcher }
|
||||
end
|
||||
private
|
||||
def cache_configured?
|
||||
self.class.cache_configured?
|
||||
end
|
||||
|
||||
module ThreadSafety #:nodoc:
|
||||
def read(name, options=nil) #:nodoc:
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def write(name, value, options=nil) #:nodoc:
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def delete(name, options=nil) #:nodoc:
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def delete_matched(matcher, options=nil) #:nodoc:
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
end
|
||||
|
||||
class MemoryStore < UnthreadedMemoryStore #:nodoc:
|
||||
def initialize #:nodoc:
|
||||
super
|
||||
if ActionController::Base.allow_concurrency
|
||||
@mutex = Mutex.new
|
||||
MemoryStore.module_eval { include ThreadSafety }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class DRbStore < MemoryStore #:nodoc:
|
||||
attr_reader :address
|
||||
|
||||
def initialize(address = 'druby://localhost:9192')
|
||||
super()
|
||||
@address = address
|
||||
@data = DRbObject.new(nil, address)
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
require_library_or_gem 'memcache'
|
||||
class MemCacheStore < MemoryStore #:nodoc:
|
||||
attr_reader :addresses
|
||||
|
||||
def initialize(*addresses)
|
||||
super()
|
||||
addresses = addresses.flatten
|
||||
addresses = ["localhost"] if addresses.empty?
|
||||
@addresses = addresses
|
||||
@data = MemCache.new(*addresses)
|
||||
end
|
||||
end
|
||||
rescue LoadError
|
||||
# MemCache wasn't available so neither can the store be
|
||||
end
|
||||
|
||||
class UnthreadedFileStore #:nodoc:
|
||||
attr_reader :cache_path
|
||||
|
||||
def initialize(cache_path)
|
||||
@cache_path = cache_path
|
||||
end
|
||||
|
||||
def write(name, value, options = nil) #:nodoc:
|
||||
ensure_cache_path(File.dirname(real_file_path(name)))
|
||||
File.open(real_file_path(name), "wb+") { |f| f.write(value) }
|
||||
rescue => e
|
||||
Base.logger.error "Couldn't create cache directory: #{name} (#{e.message})" if Base.logger
|
||||
end
|
||||
|
||||
def read(name, options = nil) #:nodoc:
|
||||
File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil
|
||||
end
|
||||
|
||||
def delete(name, options) #:nodoc:
|
||||
File.delete(real_file_path(name))
|
||||
rescue SystemCallError => e
|
||||
# If there's no cache, then there's nothing to complain about
|
||||
end
|
||||
|
||||
def delete_matched(matcher, options) #:nodoc:
|
||||
search_dir(@cache_path) do |f|
|
||||
if f =~ matcher
|
||||
begin
|
||||
File.delete(f)
|
||||
rescue SystemCallError => e
|
||||
# If there's no cache, then there's nothing to complain about
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def real_file_path(name)
|
||||
'%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')]
|
||||
end
|
||||
|
||||
def ensure_cache_path(path)
|
||||
FileUtils.makedirs(path) unless File.exist?(path)
|
||||
end
|
||||
|
||||
def search_dir(dir, &callback)
|
||||
Dir.foreach(dir) do |d|
|
||||
next if d == "." || d == ".."
|
||||
name = File.join(dir, d)
|
||||
if File.directory?(name)
|
||||
search_dir(name, &callback)
|
||||
else
|
||||
callback.call name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FileStore < UnthreadedFileStore #:nodoc:
|
||||
def initialize(cache_path)
|
||||
super(cache_path)
|
||||
if ActionController::Base.allow_concurrency
|
||||
@mutex = Mutex.new
|
||||
FileStore.module_eval { include ThreadSafety }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change.
|
||||
# They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example:
|
||||
#
|
||||
# class ListSweeper < ActionController::Caching::Sweeper
|
||||
# observe List, Item
|
||||
#
|
||||
# def after_save(record)
|
||||
# list = record.is_a?(List) ? record : record.list
|
||||
# expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
|
||||
# expire_action(:controller => "lists", :action => "all")
|
||||
# list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The sweeper is assigned in the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method:
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# caches_action :index, :show, :public, :feed
|
||||
# cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ]
|
||||
# end
|
||||
#
|
||||
# In the example above, four actions are cached and three actions are responsible for expiring those caches.
|
||||
module Sweeping
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods #:nodoc:
|
||||
def cache_sweeper(*sweepers)
|
||||
return unless perform_caching
|
||||
configuration = sweepers.extract_options!
|
||||
sweepers.each do |sweeper|
|
||||
ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
|
||||
sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance
|
||||
|
||||
if sweeper_instance.is_a?(Sweeper)
|
||||
around_filter(sweeper_instance, :only => configuration[:only])
|
||||
else
|
||||
after_filter(sweeper_instance, :only => configuration[:only])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
|
||||
class Sweeper < ActiveRecord::Observer #:nodoc:
|
||||
attr_accessor :controller
|
||||
|
||||
def before(controller)
|
||||
self.controller = controller
|
||||
callback(:before)
|
||||
end
|
||||
|
||||
def after(controller)
|
||||
callback(:after)
|
||||
# Clean up, so that the controller can be collected after this request
|
||||
self.controller = nil
|
||||
end
|
||||
|
||||
protected
|
||||
# gets the action cache path for the given options.
|
||||
def action_path_for(options)
|
||||
ActionController::Caching::Actions::ActionCachePath.path_for(controller, options)
|
||||
end
|
||||
|
||||
# Retrieve instance variables set in the controller.
|
||||
def assigns(key)
|
||||
controller.instance_variable_get("@#{key}")
|
||||
end
|
||||
|
||||
private
|
||||
def callback(timing)
|
||||
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)
|
||||
end
|
||||
|
||||
def method_missing(method, *arguments)
|
||||
return if @controller.nil?
|
||||
@controller.send!(method, *arguments)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module SqlCache
|
||||
def self.included(base) #:nodoc:
|
||||
if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache)
|
||||
base.alias_method_chain :perform_action, :caching
|
||||
end
|
||||
end
|
||||
|
||||
def perform_action_with_caching
|
||||
ActiveRecord::Base.cache do
|
||||
perform_action_without_caching
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
143
vendor/rails/actionpack/lib/action_controller/caching/actions.rb
vendored
Normal file
143
vendor/rails/actionpack/lib/action_controller/caching/actions.rb
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
require 'set'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
# Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching,
|
||||
# every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which
|
||||
# allows for authentication and other restrictions on whether someone is allowed to see the cache. Example:
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# before_filter :authenticate, :except => :public
|
||||
# caches_page :public
|
||||
# caches_action :show, :feed
|
||||
# end
|
||||
#
|
||||
# In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the
|
||||
# show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches.
|
||||
#
|
||||
# Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both
|
||||
# the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named
|
||||
# "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and
|
||||
# "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern.
|
||||
#
|
||||
# Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt>
|
||||
# are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same
|
||||
# as <tt>:action => 'list', :format => :xml</tt>.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# before_filter :authenticate, :except => :public
|
||||
# caches_page :public
|
||||
# caches_action :show, :cache_path => { :project => 1 }
|
||||
# caches_action :show, :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]) }
|
||||
# end
|
||||
module Actions
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
attr_accessor :rendered_action_cache, :action_cache_path
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Declares that +actions+ should be cached.
|
||||
# See ActionController::Caching::Actions for details.
|
||||
def caches_action(*actions)
|
||||
return unless cache_configured?
|
||||
around_filter(ActionCacheFilter.new(*actions))
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def expire_action(options = {})
|
||||
return unless cache_configured?
|
||||
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action })))
|
||||
end
|
||||
else
|
||||
expire_fragment(ActionCachePath.path_for(self, options))
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCacheFilter #:nodoc:
|
||||
def initialize(*actions, &block)
|
||||
@options = actions.extract_options!
|
||||
@actions = Set.new(actions)
|
||||
end
|
||||
|
||||
def before(controller)
|
||||
return unless @actions.include?(controller.action_name.intern)
|
||||
|
||||
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options))
|
||||
|
||||
if cache = controller.read_fragment(cache_path.path)
|
||||
controller.rendered_action_cache = true
|
||||
set_content_type!(controller, cache_path.extension)
|
||||
controller.send!(:render_for_text, cache)
|
||||
false
|
||||
else
|
||||
controller.action_cache_path = cache_path
|
||||
end
|
||||
end
|
||||
|
||||
def after(controller)
|
||||
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache || !caching_allowed(controller)
|
||||
controller.write_fragment(controller.action_cache_path.path, controller.response.body)
|
||||
end
|
||||
|
||||
private
|
||||
def set_content_type!(controller, extension)
|
||||
controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension
|
||||
end
|
||||
|
||||
def path_options_for(controller, options)
|
||||
((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {}
|
||||
end
|
||||
|
||||
def caching_allowed(controller)
|
||||
controller.request.get? && controller.response.headers['Status'].to_i == 200
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCachePath
|
||||
attr_reader :path, :extension
|
||||
|
||||
class << self
|
||||
def path_for(controller, options)
|
||||
new(controller, options).path
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(controller, options = {})
|
||||
@extension = extract_extension(controller.request.path)
|
||||
path = controller.url_for(options).split('://').last
|
||||
normalize!(path)
|
||||
add_extension!(path, @extension)
|
||||
@path = URI.unescape(path)
|
||||
end
|
||||
|
||||
private
|
||||
def normalize!(path)
|
||||
path << 'index' if path[-1] == ?/
|
||||
end
|
||||
|
||||
def add_extension!(path, extension)
|
||||
path << ".#{extension}" if extension
|
||||
end
|
||||
|
||||
def extract_extension(file_path)
|
||||
# Don't want just what comes after the last '.' to accommodate multi part extensions
|
||||
# such as tar.gz.
|
||||
file_path[/^[^.]+\.(.+)$/, 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
127
vendor/rails/actionpack/lib/action_controller/caching/fragments.rb
vendored
Normal file
127
vendor/rails/actionpack/lib/action_controller/caching/fragments.rb
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
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:
|
||||
#
|
||||
# <b>Hello <%= @name %></b>
|
||||
# <% cache do %>
|
||||
# All the topics in the system:
|
||||
# <%= render :partial => "topic", :collection => Topic.find(:all) %>
|
||||
# <% end %>
|
||||
#
|
||||
# This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would
|
||||
# be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>.
|
||||
#
|
||||
# This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using
|
||||
# <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like:
|
||||
#
|
||||
# <% cache(:action => "list", :action_suffix => "all_topics") do %>
|
||||
#
|
||||
# That would result in a name such as "/topics/list/all_topics", avoiding conflicts with the action cache and with any fragments that use a
|
||||
# different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique
|
||||
# cache names that we can refer to when we need to expire the cache.
|
||||
#
|
||||
# The expiration call for this example is:
|
||||
#
|
||||
# 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
|
||||
# ActiveSupport::Cache.expand_cache_key for the expansion.
|
||||
def fragment_cache_key(key)
|
||||
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)
|
||||
else
|
||||
pos = buffer.length
|
||||
block.call
|
||||
write_fragment(name, buffer[pos..-1], options)
|
||||
end
|
||||
end
|
||||
|
||||
# Writes <tt>content</tt> to the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
||||
def write_fragment(key, content, options = nil)
|
||||
return unless cache_configured?
|
||||
|
||||
key = fragment_cache_key(key)
|
||||
|
||||
self.class.benchmark "Cached fragment miss: #{key}" do
|
||||
cache_store.write(key, content, options)
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
# Reads a cached fragment from the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
||||
def read_fragment(key, options = nil)
|
||||
return unless cache_configured?
|
||||
|
||||
key = fragment_cache_key(key)
|
||||
|
||||
self.class.benchmark "Cached fragment hit: #{key}" do
|
||||
cache_store.read(key, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Name can take one of three forms:
|
||||
# * String: This would normally take the form of a path like "pages/45/notes"
|
||||
# * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
|
||||
# * Regexp: Will destroy all the matched fragments, example:
|
||||
# %r{pages/\d*/notes}
|
||||
# Ensure you do not specify start and finish in the regex (^$) because
|
||||
# the actual filename matched looks like ./cache/filename/path.cache
|
||||
# Regexp expiration is only supported on caches that can iterate over
|
||||
# all keys (unlike memcached).
|
||||
def expire_fragment(key, options = nil)
|
||||
return unless cache_configured?
|
||||
|
||||
key = key.is_a?(Regexp) ? key : fragment_cache_key(key)
|
||||
|
||||
if key.is_a?(Regexp)
|
||||
self.class.benchmark "Expired fragments matching: #{key.source}" do
|
||||
cache_store.delete_matched(key, options)
|
||||
end
|
||||
else
|
||||
self.class.benchmark "Expired fragment: #{key}" do
|
||||
cache_store.delete(key, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
154
vendor/rails/actionpack/lib/action_controller/caching/pages.rb
vendored
Normal file
154
vendor/rails/actionpack/lib/action_controller/caching/pages.rb
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
require 'fileutils'
|
||||
require 'uri'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
# Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server
|
||||
# can serve without going through Action Pack. This is the fastest way to cache your content as opposed to going dynamically
|
||||
# through the process of generating the content. Unfortunately, this incredible speed-up is only available to stateless pages
|
||||
# where all visitors are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are
|
||||
# a great fit for this approach, but account-based systems where people log in and manipulate their own data are often less
|
||||
# likely candidates.
|
||||
#
|
||||
# Specifying which actions to cache is done through the <tt>caches_page</tt> class method:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# caches_page :show, :new
|
||||
# end
|
||||
#
|
||||
# This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>,
|
||||
# which match the URLs used to trigger the dynamic generation. This is how the web server is able
|
||||
# pick up a cache file when it exists and otherwise let the request pass on to Action Pack to generate it.
|
||||
#
|
||||
# Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache
|
||||
# is not restored before another hit is made against it. The API for doing so mimics the options from +url_for+ and friends:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# def update
|
||||
# List.update(params[:list][:id], params[:list])
|
||||
# expire_page :action => "show", :id => params[:list][:id]
|
||||
# redirect_to :action => "show", :id => params[:list][:id]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
|
||||
# expired.
|
||||
#
|
||||
# == Setting the cache directory
|
||||
#
|
||||
# The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
|
||||
# For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>RAILS_ROOT + "/public"</tt>). Changing
|
||||
# this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
|
||||
# web server to look in the new location for cached files.
|
||||
#
|
||||
# == Setting the cache extension
|
||||
#
|
||||
# Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
|
||||
# order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
|
||||
# If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
|
||||
# extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
|
||||
module Pages
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
@@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
|
||||
cattr_accessor :page_cache_directory
|
||||
|
||||
@@page_cache_extension = '.html'
|
||||
cattr_accessor :page_cache_extension
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Expires the page that was cached with the +path+ as a key. Example:
|
||||
# expire_page "/lists/show"
|
||||
def expire_page(path)
|
||||
return unless perform_caching
|
||||
|
||||
benchmark "Expired page: #{page_cache_file(path)}" do
|
||||
File.delete(page_cache_path(path)) if File.exist?(page_cache_path(path))
|
||||
end
|
||||
end
|
||||
|
||||
# Manually cache the +content+ in the key determined by +path+. Example:
|
||||
# cache_page "I'm the cached content", "/lists/show"
|
||||
def cache_page(content, path)
|
||||
return unless perform_caching
|
||||
|
||||
benchmark "Cached page: #{page_cache_file(path)}" do
|
||||
FileUtils.makedirs(File.dirname(page_cache_path(path)))
|
||||
File.open(page_cache_path(path), "wb+") { |f| f.write(content) }
|
||||
end
|
||||
end
|
||||
|
||||
# Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
|
||||
# matches the triggering url.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# # cache the index action
|
||||
# caches_page :index
|
||||
#
|
||||
# # cache the index action except for JSON requests
|
||||
# caches_page :index, :if => Proc.new { |c| !c.request.format.json? }
|
||||
def caches_page(*actions)
|
||||
return unless perform_caching
|
||||
options = actions.extract_options!
|
||||
after_filter({:only => actions}.merge(options)) { |c| c.cache_page }
|
||||
end
|
||||
|
||||
private
|
||||
def page_cache_file(path)
|
||||
name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/'))
|
||||
name << page_cache_extension unless (name.split('/').last || name).include? '.'
|
||||
return name
|
||||
end
|
||||
|
||||
def page_cache_path(path)
|
||||
page_cache_directory + page_cache_file(path)
|
||||
end
|
||||
end
|
||||
|
||||
# Expires the page that was cached with the +options+ as a key. Example:
|
||||
# expire_page :controller => "lists", :action => "show"
|
||||
def expire_page(options = {})
|
||||
return unless perform_caching
|
||||
|
||||
if options.is_a?(Hash)
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action)))
|
||||
end
|
||||
else
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true)))
|
||||
end
|
||||
else
|
||||
self.class.expire_page(options)
|
||||
end
|
||||
end
|
||||
|
||||
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used
|
||||
# If no options are provided, the requested url is used. Example:
|
||||
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
|
||||
def cache_page(content = nil, options = nil)
|
||||
return unless perform_caching && caching_allowed
|
||||
|
||||
path = case options
|
||||
when Hash
|
||||
url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format]))
|
||||
when String
|
||||
options
|
||||
else
|
||||
request.path
|
||||
end
|
||||
|
||||
self.class.cache_page(content || response.body, path)
|
||||
end
|
||||
|
||||
private
|
||||
def caching_allowed
|
||||
request.get? && response.headers['Status'].to_i == 200
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
vendor/rails/actionpack/lib/action_controller/caching/sql_cache.rb
vendored
Normal file
18
vendor/rails/actionpack/lib/action_controller/caching/sql_cache.rb
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
module SqlCache
|
||||
def self.included(base) #:nodoc:
|
||||
if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache)
|
||||
base.alias_method_chain :perform_action, :caching
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def perform_action_with_caching
|
||||
ActiveRecord::Base.cache do
|
||||
perform_action_without_caching
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
97
vendor/rails/actionpack/lib/action_controller/caching/sweeping.rb
vendored
Normal file
97
vendor/rails/actionpack/lib/action_controller/caching/sweeping.rb
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
# Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change.
|
||||
# They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example:
|
||||
#
|
||||
# class ListSweeper < ActionController::Caching::Sweeper
|
||||
# observe List, Item
|
||||
#
|
||||
# def after_save(record)
|
||||
# list = record.is_a?(List) ? record : record.list
|
||||
# expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
|
||||
# expire_action(:controller => "lists", :action => "all")
|
||||
# list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The sweeper is assigned in the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method:
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# caches_action :index, :show, :public, :feed
|
||||
# cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ]
|
||||
# end
|
||||
#
|
||||
# In the example above, four actions are cached and three actions are responsible for expiring those caches.
|
||||
#
|
||||
# You can also name an explicit class in the declaration of a sweeper, which is needed if the sweeper is in a module:
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# caches_action :index, :show, :public, :feed
|
||||
# cache_sweeper OpenBar::Sweeper, :only => [ :edit, :destroy, :share ]
|
||||
# end
|
||||
module Sweeping
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods #:nodoc:
|
||||
def cache_sweeper(*sweepers)
|
||||
configuration = sweepers.extract_options!
|
||||
|
||||
sweepers.each do |sweeper|
|
||||
ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
|
||||
sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(Inflector.classify(sweeper)) : sweeper).instance
|
||||
|
||||
if sweeper_instance.is_a?(Sweeper)
|
||||
around_filter(sweeper_instance, :only => configuration[:only])
|
||||
else
|
||||
after_filter(sweeper_instance, :only => configuration[:only])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
|
||||
class Sweeper < ActiveRecord::Observer #:nodoc:
|
||||
attr_accessor :controller
|
||||
|
||||
def before(controller)
|
||||
self.controller = controller
|
||||
callback(:before) if controller.perform_caching
|
||||
end
|
||||
|
||||
def after(controller)
|
||||
callback(:after) if controller.perform_caching
|
||||
# Clean up, so that the controller can be collected after this request
|
||||
self.controller = nil
|
||||
end
|
||||
|
||||
protected
|
||||
# gets the action cache path for the given options.
|
||||
def action_path_for(options)
|
||||
ActionController::Caching::Actions::ActionCachePath.path_for(controller, options)
|
||||
end
|
||||
|
||||
# Retrieve instance variables set in the controller.
|
||||
def assigns(key)
|
||||
controller.instance_variable_get("@#{key}")
|
||||
end
|
||||
|
||||
private
|
||||
def callback(timing)
|
||||
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)
|
||||
end
|
||||
|
||||
def method_missing(method, *arguments)
|
||||
return if @controller.nil?
|
||||
@controller.send!(method, *arguments)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -89,13 +89,12 @@ class CGI #:nodoc:
|
|||
cookies = Hash.new([])
|
||||
|
||||
if raw_cookie
|
||||
raw_cookie.split(/[;,]\s?/).each do |pairs|
|
||||
name, values = pairs.split('=',2)
|
||||
next unless name and values
|
||||
raw_cookie.split(/;\s?/).each do |pairs|
|
||||
name, value = pairs.split('=',2)
|
||||
next unless name and value
|
||||
name = CGI::unescape(name)
|
||||
values = values.split('&').collect!{|v| CGI::unescape(v) }
|
||||
unless cookies.has_key?(name)
|
||||
cookies[name] = new(name, *values)
|
||||
cookies[name] = new(name, CGI::unescape(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ require 'action_controller/session/cookie_store'
|
|||
|
||||
module ActionController #:nodoc:
|
||||
class Base
|
||||
# Process a request extracted from an CGI object and return a response. Pass false as <tt>session_options</tt> to disable
|
||||
# Process a request extracted from a CGI object and return a response. Pass false as <tt>session_options</tt> to disable
|
||||
# sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
|
||||
#
|
||||
# * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
|
||||
|
@ -17,7 +17,7 @@ module ActionController #:nodoc:
|
|||
# an ArgumentError is raised.
|
||||
# * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object. If not set, the session will continue
|
||||
# indefinitely.
|
||||
# * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
|
||||
# * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
|
||||
# server.
|
||||
# * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
|
||||
# * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
|
||||
|
@ -34,7 +34,8 @@ module ActionController #:nodoc:
|
|||
|
||||
class CgiRequest < AbstractRequest #:nodoc:
|
||||
attr_accessor :cgi, :session_options
|
||||
class SessionFixationAttempt < StandardError; end #:nodoc:
|
||||
class SessionFixationAttempt < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
DEFAULT_SESSION_OPTIONS = {
|
||||
:database_manager => CGI::Session::CookieStore, # store data in cookie
|
||||
|
|
|
@ -39,12 +39,7 @@ module ActionController #:nodoc:
|
|||
base.class_eval do
|
||||
include InstanceMethods
|
||||
extend ClassMethods
|
||||
|
||||
helper do
|
||||
def render_component(options)
|
||||
@controller.send!(:render_component_as_string, options)
|
||||
end
|
||||
end
|
||||
helper HelperMethods
|
||||
|
||||
# If this controller was instantiated to process a component request,
|
||||
# +parent_controller+ points to the instantiator of this controller.
|
||||
|
@ -67,6 +62,12 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
module HelperMethods
|
||||
def render_component(options)
|
||||
@controller.send!(:render_component_as_string, options)
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
# Extracts the action_name from the request parameters and performs that action.
|
||||
def process_with_components(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
|
|
|
@ -1,31 +1,38 @@
|
|||
module ActionController #:nodoc:
|
||||
# Cookies are read and written through ActionController#cookies. The cookies being read are what were received along with the request,
|
||||
# the cookies being written are what will be sent out with the response. Cookies are read by value (so you won't get the cookie object
|
||||
# itself back -- just the value it holds). Examples for writing:
|
||||
# Cookies are read and written through ActionController#cookies.
|
||||
#
|
||||
# cookies[:user_name] = "david" # => Will set a simple session cookie
|
||||
# The cookies being read are the ones received along with the request, the cookies
|
||||
# being written will be sent out with the response. Reading a cookie does not get
|
||||
# the cookie object itself back, just the value it holds.
|
||||
#
|
||||
# Examples for writing:
|
||||
#
|
||||
# # Sets a simple session cookie.
|
||||
# cookies[:user_name] = "david"
|
||||
#
|
||||
# # Sets a cookie that expires in 1 hour.
|
||||
# cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
|
||||
# # => Will set a cookie that expires in 1 hour
|
||||
#
|
||||
# Examples for reading:
|
||||
#
|
||||
# cookies[:user_name] # => "david"
|
||||
# cookies.size # => 2
|
||||
# cookies.size # => 2
|
||||
#
|
||||
# Example for deleting:
|
||||
#
|
||||
# cookies.delete :user_name
|
||||
#
|
||||
# All the option symbols for setting cookies are:
|
||||
# The option symbols for setting cookies are:
|
||||
#
|
||||
# * <tt>value</tt> - the cookie's value or list of values (as an array).
|
||||
# * <tt>path</tt> - the path for which this cookie applies. Defaults to the root of the application.
|
||||
# * <tt>domain</tt> - the domain for which this cookie applies.
|
||||
# * <tt>expires</tt> - the time at which this cookie expires, as a +Time+ object.
|
||||
# * <tt>secure</tt> - whether this cookie is a secure cookie or not (default to false).
|
||||
# Secure cookies are only transmitted to HTTPS servers.
|
||||
# * <tt>http_only</tt> - whether this cookie is accessible via scripting or only HTTP (defaults to false).
|
||||
|
||||
# * <tt>:value</tt> - The cookie's value or list of values (as an array).
|
||||
# * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
|
||||
# of the application.
|
||||
# * <tt>:domain</tt> - The domain for which this cookie applies.
|
||||
# * <tt>:expires</tt> - The time at which this cookie expires, as a Time object.
|
||||
# * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
|
||||
# Default is +false+.
|
||||
# * <tt>:http_only</tt> - Whether this cookie is accessible via scripting or
|
||||
# only HTTP. Defaults to +false+.
|
||||
module Cookies
|
||||
def self.included(base)
|
||||
base.helper_method :cookies
|
||||
|
@ -45,8 +52,7 @@ module ActionController #:nodoc:
|
|||
update(@cookies)
|
||||
end
|
||||
|
||||
# Returns the value of the cookie by +name+ -- or nil if no such cookie exists. You set new cookies using cookies[]=
|
||||
# (for simple name/value cookies without options).
|
||||
# Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
|
||||
def [](name)
|
||||
cookie = @cookies[name.to_s]
|
||||
if cookie && cookie.respond_to?(:value)
|
||||
|
@ -54,6 +60,8 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Sets the cookie named +name+. The second argument may be the very cookie
|
||||
# value, or a hash of options as documented above.
|
||||
def []=(name, options)
|
||||
if options.is_a?(Hash)
|
||||
options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
|
||||
|
@ -66,14 +74,18 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
# Removes the cookie on the client machine by setting the value to an empty string
|
||||
# and setting its expiration date into the past. Like []=, you can pass in an options
|
||||
# hash to delete cookies with extra data such as a +path+.
|
||||
# and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
|
||||
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
|
||||
def delete(name, options = {})
|
||||
options.stringify_keys!
|
||||
set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0)))
|
||||
end
|
||||
|
||||
private
|
||||
# Builds a CGI::Cookie object and adds the cookie to the response headers.
|
||||
#
|
||||
# The path of the cookie defaults to "/" if there's none in +options+, and
|
||||
# everything is passed to the CGI::Cookie constructor.
|
||||
def set_cookie(options) #:doc:
|
||||
options["path"] = "/" unless options["path"]
|
||||
cookie = CGI::Cookie.new(options)
|
||||
|
|
|
@ -2,27 +2,39 @@ module ActionController
|
|||
# Dispatches requests to the appropriate controller and takes care of
|
||||
# reloading the app after each request when Dependencies.load? is true.
|
||||
class Dispatcher
|
||||
@@guard = Mutex.new
|
||||
|
||||
class << self
|
||||
def define_dispatcher_callbacks(cache_classes)
|
||||
unless cache_classes
|
||||
# Development mode callbacks
|
||||
before_dispatch :reload_application
|
||||
after_dispatch :cleanup_application
|
||||
end
|
||||
|
||||
# Common callbacks
|
||||
to_prepare :load_application_controller do
|
||||
begin
|
||||
require_dependency 'application' unless defined?(::ApplicationController)
|
||||
rescue LoadError => error
|
||||
raise unless error.message =~ /application\.rb/
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(ActiveRecord)
|
||||
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)
|
||||
end
|
||||
|
||||
# Backward-compatible class method takes CGI-specific args. Deprecated
|
||||
# in favor of Dispatcher.new(output, request, response).dispatch.
|
||||
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
|
||||
new(output).dispatch_cgi(cgi, session_options)
|
||||
end
|
||||
|
||||
# Declare a block to be called before each dispatch.
|
||||
# Run in the order declared.
|
||||
def before_dispatch(*method_names, &block)
|
||||
callbacks[:before].concat method_names
|
||||
callbacks[:before] << block if block_given?
|
||||
end
|
||||
|
||||
# Declare a block to be called after each dispatch.
|
||||
# Run in reverse of the order declared.
|
||||
def after_dispatch(*method_names, &block)
|
||||
callbacks[:after].concat method_names
|
||||
callbacks[:after] << block if block_given?
|
||||
end
|
||||
|
||||
# Add a preparation callback. Preparation callbacks are run before every
|
||||
# request in development mode, and before the first request in production
|
||||
# mode.
|
||||
|
@ -32,16 +44,9 @@ module ActionController
|
|||
# existing callback. Passing an identifier is a suggested practice if the
|
||||
# code adding a preparation block may be reloaded.
|
||||
def to_prepare(identifier = nil, &block)
|
||||
# Already registered: update the existing callback
|
||||
if identifier
|
||||
if callback = callbacks[:prepare].assoc(identifier)
|
||||
callback[1] = block
|
||||
else
|
||||
callbacks[:prepare] << [identifier, block]
|
||||
end
|
||||
else
|
||||
callbacks[:prepare] << block
|
||||
end
|
||||
@prepare_dispatch_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new
|
||||
callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier)
|
||||
@prepare_dispatch_callbacks | callback
|
||||
end
|
||||
|
||||
# If the block raises, send status code as a last-ditch response.
|
||||
|
@ -86,37 +91,26 @@ module ActionController
|
|||
end
|
||||
|
||||
cattr_accessor :error_file_path
|
||||
self.error_file_path = "#{::RAILS_ROOT}/public" if defined? ::RAILS_ROOT
|
||||
self.error_file_path = Rails.public_path if defined?(Rails.public_path)
|
||||
|
||||
cattr_accessor :callbacks
|
||||
self.callbacks = Hash.new { |h, k| h[k] = [] }
|
||||
|
||||
cattr_accessor :unprepared
|
||||
self.unprepared = true
|
||||
|
||||
|
||||
before_dispatch :reload_application
|
||||
before_dispatch :prepare_application
|
||||
after_dispatch :flush_logger
|
||||
after_dispatch :cleanup_application
|
||||
|
||||
if defined? ActiveRecord
|
||||
to_prepare :activerecord_instantiate_observers do
|
||||
ActiveRecord::Base.instantiate_observers
|
||||
end
|
||||
end
|
||||
include ActiveSupport::Callbacks
|
||||
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
|
||||
|
||||
def initialize(output, request = nil, response = nil)
|
||||
@output, @request, @response = output, request, response
|
||||
end
|
||||
|
||||
def dispatch
|
||||
run_callbacks :before
|
||||
handle_request
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
ensure
|
||||
run_callbacks :after, :reverse_each
|
||||
@@guard.synchronize do
|
||||
begin
|
||||
run_callbacks :before_dispatch
|
||||
handle_request
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
ensure
|
||||
run_callbacks :after_dispatch, :enumerator => :reverse_each
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch_cgi(cgi, session_options)
|
||||
|
@ -130,39 +124,23 @@ module ActionController
|
|||
end
|
||||
|
||||
def reload_application
|
||||
if Dependencies.load?
|
||||
Routing::Routes.reload
|
||||
self.unprepared = true
|
||||
end
|
||||
end
|
||||
# Run prepare callbacks before every request in development mode
|
||||
run_callbacks :prepare_dispatch
|
||||
|
||||
def prepare_application(force = false)
|
||||
begin
|
||||
require_dependency 'application' unless defined?(::ApplicationController)
|
||||
rescue LoadError => error
|
||||
raise unless error.message =~ /application\.rb/
|
||||
end
|
||||
|
||||
ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord)
|
||||
|
||||
if unprepared || force
|
||||
run_callbacks :prepare
|
||||
self.unprepared = false
|
||||
end
|
||||
Routing::Routes.reload
|
||||
ActionView::TemplateFinder.reload! unless ActionView::Base.cache_template_loading
|
||||
end
|
||||
|
||||
# Cleanup the application by clearing out loaded classes so they can
|
||||
# be reloaded on the next request without restarting the server.
|
||||
def cleanup_application(force = false)
|
||||
if Dependencies.load? || force
|
||||
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
|
||||
Dependencies.clear
|
||||
ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
|
||||
end
|
||||
def cleanup_application
|
||||
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
|
||||
Dependencies.clear
|
||||
ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
|
||||
end
|
||||
|
||||
def flush_logger
|
||||
RAILS_DEFAULT_LOGGER.flush if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush)
|
||||
RAILS_DEFAULT_LOGGER.flush
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -171,17 +149,6 @@ module ActionController
|
|||
@controller.process(@request, @response).out(@output)
|
||||
end
|
||||
|
||||
def run_callbacks(kind, enumerator = :each)
|
||||
callbacks[kind].send!(enumerator) do |callback|
|
||||
case callback
|
||||
when Proc; callback.call(self)
|
||||
when String, Symbol; send!(callback)
|
||||
when Array; callback[1].call(self)
|
||||
else raise ArgumentError, "Unrecognized callback #{callback.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def failsafe_rescue(exception)
|
||||
self.class.failsafe_response(@output, '500 Internal Server Error', exception) do
|
||||
if @controller ||= defined?(::ApplicationController) ? ::ApplicationController : Base
|
||||
|
|
|
@ -126,8 +126,8 @@ module ActionController #:nodoc:
|
|||
# end
|
||||
#
|
||||
# To use a filter object with around_filter, pass an object responding
|
||||
# to :filter or both :before and :after. With a filter method, yield to
|
||||
# the block as above:
|
||||
# to <tt>:filter</tt> or both <tt>:before</tt> and <tt>:after</tt>. With a
|
||||
# filter method, yield to the block as above:
|
||||
#
|
||||
# around_filter BenchmarkingFilter
|
||||
#
|
||||
|
@ -191,8 +191,9 @@ module ActionController #:nodoc:
|
|||
# == Filter conditions
|
||||
#
|
||||
# Filters may be limited to specific actions by declaring the actions to
|
||||
# include or exclude. Both options accept single actions (:only => :index)
|
||||
# or arrays of actions (:except => [:foo, :bar]).
|
||||
# include or exclude. Both options accept single actions
|
||||
# (<tt>:only => :index</tt>) or arrays of actions
|
||||
# (<tt>:except => [:foo, :bar]</tt>).
|
||||
#
|
||||
# class Journal < ActionController::Base
|
||||
# # Require authentication for edit and delete.
|
||||
|
@ -244,17 +245,212 @@ module ActionController #:nodoc:
|
|||
# filter and controller action will not be run. If #before renders or redirects,
|
||||
# the second half of #around and will still run but #after and the
|
||||
# action will not. If #around fails to yield, #after will not be run.
|
||||
|
||||
class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
|
||||
def append_filter_to_chain(filters, filter_type, &block)
|
||||
pos = find_filter_append_position(filters, filter_type)
|
||||
update_filter_chain(filters, filter_type, pos, &block)
|
||||
end
|
||||
|
||||
def prepend_filter_to_chain(filters, filter_type, &block)
|
||||
pos = find_filter_prepend_position(filters, filter_type)
|
||||
update_filter_chain(filters, filter_type, pos, &block)
|
||||
end
|
||||
|
||||
def create_filters(filters, filter_type, &block)
|
||||
filters, conditions = extract_options(filters, &block)
|
||||
filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) }
|
||||
filters
|
||||
end
|
||||
|
||||
def skip_filter_in_chain(*filters, &test)
|
||||
filters, conditions = extract_options(filters)
|
||||
filters.each do |filter|
|
||||
if callback = find(filter) then delete(callback) end
|
||||
end if conditions.empty?
|
||||
update_filter_in_chain(filters, :skip => conditions, &test)
|
||||
end
|
||||
|
||||
private
|
||||
def update_filter_chain(filters, filter_type, pos, &block)
|
||||
new_filters = create_filters(filters, filter_type, &block)
|
||||
insert(pos, new_filters).flatten!
|
||||
end
|
||||
|
||||
def find_filter_append_position(filters, filter_type)
|
||||
# appending an after filter puts it at the end of the call chain
|
||||
# before and around filters go before the first after filter in the chain
|
||||
unless filter_type == :after
|
||||
each_with_index do |f,i|
|
||||
return i if f.after?
|
||||
end
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
def find_filter_prepend_position(filters, filter_type)
|
||||
# prepending a before or around filter puts it at the front of the call chain
|
||||
# after filters go before the first after filter in the chain
|
||||
if filter_type == :after
|
||||
each_with_index do |f,i|
|
||||
return i if f.after?
|
||||
end
|
||||
return -1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
def find_or_create_filter(filter, filter_type, options = {})
|
||||
update_filter_in_chain([filter], options)
|
||||
|
||||
if found_filter = find(filter) { |f| f.type == filter_type }
|
||||
found_filter
|
||||
else
|
||||
filter_kind = case
|
||||
when filter.respond_to?(:before) && filter_type == :before
|
||||
:before
|
||||
when filter.respond_to?(:after) && filter_type == :after
|
||||
:after
|
||||
else
|
||||
:filter
|
||||
end
|
||||
|
||||
case filter_type
|
||||
when :before
|
||||
BeforeFilter.new(filter_kind, filter, options)
|
||||
when :after
|
||||
AfterFilter.new(filter_kind, filter, options)
|
||||
else
|
||||
AroundFilter.new(filter_kind, filter, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_filter_in_chain(filters, options, &test)
|
||||
filters.map! { |f| block_given? ? find(f, &test) : find(f) }
|
||||
filters.compact!
|
||||
|
||||
map! do |filter|
|
||||
if filters.include?(filter)
|
||||
new_filter = filter.dup
|
||||
new_filter.options.merge!(options)
|
||||
new_filter
|
||||
else
|
||||
filter
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
|
||||
def before?
|
||||
self.class == BeforeFilter
|
||||
end
|
||||
|
||||
def after?
|
||||
self.class == AfterFilter
|
||||
end
|
||||
|
||||
def around?
|
||||
self.class == AroundFilter
|
||||
end
|
||||
|
||||
private
|
||||
def should_not_skip?(controller)
|
||||
if options[:skip]
|
||||
!included_in_action?(controller, options[:skip])
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def included_in_action?(controller, options)
|
||||
if options[:only]
|
||||
Array(options[:only]).map(&:to_s).include?(controller.action_name)
|
||||
elsif options[:except]
|
||||
!Array(options[:except]).map(&:to_s).include?(controller.action_name)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def should_run_callback?(controller)
|
||||
should_not_skip?(controller) && included_in_action?(controller, options) && super
|
||||
end
|
||||
end
|
||||
|
||||
class AroundFilter < Filter #:nodoc:
|
||||
def type
|
||||
:around
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
if should_run_callback?(controller)
|
||||
method = filter_responds_to_before_and_after? ? around_proc : self.method
|
||||
|
||||
# For around_filter do |controller, action|
|
||||
if method.is_a?(Proc) && method.arity == 2
|
||||
evaluate_method(method, controller, block)
|
||||
else
|
||||
evaluate_method(method, controller, &block)
|
||||
end
|
||||
else
|
||||
block.call
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def filter_responds_to_before_and_after?
|
||||
method.respond_to?(:before) && method.respond_to?(:after)
|
||||
end
|
||||
|
||||
def around_proc
|
||||
Proc.new do |controller, action|
|
||||
method.before(controller)
|
||||
|
||||
if controller.send!(:performed?)
|
||||
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
|
||||
else
|
||||
begin
|
||||
action.call
|
||||
ensure
|
||||
method.after(controller)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class BeforeFilter < Filter #:nodoc:
|
||||
def type
|
||||
:before
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
super
|
||||
if controller.send!(:performed?)
|
||||
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AfterFilter < Filter #:nodoc:
|
||||
def type
|
||||
:after
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# The passed <tt>filters</tt> will be appended to the filter_chain and
|
||||
# will execute before the action on this controller is performed.
|
||||
def append_before_filter(*filters, &block)
|
||||
append_filter_to_chain(filters, :before, &block)
|
||||
filter_chain.append_filter_to_chain(filters, :before, &block)
|
||||
end
|
||||
|
||||
# The passed <tt>filters</tt> will be prepended to the filter_chain and
|
||||
# will execute before the action on this controller is performed.
|
||||
def prepend_before_filter(*filters, &block)
|
||||
prepend_filter_to_chain(filters, :before, &block)
|
||||
filter_chain.prepend_filter_to_chain(filters, :before, &block)
|
||||
end
|
||||
|
||||
# Shorthand for append_before_filter since it's the most common.
|
||||
|
@ -263,19 +459,18 @@ module ActionController #:nodoc:
|
|||
# The passed <tt>filters</tt> will be appended to the array of filters
|
||||
# that run _after_ actions on this controller are performed.
|
||||
def append_after_filter(*filters, &block)
|
||||
append_filter_to_chain(filters, :after, &block)
|
||||
filter_chain.append_filter_to_chain(filters, :after, &block)
|
||||
end
|
||||
|
||||
# The passed <tt>filters</tt> will be prepended to the array of filters
|
||||
# that run _after_ actions on this controller are performed.
|
||||
def prepend_after_filter(*filters, &block)
|
||||
prepend_filter_to_chain(filters, :after, &block)
|
||||
filter_chain.prepend_filter_to_chain(filters, :after, &block)
|
||||
end
|
||||
|
||||
# Shorthand for append_after_filter since it's the most common.
|
||||
alias :after_filter :append_after_filter
|
||||
|
||||
|
||||
# If you append_around_filter A.new, B.new, the filter chain looks like
|
||||
#
|
||||
# B#before
|
||||
|
@ -287,10 +482,7 @@ module ActionController #:nodoc:
|
|||
# With around filters which yield to the action block, #before and #after
|
||||
# are the code before and after the yield.
|
||||
def append_around_filter(*filters, &block)
|
||||
filters, conditions = extract_conditions(filters, &block)
|
||||
filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
|
||||
append_filter_to_chain([filter, conditions])
|
||||
end
|
||||
filter_chain.append_filter_to_chain(filters, :around, &block)
|
||||
end
|
||||
|
||||
# If you prepend_around_filter A.new, B.new, the filter chain looks like:
|
||||
|
@ -304,10 +496,7 @@ module ActionController #:nodoc:
|
|||
# With around filters which yield to the action block, #before and #after
|
||||
# are the code before and after the yield.
|
||||
def prepend_around_filter(*filters, &block)
|
||||
filters, conditions = extract_conditions(filters, &block)
|
||||
filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
|
||||
prepend_filter_to_chain([filter, conditions])
|
||||
end
|
||||
filter_chain.prepend_filter_to_chain(filters, :around, &block)
|
||||
end
|
||||
|
||||
# Shorthand for append_around_filter since it's the most common.
|
||||
|
@ -320,7 +509,7 @@ module ActionController #:nodoc:
|
|||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# just like when you apply the filters.
|
||||
def skip_before_filter(*filters)
|
||||
skip_filter_in_chain(*filters, &:before?)
|
||||
filter_chain.skip_filter_in_chain(*filters, &:before?)
|
||||
end
|
||||
|
||||
# Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
|
||||
|
@ -330,7 +519,7 @@ module ActionController #:nodoc:
|
|||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# just like when you apply the filters.
|
||||
def skip_after_filter(*filters)
|
||||
skip_filter_in_chain(*filters, &:after?)
|
||||
filter_chain.skip_filter_in_chain(*filters, &:after?)
|
||||
end
|
||||
|
||||
# Removes the specified filters from the filter chain. This only works for method reference (symbol)
|
||||
|
@ -340,334 +529,30 @@ module ActionController #:nodoc:
|
|||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# just like when you apply the filters.
|
||||
def skip_filter(*filters)
|
||||
skip_filter_in_chain(*filters)
|
||||
filter_chain.skip_filter_in_chain(*filters)
|
||||
end
|
||||
|
||||
# Returns an array of Filter objects for this controller.
|
||||
def filter_chain
|
||||
read_inheritable_attribute("filter_chain") || []
|
||||
if chain = read_inheritable_attribute('filter_chain')
|
||||
return chain
|
||||
else
|
||||
write_inheritable_attribute('filter_chain', FilterChain.new)
|
||||
return filter_chain
|
||||
end
|
||||
end
|
||||
|
||||
# Returns all the before filters for this class and all its ancestors.
|
||||
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
|
||||
def before_filters #:nodoc:
|
||||
filter_chain.select(&:before?).map(&:filter)
|
||||
filter_chain.select(&:before?).map(&:method)
|
||||
end
|
||||
|
||||
# Returns all the after filters for this class and all its ancestors.
|
||||
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
|
||||
def after_filters #:nodoc:
|
||||
filter_chain.select(&:after?).map(&:filter)
|
||||
filter_chain.select(&:after?).map(&:method)
|
||||
end
|
||||
|
||||
# Returns a mapping between filters and the actions that may run them.
|
||||
def included_actions #:nodoc:
|
||||
@included_actions ||= read_inheritable_attribute("included_actions") || {}
|
||||
end
|
||||
|
||||
# Returns a mapping between filters and actions that may not run them.
|
||||
def excluded_actions #:nodoc:
|
||||
@excluded_actions ||= read_inheritable_attribute("excluded_actions") || {}
|
||||
end
|
||||
|
||||
# Find a filter in the filter_chain where the filter method matches the _filter_ param
|
||||
# and (optionally) the passed block evaluates to true (mostly used for testing before?
|
||||
# and after? on the filter). Useful for symbol filters.
|
||||
#
|
||||
# The object of type Filter is passed to the block when yielded, not the filter itself.
|
||||
def find_filter(filter, &block) #:nodoc:
|
||||
filter_chain.select { |f| f.filter == filter && (!block_given? || yield(f)) }.first
|
||||
end
|
||||
|
||||
# Returns true if the filter is excluded from the given action
|
||||
def filter_excluded_from_action?(filter,action) #:nodoc:
|
||||
case
|
||||
when ia = included_actions[filter]
|
||||
!ia.include?(action)
|
||||
when ea = excluded_actions[filter]
|
||||
ea.include?(action)
|
||||
end
|
||||
end
|
||||
|
||||
# Filter class is an abstract base class for all filters. Handles all of the included/excluded actions but
|
||||
# contains no logic for calling the actual filters.
|
||||
class Filter #:nodoc:
|
||||
attr_reader :filter, :included_actions, :excluded_actions
|
||||
|
||||
def initialize(filter)
|
||||
@filter = filter
|
||||
end
|
||||
|
||||
def type
|
||||
:around
|
||||
end
|
||||
|
||||
def before?
|
||||
type == :before
|
||||
end
|
||||
|
||||
def after?
|
||||
type == :after
|
||||
end
|
||||
|
||||
def around?
|
||||
type == :around
|
||||
end
|
||||
|
||||
def run(controller)
|
||||
raise ActionControllerError, 'No filter type: Nothing to do here.'
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
run(controller)
|
||||
end
|
||||
end
|
||||
|
||||
# Abstract base class for filter proxies. FilterProxy objects are meant to mimic the behaviour of the old
|
||||
# before_filter and after_filter by moving the logic into the filter itself.
|
||||
class FilterProxy < Filter #:nodoc:
|
||||
def filter
|
||||
@filter.filter
|
||||
end
|
||||
end
|
||||
|
||||
class BeforeFilterProxy < FilterProxy #:nodoc:
|
||||
def type
|
||||
:before
|
||||
end
|
||||
|
||||
def run(controller)
|
||||
# only filters returning false are halted.
|
||||
@filter.call(controller)
|
||||
if controller.send!(:performed?)
|
||||
controller.send!(:halt_filter_chain, @filter, :rendered_or_redirected)
|
||||
end
|
||||
end
|
||||
|
||||
def call(controller)
|
||||
yield unless run(controller)
|
||||
end
|
||||
end
|
||||
|
||||
class AfterFilterProxy < FilterProxy #:nodoc:
|
||||
def type
|
||||
:after
|
||||
end
|
||||
|
||||
def run(controller)
|
||||
@filter.call(controller)
|
||||
end
|
||||
|
||||
def call(controller)
|
||||
yield
|
||||
run(controller)
|
||||
end
|
||||
end
|
||||
|
||||
class SymbolFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
controller.send!(@filter, &block)
|
||||
end
|
||||
end
|
||||
|
||||
class ProcFilter < Filter #:nodoc:
|
||||
def call(controller)
|
||||
@filter.call(controller)
|
||||
rescue LocalJumpError # a yield from a proc... no no bad dog.
|
||||
raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
|
||||
end
|
||||
end
|
||||
|
||||
class ProcWithCallFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.call(controller, block)
|
||||
rescue LocalJumpError # a yield from a proc... no no bad dog.
|
||||
raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
|
||||
end
|
||||
end
|
||||
|
||||
class MethodFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.call(controller, &block)
|
||||
end
|
||||
end
|
||||
|
||||
class ClassFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.filter(controller, &block)
|
||||
end
|
||||
end
|
||||
|
||||
class ClassBeforeFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.before(controller)
|
||||
end
|
||||
end
|
||||
|
||||
class ClassAfterFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.after(controller)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def append_filter_to_chain(filters, filter_type = :around, &block)
|
||||
pos = find_filter_append_position(filters, filter_type)
|
||||
update_filter_chain(filters, filter_type, pos, &block)
|
||||
end
|
||||
|
||||
def prepend_filter_to_chain(filters, filter_type = :around, &block)
|
||||
pos = find_filter_prepend_position(filters, filter_type)
|
||||
update_filter_chain(filters, filter_type, pos, &block)
|
||||
end
|
||||
|
||||
def update_filter_chain(filters, filter_type, pos, &block)
|
||||
new_filters = create_filters(filters, filter_type, &block)
|
||||
new_chain = filter_chain.insert(pos, new_filters).flatten
|
||||
write_inheritable_attribute('filter_chain', new_chain)
|
||||
end
|
||||
|
||||
def find_filter_append_position(filters, filter_type)
|
||||
# appending an after filter puts it at the end of the call chain
|
||||
# before and around filters go before the first after filter in the chain
|
||||
unless filter_type == :after
|
||||
filter_chain.each_with_index do |f,i|
|
||||
return i if f.after?
|
||||
end
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
def find_filter_prepend_position(filters, filter_type)
|
||||
# prepending a before or around filter puts it at the front of the call chain
|
||||
# after filters go before the first after filter in the chain
|
||||
if filter_type == :after
|
||||
filter_chain.each_with_index do |f,i|
|
||||
return i if f.after?
|
||||
end
|
||||
return -1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
def create_filters(filters, filter_type, &block) #:nodoc:
|
||||
filters, conditions = extract_conditions(filters, &block)
|
||||
filters.map! { |filter| find_or_create_filter(filter, filter_type) }
|
||||
update_conditions(filters, conditions)
|
||||
filters
|
||||
end
|
||||
|
||||
def find_or_create_filter(filter, filter_type)
|
||||
if found_filter = find_filter(filter) { |f| f.type == filter_type }
|
||||
found_filter
|
||||
else
|
||||
f = class_for_filter(filter, filter_type).new(filter)
|
||||
# apply proxy to filter if necessary
|
||||
case filter_type
|
||||
when :before
|
||||
BeforeFilterProxy.new(f)
|
||||
when :after
|
||||
AfterFilterProxy.new(f)
|
||||
else
|
||||
f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The determination of the filter type was once done at run time.
|
||||
# This method is here to extract as much logic from the filter run time as possible
|
||||
def class_for_filter(filter, filter_type) #:nodoc:
|
||||
case
|
||||
when filter.is_a?(Symbol)
|
||||
SymbolFilter
|
||||
when filter.respond_to?(:call)
|
||||
if filter.is_a?(Method)
|
||||
MethodFilter
|
||||
elsif filter.arity == 1
|
||||
ProcFilter
|
||||
else
|
||||
ProcWithCallFilter
|
||||
end
|
||||
when filter.respond_to?(:filter)
|
||||
ClassFilter
|
||||
when filter.respond_to?(:before) && filter_type == :before
|
||||
ClassBeforeFilter
|
||||
when filter.respond_to?(:after) && filter_type == :after
|
||||
ClassAfterFilter
|
||||
else
|
||||
raise(ActionControllerError, 'A filter must be a Symbol, Proc, Method, or object responding to filter, after or before.')
|
||||
end
|
||||
end
|
||||
|
||||
def extract_conditions(*filters, &block) #:nodoc:
|
||||
filters.flatten!
|
||||
conditions = filters.extract_options!
|
||||
filters << block if block_given?
|
||||
return filters, conditions
|
||||
end
|
||||
|
||||
def update_conditions(filters, conditions)
|
||||
return if conditions.empty?
|
||||
if conditions[:only]
|
||||
write_inheritable_hash('included_actions', condition_hash(filters, conditions[:only]))
|
||||
elsif conditions[:except]
|
||||
write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except]))
|
||||
end
|
||||
end
|
||||
|
||||
def condition_hash(filters, *actions)
|
||||
actions = actions.flatten.map(&:to_s)
|
||||
filters.inject({}) { |h,f| h.update( f => (actions.blank? ? nil : actions)) }
|
||||
end
|
||||
|
||||
def skip_filter_in_chain(*filters, &test) #:nodoc:
|
||||
filters, conditions = extract_conditions(filters)
|
||||
filters.map! { |f| block_given? ? find_filter(f, &test) : find_filter(f) }
|
||||
filters.compact!
|
||||
|
||||
if conditions.empty?
|
||||
delete_filters_in_chain(filters)
|
||||
else
|
||||
remove_actions_from_included_actions!(filters,conditions[:only] || [])
|
||||
conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
|
||||
update_conditions(filters,conditions)
|
||||
end
|
||||
end
|
||||
|
||||
def remove_actions_from_included_actions!(filters,*actions)
|
||||
actions = actions.flatten.map(&:to_s)
|
||||
updated_hash = filters.inject(read_inheritable_attribute('included_actions')||{}) do |hash,filter|
|
||||
ia = (hash[filter] || []) - actions
|
||||
ia.empty? ? hash.delete(filter) : hash[filter] = ia
|
||||
hash
|
||||
end
|
||||
write_inheritable_attribute('included_actions', updated_hash)
|
||||
end
|
||||
|
||||
def delete_filters_in_chain(filters) #:nodoc:
|
||||
write_inheritable_attribute('filter_chain', filter_chain.reject { |f| filters.include?(f) })
|
||||
end
|
||||
|
||||
def filter_responds_to_before_and_after(filter) #:nodoc:
|
||||
filter.respond_to?(:before) && filter.respond_to?(:after)
|
||||
end
|
||||
|
||||
def proxy_before_and_after_filter(filter) #:nodoc:
|
||||
return filter unless filter_responds_to_before_and_after(filter)
|
||||
Proc.new do |controller, action|
|
||||
filter.before(controller)
|
||||
|
||||
if controller.send!(:performed?)
|
||||
controller.send!(:halt_filter_chain, filter, :rendered_or_redirected)
|
||||
else
|
||||
begin
|
||||
action.call
|
||||
ensure
|
||||
filter.after(controller)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods # :nodoc:
|
||||
|
@ -679,89 +564,80 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
protected
|
||||
def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
@before_filter_chain_aborted = false
|
||||
process_without_filters(request, response, method, *arguments)
|
||||
end
|
||||
|
||||
def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
@before_filter_chain_aborted = false
|
||||
process_without_filters(request, response, method, *arguments)
|
||||
end
|
||||
|
||||
def perform_action_with_filters
|
||||
call_filters(self.class.filter_chain, 0, 0)
|
||||
end
|
||||
def perform_action_with_filters
|
||||
call_filters(self.class.filter_chain, 0, 0)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def call_filters(chain, index, nesting)
|
||||
index = run_before_filters(chain, index, nesting)
|
||||
aborted = @before_filter_chain_aborted
|
||||
perform_action_without_filters unless performed? || aborted
|
||||
return index if nesting != 0 || aborted
|
||||
run_after_filters(chain, index)
|
||||
end
|
||||
|
||||
def skip_excluded_filters(chain, index)
|
||||
while (filter = chain[index]) && self.class.filter_excluded_from_action?(filter, action_name)
|
||||
index = index.next
|
||||
def call_filters(chain, index, nesting)
|
||||
index = run_before_filters(chain, index, nesting)
|
||||
aborted = @before_filter_chain_aborted
|
||||
perform_action_without_filters unless performed? || aborted
|
||||
return index if nesting != 0 || aborted
|
||||
run_after_filters(chain, index)
|
||||
end
|
||||
[filter, index]
|
||||
end
|
||||
|
||||
def run_before_filters(chain, index, nesting)
|
||||
while chain[index]
|
||||
filter, index = skip_excluded_filters(chain, index)
|
||||
break unless filter # end of call chain reached
|
||||
def run_before_filters(chain, index, nesting)
|
||||
while chain[index]
|
||||
filter, index = chain[index], index
|
||||
break unless filter # end of call chain reached
|
||||
|
||||
case filter.type
|
||||
when :before
|
||||
filter.run(self) # invoke before filter
|
||||
index = index.next
|
||||
break if @before_filter_chain_aborted
|
||||
when :around
|
||||
yielded = false
|
||||
case filter
|
||||
when BeforeFilter
|
||||
filter.call(self) # invoke before filter
|
||||
index = index.next
|
||||
break if @before_filter_chain_aborted
|
||||
when AroundFilter
|
||||
yielded = false
|
||||
|
||||
filter.call(self) do
|
||||
yielded = true
|
||||
# all remaining before and around filters will be run in this call
|
||||
index = call_filters(chain, index.next, nesting.next)
|
||||
filter.call(self) do
|
||||
yielded = true
|
||||
# all remaining before and around filters will be run in this call
|
||||
index = call_filters(chain, index.next, nesting.next)
|
||||
end
|
||||
|
||||
halt_filter_chain(filter, :did_not_yield) unless yielded
|
||||
|
||||
break
|
||||
else
|
||||
break # no before or around filters left
|
||||
end
|
||||
end
|
||||
|
||||
index
|
||||
end
|
||||
|
||||
def run_after_filters(chain, index)
|
||||
seen_after_filter = false
|
||||
|
||||
while chain[index]
|
||||
filter, index = chain[index], index
|
||||
break unless filter # end of call chain reached
|
||||
|
||||
case filter
|
||||
when AfterFilter
|
||||
seen_after_filter = true
|
||||
filter.call(self) # invoke after filter
|
||||
else
|
||||
# implementation error or someone has mucked with the filter chain
|
||||
raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
|
||||
end
|
||||
|
||||
halt_filter_chain(filter, :did_not_yield) unless yielded
|
||||
|
||||
break
|
||||
else
|
||||
break # no before or around filters left
|
||||
end
|
||||
end
|
||||
|
||||
index
|
||||
end
|
||||
|
||||
def run_after_filters(chain, index)
|
||||
seen_after_filter = false
|
||||
|
||||
while chain[index]
|
||||
filter, index = skip_excluded_filters(chain, index)
|
||||
break unless filter # end of call chain reached
|
||||
|
||||
case filter.type
|
||||
when :after
|
||||
seen_after_filter = true
|
||||
filter.run(self) # invoke after filter
|
||||
else
|
||||
# implementation error or someone has mucked with the filter chain
|
||||
raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
|
||||
index = index.next
|
||||
end
|
||||
|
||||
index = index.next
|
||||
index.next
|
||||
end
|
||||
|
||||
index.next
|
||||
end
|
||||
|
||||
def halt_filter_chain(filter, reason)
|
||||
@before_filter_chain_aborted = true
|
||||
logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger
|
||||
end
|
||||
def halt_filter_chain(filter, reason)
|
||||
@before_filter_chain_aborted = true
|
||||
logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,7 +28,6 @@ module ActionController #:nodoc:
|
|||
base.class_eval do
|
||||
include InstanceMethods
|
||||
alias_method_chain :assign_shortcuts, :flash
|
||||
alias_method_chain :process_cleanup, :flash
|
||||
alias_method_chain :reset_session, :flash
|
||||
end
|
||||
end
|
||||
|
@ -166,11 +165,7 @@ module ActionController #:nodoc:
|
|||
def assign_shortcuts_with_flash(request, response) #:nodoc:
|
||||
assign_shortcuts_without_flash(request, response)
|
||||
flash(:refresh)
|
||||
end
|
||||
|
||||
def process_cleanup_with_flash
|
||||
flash.sweep if @_session
|
||||
process_cleanup_without_flash
|
||||
flash.sweep if @_session && !component_request?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
31
vendor/rails/actionpack/lib/action_controller/headers.rb
vendored
Normal file
31
vendor/rails/actionpack/lib/action_controller/headers.rb
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
module ActionController
|
||||
module Http
|
||||
class Headers < ::Hash
|
||||
|
||||
def initialize(constructor = {})
|
||||
if constructor.is_a?(Hash)
|
||||
super()
|
||||
update(constructor)
|
||||
else
|
||||
super(constructor)
|
||||
end
|
||||
end
|
||||
|
||||
def [](header_name)
|
||||
if include?(header_name)
|
||||
super
|
||||
else
|
||||
super(normalize_header(header_name))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
# Takes an HTTP header name and returns it in the
|
||||
# format
|
||||
def normalize_header(header_name)
|
||||
"HTTP_#{header_name.upcase.gsub(/-/, '_')}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -143,11 +143,19 @@ module ActionController #:nodoc:
|
|||
# Declare a controller method as a helper. For example, the following
|
||||
# makes the +current_user+ controller method available to the view:
|
||||
# class ApplicationController < ActionController::Base
|
||||
# helper_method :current_user
|
||||
# helper_method :current_user, :logged_in?
|
||||
#
|
||||
# def current_user
|
||||
# @current_user ||= User.find(session[:user])
|
||||
# @current_user ||= User.find_by_id(session[:user])
|
||||
# end
|
||||
#
|
||||
# def logged_in?
|
||||
# current_user != nil
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# In a view:
|
||||
# <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%>
|
||||
def helper_method(*methods)
|
||||
methods.flatten.each do |method|
|
||||
master_helper_module.module_eval <<-end_eval
|
||||
|
@ -167,6 +175,15 @@ module ActionController #:nodoc:
|
|||
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
|
||||
end
|
||||
|
||||
# Provides a proxy to access helpers methods from outside the view.
|
||||
def helpers
|
||||
unless @helper_proxy
|
||||
@helper_proxy = ActionView::Base.new
|
||||
@helper_proxy.extend master_helper_module
|
||||
else
|
||||
@helper_proxy
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def default_helper_module!
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
require 'base64'
|
||||
|
||||
module ActionController
|
||||
module HttpAuthentication
|
||||
# Makes it dead easy to do HTTP Basic authentication.
|
||||
|
@ -72,7 +70,7 @@ module ActionController
|
|||
#
|
||||
# On shared hosts, Apache sometimes doesn't pass authentication headers to
|
||||
# FCGI instances. If your environment matches this description and you cannot
|
||||
# authenticate, try this rule in public/.htaccess (replace the plain one):
|
||||
# authenticate, try this rule in your Apache setup:
|
||||
#
|
||||
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
|
||||
module Basic
|
||||
|
@ -110,11 +108,11 @@ module ActionController
|
|||
end
|
||||
|
||||
def decode_credentials(request)
|
||||
Base64.decode64(authorization(request).split.last || '')
|
||||
ActiveSupport::Base64.decode64(authorization(request).split.last || '')
|
||||
end
|
||||
|
||||
def encode_credentials(user_name, password)
|
||||
"Basic #{Base64.encode64("#{user_name}:#{password}")}"
|
||||
"Basic #{ActiveSupport::Base64.encode64("#{user_name}:#{password}")}"
|
||||
end
|
||||
|
||||
def authentication_request(controller, realm)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require 'dispatcher'
|
||||
require 'stringio'
|
||||
require 'uri'
|
||||
|
||||
require 'action_controller/dispatcher'
|
||||
require 'action_controller/test_process'
|
||||
|
||||
module ActionController
|
||||
|
@ -54,6 +55,9 @@ module ActionController
|
|||
# A running counter of the number of requests processed.
|
||||
attr_accessor :request_count
|
||||
|
||||
class MultiPartNeededException < Exception
|
||||
end
|
||||
|
||||
# Create and initialize a new +Session+ instance.
|
||||
def initialize
|
||||
reset!
|
||||
|
@ -276,7 +280,7 @@ module ActionController
|
|||
ActionController::Base.clear_last_instantiation!
|
||||
|
||||
cgi = StubCGI.new(env, data)
|
||||
Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
|
||||
ActionController::Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
|
||||
@result = cgi.stdoutput.string
|
||||
@request_count += 1
|
||||
|
||||
|
@ -293,15 +297,19 @@ module ActionController
|
|||
|
||||
parse_result
|
||||
return status
|
||||
rescue MultiPartNeededException
|
||||
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
|
||||
status = process(method, path, multipart_body(parameters, boundary), (headers || {}).merge({"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
|
||||
return status
|
||||
end
|
||||
|
||||
# Parses the result of the response and extracts the various values,
|
||||
# like cookies, status, headers, etc.
|
||||
def parse_result
|
||||
headers, result_body = @result.split(/\r\n\r\n/, 2)
|
||||
response_headers, result_body = @result.split(/\r\n\r\n/, 2)
|
||||
|
||||
@headers = Hash.new { |h,k| h[k] = [] }
|
||||
headers.each_line do |line|
|
||||
response_headers.to_s.each_line do |line|
|
||||
key, value = line.strip.split(/:\s*/, 2)
|
||||
@headers[key.downcase] << value
|
||||
end
|
||||
|
@ -311,7 +319,7 @@ module ActionController
|
|||
@cookies[name] = value
|
||||
end
|
||||
|
||||
@status, @status_message = @headers["status"].first.split(/ /)
|
||||
@status, @status_message = @headers["status"].first.to_s.split(/ /)
|
||||
@status = @status.to_i
|
||||
end
|
||||
|
||||
|
@ -341,7 +349,9 @@ module ActionController
|
|||
# Convert the given parameters to a request string. The parameters may
|
||||
# be a string, +nil+, or a Hash.
|
||||
def requestify(parameters, prefix=nil)
|
||||
if Hash === parameters
|
||||
if TestUploadedFile === parameters
|
||||
raise MultiPartNeededException
|
||||
elsif Hash === parameters
|
||||
return nil if parameters.empty?
|
||||
parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
|
||||
elsif Array === parameters
|
||||
|
@ -352,6 +362,45 @@ module ActionController
|
|||
"#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
|
||||
end
|
||||
end
|
||||
|
||||
def multipart_requestify(params, first=true)
|
||||
returning Hash.new do |p|
|
||||
params.each do |key, value|
|
||||
k = first ? CGI.escape(key.to_s) : "[#{CGI.escape(key.to_s)}]"
|
||||
if Hash === value
|
||||
multipart_requestify(value, false).each do |subkey, subvalue|
|
||||
p[k + subkey] = subvalue
|
||||
end
|
||||
else
|
||||
p[k] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def multipart_body(params, boundary)
|
||||
multipart_requestify(params).map do |key, value|
|
||||
if value.respond_to?(:original_filename)
|
||||
File.open(value.path) do |f|
|
||||
<<-EOF
|
||||
--#{boundary}\r
|
||||
Content-Disposition: form-data; name="#{key}"; filename="#{CGI.escape(value.original_filename)}"\r
|
||||
Content-Type: #{value.content_type}\r
|
||||
Content-Length: #{File.stat(value.path).size}\r
|
||||
\r
|
||||
#{f.read}\r
|
||||
EOF
|
||||
end
|
||||
else
|
||||
<<-EOF
|
||||
--#{boundary}\r
|
||||
Content-Disposition: form-data; name="#{key}"\r
|
||||
\r
|
||||
#{value}\r
|
||||
EOF
|
||||
end
|
||||
end.join("")+"--#{boundary}--\r"
|
||||
end
|
||||
end
|
||||
|
||||
# A module used to extend ActionController::Base, so that integration tests
|
||||
|
|
|
@ -29,18 +29,20 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# // The header part of this layout
|
||||
# <%= yield %>
|
||||
# // The footer part of this layout -->
|
||||
# // The footer part of this layout
|
||||
#
|
||||
# And then you have content pages that look like this:
|
||||
#
|
||||
# hello world
|
||||
#
|
||||
# Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
|
||||
# like this:
|
||||
# At rendering time, the content page is computed and then inserted in the layout, like this:
|
||||
#
|
||||
# // The header part of this layout
|
||||
# hello world
|
||||
# // The footer part of this layout -->
|
||||
# // The footer part of this layout
|
||||
#
|
||||
# NOTE: The old notation for rendering the view from a layout was to expose the magic <tt>@content_for_layout</tt> instance
|
||||
# variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
|
||||
#
|
||||
# == Accessing shared variables
|
||||
#
|
||||
|
@ -63,15 +65,15 @@ module ActionController #:nodoc:
|
|||
# == Automatic layout assignment
|
||||
#
|
||||
# If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically
|
||||
# set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named
|
||||
# set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named
|
||||
# <tt>app/views/layouts/weblog.erb</tt> or <tt>app/views/layouts/weblog.builder</tt> exists then it will be automatically set as
|
||||
# the layout for your WeblogController. You can create a layout with the name <tt>application.erb</tt> or <tt>application.builder</tt>
|
||||
# and this will be set as the default controller if there is no layout with the same name as the current controller and there is
|
||||
# and this will be set as the default controller if there is no layout with the same name as the current controller and there is
|
||||
# no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout.
|
||||
# assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.erb</tt>.
|
||||
# Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set.
|
||||
# Explicitly setting the layout in a parent class, though, will not override the child class's layout assignment if the child
|
||||
# class has a layout with the same name.
|
||||
# class has a layout with the same name.
|
||||
#
|
||||
# == Inheritance for layouts
|
||||
#
|
||||
|
@ -111,7 +113,7 @@ module ActionController #:nodoc:
|
|||
# logged_in? ? "writer_layout" : "reader_layout"
|
||||
# end
|
||||
#
|
||||
# Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
|
||||
# Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
|
||||
# is logged in or not.
|
||||
#
|
||||
# If you want to use an inline method, such as a proc, do something like this:
|
||||
|
@ -124,48 +126,45 @@ module ActionController #:nodoc:
|
|||
# class WeblogController < ActionController::Base
|
||||
# layout "weblog_standard"
|
||||
#
|
||||
# If no directory is specified for the template name, the template will by default be looked for in +app/views/layouts/+.
|
||||
# If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>.
|
||||
# Otherwise, it will be looked up relative to the template root.
|
||||
#
|
||||
# == Conditional layouts
|
||||
#
|
||||
# If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
|
||||
# a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
|
||||
# a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
|
||||
# <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# layout "weblog_standard", :except => :rss
|
||||
#
|
||||
#
|
||||
# # ...
|
||||
#
|
||||
# end
|
||||
#
|
||||
# This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
|
||||
# This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
|
||||
# around the rendered view.
|
||||
#
|
||||
# Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
|
||||
# Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
|
||||
# #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
|
||||
#
|
||||
# == Using a different layout in the action render call
|
||||
#
|
||||
#
|
||||
# If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
|
||||
# Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller.
|
||||
# This is possible using the <tt>render</tt> method. It's just a bit more manual work as you'll have to supply fully
|
||||
# qualified template and layout names as this example shows:
|
||||
# Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller.
|
||||
# You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# layout "weblog_standard"
|
||||
#
|
||||
# def help
|
||||
# render :action => "help/index", :layout => "help"
|
||||
# render :action => "help", :layout => "help"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# As you can see, you pass the template as the first parameter, the status code as the second ("200" is OK), and the layout
|
||||
# as the third.
|
||||
#
|
||||
# NOTE: The old notation for rendering the view from a layout was to expose the magic <tt>@content_for_layout</tt> instance
|
||||
# variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
|
||||
# This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout.
|
||||
module ClassMethods
|
||||
# If a layout is specified, all rendered actions will have their result rendered
|
||||
# If a layout is specified, all rendered actions will have their result rendered
|
||||
# when the layout <tt>yield</tt>s. This layout can itself depend on instance variables assigned during action
|
||||
# performance and have access to them as any normal template would.
|
||||
def layout(template_name, conditions = {}, auto = false)
|
||||
|
@ -177,21 +176,19 @@ module ActionController #:nodoc:
|
|||
def layout_conditions #:nodoc:
|
||||
@layout_conditions ||= read_inheritable_attribute("layout_conditions")
|
||||
end
|
||||
|
||||
|
||||
def default_layout(format) #:nodoc:
|
||||
layout = read_inheritable_attribute("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]
|
||||
end
|
||||
|
||||
|
||||
def layout_list #:nodoc:
|
||||
view_paths.collect do |path|
|
||||
Dir["#{path}/layouts/**/*"]
|
||||
end.flatten
|
||||
Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] }
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def inherited_with_layout(child)
|
||||
inherited_without_layout(child)
|
||||
|
@ -208,13 +205,7 @@ module ActionController #:nodoc:
|
|||
def normalize_conditions(conditions)
|
||||
conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})}
|
||||
end
|
||||
|
||||
def layout_directory_exists_cache
|
||||
@@layout_directory_exists_cache ||= Hash.new do |h, dirname|
|
||||
h[dirname] = File.directory? dirname
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def default_layout_with_format(format, layout)
|
||||
list = layout_list
|
||||
if list.grep(%r{layouts/#{layout}\.#{format}(\.[a-z][0-9a-z]*)+$}).empty?
|
||||
|
@ -236,7 +227,7 @@ module ActionController #:nodoc:
|
|||
when Symbol then send!(layout)
|
||||
when Proc then layout.call(self)
|
||||
end
|
||||
|
||||
|
||||
# Explicitly passed layout names with slashes are looked up relative to the template root,
|
||||
# but auto-discovered layouts derived from a nested controller will contain a slash, though be relative
|
||||
# to the 'layouts' directory so we have to check the file system to infer which case the layout name came from.
|
||||
|
@ -250,16 +241,14 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
protected
|
||||
def render_with_a_layout(options = nil, &block) #:nodoc:
|
||||
def render_with_a_layout(options = nil, extra_options = {}, &block) #:nodoc:
|
||||
template_with_options = options.is_a?(Hash)
|
||||
|
||||
if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options))
|
||||
assert_existence_of_template_file(layout)
|
||||
|
||||
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, &block)
|
||||
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)
|
||||
|
@ -267,7 +256,7 @@ module ActionController #:nodoc:
|
|||
status = template_with_options ? options[:status] : nil
|
||||
render_for_text(@template.render_file(layout, true), status)
|
||||
else
|
||||
render_with_no_layout(options, &block)
|
||||
render_with_no_layout(options, extra_options, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -279,7 +268,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def candidate_for_layout?(options)
|
||||
(options.has_key?(:layout) && options[:layout] != false) ||
|
||||
(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]))
|
||||
end
|
||||
|
@ -305,7 +294,7 @@ module ActionController #:nodoc:
|
|||
when only = conditions[:only]
|
||||
only.include?(action_name)
|
||||
when except = conditions[:except]
|
||||
!except.include?(action_name)
|
||||
!except.include?(action_name)
|
||||
else
|
||||
true
|
||||
end
|
||||
|
@ -313,14 +302,9 @@ module ActionController #:nodoc:
|
|||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Does a layout directory for this class exist?
|
||||
# we cache this info in a class level hash
|
||||
|
||||
def layout_directory?(layout_name)
|
||||
view_paths.find do |path|
|
||||
next unless template_path = Dir[File.join(path, 'layouts', layout_name) + ".*"].first
|
||||
self.class.send!(:layout_directory_exists_cache)[File.dirname(template_path)]
|
||||
end
|
||||
@template.finder.find_template_extension_from_handler(File.join('layouts', layout_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -125,7 +125,7 @@ module ActionController #:nodoc:
|
|||
|
||||
@order << mime_type
|
||||
|
||||
@responses[mime_type] = Proc.new do
|
||||
@responses[mime_type] ||= Proc.new do
|
||||
@response.template.template_format = mime_type.to_sym
|
||||
@response.content_type = mime_type.to_s
|
||||
block_given? ? block.call : @controller.send(:render, :action => @controller.action_name)
|
||||
|
@ -133,7 +133,11 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def any(*args, &block)
|
||||
args.each { |type| send(type, &block) }
|
||||
if args.any?
|
||||
args.each { |type| send(type, &block) }
|
||||
else
|
||||
custom(@mime_type_priority.first, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def method_missing(symbol, &block)
|
||||
|
|
|
@ -71,8 +71,11 @@ module Mime
|
|||
# keep track of creation order to keep the subsequent sort stable
|
||||
list = []
|
||||
accept_header.split(/,/).each_with_index do |header, index|
|
||||
params = header.split(/;\s*q=/)
|
||||
list << AcceptItem.new(index, *params) unless params.empty?
|
||||
params, q = header.split(/;\s*q=/)
|
||||
if params
|
||||
params.strip!
|
||||
list << AcceptItem.new(index, params, q) unless params.empty?
|
||||
end
|
||||
end
|
||||
list.sort!
|
||||
|
||||
|
@ -145,7 +148,10 @@ module Mime
|
|||
end
|
||||
|
||||
def ==(mime_type)
|
||||
(@synonyms + [ self ]).any? { |synonym| synonym.to_s == mime_type.to_s } if mime_type
|
||||
return false if mime_type.blank?
|
||||
(@synonyms + [ self ]).any? do |synonym|
|
||||
synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,8 +1,81 @@
|
|||
module ActionController
|
||||
# Polymorphic URL helpers are methods for smart resolution to a named route call when
|
||||
# given an ActiveRecord model instance. They are to be used in combination with
|
||||
# ActionController::Resources.
|
||||
#
|
||||
# These methods are useful when you want to generate correct URL or path to a RESTful
|
||||
# resource without having to know the exact type of the record in question.
|
||||
#
|
||||
# Nested resources and/or namespaces are also supported, as illustrated in the example:
|
||||
#
|
||||
# polymorphic_url([:admin, @article, @comment])
|
||||
# #-> results in:
|
||||
# admin_article_comment_url(@article, @comment)
|
||||
#
|
||||
# == Usage within the framework
|
||||
#
|
||||
# Polymorphic URL helpers are used in a number of places throughout the Rails framework:
|
||||
#
|
||||
# * <tt>url_for</tt>, so you can use it with a record as the argument, e.g.
|
||||
# <tt>url_for(@article)</tt>;
|
||||
# * ActionView::Helpers::FormHelper uses <tt>polymorphic_path</tt>, so you can write
|
||||
# <tt>form_for(@article)</tt> without having to specify <tt>:url</tt> parameter for the form
|
||||
# action;
|
||||
# * <tt>redirect_to</tt> (which, in fact, uses <tt>url_for</tt>) so you can write
|
||||
# <tt>redirect_to(post)</tt> in your controllers;
|
||||
# * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
|
||||
# for feed entries.
|
||||
#
|
||||
# == Prefixed polymorphic helpers
|
||||
#
|
||||
# In addition to <tt>polymorphic_url</tt> and <tt>polymorphic_path</tt> methods, a
|
||||
# number of prefixed helpers are available as a shorthand to <tt>:action => "..."</tt>
|
||||
# in options. Those are:
|
||||
#
|
||||
# * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
|
||||
# * <tt>new_polymorphic_url</tt>, <tt>new_polymorphic_path</tt>
|
||||
# * <tt>formatted_polymorphic_url</tt>, <tt>formatted_polymorphic_path</tt>
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# edit_polymorphic_path(@post)
|
||||
# #=> /posts/1/edit
|
||||
#
|
||||
# formatted_polymorphic_path([@post, :pdf])
|
||||
# #=> /posts/1.pdf
|
||||
module PolymorphicRoutes
|
||||
# Constructs a call to a named RESTful route for the given record and returns the
|
||||
# resulting URL string. For example:
|
||||
#
|
||||
# # calls post_url(post)
|
||||
# polymorphic_url(post) # => "http://example.com/posts/1"
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:action</tt> - Specifies the action prefix for the named route:
|
||||
# <tt>:new</tt>, <tt>:edit</tt>, or <tt>:formatted</tt>. Default is no prefix.
|
||||
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
|
||||
# Default is <tt>:url</tt>.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # an Article record
|
||||
# polymorphic_url(record) # same as article_url(record)
|
||||
#
|
||||
# # a Comment record
|
||||
# polymorphic_url(record) # same as comment_url(record)
|
||||
#
|
||||
# # it recognizes new records and maps to the collection
|
||||
# record = Comment.new
|
||||
# polymorphic_url(record) # same as comments_url()
|
||||
#
|
||||
def polymorphic_url(record_or_hash_or_array, options = {})
|
||||
record = extract_record(record_or_hash_or_array)
|
||||
if record_or_hash_or_array.kind_of?(Array)
|
||||
record_or_hash_or_array = record_or_hash_or_array.dup
|
||||
end
|
||||
|
||||
record = extract_record(record_or_hash_or_array)
|
||||
format = extract_format(record_or_hash_or_array, options)
|
||||
namespace = extract_namespace(record_or_hash_or_array)
|
||||
|
||||
args = case record_or_hash_or_array
|
||||
|
@ -11,9 +84,11 @@ module ActionController
|
|||
else [ record_or_hash_or_array ]
|
||||
end
|
||||
|
||||
args << format if format
|
||||
|
||||
inflection =
|
||||
case
|
||||
when options[:action] == "new"
|
||||
when options[:action].to_s == "new"
|
||||
args.pop
|
||||
:singular
|
||||
when record.respond_to?(:new_record?) && record.new_record?
|
||||
|
@ -27,8 +102,11 @@ module ActionController
|
|||
send!(named_route, *args)
|
||||
end
|
||||
|
||||
def polymorphic_path(record_or_hash_or_array)
|
||||
polymorphic_url(record_or_hash_or_array, :routing_type => :path)
|
||||
# Returns the path component of a URL for the given record. It uses
|
||||
# <tt>polymorphic_url</tt> with <tt>:routing_type => :path</tt>.
|
||||
def polymorphic_path(record_or_hash_or_array, options = {})
|
||||
options[:routing_type] = :path
|
||||
polymorphic_url(record_or_hash_or_array, options)
|
||||
end
|
||||
|
||||
%w(edit new formatted).each do |action|
|
||||
|
@ -43,26 +121,29 @@ module ActionController
|
|||
EOT
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def action_prefix(options)
|
||||
options[:action] ? "#{options[:action]}_" : ""
|
||||
end
|
||||
|
||||
def routing_type(options)
|
||||
"#{options[:routing_type] || "url"}"
|
||||
options[:routing_type] || :url
|
||||
end
|
||||
|
||||
def build_named_route_call(records, namespace, inflection, options = {})
|
||||
records = Array.new([extract_record(records)]) unless records.is_a?(Array)
|
||||
base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", records.pop)}_"
|
||||
|
||||
method_root = records.reverse.inject(base_segment) do |string, name|
|
||||
segment = "#{RecordIdentifier.send!("singular_class_name", name)}_"
|
||||
segment << string
|
||||
unless records.is_a?(Array)
|
||||
record = extract_record(records)
|
||||
route = ''
|
||||
else
|
||||
record = records.pop
|
||||
route = records.inject("") do |string, parent|
|
||||
string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
|
||||
end
|
||||
end
|
||||
|
||||
action_prefix(options) + namespace + method_root + routing_type(options)
|
||||
route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
|
||||
|
||||
action_prefix(options) + namespace + route + routing_type(options).to_s
|
||||
end
|
||||
|
||||
def extract_record(record_or_hash_or_array)
|
||||
|
@ -73,12 +154,22 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
def extract_format(record_or_hash_or_array, options)
|
||||
if options[:action].to_s == "formatted" && record_or_hash_or_array.is_a?(Array)
|
||||
record_or_hash_or_array.pop
|
||||
elsif options[:format]
|
||||
options[:format]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def extract_namespace(record_or_hash_or_array)
|
||||
returning "" do |namespace|
|
||||
if record_or_hash_or_array.is_a?(Array)
|
||||
record_or_hash_or_array.delete_if do |record_or_namespace|
|
||||
if record_or_namespace.is_a?(String) || record_or_namespace.is_a?(Symbol)
|
||||
namespace << "#{record_or_namespace.to_s}_"
|
||||
namespace << "#{record_or_namespace}_"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,11 +33,17 @@ module ActionController
|
|||
|
||||
# Returns plural/singular for a record or class. Example:
|
||||
#
|
||||
# partial_path(post) # => "posts/post"
|
||||
# partial_path(Person) # => "people/person"
|
||||
def partial_path(record_or_class)
|
||||
# partial_path(post) # => "posts/post"
|
||||
# partial_path(Person) # => "people/person"
|
||||
# partial_path(Person, "admin/games") # => "admin/people/person"
|
||||
def partial_path(record_or_class, controller_path = nil)
|
||||
klass = class_from_record_or_class(record_or_class)
|
||||
"#{klass.name.tableize}/#{klass.name.demodulize.underscore}"
|
||||
|
||||
if controller_path && controller_path.include?("/")
|
||||
"#{File.dirname(controller_path)}/#{klass.name.tableize}/#{klass.name.demodulize.underscore}"
|
||||
else
|
||||
"#{klass.name.tableize}/#{klass.name.demodulize.underscore}"
|
||||
end
|
||||
end
|
||||
|
||||
# The DOM class convention is to use the singular form of an object or class. Examples:
|
||||
|
@ -53,15 +59,15 @@ module ActionController
|
|||
[ prefix, singular_class_name(record_or_class) ].compact * '_'
|
||||
end
|
||||
|
||||
# The DOM class convention is to use the singular form of an object or class with the id following an underscore.
|
||||
# The DOM id convention is to use the singular form of an object or class with the id following an underscore.
|
||||
# If no id is found, prefix with "new_" instead. Examples:
|
||||
#
|
||||
# dom_class(Post.new(:id => 45)) # => "post_45"
|
||||
# dom_class(Post.new) # => "new_post"
|
||||
# dom_id(Post.new(:id => 45)) # => "post_45"
|
||||
# dom_id(Post.new) # => "new_post"
|
||||
#
|
||||
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
|
||||
#
|
||||
# dom_class(Post.new(:id => 45), :edit) # => "edit_post_45"
|
||||
# dom_id(Post.new(:id => 45), :edit) # => "edit_post_45"
|
||||
def dom_id(record, prefix = nil)
|
||||
prefix ||= 'new' unless record.id
|
||||
[ prefix, singular_class_name(record), record.id ].compact * '_'
|
||||
|
|
|
@ -15,7 +15,7 @@ module ActionController
|
|||
# such as { 'RAILS_ENV' => 'production' }.
|
||||
attr_reader :env
|
||||
|
||||
# The true HTTP request method as a lowercase symbol, such as :get.
|
||||
# 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
|
||||
|
@ -28,41 +28,43 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
# The HTTP request method as a lowercase symbol, such as :get.
|
||||
# Note, HEAD is returned as :get since the two are functionally
|
||||
# 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
|
||||
request_method == :head ? :get : request_method
|
||||
end
|
||||
|
||||
# Is this a GET (or HEAD) request? Equivalent to request.method == :get
|
||||
# Is this a GET (or HEAD) request? Equivalent to <tt>request.method == :get</tt>.
|
||||
def get?
|
||||
method == :get
|
||||
end
|
||||
|
||||
# Is this a POST request? Equivalent to request.method == :post
|
||||
# Is this a POST request? Equivalent to <tt>request.method == :post</tt>.
|
||||
def post?
|
||||
request_method == :post
|
||||
end
|
||||
|
||||
# Is this a PUT request? Equivalent to request.method == :put
|
||||
# Is this a PUT request? Equivalent to <tt>request.method == :put</tt>.
|
||||
def put?
|
||||
request_method == :put
|
||||
end
|
||||
|
||||
# Is this a DELETE request? Equivalent to request.method == :delete
|
||||
# Is this a DELETE request? Equivalent to <tt>request.method == :delete</tt>.
|
||||
def delete?
|
||||
request_method == :delete
|
||||
end
|
||||
|
||||
# Is this a HEAD request? request.method sees HEAD as :get, so check the
|
||||
# HTTP method directly.
|
||||
# Is this a HEAD request? <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
|
||||
# so check the 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"
|
||||
def headers
|
||||
@env
|
||||
@headers ||= ActionController::Http::Headers.new(@env)
|
||||
end
|
||||
|
||||
def content_length
|
||||
|
@ -111,7 +113,7 @@ module ActionController
|
|||
# end
|
||||
def format=(extension)
|
||||
parameters[:format] = extension.to_s
|
||||
format
|
||||
@format = Mime::Type.lookup_by_extension(parameters[:format])
|
||||
end
|
||||
|
||||
# Returns true if the request's "X-Requested-With" header contains
|
||||
|
@ -122,26 +124,41 @@ module ActionController
|
|||
end
|
||||
alias xhr? :xml_http_request?
|
||||
|
||||
# Which IP addresses are "trusted proxies" that can be stripped from
|
||||
# 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
|
||||
# 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 before
|
||||
# falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma-
|
||||
# delimited list in the case of multiple chained proxies; the first is
|
||||
# the originating IP.
|
||||
#
|
||||
# Security note: do not use if IP spoofing is a concern for your
|
||||
# application. Since remote_ip checks HTTP headers for addresses forwarded
|
||||
# by proxies, the client may send any IP. remote_addr can't be spoofed but
|
||||
# also doesn't work behind a proxy, since it's always the proxy's IP.
|
||||
# HTTP_X_FORWARDED_FOR are set by proxies so check for these if
|
||||
# REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
|
||||
# delimited list in the case of multiple chained proxies; the last
|
||||
# address which is not trusted is the originating IP.
|
||||
|
||||
def remote_ip
|
||||
return @env['HTTP_CLIENT_IP'] if @env.include? 'HTTP_CLIENT_IP'
|
||||
if TRUSTED_PROXIES !~ @env['REMOTE_ADDR']
|
||||
return @env['REMOTE_ADDR']
|
||||
end
|
||||
|
||||
if @env.include? 'HTTP_CLIENT_IP'
|
||||
if @env.include? 'HTTP_X_FORWARDED_FOR'
|
||||
# We don't know which came from the proxy, and which from the user
|
||||
raise ActionControllerError.new(<<EOM)
|
||||
IP spoofing attack?!
|
||||
HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}
|
||||
HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}
|
||||
EOM
|
||||
end
|
||||
return @env['HTTP_CLIENT_IP']
|
||||
end
|
||||
|
||||
if @env.include? 'HTTP_X_FORWARDED_FOR' then
|
||||
remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',').reject do |ip|
|
||||
ip.strip =~ /^unknown$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
|
||||
remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',')
|
||||
while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip
|
||||
remote_ips.pop
|
||||
end
|
||||
|
||||
return remote_ips.first.strip unless remote_ips.empty?
|
||||
return remote_ips.last.strip
|
||||
end
|
||||
|
||||
@env['REMOTE_ADDR']
|
||||
|
@ -385,6 +402,14 @@ module ActionController
|
|||
body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
|
||||
when :yaml
|
||||
YAML.load(body)
|
||||
when :json
|
||||
if body.blank?
|
||||
{}
|
||||
else
|
||||
data = ActiveSupport::JSON.decode(body)
|
||||
data = {:_json => data} unless data.is_a?(Hash)
|
||||
data.with_indifferent_access
|
||||
end
|
||||
else
|
||||
{}
|
||||
end
|
||||
|
@ -473,7 +498,7 @@ module ActionController
|
|||
when Array
|
||||
value.map { |v| get_typed_value(v) }
|
||||
else
|
||||
if value.is_a?(UploadedFile)
|
||||
if value.respond_to? :original_filename
|
||||
# Uploaded file
|
||||
if value.original_filename
|
||||
value
|
||||
|
@ -490,7 +515,6 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
|
||||
|
||||
EOL = "\015\012"
|
||||
|
@ -498,7 +522,7 @@ module ActionController
|
|||
def read_multipart(body, boundary, content_length, env)
|
||||
params = Hash.new([])
|
||||
boundary = "--" + boundary
|
||||
quoted_boundary = Regexp.quote(boundary, "n")
|
||||
quoted_boundary = Regexp.quote(boundary)
|
||||
buf = ""
|
||||
bufsize = 10 * 1024
|
||||
boundary_end=""
|
||||
|
@ -583,17 +607,16 @@ module ActionController
|
|||
else
|
||||
params[name] = [content]
|
||||
end
|
||||
break if buf.size == 0
|
||||
break if content_length == -1
|
||||
end
|
||||
raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
|
||||
|
||||
begin
|
||||
begin
|
||||
body.rewind if body.respond_to?(:rewind)
|
||||
rescue Errno::ESPIPE
|
||||
rescue Errno::ESPIPE
|
||||
# Handles exceptions raised by input streams that cannot be rewound
|
||||
# such as when using plain CGI under Apache
|
||||
end
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
|
@ -672,6 +695,7 @@ module ActionController
|
|||
else
|
||||
top << {key => value}.with_indifferent_access
|
||||
push top.last
|
||||
value = top[key]
|
||||
end
|
||||
else
|
||||
top << value
|
||||
|
@ -679,7 +703,8 @@ module ActionController
|
|||
elsif top.is_a? Hash
|
||||
key = CGI.unescape(key)
|
||||
parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
|
||||
return top[key] ||= value
|
||||
top[key] ||= value
|
||||
return top[key]
|
||||
else
|
||||
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
|
||||
end
|
||||
|
@ -688,7 +713,7 @@ module ActionController
|
|||
end
|
||||
|
||||
def type_conflict!(klass, value)
|
||||
raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value."
|
||||
raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -13,33 +13,46 @@ module ActionController #:nodoc:
|
|||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a
|
||||
# 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.
|
||||
#
|
||||
# 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
|
||||
# production by editing public/422.html. A call to this method in ApplicationController is generated by default in post-Rails 2.0
|
||||
# applications.
|
||||
#
|
||||
# The token parameter is named <tt>authenticity_token</tt> by default. If you are generating an HTML form manually (without the
|
||||
# use of Rails' <tt>form_for</tt>, <tt>form_tag</tt> or other helpers), you have to include a hidden field named like that and
|
||||
# set its value to what is returned by <tt>form_authenticity_token</tt>. Same applies to manually constructed Ajax requests. To
|
||||
# make the token available through a global variable to scripts on a certain page, you could add something like this to a view:
|
||||
#
|
||||
# <%= javascript_tag "window._token = '#{form_authenticity_token}'" %>
|
||||
#
|
||||
# Request forgery protection is disabled by default in test environment. If you are upgrading from Rails 1.x, add this to
|
||||
# config/environments/test.rb:
|
||||
#
|
||||
# # Disable request forgery protection in test environment
|
||||
# config.action_controller.allow_forgery_protection = false
|
||||
#
|
||||
# == Learn more about CSRF (Cross-Site Request Forgery) attacks
|
||||
#
|
||||
# Here are some resources:
|
||||
# * http://isc.sans.org/diary.html?storyid=1750
|
||||
# * http://en.wikipedia.org/wiki/Cross-site_request_forgery
|
||||
#
|
||||
# Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application.
|
||||
# There are a few guidelines you should follow:
|
||||
#
|
||||
# * Keep your GET requests safe and idempotent. More reading material:
|
||||
# * http://www.xml.com/pub/a/2002/04/24/deviant.html
|
||||
# * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
|
||||
# * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look for "Expires: at end of session"
|
||||
#
|
||||
module ClassMethods
|
||||
# Protect a controller's actions from CSRF attacks by ensuring that all forms are coming from the current web application, not
|
||||
# a forged link from another site. This 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.
|
||||
#
|
||||
# You turn this on with the #protect_from_forgery method, which will perform the check and raise
|
||||
# an ActionController::InvalidAuthenticityToken if the token doesn't match what was expected. And it will add
|
||||
# a _authenticity_token parameter to all forms that are automatically generated by Rails. You can customize the error message
|
||||
# given through public/422.html.
|
||||
#
|
||||
# Learn more about CSRF (Cross-Site Request Forgery) attacks:
|
||||
#
|
||||
# * http://isc.sans.org/diary.html?storyid=1750
|
||||
# * http://en.wikipedia.org/wiki/Cross-site_request_forgery
|
||||
#
|
||||
# Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application.
|
||||
# There are a few guidelines you should follow:
|
||||
#
|
||||
# * Keep your GET requests safe and idempotent. More reading material:
|
||||
# * http://www.xml.com/pub/a/2002/04/24/deviant.html
|
||||
# * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
|
||||
# * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look for "Expires: at end of session"
|
||||
#
|
||||
# If you need to construct a request yourself, but still want to take advantage of forgery protection, you can grab the
|
||||
# authenticity_token using the form_authenticity_token helper method and make it part of the parameters yourself.
|
||||
# Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
|
@ -54,18 +67,12 @@ module ActionController #:nodoc:
|
|||
# skip_before_filter :verify_authenticity_token
|
||||
# end
|
||||
#
|
||||
# If you are upgrading from Rails 1.x, disable forgery protection to
|
||||
# simplify your tests. Add this to config/environments/test.rb:
|
||||
#
|
||||
# # Disable request forgery protection in test environment
|
||||
# config.action_controller.allow_forgery_protection = false
|
||||
#
|
||||
# Valid Options:
|
||||
#
|
||||
# * <tt>:only/:except</tt> - passed to the before_filter call. Set which actions are verified.
|
||||
# * <tt>:secret</tt> - Custom salt used to generate the form_authenticity_token.
|
||||
# * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
|
||||
# * <tt>:secret</tt> - Custom salt used to generate the <tt>form_authenticity_token</tt>.
|
||||
# Leave this off if you are using the cookie session store.
|
||||
# * <tt>:digest</tt> - Message digest used for hashing. Defaults to 'SHA1'
|
||||
# * <tt>:digest</tt> - Message digest used for hashing. Defaults to 'SHA1'.
|
||||
def protect_from_forgery(options = {})
|
||||
self.request_forgery_protection_token ||= :authenticity_token
|
||||
before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except)
|
||||
|
@ -95,14 +102,15 @@ module ActionController #:nodoc:
|
|||
request.format.html? || request.format.js?
|
||||
end
|
||||
|
||||
# Sets the token value for the current session. Pass a :secret option in #protect_from_forgery to add a custom salt to the hash.
|
||||
# Sets the token value for the current session. Pass a <tt>:secret</tt> option
|
||||
# in +protect_from_forgery+ to add a custom salt to the hash.
|
||||
def form_authenticity_token
|
||||
@form_authenticity_token ||= if request_forgery_protection_options[:secret]
|
||||
@form_authenticity_token ||= if !session.respond_to?(:session_id)
|
||||
raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session."
|
||||
elsif request_forgery_protection_options[:secret]
|
||||
authenticity_token_from_session_id
|
||||
elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
|
||||
authenticity_token_from_cookie_session
|
||||
elsif session.nil?
|
||||
raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session."
|
||||
else
|
||||
raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store)."
|
||||
end
|
||||
|
|
|
@ -13,20 +13,21 @@ module ActionController
|
|||
|
||||
def initialize(script_path)
|
||||
@quiet = false
|
||||
define_run_method(File.read(script_path))
|
||||
define_run_method(script_path)
|
||||
reset!
|
||||
end
|
||||
|
||||
def benchmark(n)
|
||||
@quiet = true
|
||||
print ' '
|
||||
|
||||
result = Benchmark.realtime do
|
||||
n.times do |i|
|
||||
run
|
||||
print i % 10 == 0 ? 'x' : '.'
|
||||
$stdout.flush
|
||||
print_progress(i)
|
||||
end
|
||||
end
|
||||
|
||||
puts
|
||||
result
|
||||
ensure
|
||||
|
@ -38,8 +39,26 @@ module ActionController
|
|||
end
|
||||
|
||||
private
|
||||
def define_run_method(script)
|
||||
instance_eval "def run; #{script}; end", __FILE__, __LINE__
|
||||
def define_run_method(script_path)
|
||||
script = File.read(script_path)
|
||||
|
||||
source = <<-end_source
|
||||
def run
|
||||
#{script}
|
||||
old_request_count = request_count
|
||||
reset!
|
||||
self.request_count = old_request_count
|
||||
end
|
||||
end_source
|
||||
|
||||
instance_eval source, script_path, 1
|
||||
end
|
||||
|
||||
def print_progress(i)
|
||||
print "\n " if i % 60 == 0
|
||||
print ' ' if i % 10 == 0
|
||||
print '.'
|
||||
$stdout.flush
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -98,8 +117,9 @@ module ActionController
|
|||
OptionParser.new do |opt|
|
||||
opt.banner = "USAGE: #{$0} [options] [session script path]"
|
||||
|
||||
opt.on('-n', '--times [0000]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i }
|
||||
opt.on('-n', '--times [100]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i if v }
|
||||
opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v }
|
||||
opt.on('-m', '--measure [mode]', 'Which ruby-prof measure mode to use: process_time, wall_time, cpu_time, allocations, or memory. Defaults to process_time.') { |v| options[:measure] = v }
|
||||
opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v }
|
||||
opt.on('-h', '--help', 'Show this help') { puts opt; exit }
|
||||
|
||||
|
@ -117,7 +137,9 @@ module ActionController
|
|||
def load_ruby_prof
|
||||
begin
|
||||
require 'ruby-prof'
|
||||
#RubyProf.measure_mode = RubyProf::ALLOCATED_OBJECTS
|
||||
if mode = options[:measure]
|
||||
RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
|
||||
end
|
||||
rescue LoadError
|
||||
abort '`gem install ruby-prof` to use the profiler'
|
||||
end
|
||||
|
|
|
@ -26,7 +26,7 @@ module ActionController #:nodoc:
|
|||
|
||||
DEFAULT_RESCUE_TEMPLATE = 'diagnostics'
|
||||
DEFAULT_RESCUE_TEMPLATES = {
|
||||
'ActionController::MissingTemplate' => 'missing_template',
|
||||
'ActionView::MissingTemplate' => 'missing_template',
|
||||
'ActionController::RoutingError' => 'routing_error',
|
||||
'ActionController::UnknownAction' => 'unknown_action',
|
||||
'ActionView::TemplateError' => 'template_error'
|
||||
|
@ -58,33 +58,35 @@ module ActionController #:nodoc:
|
|||
# Rescue exceptions raised in controller actions.
|
||||
#
|
||||
# <tt>rescue_from</tt> receives a series of exception classes or class
|
||||
# names, and a trailing :with option with the name of a method or a Proc
|
||||
# object to be called to handle them. Alternatively a block can be given.
|
||||
# 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 exception.is_a?(klass) holds true is the one invoked, if any.
|
||||
# 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
|
||||
# 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
|
||||
# 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
|
||||
#
|
||||
# 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)
|
||||
|
@ -153,7 +155,7 @@ module ActionController #:nodoc:
|
|||
# If the file doesn't exist, the body of the response will be left empty.
|
||||
def render_optional_error_file(status_code)
|
||||
status = interpret_status(status_code)
|
||||
path = "#{RAILS_ROOT}/public/#{status[0,3]}.html"
|
||||
path = "#{Rails.public_path}/#{status[0,3]}.html"
|
||||
if File.exist?(path)
|
||||
render :file => path, :status => status
|
||||
else
|
||||
|
@ -165,7 +167,7 @@ module ActionController #:nodoc:
|
|||
# method if you wish to redefine the meaning of a local request to
|
||||
# include remote IP addresses or other criteria.
|
||||
def local_request? #:doc:
|
||||
request.remote_addr == LOCALHOST and request.remote_ip == LOCALHOST
|
||||
request.remote_addr == LOCALHOST && request.remote_ip == LOCALHOST
|
||||
end
|
||||
|
||||
# Render detailed diagnostics for unhandled exceptions rescued from
|
||||
|
|
|
@ -44,13 +44,14 @@ module ActionController
|
|||
module Resources
|
||||
class Resource #:nodoc:
|
||||
attr_reader :collection_methods, :member_methods, :new_methods
|
||||
attr_reader :path_prefix, :name_prefix
|
||||
attr_reader :path_prefix, :name_prefix, :path_segment
|
||||
attr_reader :plural, :singular
|
||||
attr_reader :options
|
||||
|
||||
def initialize(entities, options)
|
||||
@plural ||= entities
|
||||
@singular ||= options[:singular] || plural.to_s.singularize
|
||||
@path_segment = options.delete(:as) || @plural
|
||||
|
||||
@options = options
|
||||
|
||||
|
@ -75,11 +76,13 @@ module ActionController
|
|||
end
|
||||
|
||||
def path
|
||||
@path ||= "#{path_prefix}/#{plural}"
|
||||
@path ||= "#{path_prefix}/#{path_segment}"
|
||||
end
|
||||
|
||||
def new_path
|
||||
@new_path ||= "#{path}/new"
|
||||
new_action = self.options[:path_names][:new] if self.options[:path_names]
|
||||
new_action ||= Base.resources_path_names[:new]
|
||||
@new_path ||= "#{path}/#{new_action}"
|
||||
end
|
||||
|
||||
def member_path
|
||||
|
@ -226,17 +229,53 @@ module ActionController
|
|||
#
|
||||
# <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
|
||||
#
|
||||
# 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.
|
||||
# or
|
||||
#
|
||||
# <% form_for @message do |f| %>
|
||||
#
|
||||
# which takes into account whether <tt>@message</tt> is a new record or not and generates the
|
||||
# path and method accordingly.
|
||||
#
|
||||
# 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.
|
||||
# * <tt>:member</tt> - same as :collection, but for actions that operate on a specific member.
|
||||
# * <tt>:new</tt> - same as :collection, 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 :method value for the method-specific routes.
|
||||
# * <tt>:path_prefix</tt> - set a prefix to the routes with required route variables.
|
||||
# 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>: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:
|
||||
# # 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.
|
||||
#
|
||||
# You may directly specify the routing association with +has_one+ and +has_many+ like:
|
||||
#
|
||||
# map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
|
||||
#
|
||||
# This is the same as:
|
||||
#
|
||||
# map.resources :notes do |notes|
|
||||
# notes.resource :author
|
||||
# notes.resources :comments
|
||||
# notes.resources :attachments
|
||||
# end
|
||||
#
|
||||
# * <tt>:path_names</tt> - Specify different names for the 'new' and 'edit' actions. For example:
|
||||
# # new_products_path == '/productos/nuevo'
|
||||
# map.resources :products, :as => 'productos', :path_names => { :new => 'nuevo', :edit => 'editar' }
|
||||
#
|
||||
# You can also set default action names from an environment, like this:
|
||||
# config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
|
||||
#
|
||||
# * <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:
|
||||
#
|
||||
|
@ -249,7 +288,7 @@ module ActionController
|
|||
# article.resources :comments
|
||||
# end
|
||||
#
|
||||
# The comment resources work the same, but must now include a value for :article_id.
|
||||
# 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)
|
||||
|
@ -257,13 +296,13 @@ module ActionController
|
|||
# article_comments_url(:article_id => @article)
|
||||
# article_comment_url(:article_id => @article, :id => @comment)
|
||||
#
|
||||
# * <tt>:name_prefix</tt> - define a prefix for all generated routes, usually ending in an underscore.
|
||||
# * <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 :name_prefix 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
|
||||
|
@ -304,7 +343,7 @@ module ActionController
|
|||
# # --> GET /categories/7/messages/1
|
||||
# # has named route "category_message"
|
||||
#
|
||||
# The #resources method sets HTTP method restrictions on the routes it generates. For example, making an
|
||||
# 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.
|
||||
def resources(*entities, &block)
|
||||
|
@ -325,7 +364,7 @@ module ActionController
|
|||
#
|
||||
# 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.
|
||||
# * To specify a custom plural name, use the :plural option. There is no :singular option.
|
||||
# * 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')
|
||||
#
|
||||
|
@ -485,8 +524,12 @@ module ActionController
|
|||
resource.member_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.singular}", "#{resource.member_path}#{resource.action_separator}#{action}", action_options)
|
||||
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}.:format",action_options)
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
1347
vendor/rails/actionpack/lib/action_controller/routing.rb
vendored
1347
vendor/rails/actionpack/lib/action_controller/routing.rb
vendored
File diff suppressed because it is too large
Load diff
204
vendor/rails/actionpack/lib/action_controller/routing/builder.rb
vendored
Normal file
204
vendor/rails/actionpack/lib/action_controller/routing/builder.rb
vendored
Normal file
|
@ -0,0 +1,204 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
class RouteBuilder #:nodoc:
|
||||
attr_accessor :separators, :optional_separators
|
||||
|
||||
def initialize
|
||||
self.separators = Routing::SEPARATORS
|
||||
self.optional_separators = %w( / )
|
||||
end
|
||||
|
||||
def separator_pattern(inverted = false)
|
||||
"[#{'^' if inverted}#{Regexp.escape(separators.join)}]"
|
||||
end
|
||||
|
||||
def interval_regexp
|
||||
Regexp.new "(.*?)(#{separators.source}|$)"
|
||||
end
|
||||
|
||||
def multiline_regexp?(expression)
|
||||
expression.options & Regexp::MULTILINE == Regexp::MULTILINE
|
||||
end
|
||||
|
||||
# Accepts a "route path" (a string defining a route), and returns the array
|
||||
# of segments that corresponds to it. Note that the segment array is only
|
||||
# partially initialized--the defaults and requirements, for instance, need
|
||||
# to be set separately, via the #assign_route_options method, and the
|
||||
# #optional? method for each segment will not be reliable until after
|
||||
# #assign_route_options is called, as well.
|
||||
def segments_for_route_path(path)
|
||||
rest, segments = path, []
|
||||
|
||||
until rest.empty?
|
||||
segment, rest = segment_for rest
|
||||
segments << segment
|
||||
end
|
||||
segments
|
||||
end
|
||||
|
||||
# A factory method that returns a new segment instance appropriate for the
|
||||
# format of the given string.
|
||||
def segment_for(string)
|
||||
segment = case string
|
||||
when /\A:(\w+)/
|
||||
key = $1.to_sym
|
||||
case key
|
||||
when :controller then ControllerSegment.new(key)
|
||||
else DynamicSegment.new key
|
||||
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
|
||||
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
|
||||
end
|
||||
[segment, $~.post_match]
|
||||
end
|
||||
|
||||
# Split the given hash of options into requirement and default hashes. The
|
||||
# segments are passed alongside in order to distinguish between default values
|
||||
# and requirements.
|
||||
def divide_route_options(segments, options)
|
||||
options = options.dup
|
||||
|
||||
if options[:namespace]
|
||||
options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}"
|
||||
options.delete(:path_prefix)
|
||||
options.delete(:name_prefix)
|
||||
options.delete(:namespace)
|
||||
end
|
||||
|
||||
requirements = (options.delete(:requirements) || {}).dup
|
||||
defaults = (options.delete(:defaults) || {}).dup
|
||||
conditions = (options.delete(:conditions) || {}).dup
|
||||
|
||||
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
|
||||
hash[key] = value
|
||||
end
|
||||
|
||||
[defaults, requirements, conditions]
|
||||
end
|
||||
|
||||
# Takes a hash of defaults and a hash of requirements, and assigns them to
|
||||
# the segments. Any unused requirements (which do not correspond to a segment)
|
||||
# are returned as a hash.
|
||||
def assign_route_options(segments, defaults, requirements)
|
||||
route_requirements = {} # Requirements that do not belong to a segment
|
||||
|
||||
segment_named = Proc.new do |key|
|
||||
segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
|
||||
end
|
||||
|
||||
requirements.each do |key, requirement|
|
||||
segment = segment_named[key]
|
||||
if segment
|
||||
raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp)
|
||||
if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
||||
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
||||
end
|
||||
if multiline_regexp?(requirement)
|
||||
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
|
||||
end
|
||||
segment.regexp = requirement
|
||||
else
|
||||
route_requirements[key] = requirement
|
||||
end
|
||||
end
|
||||
|
||||
defaults.each do |key, default|
|
||||
segment = segment_named[key]
|
||||
raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
|
||||
segment.is_optional = true
|
||||
segment.default = default.to_param if default
|
||||
end
|
||||
|
||||
assign_default_route_options(segments)
|
||||
ensure_required_segments(segments)
|
||||
route_requirements
|
||||
end
|
||||
|
||||
# Assign default options, such as 'index' as a default for <tt>:action</tt>. This
|
||||
# method must be run *after* user supplied requirements and defaults have
|
||||
# been applied to the segments.
|
||||
def assign_default_route_options(segments)
|
||||
segments.each do |segment|
|
||||
next unless segment.is_a? DynamicSegment
|
||||
case segment.key
|
||||
when :action
|
||||
if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index'
|
||||
segment.default ||= 'index'
|
||||
segment.is_optional = true
|
||||
end
|
||||
when :id
|
||||
if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ ''
|
||||
segment.is_optional = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Makes sure that there are no optional segments that precede a required
|
||||
# segment. If any are found that precede a required segment, they are
|
||||
# made required.
|
||||
def ensure_required_segments(segments)
|
||||
allow_optional = true
|
||||
segments.reverse_each do |segment|
|
||||
allow_optional &&= segment.optional?
|
||||
if !allow_optional && segment.optional?
|
||||
unless segment.optionality_implied?
|
||||
warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
|
||||
end
|
||||
segment.is_optional = false
|
||||
elsif allow_optional && segment.respond_to?(:default) && segment.default
|
||||
# if a segment has a default, then it is optional
|
||||
segment.is_optional = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Construct and return a route with the given path and options.
|
||||
def build(path, options)
|
||||
# Wrap the path with slashes
|
||||
path = "/#{path}" unless path[0] == ?/
|
||||
path = "#{path}/" unless path[-1] == ?/
|
||||
|
||||
path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix]
|
||||
|
||||
segments = segments_for_route_path(path)
|
||||
defaults, requirements, conditions = divide_route_options(segments, options)
|
||||
requirements = assign_route_options(segments, defaults, requirements)
|
||||
|
||||
route = Route.new
|
||||
|
||||
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
|
||||
|
||||
if !route.significant_keys.include?(:controller)
|
||||
raise ArgumentError, "Illegal route: the :controller must be specified!"
|
||||
end
|
||||
|
||||
route
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,11 +1,11 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
# Much of the slow performance from routes comes from the
|
||||
# complexity of expiry, :requirements matching, defaults providing
|
||||
# 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
|
||||
# they've provided the right number of arguments, and have no
|
||||
# :requirements, we can just build up a string and return it.
|
||||
# <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
|
||||
|
@ -41,28 +41,29 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
# Temporarily disabled :url optimisation pending proper solution to
|
||||
# Temporarily disabled <tt>:url</tt> optimisation pending proper solution to
|
||||
# Issues around request.host etc.
|
||||
def applicable?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Given a route:
|
||||
# map.person '/people/:id'
|
||||
# Given a route
|
||||
#
|
||||
# If the user calls person_url(@person), we can simply
|
||||
# map.person '/people/:id'
|
||||
#
|
||||
# If the user calls <tt>person_url(@person)</tt>, we can simply
|
||||
# return a string like "/people/#{@person.to_param}"
|
||||
# rather than triggering the expensive logic in url_for
|
||||
# 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
|
||||
# argument, but we don't want to generate /foos/id2
|
||||
if number_of_arguments == 1
|
||||
"defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)"
|
||||
"(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)"
|
||||
else
|
||||
"defined?(request) && request && args.size == #{number_of_arguments}"
|
||||
"(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{number_of_arguments}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -77,7 +78,7 @@ module ActionController
|
|||
|
||||
elements << '#{request.relative_url_root if request.relative_url_root}'
|
||||
|
||||
# The last entry in route.segments appears to # *always* be a
|
||||
# The last entry in <tt>route.segments</tt> appears to *always* be a
|
||||
# 'divider segment' for '/' but we have assertions to ensure that
|
||||
# we don't include the trailing slashes, so skip them.
|
||||
(route.segments.size == 1 ? route.segments : route.segments[0..-2]).each do |segment|
|
||||
|
@ -97,7 +98,7 @@ module ActionController
|
|||
# argument
|
||||
class PositionalArgumentsWithAdditionalParams < PositionalArguments
|
||||
def guard_condition
|
||||
"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)"
|
||||
"(!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,
|
||||
|
@ -106,7 +107,7 @@ module ActionController
|
|||
super.insert(-2, '?#{args.last.to_query}')
|
||||
end
|
||||
|
||||
# To avoid generating http://localhost/?host=foo.example.com we
|
||||
# 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
|
158
vendor/rails/actionpack/lib/action_controller/routing/recognition_optimisation.rb
vendored
Normal file
158
vendor/rails/actionpack/lib/action_controller/routing/recognition_optimisation.rb
vendored
Normal file
|
@ -0,0 +1,158 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
# BEFORE: 0.191446860631307 ms/url
|
||||
# AFTER: 0.029847304022858 ms/url
|
||||
# Speed up: 6.4 times
|
||||
#
|
||||
# Route recognition is slow due to one-by-one iterating over
|
||||
# a whole routeset (each map.resources generates at least 14 routes)
|
||||
# and matching weird regexps on each step.
|
||||
#
|
||||
# We optimize this by skipping all URI segments that 100% sure can't
|
||||
# be matched, moving deeper in a tree of routes (where node == segment)
|
||||
# until first possible match is accured. In such case, we start walking
|
||||
# a flat list of routes, matching them with accurate matcher.
|
||||
# So, first step: search a segment tree for the first relevant index.
|
||||
# Second step: iterate routes starting with that index.
|
||||
#
|
||||
# How tree is walked? We can do a recursive tests, but it's smarter:
|
||||
# We just create a tree of if-s and elsif-s matching segments.
|
||||
#
|
||||
# We have segments of 3 flavors:
|
||||
# 1) nil (no segment, route finished)
|
||||
# 2) const-dot-dynamic (like "/posts.:xml", "/preview.:size.jpg")
|
||||
# 3) const (like "/posts", "/comments")
|
||||
# 4) dynamic ("/:id", "file.:size.:extension")
|
||||
#
|
||||
# We split incoming string into segments and iterate over them.
|
||||
# When segment is nil, we drop immediately, on a current node index.
|
||||
# When segment is equal to some const, we step into branch.
|
||||
# If none constants matched, we step into 'dynamic' branch (it's a last).
|
||||
# If we can't match anything, we drop to last index on a level.
|
||||
#
|
||||
# Note: we maintain the original routes order, so we finish building
|
||||
# steps on a first dynamic segment.
|
||||
#
|
||||
#
|
||||
# Example. Given the routes:
|
||||
# 0 /posts/
|
||||
# 1 /posts/:id
|
||||
# 2 /posts/:id/comments
|
||||
# 3 /posts/blah
|
||||
# 4 /users/
|
||||
# 5 /users/:id
|
||||
# 6 /users/:id/profile
|
||||
#
|
||||
# request_uri = /users/123
|
||||
#
|
||||
# There will be only 4 iterations:
|
||||
# 1) segm test for /posts prefix, skip all /posts/* routes
|
||||
# 2) segm test for /users/
|
||||
# 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
|
||||
|
||||
# Route was not recognized. Try to find out why (maybe wrong verb).
|
||||
allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } }
|
||||
|
||||
if environment[:method] && !HTTP_METHODS.include?(environment[:method])
|
||||
raise NotImplemented.new(*allows)
|
||||
elsif !allows.empty?
|
||||
raise MethodNotAllowed.new(*allows)
|
||||
else
|
||||
raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
|
||||
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]
|
||||
|
||||
i = -1
|
||||
routes.each do |route|
|
||||
i += 1
|
||||
# not fast, but runs only once
|
||||
segments = to_plain_segments(route.segments.inject("") { |str,s| str << s.to_s })
|
||||
|
||||
node = tree
|
||||
segments.each do |seg|
|
||||
seg = :dynamic if seg && seg[0] == ?:
|
||||
node << [seg, [i]] if node.empty? || node[node.size - 1][0] != seg
|
||||
node = node[node.size - 1][1]
|
||||
end
|
||||
end
|
||||
tree
|
||||
end
|
||||
|
||||
def generate_code(list, padding=' ', level = 0)
|
||||
# a digit
|
||||
return padding + "#{list[0]}\n" if list.size == 1 && !(Array === list[0])
|
||||
|
||||
body = padding + "(seg = segments[#{level}]; \n"
|
||||
|
||||
i = 0
|
||||
was_nil = false
|
||||
list.each do |item|
|
||||
if Array === item
|
||||
i += 1
|
||||
start = (i == 1)
|
||||
final = (i == list.size)
|
||||
tag, sub = item
|
||||
if tag == :dynamic
|
||||
body += padding + "#{start ? 'if' : 'elsif'} true\n"
|
||||
body += generate_code(sub, padding + " ", level + 1)
|
||||
break
|
||||
elsif tag == nil && !was_nil
|
||||
was_nil = true
|
||||
body += padding + "#{start ? 'if' : 'elsif'} seg.nil?\n"
|
||||
body += generate_code(sub, padding + " ", level + 1)
|
||||
else
|
||||
body += padding + "#{start ? 'if' : 'elsif'} seg == '#{tag}'\n"
|
||||
body += generate_code(sub, padding + " ", level + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
body += padding + "else\n"
|
||||
body += padding + " #{list[0]}\n"
|
||||
body += padding + "end)\n"
|
||||
body
|
||||
end
|
||||
|
||||
# this must be really fast
|
||||
def to_plain_segments(str)
|
||||
str = str.dup
|
||||
str.sub!(/^\/+/,'')
|
||||
str.sub!(/\/+$/,'')
|
||||
segments = str.split(/\.[^\/]+\/+|\/+|\.[^\/]+\Z/) # cut off ".format" also
|
||||
segments << nil
|
||||
segments
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
240
vendor/rails/actionpack/lib/action_controller/routing/route.rb
vendored
Normal file
240
vendor/rails/actionpack/lib/action_controller/routing/route.rb
vendored
Normal file
|
@ -0,0 +1,240 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
class Route #:nodoc:
|
||||
attr_accessor :segments, :requirements, :conditions, :optimise
|
||||
|
||||
def initialize
|
||||
@segments = []
|
||||
@requirements = {}
|
||||
@conditions = {}
|
||||
@optimise = true
|
||||
end
|
||||
|
||||
# Indicates whether the routes should be optimised with the string interpolation
|
||||
# version of the named routes methods.
|
||||
def optimise?
|
||||
@optimise && ActionController::Base::optimise_named_routes
|
||||
end
|
||||
|
||||
def segment_keys
|
||||
segments.collect do |segment|
|
||||
segment.key if segment.respond_to? :key
|
||||
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
|
||||
# values.
|
||||
def build_query_string(hash, only_keys = nil)
|
||||
elements = []
|
||||
|
||||
(only_keys || hash.keys).each do |key|
|
||||
if value = hash[key]
|
||||
elements << value.to_query(key)
|
||||
end
|
||||
end
|
||||
|
||||
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.
|
||||
#
|
||||
# For example, +{:controller => 'pages', :action => 'show'} is the shell for the route:
|
||||
#
|
||||
# map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
|
||||
#
|
||||
def parameter_shell
|
||||
@parameter_shell ||= returning({}) do |shell|
|
||||
requirements.each do |key, requirement|
|
||||
shell[key] = requirement unless requirement.is_a? Regexp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Return an array containing all the keys that are used in this route. This
|
||||
# includes keys that appear inside the path, and keys that have requirements
|
||||
# placed upon them.
|
||||
def significant_keys
|
||||
@significant_keys ||= returning [] do |sk|
|
||||
segments.each { |segment| sk << segment.key if segment.respond_to? :key }
|
||||
sk.concat requirements.keys
|
||||
sk.uniq!
|
||||
end
|
||||
end
|
||||
|
||||
# Return a hash of key/value pairs representing the keys in the route that
|
||||
# have defaults, or which are specified by non-regexp requirements.
|
||||
def defaults
|
||||
@defaults ||= returning({}) do |hash|
|
||||
segments.each do |segment|
|
||||
next unless segment.respond_to? :default
|
||||
hash[segment.key] = segment.default unless segment.default.nil?
|
||||
end
|
||||
requirements.each do |key,req|
|
||||
next if Regexp === req || req.nil?
|
||||
hash[key] = req
|
||||
end
|
||||
end
|
||||
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
|
||||
|
||||
(@controller_requirement.nil? || @controller_requirement === controller) &&
|
||||
(@action_requirement.nil? || @action_requirement === action)
|
||||
end
|
||||
|
||||
def to_s
|
||||
@to_s ||= begin
|
||||
segs = segments.inject("") { |str,s| str << s.to_s }
|
||||
"%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect]
|
||||
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
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
435
vendor/rails/actionpack/lib/action_controller/routing/route_set.rb
vendored
Normal file
435
vendor/rails/actionpack/lib/action_controller/routing/route_set.rb
vendored
Normal file
|
@ -0,0 +1,435 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
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.
|
||||
#
|
||||
# Mapper instances have relatively few instance methods, in order to avoid
|
||||
# clashes with named routes.
|
||||
class Mapper #:doc:
|
||||
def initialize(set) #:nodoc:
|
||||
@set = set
|
||||
end
|
||||
|
||||
# Create an unnamed route with the provided +path+ and +options+. See
|
||||
# ActionController::Routing for an introduction to routes.
|
||||
def connect(path, options = {})
|
||||
@set.add_route(path, options)
|
||||
end
|
||||
|
||||
# Creates a named route called "root" for matching the root level request.
|
||||
def root(options = {})
|
||||
if options.is_a?(Symbol)
|
||||
if source_route = @set.named_routes.routes[options]
|
||||
options = source_route.defaults.merge({ :conditions => source_route.conditions })
|
||||
end
|
||||
end
|
||||
named_route("root", '', options)
|
||||
end
|
||||
|
||||
def named_route(name, path, options = {}) #:nodoc:
|
||||
@set.add_named_route(name, path, options)
|
||||
end
|
||||
|
||||
# Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
|
||||
# Example:
|
||||
#
|
||||
# map.namespace(:admin) do |admin|
|
||||
# admin.resources :products,
|
||||
# :has_many => [ :tags, :images, :variants ]
|
||||
# end
|
||||
#
|
||||
# This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
|
||||
# It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
|
||||
# Admin::TagsController.
|
||||
def namespace(name, options = {}, &block)
|
||||
if options[:namespace]
|
||||
with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
|
||||
else
|
||||
with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
|
||||
end
|
||||
end
|
||||
|
||||
def method_missing(route_name, *args, &proc) #:nodoc:
|
||||
super unless args.length >= 1 && proc.nil?
|
||||
@set.add_named_route(route_name, *args)
|
||||
end
|
||||
end
|
||||
|
||||
# A NamedRouteCollection instance is a collection of named routes, and also
|
||||
# maintains an anonymous module that can be used to install helpers for the
|
||||
# named routes.
|
||||
class NamedRouteCollection #:nodoc:
|
||||
include Enumerable
|
||||
include ActionController::Routing::Optimisation
|
||||
attr_reader :routes, :helpers
|
||||
|
||||
def initialize
|
||||
clear!
|
||||
end
|
||||
|
||||
def clear!
|
||||
@routes = {}
|
||||
@helpers = []
|
||||
|
||||
@module ||= Module.new
|
||||
@module.instance_methods.each do |selector|
|
||||
@module.class_eval { remove_method selector }
|
||||
end
|
||||
end
|
||||
|
||||
def add(name, route)
|
||||
routes[name.to_sym] = route
|
||||
define_named_route_methods(name, route)
|
||||
end
|
||||
|
||||
def get(name)
|
||||
routes[name.to_sym]
|
||||
end
|
||||
|
||||
alias []= add
|
||||
alias [] get
|
||||
alias clear clear!
|
||||
|
||||
def each
|
||||
routes.each { |name, route| yield name, route }
|
||||
self
|
||||
end
|
||||
|
||||
def names
|
||||
routes.keys
|
||||
end
|
||||
|
||||
def length
|
||||
routes.length
|
||||
end
|
||||
|
||||
def reset!
|
||||
old_routes = routes.dup
|
||||
clear!
|
||||
old_routes.each do |name, route|
|
||||
add(name, route)
|
||||
end
|
||||
end
|
||||
|
||||
def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
|
||||
reset! if regenerate
|
||||
Array(destinations).each do |dest|
|
||||
dest.send! :include, @module
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def url_helper_name(name, kind = :url)
|
||||
:"#{name}_#{kind}"
|
||||
end
|
||||
|
||||
def hash_access_name(name, kind = :url)
|
||||
:"hash_for_#{name}_#{kind}"
|
||||
end
|
||||
|
||||
def define_named_route_methods(name, route)
|
||||
{:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
|
||||
hash = route.defaults.merge(:use_route => name).merge(opts)
|
||||
define_hash_access route, name, kind, hash
|
||||
define_url_helper route, name, kind, hash
|
||||
end
|
||||
end
|
||||
|
||||
def define_hash_access(route, name, kind, options)
|
||||
selector = hash_access_name(name, kind)
|
||||
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
||||
def #{selector}(options = nil)
|
||||
options ? #{options.inspect}.merge(options) : #{options.inspect}
|
||||
end
|
||||
protected :#{selector}
|
||||
end_eval
|
||||
helpers << selector
|
||||
end
|
||||
|
||||
def define_url_helper(route, name, kind, options)
|
||||
selector = url_helper_name(name, kind)
|
||||
# The segment keys used for positional paramters
|
||||
|
||||
hash_access_method = hash_access_name(name, kind)
|
||||
|
||||
# allow ordered parameters to be associated with corresponding
|
||||
# dynamic segments, so you can do
|
||||
#
|
||||
# foo_url(bar, baz, bang)
|
||||
#
|
||||
# instead of
|
||||
#
|
||||
# foo_url(:bar => bar, :baz => baz, :bang => bang)
|
||||
#
|
||||
# Also allow options hash, so you can do
|
||||
#
|
||||
# foo_url(bar, baz, bang, :sort_by => 'baz')
|
||||
#
|
||||
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
||||
def #{selector}(*args)
|
||||
#{generate_optimisation_block(route, kind)}
|
||||
|
||||
opts = if args.empty? || Hash === args.first
|
||||
args.first || {}
|
||||
else
|
||||
options = args.extract_options!
|
||||
args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
|
||||
h[k] = v
|
||||
h
|
||||
end
|
||||
options.merge(args)
|
||||
end
|
||||
|
||||
url_for(#{hash_access_method}(opts))
|
||||
end
|
||||
protected :#{selector}
|
||||
end_eval
|
||||
helpers << selector
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :routes, :named_routes, :configuration_file
|
||||
|
||||
def initialize
|
||||
self.routes = []
|
||||
self.named_routes = NamedRouteCollection.new
|
||||
end
|
||||
|
||||
# Subclasses and plugins may override this method to specify a different
|
||||
# RouteBuilder instance, so that other route DSL's can be created.
|
||||
def builder
|
||||
@builder ||= RouteBuilder.new
|
||||
end
|
||||
|
||||
def draw
|
||||
clear!
|
||||
yield Mapper.new(self)
|
||||
install_helpers
|
||||
end
|
||||
|
||||
def clear!
|
||||
routes.clear
|
||||
named_routes.clear
|
||||
@combined_regexp = nil
|
||||
@routes_by_controller = nil
|
||||
# This will force routing/recognition_optimization.rb
|
||||
# to refresh optimisations.
|
||||
@compiled_recognize_optimized = nil
|
||||
end
|
||||
|
||||
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
|
||||
Array(destinations).each { |d| d.module_eval { include Helpers } }
|
||||
named_routes.install(destinations, regenerate_code)
|
||||
end
|
||||
|
||||
def empty?
|
||||
routes.empty?
|
||||
end
|
||||
|
||||
def load!
|
||||
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
|
||||
alias reload! load!
|
||||
|
||||
def reload
|
||||
if @routes_last_modified && configuration_file
|
||||
mtime = File.stat(configuration_file).mtime
|
||||
# if it hasn't been changed, then just return
|
||||
return if mtime == @routes_last_modified
|
||||
# if it has changed then record the new time and fall to the load! below
|
||||
@routes_last_modified = mtime
|
||||
end
|
||||
load!
|
||||
end
|
||||
|
||||
def load_routes!
|
||||
if configuration_file
|
||||
load configuration_file
|
||||
@routes_last_modified = File.stat(configuration_file).mtime
|
||||
else
|
||||
add_route ":controller/:action/:id"
|
||||
end
|
||||
end
|
||||
|
||||
def add_route(path, options = {})
|
||||
route = builder.build(path, options)
|
||||
routes << route
|
||||
route
|
||||
end
|
||||
|
||||
def add_named_route(name, path, options = {})
|
||||
# TODO - is options EVER used?
|
||||
name = options[:name_prefix] + name.to_s if options[:name_prefix]
|
||||
named_routes[name.to_sym] = add_route(path, options)
|
||||
end
|
||||
|
||||
def options_as_params(options)
|
||||
# If an explicit :controller was given, always make :action explicit
|
||||
# too, so that action expiry works as expected for things like
|
||||
#
|
||||
# generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
|
||||
#
|
||||
# (the above is from the unit tests). In the above case, because the
|
||||
# controller was explicitly given, but no action, the action is implied to
|
||||
# be "index", not the recalled action of "show".
|
||||
#
|
||||
# great fun, eh?
|
||||
|
||||
options_as_params = options.clone
|
||||
options_as_params[:action] ||= 'index' if options[:controller]
|
||||
options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
|
||||
options_as_params
|
||||
end
|
||||
|
||||
def build_expiry(options, recall)
|
||||
recall.inject({}) do |expiry, (key, recalled_value)|
|
||||
expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
|
||||
expiry
|
||||
end
|
||||
end
|
||||
|
||||
# Generate the path indicated by the arguments, and return an array of
|
||||
# the keys that were not used to generate it.
|
||||
def extra_keys(options, recall={})
|
||||
generate_extras(options, recall).last
|
||||
end
|
||||
|
||||
def generate_extras(options, recall={})
|
||||
generate(options, recall, :generate_extras)
|
||||
end
|
||||
|
||||
def generate(options, recall = {}, method=:generate)
|
||||
named_route_name = options.delete(:use_route)
|
||||
generate_all = options.delete(:generate_all)
|
||||
if named_route_name
|
||||
named_route = named_routes[named_route_name]
|
||||
options = named_route.parameter_shell.merge(options)
|
||||
end
|
||||
|
||||
options = options_as_params(options)
|
||||
expire_on = build_expiry(options, recall)
|
||||
|
||||
if options[:controller]
|
||||
options[:controller] = options[:controller].to_s
|
||||
end
|
||||
# if the controller has changed, make sure it changes relative to the
|
||||
# current controller module, if any. In other words, if we're currently
|
||||
# on admin/get, and the new controller is 'set', the new controller
|
||||
# should really be admin/set.
|
||||
if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
|
||||
old_parts = recall[:controller].split('/')
|
||||
new_parts = options[:controller].split('/')
|
||||
parts = old_parts[0..-(new_parts.length + 1)] + new_parts
|
||||
options[:controller] = parts.join('/')
|
||||
end
|
||||
|
||||
# drop the leading '/' on the controller name
|
||||
options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
|
||||
merged = recall.merge(options)
|
||||
|
||||
if named_route
|
||||
path = named_route.generate(options, merged, expire_on)
|
||||
if path.nil?
|
||||
raise_named_route_error(options, named_route, named_route_name)
|
||||
else
|
||||
return path
|
||||
end
|
||||
else
|
||||
merged[:action] ||= 'index'
|
||||
options[:action] ||= 'index'
|
||||
|
||||
controller = merged[:controller]
|
||||
action = merged[:action]
|
||||
|
||||
raise RoutingError, "Need controller and action!" unless controller && action
|
||||
|
||||
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)
|
||||
end.compact
|
||||
end
|
||||
|
||||
# don't use the recalled keys when determining which routes to check
|
||||
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)
|
||||
return results if results && (!results.is_a?(Array) || results.first)
|
||||
end
|
||||
end
|
||||
|
||||
raise RoutingError, "No route matches #{options.inspect}"
|
||||
end
|
||||
|
||||
# try to give a helpful error message when named route generation fails
|
||||
def raise_named_route_error(options, named_route, named_route_name)
|
||||
diff = named_route.requirements.diff(options)
|
||||
unless diff.empty?
|
||||
raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
|
||||
else
|
||||
required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
|
||||
required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
|
||||
raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?"
|
||||
end
|
||||
end
|
||||
|
||||
def recognize(request)
|
||||
params = recognize_path(request.path, extract_request_environment(request))
|
||||
request.path_parameters = params.with_indifferent_access
|
||||
"#{params[:controller].camelize}Controller".constantize
|
||||
end
|
||||
|
||||
def recognize_path(path, environment={})
|
||||
raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path."
|
||||
end
|
||||
|
||||
def routes_by_controller
|
||||
@routes_by_controller ||= Hash.new do |controller_hash, controller|
|
||||
controller_hash[controller] = Hash.new do |action_hash, action|
|
||||
action_hash[action] = Hash.new do |key_hash, keys|
|
||||
key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def routes_for(options, merged, expire_on)
|
||||
raise "Need controller and action!" unless controller && action
|
||||
controller = merged[:controller]
|
||||
merged = options if expire_on[:controller]
|
||||
action = merged[:action] || 'index'
|
||||
|
||||
routes_by_controller[controller][action][merged.keys]
|
||||
end
|
||||
|
||||
def routes_for_controller_and_action(controller, action)
|
||||
selected = routes.select do |route|
|
||||
route.matches_controller_and_action? controller, action
|
||||
end
|
||||
(selected.length == routes.length) ? routes : selected
|
||||
end
|
||||
|
||||
def routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||
selected = routes.select do |route|
|
||||
route.matches_controller_and_action? controller, action
|
||||
end
|
||||
selected.sort_by do |route|
|
||||
(keys - route.significant_keys).length
|
||||
end
|
||||
end
|
||||
|
||||
# Subclasses and plugins may override this method to extract further attributes
|
||||
# from the request, for use by route conditions and such.
|
||||
def extract_request_environment(request)
|
||||
{ :method => request.method }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
46
vendor/rails/actionpack/lib/action_controller/routing/routing_ext.rb
vendored
Normal file
46
vendor/rails/actionpack/lib/action_controller/routing/routing_ext.rb
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
|
||||
class Object
|
||||
def to_param
|
||||
to_s
|
||||
end
|
||||
end
|
||||
|
||||
class TrueClass
|
||||
def to_param
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class FalseClass
|
||||
def to_param
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class NilClass
|
||||
def to_param
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Regexp #:nodoc:
|
||||
def number_of_captures
|
||||
Regexp.new("|#{source}").match('').captures.length
|
||||
end
|
||||
|
||||
class << self
|
||||
def optionalize(pattern)
|
||||
case unoptionalize(pattern)
|
||||
when /\A(.|\(.*\))\Z/ then "#{pattern}?"
|
||||
else "(?:#{pattern})?"
|
||||
end
|
||||
end
|
||||
|
||||
def unoptionalize(pattern)
|
||||
[/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp|
|
||||
return $1 if regexp =~ pattern
|
||||
end
|
||||
return pattern
|
||||
end
|
||||
end
|
||||
end
|
282
vendor/rails/actionpack/lib/action_controller/routing/segments.rb
vendored
Normal file
282
vendor/rails/actionpack/lib/action_controller/routing/segments.rb
vendored
Normal file
|
@ -0,0 +1,282 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
class Segment #:nodoc:
|
||||
RESERVED_PCHAR = ':@&=+$,;'
|
||||
UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
|
||||
|
||||
attr_accessor :is_optional
|
||||
alias_method :optional?, :is_optional
|
||||
|
||||
def initialize
|
||||
self.is_optional = false
|
||||
end
|
||||
|
||||
def extraction_code
|
||||
nil
|
||||
end
|
||||
|
||||
# Continue generating string for the prior segments.
|
||||
def continue_string_structure(prior_segments)
|
||||
if prior_segments.empty?
|
||||
interpolation_statement(prior_segments)
|
||||
else
|
||||
new_priors = prior_segments[0..-2]
|
||||
prior_segments.last.string_structure(new_priors)
|
||||
end
|
||||
end
|
||||
|
||||
def interpolation_chunk
|
||||
CGI.escape(value)
|
||||
end
|
||||
|
||||
# Return a string interpolation statement for this segment and those before it.
|
||||
def interpolation_statement(prior_segments)
|
||||
chunks = prior_segments.collect { |s| s.interpolation_chunk }
|
||||
chunks << interpolation_chunk
|
||||
"\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}"
|
||||
end
|
||||
|
||||
def string_structure(prior_segments)
|
||||
optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments)
|
||||
end
|
||||
|
||||
# Return an if condition that is true if all the prior segments can be generated.
|
||||
# If there are no optional segments before this one, then nil is returned.
|
||||
def all_optionals_available_condition(prior_segments)
|
||||
optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact
|
||||
optional_locals.empty? ? nil : " if #{optional_locals * ' && '}"
|
||||
end
|
||||
|
||||
# Recognition
|
||||
|
||||
def match_extraction(next_capture)
|
||||
nil
|
||||
end
|
||||
|
||||
# Warning
|
||||
|
||||
# Returns true if this segment is optional? because of a default. If so, then
|
||||
# no warning will be emitted regarding this segment.
|
||||
def optionality_implied?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class StaticSegment < Segment #:nodoc:
|
||||
attr_accessor :value, :raw
|
||||
alias_method :raw?, :raw
|
||||
|
||||
def initialize(value = nil)
|
||||
super()
|
||||
self.value = value
|
||||
end
|
||||
|
||||
def interpolation_chunk
|
||||
raw? ? value : super
|
||||
end
|
||||
|
||||
def regexp_chunk
|
||||
chunk = Regexp.escape(value)
|
||||
optional? ? Regexp.optionalize(chunk) : chunk
|
||||
end
|
||||
|
||||
def build_pattern(pattern)
|
||||
escaped = Regexp.escape(value)
|
||||
if optional? && ! pattern.empty?
|
||||
"(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})"
|
||||
elsif optional?
|
||||
Regexp.optionalize escaped
|
||||
else
|
||||
escaped + pattern
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
class DividerSegment < StaticSegment #:nodoc:
|
||||
def initialize(value = nil)
|
||||
super(value)
|
||||
self.raw = true
|
||||
self.is_optional = true
|
||||
end
|
||||
|
||||
def optionality_implied?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class DynamicSegment < Segment #:nodoc:
|
||||
attr_accessor :key, :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)
|
||||
end
|
||||
|
||||
def to_s
|
||||
":#{key}"
|
||||
end
|
||||
|
||||
# The local variable name that the value of this segment will be extracted to.
|
||||
def local_name
|
||||
"#{key}_value"
|
||||
end
|
||||
|
||||
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
|
||||
elsif optional?
|
||||
# If we have a regexp check that the value is not given, or that it matches.
|
||||
# If we have no regexp, return nil since we do not require a condition.
|
||||
"#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp
|
||||
else # Then it must be present, and if we have a regexp, it must match too.
|
||||
"#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
|
||||
end
|
||||
end
|
||||
def expiry_statement
|
||||
"expired, hash = true, options if !expired && expire_on[:#{key}]"
|
||||
end
|
||||
|
||||
def extraction_code
|
||||
s = extract_value
|
||||
vc = value_check
|
||||
s << "\nreturn [nil,nil] unless #{vc}" if vc
|
||||
s << "\n#{expiry_statement}"
|
||||
end
|
||||
|
||||
def interpolation_chunk(value_code = "#{local_name}")
|
||||
"\#{CGI.escape(#{value_code}.to_s)}"
|
||||
end
|
||||
|
||||
def string_structure(prior_segments)
|
||||
if optional? # We have a conditional to do...
|
||||
# If we should not appear in the url, just write the code for the prior
|
||||
# segments. This occurs if our value is the default value, or, if we are
|
||||
# optional, if we have nil as our value.
|
||||
"if #{local_name} == #{default.inspect}\n" +
|
||||
continue_string_structure(prior_segments) +
|
||||
"\nelse\n" + # Otherwise, write the code up to here
|
||||
"#{interpolation_statement(prior_segments)}\nend"
|
||||
else
|
||||
interpolation_statement(prior_segments)
|
||||
end
|
||||
end
|
||||
|
||||
def value_regexp
|
||||
Regexp.new "\\A#{regexp.to_s}\\Z" if regexp
|
||||
end
|
||||
|
||||
def regexp_chunk
|
||||
if regexp
|
||||
if regexp_has_modifiers?
|
||||
"(#{regexp.to_s})"
|
||||
else
|
||||
"(#{regexp.source})"
|
||||
end
|
||||
else
|
||||
"([^#{Routing::SEPARATORS.join}]+)"
|
||||
end
|
||||
end
|
||||
|
||||
def build_pattern(pattern)
|
||||
chunk = regexp_chunk
|
||||
chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0
|
||||
pattern = "#{chunk}#{pattern}"
|
||||
optional? ? Regexp.optionalize(pattern) : pattern
|
||||
end
|
||||
|
||||
def match_extraction(next_capture)
|
||||
# All non code-related keys (such as :id, :slug) are URI-unescaped as
|
||||
# path parameters.
|
||||
default_value = default ? default.inspect : nil
|
||||
%[
|
||||
value = if (m = match[#{next_capture}])
|
||||
CGI.unescape(m)
|
||||
else
|
||||
#{default_value}
|
||||
end
|
||||
params[:#{key}] = value if value
|
||||
]
|
||||
end
|
||||
|
||||
def optionality_implied?
|
||||
[:action, :id].include? key
|
||||
end
|
||||
|
||||
def regexp_has_modifiers?
|
||||
regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ControllerSegment < DynamicSegment #:nodoc:
|
||||
def regexp_chunk
|
||||
possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name }
|
||||
"(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
|
||||
end
|
||||
|
||||
# Don't URI.escape the controller name since it may contain slashes.
|
||||
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
|
||||
"#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase"
|
||||
end
|
||||
|
||||
def match_extraction(next_capture)
|
||||
if default
|
||||
"params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'"
|
||||
else
|
||||
"params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class PathSegment < DynamicSegment #:nodoc:
|
||||
def interpolation_chunk(value_code = "#{local_name}")
|
||||
"\#{#{value_code}}"
|
||||
end
|
||||
|
||||
def extract_value
|
||||
"#{local_name} = hash[:#{key}] && hash[:#{key}].collect { |path_component| CGI.escape(path_component) }.to_param #{"|| #{default.inspect}" if default}"
|
||||
end
|
||||
|
||||
def default
|
||||
''
|
||||
end
|
||||
|
||||
def default=(path)
|
||||
raise RoutingError, "paths cannot have non-empty default values" unless path.blank?
|
||||
end
|
||||
|
||||
def match_extraction(next_capture)
|
||||
"params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
|
||||
end
|
||||
|
||||
def regexp_chunk
|
||||
regexp || "(.*)"
|
||||
end
|
||||
|
||||
def optionality_implied?
|
||||
true
|
||||
end
|
||||
|
||||
class Result < ::Array #:nodoc:
|
||||
def to_s() join '/' end
|
||||
def self.new_escaped(strings)
|
||||
new strings.collect {|str| CGI.unescape str}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,6 @@
|
|||
require 'cgi'
|
||||
require 'cgi/session'
|
||||
require 'digest/md5'
|
||||
require 'base64'
|
||||
|
||||
class CGI
|
||||
class Session
|
||||
|
@ -14,7 +13,7 @@ class CGI
|
|||
|
||||
|
||||
# A session store backed by an Active Record class. A default class is
|
||||
# provided, but any object duck-typing to an Active Record +Session+ class
|
||||
# provided, but any object duck-typing to an Active Record Session class
|
||||
# with text +session_id+ and +data+ attributes is sufficient.
|
||||
#
|
||||
# The default assumes a +sessions+ tables with columns:
|
||||
|
@ -27,13 +26,13 @@ class CGI
|
|||
# ActionController::SessionOverflowError will be raised.
|
||||
#
|
||||
# You may configure the table name, primary key, and data column.
|
||||
# For example, at the end of config/environment.rb:
|
||||
# For example, at the end of <tt>config/environment.rb</tt>:
|
||||
# CGI::Session::ActiveRecordStore::Session.table_name = 'legacy_session_table'
|
||||
# CGI::Session::ActiveRecordStore::Session.primary_key = 'session_id'
|
||||
# CGI::Session::ActiveRecordStore::Session.data_column_name = 'legacy_session_data'
|
||||
# Note that setting the primary key to the session_id frees you from
|
||||
# having a separate id column if you don't want it. However, you must
|
||||
# set session.model.id = session.session_id by hand! A before_filter
|
||||
# Note that setting the primary key to the +session_id+ frees you from
|
||||
# having a separate +id+ column if you don't want it. However, you must
|
||||
# set <tt>session.model.id = session.session_id</tt> by hand! A before filter
|
||||
# on ApplicationController is a good place.
|
||||
#
|
||||
# Since the default class is a simple Active Record, you get timestamps
|
||||
|
@ -43,7 +42,7 @@ class CGI
|
|||
# You may provide your own session class implementation, whether a
|
||||
# feature-packed Active Record or a bare-metal high-performance SQL
|
||||
# store, by setting
|
||||
# +CGI::Session::ActiveRecordStore.session_class = MySessionClass+
|
||||
# CGI::Session::ActiveRecordStore.session_class = MySessionClass
|
||||
# You must implement these methods:
|
||||
# self.find_by_session_id(session_id)
|
||||
# initialize(hash_of_session_id_and_data)
|
||||
|
@ -80,8 +79,8 @@ class CGI
|
|||
find_by_session_id(session_id)
|
||||
end
|
||||
|
||||
def marshal(data) Base64.encode64(Marshal.dump(data)) if data end
|
||||
def unmarshal(data) Marshal.load(Base64.decode64(data)) if data end
|
||||
def marshal(data) ActiveSupport::Base64.encode64(Marshal.dump(data)) if data end
|
||||
def unmarshal(data) Marshal.load(ActiveSupport::Base64.decode64(data)) if data end
|
||||
|
||||
def create_table!
|
||||
connection.execute <<-end_sql
|
||||
|
@ -155,8 +154,13 @@ class CGI
|
|||
# The database connection, table name, and session id and data columns
|
||||
# are configurable class attributes. Marshaling and unmarshaling
|
||||
# are implemented as class methods that you may override. By default,
|
||||
# marshaling data is +Base64.encode64(Marshal.dump(data))+ and
|
||||
# unmarshaling data is +Marshal.load(Base64.decode64(data))+.
|
||||
# marshaling data is
|
||||
#
|
||||
# ActiveSupport::Base64.encode64(Marshal.dump(data))
|
||||
#
|
||||
# and unmarshaling data is
|
||||
#
|
||||
# Marshal.load(ActiveSupport::Base64.decode64(data))
|
||||
#
|
||||
# This marshaling behavior is intended to store the widest range of
|
||||
# binary session data in a +text+ column. For higher performance,
|
||||
|
@ -190,8 +194,8 @@ class CGI
|
|||
end
|
||||
end
|
||||
|
||||
def marshal(data) Base64.encode64(Marshal.dump(data)) if data end
|
||||
def unmarshal(data) Marshal.load(Base64.decode64(data)) if data end
|
||||
def marshal(data) ActiveSupport::Base64.encode64(Marshal.dump(data)) if data end
|
||||
def unmarshal(data) Marshal.load(ActiveSupport::Base64.decode64(data)) if data end
|
||||
|
||||
def create_table!
|
||||
@@connection.execute <<-end_sql
|
||||
|
@ -333,4 +337,4 @@ class CGI
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
require 'cgi'
|
||||
require 'cgi/session'
|
||||
require 'base64' # to convert Marshal.dump to ASCII
|
||||
require 'openssl' # to generate the HMAC message digest
|
||||
|
||||
# This cookie-based session store is the Rails default. Sessions typically
|
||||
|
@ -15,27 +14,27 @@ require 'openssl' # to generate the HMAC message digest
|
|||
# TamperedWithCookie is raised if the data integrity check fails.
|
||||
#
|
||||
# A message digest is included with the cookie to ensure data integrity:
|
||||
# a user cannot alter his user_id without knowing the secret key included in
|
||||
# a user cannot alter his +user_id+ without knowing the secret key included in
|
||||
# the hash. New apps are generated with a pregenerated secret in
|
||||
# config/environment.rb. Set your own for old apps you're upgrading.
|
||||
#
|
||||
# Session options:
|
||||
# :secret An application-wide key string or block returning a string
|
||||
# called per generated digest. The block is called with the
|
||||
# CGI::Session instance as an argument. It's important that the
|
||||
# secret is not vulnerable to a dictionary attack. Therefore,
|
||||
# you should choose a secret consisting of random numbers and
|
||||
# letters and more than 30 characters.
|
||||
#
|
||||
# Example: :secret => '449fe2e7daee471bffae2fd8dc02313d'
|
||||
# :secret => Proc.new { User.current_user.secret_key }
|
||||
# * <tt>:secret</tt>: An application-wide key string or block returning a string
|
||||
# called per generated digest. The block is called with the CGI::Session
|
||||
# instance as an argument. It's important that the secret is not vulnerable to
|
||||
# a dictionary attack. Therefore, you should choose a secret consisting of
|
||||
# random numbers and letters and more than 30 characters. Examples:
|
||||
#
|
||||
# :digest The message digest algorithm used to verify session integrity
|
||||
# defaults to 'SHA1' but may be any digest provided by OpenSSL,
|
||||
# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
|
||||
# :secret => '449fe2e7daee471bffae2fd8dc02313d'
|
||||
# :secret => Proc.new { User.current_user.secret_key }
|
||||
#
|
||||
# * <tt>:digest</tt>: The message digest algorithm used to verify session
|
||||
# integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
|
||||
# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
|
||||
#
|
||||
# To generate a secret key for an existing application, run
|
||||
# `rake secret` and set the key in config/environment.rb
|
||||
# `rake secret` and set the key in config/environment.rb.
|
||||
#
|
||||
# Note that changing digest or secret invalidates all existing sessions!
|
||||
class CGI::Session::CookieStore
|
||||
|
@ -118,7 +117,7 @@ class CGI::Session::CookieStore
|
|||
def delete
|
||||
@data = nil
|
||||
clear_old_cookie_value
|
||||
write_cookie('value' => '', 'expires' => 1.year.ago)
|
||||
write_cookie('value' => nil, 'expires' => 1.year.ago)
|
||||
end
|
||||
|
||||
# Generate the HMAC keyed message digest. Uses SHA1 by default.
|
||||
|
@ -130,7 +129,7 @@ class CGI::Session::CookieStore
|
|||
private
|
||||
# Marshal a session hash into safe cookie data. Include an integrity hash.
|
||||
def marshal(session)
|
||||
data = Base64.encode64(Marshal.dump(session)).chop
|
||||
data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop
|
||||
CGI.escape "#{data}--#{generate_digest(data)}"
|
||||
end
|
||||
|
||||
|
@ -142,7 +141,7 @@ class CGI::Session::CookieStore
|
|||
delete
|
||||
raise TamperedWithCookie
|
||||
end
|
||||
Marshal.load(Base64.decode64(data))
|
||||
Marshal.load(ActiveSupport::Base64.decode64(data))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -16,9 +16,11 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
module ClassMethods
|
||||
# Set the session store to be used for keeping the session data between requests. By default, sessions are stored
|
||||
# in browser cookies (:cookie_store), but you can also specify one of the other included stores
|
||||
# (:active_record_store, :p_store, drb_store, :mem_cache_store, or :memory_store) or your own custom class.
|
||||
# Set the session store to be used for keeping the session data between requests.
|
||||
# By default, sessions are stored in browser cookies (<tt>:cookie_store</tt>),
|
||||
# but you can also specify one of the other included stores (<tt>:active_record_store</tt>,
|
||||
# <tt>:p_store</tt>, <tt>:drb_store</tt>, <tt>:mem_cache_store</tt>, or
|
||||
# <tt>:memory_store</tt>) or your own custom class.
|
||||
def session_store=(store)
|
||||
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager] =
|
||||
store.is_a?(Symbol) ? CGI::Session.const_get(store == :drb_store ? "DRbStore" : store.to_s.camelize) : store
|
||||
|
@ -67,11 +69,16 @@ module ActionController #:nodoc:
|
|||
# session :off,
|
||||
# :if => Proc.new { |req| !(req.format.html? || req.format.js?) }
|
||||
#
|
||||
# # turn the session back on, useful when it was turned off in the
|
||||
# # application controller, and you need it on in another controller
|
||||
# session :on
|
||||
#
|
||||
# All session options described for ActionController::Base.process_cgi
|
||||
# are valid arguments.
|
||||
def session(*args)
|
||||
options = args.extract_options!
|
||||
|
||||
options[:disabled] = false if args.delete(:on)
|
||||
options[:disabled] = true if !args.empty?
|
||||
options[:only] = [*options[:only]].map { |o| o.to_s } if options[:only]
|
||||
options[:except] = [*options[:except]].map { |o| o.to_s } if options[:except]
|
||||
|
|
|
@ -4,34 +4,37 @@ module ActionController #:nodoc:
|
|||
DEFAULT_SEND_FILE_OPTIONS = {
|
||||
:type => 'application/octet-stream'.freeze,
|
||||
:disposition => 'attachment'.freeze,
|
||||
:stream => true,
|
||||
:buffer_size => 4096
|
||||
:stream => true,
|
||||
:buffer_size => 4096,
|
||||
:x_sendfile => false
|
||||
}.freeze
|
||||
|
||||
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.
|
||||
#
|
||||
# Be careful to sanitize the path parameter if it coming from a web
|
||||
# page. send_file(params[:path]) allows a malicious user to
|
||||
# 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 File.basename(path).
|
||||
# Defaults to <tt>File.basename(path)</tt>.
|
||||
# * <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.
|
||||
# * <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)
|
||||
# or to read the entire file before sending (false). Defaults to true.
|
||||
# * <tt>:stream</tt> - whether to send the file to the user agent as it is read (+true+)
|
||||
# or to read the entire file before sending (+false+). Defaults to +true+.
|
||||
# * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
|
||||
# Defaults to 4096.
|
||||
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
||||
# * <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 :filename overrides this option).
|
||||
# * <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).
|
||||
#
|
||||
# The default Content-Type and Content-Disposition headers are
|
||||
# set to download arbitrary binary files in as many browsers as
|
||||
|
@ -39,17 +42,20 @@ module ActionController #:nodoc:
|
|||
# a variety of quirks (especially when downloading over SSL).
|
||||
#
|
||||
# Simple download:
|
||||
#
|
||||
# send_file '/path/to.zip'
|
||||
#
|
||||
# Show a JPEG in the browser:
|
||||
#
|
||||
# send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
|
||||
#
|
||||
# Show a 404 page in the browser:
|
||||
#
|
||||
# send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404
|
||||
#
|
||||
# Read about the other Content-* HTTP headers if you'd like to
|
||||
# provide the user with more information (such as Content-Description).
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
|
||||
# provide the user with more information (such as Content-Description) in
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11.
|
||||
#
|
||||
# Also be aware that the document may be cached by proxies and browsers.
|
||||
# The Pragma and Cache-Control headers declare how the file may be cached
|
||||
|
@ -67,19 +73,24 @@ module ActionController #:nodoc:
|
|||
|
||||
@performed_render = false
|
||||
|
||||
if options[:stream]
|
||||
render :status => options[:status], :text => Proc.new { |response, output|
|
||||
logger.info "Streaming file #{path}" unless logger.nil?
|
||||
len = options[:buffer_size] || 4096
|
||||
File.open(path, 'rb') do |file|
|
||||
while buf = file.read(len)
|
||||
output.write(buf)
|
||||
end
|
||||
end
|
||||
}
|
||||
if options[:x_sendfile]
|
||||
logger.info "Sending #{X_SENDFILE_HEADER} header #{path}" if logger
|
||||
head options[:status], X_SENDFILE_HEADER => path
|
||||
else
|
||||
logger.info "Sending file #{path}" unless logger.nil?
|
||||
File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read }
|
||||
if options[:stream]
|
||||
render :status => options[:status], :text => Proc.new { |response, output|
|
||||
logger.info "Streaming file #{path}" unless logger.nil?
|
||||
len = options[:buffer_size] || 4096
|
||||
File.open(path, 'rb') do |file|
|
||||
while buf = file.read(len)
|
||||
output.write(buf)
|
||||
end
|
||||
end
|
||||
}
|
||||
else
|
||||
logger.info "Sending file #{path}" unless logger.nil?
|
||||
File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -87,25 +98,28 @@ module ActionController #:nodoc:
|
|||
# and specify whether to show data inline or download as an attachment.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:filename</tt> - Suggests a filename for the browser to use.
|
||||
# * <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>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
||||
# * <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'.
|
||||
#
|
||||
# Generic data download:
|
||||
#
|
||||
# send_data buffer
|
||||
#
|
||||
# Download a dynamically-generated tarball:
|
||||
#
|
||||
# send_data generate_tgz('dir'), :filename => 'dir.tgz'
|
||||
#
|
||||
# Display an image Active Record in the browser:
|
||||
#
|
||||
# send_data image.data, :type => image.content_type, :disposition => 'inline'
|
||||
#
|
||||
# See +send_file+ for more information on HTTP Content-* headers and caching.
|
||||
def send_data(data, options = {}) #:doc:
|
||||
logger.info "Sending data #{options[:filename]}" unless logger.nil?
|
||||
logger.info "Sending data #{options[:filename]}" if logger
|
||||
send_file_headers! options.merge(:length => data.size)
|
||||
@performed_render = false
|
||||
render :status => options[:status], :text => data
|
||||
|
@ -130,10 +144,10 @@ module ActionController #:nodoc:
|
|||
)
|
||||
|
||||
# Fix a problem with IE 6.0 on opening downloaded files:
|
||||
# If Cache-Control: no-cache is set (which Rails does by default),
|
||||
# IE removes the file it just downloaded from its cache immediately
|
||||
# after it displays the "open/save" dialog, which means that if you
|
||||
# hit "open" the file isn't there anymore when the application that
|
||||
# If Cache-Control: no-cache is set (which Rails does by default),
|
||||
# IE removes the file it just downloaded from its cache immediately
|
||||
# after it displays the "open/save" dialog, which means that if you
|
||||
# hit "open" the file isn't there anymore when the application that
|
||||
# is called for handling the download is run, so let's workaround that
|
||||
headers['Cache-Control'] = 'private' if headers['Cache-Control'] == 'no-cache'
|
||||
end
|
||||
|
|
|
@ -10,17 +10,17 @@
|
|||
<p><code>RAILS_ROOT: <%= defined?(RAILS_ROOT) ? RAILS_ROOT : "unset" %></code></p>
|
||||
|
||||
<div id="traces">
|
||||
<% names.each do |name| -%>
|
||||
<% names.each do |name| %>
|
||||
<%
|
||||
show = "document.getElementById('#{name.gsub /\s/, '-'}').style.display='block';"
|
||||
hide = (names - [name]).collect {|hide_name| "document.getElementById('#{hide_name.gsub /\s/, '-'}').style.display='none';"}
|
||||
%>
|
||||
<a href="#" onclick="<%= hide %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
|
||||
<% end -%>
|
||||
<% end %>
|
||||
|
||||
<% traces.each do |name, trace| -%>
|
||||
<% traces.each do |name, trace| %>
|
||||
<div id="<%= name.gsub /\s/, '-' %>" style="display: <%= name == "Application Trace" ? 'block' : 'none' %>;">
|
||||
<pre><code><%= trace.join "\n" %></code></pre>
|
||||
</div>
|
||||
<% end -%>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -3,14 +3,43 @@ require 'active_support/test_case'
|
|||
module ActionController
|
||||
class NonInferrableControllerError < ActionControllerError
|
||||
def initialize(name)
|
||||
@name = name
|
||||
super "Unable to determine the controller to test from #{name}. " +
|
||||
"You'll need to specify it using 'tests YourController' in your " +
|
||||
"test case definition"
|
||||
"test case definition. This could mean that #{inferred_controller_name} does not exist " +
|
||||
"or it contains syntax errors"
|
||||
end
|
||||
|
||||
def inferred_controller_name
|
||||
@name.sub(/Test$/, '')
|
||||
end
|
||||
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
|
||||
# rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
|
||||
# than 0.0.0.0.
|
||||
#
|
||||
# The exception is stored in the exception accessor for further inspection.
|
||||
module RaiseActionExceptions
|
||||
attr_accessor :exception
|
||||
|
||||
def rescue_action(e)
|
||||
self.exception = e
|
||||
|
||||
if request.remote_addr == "0.0.0.0"
|
||||
raise(e)
|
||||
else
|
||||
super(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
setup :setup_controller_request_and_response
|
||||
|
||||
@@controller_class = nil
|
||||
|
||||
class << self
|
||||
def tests(controller_class)
|
||||
self.controller_class = controller_class
|
||||
|
@ -25,7 +54,7 @@ module ActionController
|
|||
if current_controller_class = read_inheritable_attribute(:controller_class)
|
||||
current_controller_class
|
||||
else
|
||||
self.controller_class= determine_default_controller_class(name)
|
||||
self.controller_class = determine_default_controller_class(name)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -36,18 +65,19 @@ module ActionController
|
|||
end
|
||||
|
||||
def prepare_controller_class(new_class)
|
||||
new_class.class_eval do
|
||||
def rescue_action(e)
|
||||
raise e
|
||||
end
|
||||
end
|
||||
new_class.send :include, RaiseActionExceptions
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
def setup_controller_request_and_response
|
||||
@controller = self.class.controller_class.new
|
||||
@request = TestRequest.new
|
||||
@response = TestResponse.new
|
||||
@controller.request = @request = TestRequest.new
|
||||
@response = TestResponse.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
|
||||
def rescue_action_in_public!
|
||||
@request.remote_addr = '208.77.188.166' # example.com
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'action_controller/assertions'
|
||||
require 'action_controller/test_case'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class Base
|
||||
|
@ -154,12 +155,12 @@ module ActionController #:nodoc:
|
|||
# A refactoring of TestResponse to allow the same behavior to be applied
|
||||
# to the "real" CgiResponse class in integration tests.
|
||||
module TestResponseBehavior #:nodoc:
|
||||
# the response code of the request
|
||||
# The response code of the request
|
||||
def response_code
|
||||
headers['Status'][0,3].to_i rescue 0
|
||||
end
|
||||
|
||||
# returns a String to ensure compatibility with Net::HTTPResponse
|
||||
# Returns a String to ensure compatibility with Net::HTTPResponse
|
||||
def code
|
||||
headers['Status'].to_s.split(' ')[0]
|
||||
end
|
||||
|
@ -168,34 +169,34 @@ module ActionController #:nodoc:
|
|||
headers['Status'].to_s.split(' ',2)[1]
|
||||
end
|
||||
|
||||
# was the response successful?
|
||||
# Was the response successful?
|
||||
def success?
|
||||
response_code == 200
|
||||
end
|
||||
|
||||
# was the URL not found?
|
||||
# Was the URL not found?
|
||||
def missing?
|
||||
response_code == 404
|
||||
end
|
||||
|
||||
# were we redirected?
|
||||
# Were we redirected?
|
||||
def redirect?
|
||||
(300..399).include?(response_code)
|
||||
end
|
||||
|
||||
# was there a server-side error?
|
||||
# Was there a server-side error?
|
||||
def error?
|
||||
(500..599).include?(response_code)
|
||||
end
|
||||
|
||||
alias_method :server_error?, :error?
|
||||
|
||||
# returns the redirection location or nil
|
||||
# Returns the redirection location or nil
|
||||
def redirect_url
|
||||
headers['Location']
|
||||
end
|
||||
|
||||
# does the redirect location match this regexp pattern?
|
||||
# Does the redirect location match this regexp pattern?
|
||||
def redirect_url_match?( pattern )
|
||||
return false if redirect_url.nil?
|
||||
p = Regexp.new(pattern) if pattern.class == String
|
||||
|
@ -204,7 +205,7 @@ module ActionController #:nodoc:
|
|||
p.match(redirect_url) != nil
|
||||
end
|
||||
|
||||
# returns the template path of the file which was used to
|
||||
# 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?
|
||||
|
@ -216,50 +217,49 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# was this template rendered by a file?
|
||||
# Was this template rendered by a file?
|
||||
def rendered_with_file?
|
||||
!rendered_file.nil?
|
||||
end
|
||||
|
||||
# a shortcut to the flash (or an empty hash if no flash.. hey! that rhymes!)
|
||||
# A shortcut to the flash. Returns an empyt hash if no session flash exists.
|
||||
def flash
|
||||
session['flash'] || {}
|
||||
end
|
||||
|
||||
# do we have a flash?
|
||||
# Do we have a flash?
|
||||
def has_flash?
|
||||
!session['flash'].empty?
|
||||
end
|
||||
|
||||
# do we have a flash that has contents?
|
||||
# Do we have a flash that has contents?
|
||||
def has_flash_with_contents?
|
||||
!flash.empty?
|
||||
end
|
||||
|
||||
# does the specified flash object exist?
|
||||
# Does the specified flash object exist?
|
||||
def has_flash_object?(name=nil)
|
||||
!flash[name].nil?
|
||||
end
|
||||
|
||||
# does the specified object exist in the session?
|
||||
# Does the specified object exist in the session?
|
||||
def has_session_object?(name=nil)
|
||||
!session[name].nil?
|
||||
end
|
||||
|
||||
# a shortcut to the template.assigns
|
||||
# A shortcut to the template.assigns
|
||||
def template_objects
|
||||
template.assigns || {}
|
||||
end
|
||||
|
||||
# does the specified template object exist?
|
||||
# Does the specified template object exist?
|
||||
def has_template_object?(name=nil)
|
||||
!template_objects[name].nil?
|
||||
end
|
||||
|
||||
# Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs
|
||||
# Example:
|
||||
#
|
||||
# assert_equal ['AuthorOfNewPage'], r.cookies['author'].value
|
||||
# assert_equal ['AuthorOfNewPage'], r.cookies['author'].value
|
||||
def cookies
|
||||
headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash }
|
||||
end
|
||||
|
@ -286,7 +286,7 @@ module ActionController #:nodoc:
|
|||
|
||||
def initialize(attributes = nil)
|
||||
@session_id = ''
|
||||
@attributes = attributes
|
||||
@attributes = attributes.nil? ? nil : attributes.stringify_keys
|
||||
@saved_attributes = nil
|
||||
end
|
||||
|
||||
|
@ -295,11 +295,11 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def [](key)
|
||||
data[key]
|
||||
data[key.to_s]
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
data[key] = value
|
||||
data[key.to_s] = value
|
||||
end
|
||||
|
||||
def update
|
||||
|
@ -373,7 +373,7 @@ module ActionController #:nodoc:
|
|||
# Sanity check for required instance variables so we can give an
|
||||
# understandable error message.
|
||||
%w(@controller @request @response).each do |iv_name|
|
||||
if !(instance_variables.include?(iv_name) || instance_variables.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
|
||||
if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
|
||||
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
|
||||
end
|
||||
end
|
||||
|
@ -464,10 +464,13 @@ module ActionController #:nodoc:
|
|||
return super
|
||||
end
|
||||
|
||||
# Shortcut for ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type). Example:
|
||||
# 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')
|
||||
#
|
||||
# To upload binary files on Windows, pass :binary as the last parameter. This will not affect other platforms.
|
||||
# To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
|
||||
# This will not affect other platforms:
|
||||
#
|
||||
# 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(
|
||||
|
@ -482,17 +485,17 @@ module ActionController #:nodoc:
|
|||
# with a new RouteSet instance.
|
||||
#
|
||||
# The new instance is yielded to the passed block. Typically the block
|
||||
# will create some routes using map.draw { map.connect ... }:
|
||||
# will create some routes using <tt>map.draw { map.connect ... }</tt>:
|
||||
#
|
||||
# with_routing do |set|
|
||||
# set.draw do |map|
|
||||
# map.connect ':controller/:action/:id'
|
||||
# assert_equal(
|
||||
# ['/content/10/show', {}],
|
||||
# map.generate(:controller => 'content', :id => 10, :action => 'show')
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# with_routing do |set|
|
||||
# set.draw do |map|
|
||||
# map.connect ':controller/:action/:id'
|
||||
# assert_equal(
|
||||
# ['/content/10/show', {}],
|
||||
# map.generate(:controller => 'content', :id => 10, :action => 'show')
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def with_routing
|
||||
real_routes = ActionController::Routing::Routes
|
||||
|
|
|
@ -1,82 +1,89 @@
|
|||
module ActionController
|
||||
module ActionController
|
||||
# Write URLs from arbitrary places in your codebase, such as your mailers.
|
||||
#
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
#
|
||||
# class MyMailer
|
||||
# include ActionController::UrlWriter
|
||||
# default_url_options[:host] = 'www.basecamphq.com'
|
||||
#
|
||||
#
|
||||
# def signup_url(token)
|
||||
# url_for(:controller => 'signup', action => 'index', :token => token)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# In addition to providing +url_for+, named routes are also accessible after
|
||||
# including UrlWriter.
|
||||
module UrlWriter
|
||||
# The default options for urls written by this writer. Typically a :host pair
|
||||
# is provided.
|
||||
# The default options for urls written by this writer. Typically a <tt>:host</tt>
|
||||
# pair is provided.
|
||||
mattr_accessor :default_url_options
|
||||
self.default_url_options = {}
|
||||
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
ActionController::Routing::Routes.install_helpers base
|
||||
ActionController::Routing::Routes.install_helpers(base)
|
||||
base.mattr_accessor :default_url_options
|
||||
base.default_url_options ||= default_url_options
|
||||
end
|
||||
|
||||
# Generate a url based on the options provided, default_url_options and the
|
||||
|
||||
# Generate a url based on the options provided, default_url_options and the
|
||||
# routes defined in routes.rb. The following options are supported:
|
||||
#
|
||||
# * <tt>:only_path</tt> If true, the relative url is returned. Defaults to false.
|
||||
# * <tt>:protocol</tt> The protocol to connect to. Defaults to 'http'.
|
||||
# * <tt>:host</tt> Specifies the host the link should be targetted at. If <tt>:only_path</tt> is false, this option must be
|
||||
# provided either explicitly, or via default_url_options.
|
||||
# * <tt>:port</tt> Optionally specify the port to connect to.
|
||||
# * <tt>:anchor</tt> An anchor name to be appended to the path.
|
||||
#
|
||||
# Any other key(:controller, :action, etc...) given to <tt>url_for</tt> is forwarded to the Routes module.
|
||||
#
|
||||
#
|
||||
# * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
|
||||
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
|
||||
# * <tt>:host</tt> - Specifies the host the link should be targetted at.
|
||||
# If <tt>:only_path</tt> is false, this option must be
|
||||
# provided either explicitly, or via +default_url_options+.
|
||||
# * <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.
|
||||
# * <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
|
||||
# +url_for+ is forwarded to the Routes module.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
#
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
|
||||
#
|
||||
def url_for(options)
|
||||
options = self.class.default_url_options.merge(options)
|
||||
|
||||
|
||||
url = ''
|
||||
|
||||
unless options.delete :only_path
|
||||
unless options.delete(:only_path)
|
||||
url << (options.delete(:protocol) || 'http')
|
||||
url << '://' unless url.match("://") #dont add separator if its already been specified in :protocol
|
||||
|
||||
url << '://' unless url.match("://")
|
||||
|
||||
raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
|
||||
|
||||
url << options.delete(:host)
|
||||
url << ":#{options.delete(:port)}" if options.key?(:port)
|
||||
else
|
||||
# Delete the unused options to prevent their appearance in the query string
|
||||
[:protocol, :host, :port].each { |k| options.delete k }
|
||||
# Delete the unused options to prevent their appearance in the query string.
|
||||
[:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
|
||||
end
|
||||
|
||||
anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options.key?(:anchor)
|
||||
url << Routing::Routes.generate(options, {})
|
||||
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]
|
||||
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)
|
||||
url << anchor if anchor
|
||||
|
||||
return url
|
||||
end
|
||||
url
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
|
||||
class UrlRewriter #:nodoc:
|
||||
RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root]
|
||||
def initialize(request, parameters)
|
||||
@request, @parameters = request, parameters
|
||||
end
|
||||
|
||||
|
||||
def rewrite(options = {})
|
||||
rewrite_url(options)
|
||||
end
|
||||
|
@ -123,7 +130,7 @@ module ActionController
|
|||
# Generates the query string, too
|
||||
Routing::Routes.generate(options, @request.symbolized_path_parameters)
|
||||
end
|
||||
|
||||
|
||||
def rewrite_authentication(options)
|
||||
if options[:user] && options[:password]
|
||||
"#{CGI.escape(options.delete(:user))}:#{CGI.escape(options.delete(:password))}@"
|
||||
|
|
|
@ -107,7 +107,7 @@ module HTML
|
|||
|
||||
# gauntlet
|
||||
if style !~ /^([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*$/ ||
|
||||
style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$))*$/
|
||||
style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*$/
|
||||
return ''
|
||||
end
|
||||
|
||||
|
@ -170,4 +170,4 @@ module HTML
|
|||
(value =~ /(^[^\/:]*):|(�*58)|(p)|(%|%)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -54,7 +54,7 @@ module HTML #:nodoc:
|
|||
tag << (@scanner.scan_until(/--\s*>/) || @scanner.scan_until(/\Z/))
|
||||
elsif @scanner.scan(/!\[CDATA\[/)
|
||||
tag << @scanner.matched
|
||||
tag << @scanner.scan_until(/\]\]>/)
|
||||
tag << (@scanner.scan_until(/\]\]>/) || @scanner.scan_until(/\Z/))
|
||||
elsif @scanner.scan(/!/) # doctype
|
||||
tag << @scanner.matched
|
||||
tag << consume_quoted_regions
|
||||
|
|
|
@ -43,72 +43,88 @@ module ActionController #:nodoc:
|
|||
# the user is redirected to a different action. The +options+ parameter
|
||||
# is a hash consisting of the following key/value pairs:
|
||||
#
|
||||
# * <tt>:params</tt> - a single key or an array of keys that must
|
||||
# be in the <tt>params</tt> hash in order for the action(s) to be safely
|
||||
# called.
|
||||
# * <tt>:session</tt> - a single key or an array of keys that must
|
||||
# be in the <tt>session</tt> in order for the action(s) to be safely called.
|
||||
# * <tt>:flash</tt> - a single key or an array of keys that must
|
||||
# be in the flash in order for the action(s) to be safely called.
|
||||
# * <tt>:method</tt> - a single key or an array of keys--any one of which
|
||||
# must match the current request method in order for the action(s) to
|
||||
# be safely called. (The key should be a symbol: <tt>:get</tt> or
|
||||
# <tt>:post</tt>, for example.)
|
||||
# * <tt>:xhr</tt> - true/false option to ensure that the request is coming
|
||||
# from an Ajax call or not.
|
||||
# * <tt>:add_flash</tt> - a hash of name/value pairs that should be merged
|
||||
# into the session's flash if the prerequisites cannot be satisfied.
|
||||
# * <tt>:add_headers</tt> - a hash of name/value pairs that should be
|
||||
# merged into the response's headers hash if the prerequisites cannot
|
||||
# be satisfied.
|
||||
# * <tt>:redirect_to</tt> - the redirection parameters to be used when
|
||||
# redirecting if the prerequisites cannot be satisfied. You can
|
||||
# redirect either to named route or to the action in some controller.
|
||||
# * <tt>:render</tt> - the render parameters to be used when
|
||||
# the prerequisites cannot be satisfied.
|
||||
# * <tt>:only</tt> - only apply this verification to the actions specified
|
||||
# in the associated array (may also be a single value).
|
||||
# * <tt>:except</tt> - do not apply this verification to the actions
|
||||
# specified in the associated array (may also be a single value).
|
||||
# <tt>:params</tt>::
|
||||
# a single key or an array of keys that must be in the <tt>params</tt>
|
||||
# hash in order for the action(s) to be safely called.
|
||||
# <tt>:session</tt>::
|
||||
# a single key or an array of keys that must be in the <tt>session</tt>
|
||||
# in order for the action(s) to be safely called.
|
||||
# <tt>:flash</tt>::
|
||||
# a single key or an array of keys that must be in the flash in order
|
||||
# for the action(s) to be safely called.
|
||||
# <tt>:method</tt>::
|
||||
# a single key or an array of keys--any one of which must match the
|
||||
# current request method in order for the action(s) to be safely called.
|
||||
# (The key should be a symbol: <tt>:get</tt> or <tt>:post</tt>, for
|
||||
# example.)
|
||||
# <tt>:xhr</tt>::
|
||||
# true/false option to ensure that the request is coming from an Ajax
|
||||
# call or not.
|
||||
# <tt>:add_flash</tt>::
|
||||
# a hash of name/value pairs that should be merged into the session's
|
||||
# flash if the prerequisites cannot be satisfied.
|
||||
# <tt>:add_headers</tt>::
|
||||
# a hash of name/value pairs that should be merged into the response's
|
||||
# headers hash if the prerequisites cannot be satisfied.
|
||||
# <tt>:redirect_to</tt>::
|
||||
# the redirection parameters to be used when redirecting if the
|
||||
# prerequisites cannot be satisfied. You can redirect either to named
|
||||
# route or to the action in some controller.
|
||||
# <tt>:render</tt>::
|
||||
# the render parameters to be used when the prerequisites cannot be satisfied.
|
||||
# <tt>:only</tt>::
|
||||
# only apply this verification to the actions specified in the associated
|
||||
# array (may also be a single value).
|
||||
# <tt>:except</tt>::
|
||||
# do not apply this verification to the actions specified in the associated
|
||||
# array (may also be a single value).
|
||||
def verify(options={})
|
||||
filter_opts = { :only => options[:only], :except => options[:except] }
|
||||
before_filter(filter_opts) do |c|
|
||||
before_filter :only => options[:only], :except => options[:except] do |c|
|
||||
c.send! :verify_action, options
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def verify_action(options) #:nodoc:
|
||||
prereqs_invalid =
|
||||
[*options[:params] ].find { |v| params[v].nil? } ||
|
||||
[*options[:session]].find { |v| session[v].nil? } ||
|
||||
[*options[:flash] ].find { |v| flash[v].nil? }
|
||||
|
||||
if !prereqs_invalid && options[:method]
|
||||
prereqs_invalid ||=
|
||||
[*options[:method]].all? { |v| request.method != v.to_sym }
|
||||
end
|
||||
|
||||
prereqs_invalid ||= (request.xhr? != options[:xhr]) unless options[:xhr].nil?
|
||||
|
||||
if prereqs_invalid
|
||||
flash.update(options[:add_flash]) if options[:add_flash]
|
||||
response.headers.update(options[:add_headers]) if options[:add_headers]
|
||||
private
|
||||
|
||||
unless performed?
|
||||
case
|
||||
when options[:render]
|
||||
render(options[:render])
|
||||
when options[:redirect_to]
|
||||
options[:redirect_to] = self.send!(options[:redirect_to]) if options[:redirect_to].is_a?(Symbol)
|
||||
redirect_to(options[:redirect_to])
|
||||
else
|
||||
head(:bad_request)
|
||||
end
|
||||
end
|
||||
def verify_action(options) #:nodoc:
|
||||
if prereqs_invalid?(options)
|
||||
flash.update(options[:add_flash]) if options[:add_flash]
|
||||
response.headers.update(options[:add_headers]) if options[:add_headers]
|
||||
apply_remaining_actions(options) unless performed?
|
||||
end
|
||||
end
|
||||
|
||||
def prereqs_invalid?(options) # :nodoc:
|
||||
verify_presence_of_keys_in_hash_flash_or_params(options) ||
|
||||
verify_method(options) ||
|
||||
verify_request_xhr_status(options)
|
||||
end
|
||||
|
||||
def verify_presence_of_keys_in_hash_flash_or_params(options) # :nodoc:
|
||||
[*options[:params] ].find { |v| params[v].nil? } ||
|
||||
[*options[:session]].find { |v| session[v].nil? } ||
|
||||
[*options[:flash] ].find { |v| flash[v].nil? }
|
||||
end
|
||||
|
||||
def verify_method(options) # :nodoc:
|
||||
[*options[:method]].all? { |v| request.method != v.to_sym } if options[:method]
|
||||
end
|
||||
|
||||
def verify_request_xhr_status(options) # :nodoc:
|
||||
request.xhr? != options[:xhr] unless options[:xhr].nil?
|
||||
end
|
||||
|
||||
def apply_redirect_to(redirect_to_option) # :nodoc:
|
||||
redirect_to_option.is_a?(Symbol) ? self.send!(redirect_to_option) : redirect_to_option
|
||||
end
|
||||
|
||||
def apply_remaining_actions(options) # :nodoc:
|
||||
case
|
||||
when options[:render] ; render(options[:render])
|
||||
when options[:redirect_to] ; redirect_to(apply_redirect_to(options[:redirect_to]))
|
||||
else head(:bad_request)
|
||||
end
|
||||
end
|
||||
|
||||
private :verify_action
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue