TeX and CSS tweaks.
Sync with latest Instiki Trunk (Updates Rails to 1.2.2)
This commit is contained in:
parent
0ac586ee25
commit
c358389f25
443 changed files with 24218 additions and 9823 deletions
|
@ -1,320 +1,82 @@
|
|||
require 'test/unit'
|
||||
require 'test/unit/assertions'
|
||||
require 'rexml/document'
|
||||
require File.dirname(__FILE__) + "/vendor/html-scanner/html/document"
|
||||
|
||||
module Test #:nodoc:
|
||||
module Unit #:nodoc:
|
||||
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
|
||||
# can be used against. These collections are:
|
||||
#
|
||||
# * assigns: Instance variables assigned in the action that are available for the view.
|
||||
# * session: Objects being saved in the session.
|
||||
# * flash: The flash objects currently in the session.
|
||||
# * cookies: Cookies being sent to the user on this request.
|
||||
#
|
||||
# These collections can be used just like any other hash:
|
||||
#
|
||||
# assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
|
||||
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
|
||||
# assert flash.empty? # makes sure that there's nothing in the flash
|
||||
#
|
||||
# For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
|
||||
# appease our yearning for symbols, though, an alternative accessor has been deviced using a method call instead of index referencing.
|
||||
# So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
|
||||
#
|
||||
# On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
|
||||
#
|
||||
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
|
||||
# action call which can then be asserted against.
|
||||
#
|
||||
# == Manipulating the request collections
|
||||
#
|
||||
# The collections described above link to the response, so you can test if what the actions were expected to do happened. But
|
||||
# sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
|
||||
# and cookies, though. For sessions, you just do:
|
||||
#
|
||||
# @request.session[:key] = "value"
|
||||
#
|
||||
# For cookies, you need to manually create the cookie, like this:
|
||||
#
|
||||
# @request.cookies["key"] = CGI::Cookie.new("key", "value")
|
||||
#
|
||||
# == Testing named routes
|
||||
#
|
||||
# If you're using named routes, they can be easily tested using the original named routes methods straight in the test case.
|
||||
# Example:
|
||||
#
|
||||
# assert_redirected_to page_url(:title => 'foo')
|
||||
module Assertions
|
||||
# Asserts that the response is one of the following types:
|
||||
#
|
||||
# * <tt>:success</tt>: Status code was 200
|
||||
# * <tt>:redirect</tt>: Status code was in the 300-399 range
|
||||
# * <tt>:missing</tt>: Status code was 404
|
||||
# * <tt>:error</tt>: Status code was in the 500-599 range
|
||||
#
|
||||
# You can also pass an explicit status code number as the type, like assert_response(501)
|
||||
def assert_response(type, message = nil)
|
||||
clean_backtrace do
|
||||
if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
|
||||
assert_block("") { true } # to count the assertion
|
||||
elsif type.is_a?(Fixnum) && @response.response_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 }
|
||||
end
|
||||
end
|
||||
module ActionController #:nodoc:
|
||||
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
|
||||
# can be used against. These collections are:
|
||||
#
|
||||
# * assigns: Instance variables assigned in the action that are available for the view.
|
||||
# * session: Objects being saved in the session.
|
||||
# * flash: The flash objects currently in the session.
|
||||
# * cookies: Cookies being sent to the user on this request.
|
||||
#
|
||||
# These collections can be used just like any other hash:
|
||||
#
|
||||
# assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
|
||||
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
|
||||
# assert flash.empty? # makes sure that there's nothing in the flash
|
||||
#
|
||||
# For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
|
||||
# appease our yearning for symbols, though, an alternative accessor has been deviced using a method call instead of index referencing.
|
||||
# So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
|
||||
#
|
||||
# On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
|
||||
#
|
||||
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
|
||||
# action call which can then be asserted against.
|
||||
#
|
||||
# == Manipulating the request collections
|
||||
#
|
||||
# The collections described above link to the response, so you can test if what the actions were expected to do happened. But
|
||||
# sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
|
||||
# and cookies, though. For sessions, you just do:
|
||||
#
|
||||
# @request.session[:key] = "value"
|
||||
#
|
||||
# For cookies, you need to manually create the cookie, like this:
|
||||
#
|
||||
# @request.cookies["key"] = CGI::Cookie.new("key", "value")
|
||||
#
|
||||
# == Testing named routes
|
||||
#
|
||||
# If you're using named routes, they can be easily tested using the original named routes methods straight in the test case.
|
||||
# Example:
|
||||
#
|
||||
# assert_redirected_to page_url(:title => 'foo')
|
||||
module Assertions
|
||||
def self.included(klass)
|
||||
klass.class_eval do
|
||||
include ActionController::Assertions::ResponseAssertions
|
||||
include ActionController::Assertions::SelectorAssertions
|
||||
include ActionController::Assertions::RoutingAssertions
|
||||
include ActionController::Assertions::TagAssertions
|
||||
include ActionController::Assertions::DomAssertions
|
||||
include ActionController::Assertions::ModelAssertions
|
||||
include ActionController::Assertions::DeprecatedAssertions
|
||||
end
|
||||
end
|
||||
|
||||
# Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial,
|
||||
# such that assert_redirected_to(:controller => "weblog") will also match the redirection of
|
||||
# redirect_to(:controller => "weblog", :action => "show") and so on.
|
||||
def assert_redirected_to(options = {}, message=nil)
|
||||
clean_backtrace do
|
||||
assert_response(:redirect, message)
|
||||
|
||||
if options.is_a?(String)
|
||||
msg = build_message(message, "expected a redirect to <?>, found one to <?>", options, @response.redirect_url)
|
||||
url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$}
|
||||
eurl, epath, url, path = [options, @response.redirect_url].collect do |url|
|
||||
u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url]
|
||||
[u, (p[0..0] == '/') ? p : '/' + p]
|
||||
end.flatten
|
||||
|
||||
assert_equal(eurl, url, msg) if eurl && url
|
||||
assert_equal(epath, path, msg) if epath && path
|
||||
else
|
||||
@response_diff = options.diff(@response.redirected_to) if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
|
||||
msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is <?>)#{', difference: <?>' if @response_diff}",
|
||||
@response.redirected_to || @response.redirect_url, @response_diff)
|
||||
|
||||
assert_block(msg) do
|
||||
if options.is_a?(Symbol)
|
||||
@response.redirected_to == options
|
||||
else
|
||||
options.keys.all? do |k|
|
||||
if k == :controller then options[k] == ActionController::Routing.controller_relative_to(@response.redirected_to[k], @controller.class.controller_path)
|
||||
else options[k] == (@response.redirected_to[k].respond_to?(:to_param) ? @response.redirected_to[k].to_param : @response.redirected_to[k] unless @response.redirected_to[k].nil?)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the request was rendered with the appropriate template file.
|
||||
def assert_template(expected = nil, message=nil)
|
||||
clean_backtrace do
|
||||
rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
|
||||
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
|
||||
assert_block(msg) do
|
||||
if expected.nil?
|
||||
!@response.rendered_with_file?
|
||||
else
|
||||
expected == rendered
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the routing of the given path was handled correctly and that the parsed options match.
|
||||
def assert_recognizes(expected_options, path, extras={}, message=nil)
|
||||
clean_backtrace do
|
||||
path = "/#{path}" unless path[0..0] == '/'
|
||||
# Load routes.rb if it hasn't been loaded.
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
|
||||
# Assume given controller
|
||||
request = ActionController::TestRequest.new({}, {}, nil)
|
||||
request.path = path
|
||||
ActionController::Routing::Routes.recognize!(request)
|
||||
|
||||
expected_options = expected_options.clone
|
||||
extras.each_key { |key| expected_options.delete key } unless extras.nil?
|
||||
|
||||
expected_options.stringify_keys!
|
||||
msg = build_message(message, "The recognized options <?> did not match <?>",
|
||||
request.path_parameters, expected_options)
|
||||
assert_block(msg) { request.path_parameters == expected_options }
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the provided options can be used to generate the provided path.
|
||||
def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
|
||||
clean_backtrace do
|
||||
expected_path = "/#{expected_path}" unless expected_path[0] == ?/
|
||||
# Load routes.rb if it hasn't been loaded.
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
|
||||
generated_path, extra_keys = ActionController::Routing::Routes.generate(options, extras)
|
||||
found_extras = options.reject {|k, v| ! extra_keys.include? k}
|
||||
|
||||
msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
|
||||
assert_block(msg) { found_extras == extras }
|
||||
|
||||
msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
|
||||
expected_path)
|
||||
assert_block(msg) { expected_path == generated_path }
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that path and options match both ways; in other words, the URL generated from
|
||||
# options is the same as path, and also that the options recognized from path are the same as options
|
||||
def assert_routing(path, options, defaults={}, extras={}, message=nil)
|
||||
assert_recognizes(options, path, extras, message)
|
||||
|
||||
controller, default_controller = options[:controller], defaults[:controller]
|
||||
if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
|
||||
options[:controller] = "/#{controller}"
|
||||
end
|
||||
|
||||
assert_generates(path, options, defaults, extras, message)
|
||||
end
|
||||
|
||||
# Asserts that there is a tag/node/element in the body of the response
|
||||
# that meets all of the given conditions. The +conditions+ parameter must
|
||||
# be a hash of any of the following keys (all are optional):
|
||||
#
|
||||
# * <tt>:tag</tt>: the node type must match the corresponding value
|
||||
# * <tt>:attributes</tt>: a hash. The node's attributes must match the
|
||||
# corresponding values in the hash.
|
||||
# * <tt>:parent</tt>: a hash. The node's parent must match the
|
||||
# corresponding hash.
|
||||
# * <tt>:child</tt>: a hash. At least one of the node's immediate children
|
||||
# must meet the criteria described by the hash.
|
||||
# * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
|
||||
# meet the criteria described by the hash.
|
||||
# * <tt>:descendant</tt>: a hash. At least one of the node's descendants
|
||||
# must meet the criteria described by the hash.
|
||||
# * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
|
||||
# meet the criteria described by the hash.
|
||||
# * <tt>:after</tt>: a hash. The node must be after any sibling meeting
|
||||
# the criteria described by the hash, and at least one sibling must match.
|
||||
# * <tt>:before</tt>: a hash. The node must be before any sibling meeting
|
||||
# the criteria described by the hash, and at least one sibling must match.
|
||||
# * <tt>:children</tt>: a hash, for counting children of a node. Accepts
|
||||
# the keys:
|
||||
# * <tt>:count</tt>: either a number or a range which must equal (or
|
||||
# include) the number of children that match.
|
||||
# * <tt>:less_than</tt>: the number of matching children must be less
|
||||
# than this number.
|
||||
# * <tt>:greater_than</tt>: the number of matching children must be
|
||||
# greater than this number.
|
||||
# * <tt>:only</tt>: another hash consisting of the keys to use
|
||||
# to match on the children, and only matching children will be
|
||||
# counted.
|
||||
# * <tt>:content</tt>: the textual content of the node must match the
|
||||
# given value. This will not match HTML tags in the body of a
|
||||
# tag--only text.
|
||||
#
|
||||
# Conditions are matched using the following algorithm:
|
||||
#
|
||||
# * if the condition is a string, it must be a substring of the value.
|
||||
# * if the condition is a regexp, it must match the value.
|
||||
# * if the condition is a number, the value must match number.to_s.
|
||||
# * if the condition is +true+, the value must not be +nil+.
|
||||
# * if the condition is +false+ or +nil+, the value must be +nil+.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# # assert that there is a "span" tag
|
||||
# assert_tag :tag => "span"
|
||||
#
|
||||
# # assert that there is a "span" tag with id="x"
|
||||
# assert_tag :tag => "span", :attributes => { :id => "x" }
|
||||
#
|
||||
# # assert that there is a "span" tag using the short-hand
|
||||
# assert_tag :span
|
||||
#
|
||||
# # assert that there is a "span" tag with id="x" using the short-hand
|
||||
# assert_tag :span, :attributes => { :id => "x" }
|
||||
#
|
||||
# # assert that there is a "span" inside of a "div"
|
||||
# assert_tag :tag => "span", :parent => { :tag => "div" }
|
||||
#
|
||||
# # assert that there is a "span" somewhere inside a table
|
||||
# assert_tag :tag => "span", :ancestor => { :tag => "table" }
|
||||
#
|
||||
# # assert that there is a "span" with at least one "em" child
|
||||
# assert_tag :tag => "span", :child => { :tag => "em" }
|
||||
#
|
||||
# # assert that there is a "span" containing a (possibly nested)
|
||||
# # "strong" tag.
|
||||
# assert_tag :tag => "span", :descendant => { :tag => "strong" }
|
||||
#
|
||||
# # assert that there is a "span" containing between 2 and 4 "em" tags
|
||||
# # as immediate children
|
||||
# assert_tag :tag => "span",
|
||||
# :children => { :count => 2..4, :only => { :tag => "em" } }
|
||||
#
|
||||
# # get funky: assert that there is a "div", with an "ul" ancestor
|
||||
# # and an "li" parent (with "class" = "enum"), and containing a
|
||||
# # "span" descendant that contains text matching /hello world/
|
||||
# assert_tag :tag => "div",
|
||||
# :ancestor => { :tag => "ul" },
|
||||
# :parent => { :tag => "li",
|
||||
# :attributes => { :class => "enum" } },
|
||||
# :descendant => { :tag => "span",
|
||||
# :child => /hello world/ }
|
||||
#
|
||||
# <strong>Please note</strong: #assert_tag and #assert_no_tag only work
|
||||
# with well-formed XHTML. They recognize a few tags as implicitly self-closing
|
||||
# (like br and hr and such) but will not work correctly with tags
|
||||
# that allow optional closing tags (p, li, td). <em>You must explicitly
|
||||
# close all of your tags to use these assertions.</em>
|
||||
def assert_tag(*opts)
|
||||
clean_backtrace do
|
||||
opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
|
||||
tag = find_tag(opts)
|
||||
assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
# Identical to #assert_tag, but asserts that a matching tag does _not_
|
||||
# exist. (See #assert_tag for a full discussion of the syntax.)
|
||||
def assert_no_tag(*opts)
|
||||
clean_backtrace do
|
||||
opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
|
||||
tag = find_tag(opts)
|
||||
assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
# test 2 html strings to be equivalent, i.e. identical up to reordering of attributes
|
||||
def assert_dom_equal(expected, actual, message="")
|
||||
clean_backtrace do
|
||||
expected_dom = HTML::Document.new(expected).root
|
||||
actual_dom = HTML::Document.new(actual).root
|
||||
full_message = build_message(message, "<?> expected to be == to\n<?>.", expected_dom.to_s, actual_dom.to_s)
|
||||
assert_block(full_message) { expected_dom == actual_dom }
|
||||
end
|
||||
end
|
||||
|
||||
# negated form of +assert_dom_equivalent+
|
||||
def assert_dom_not_equal(expected, actual, message="")
|
||||
clean_backtrace do
|
||||
expected_dom = HTML::Document.new(expected).root
|
||||
actual_dom = HTML::Document.new(actual).root
|
||||
full_message = build_message(message, "<?> expected to be != to\n<?>.", expected_dom.to_s, actual_dom.to_s)
|
||||
assert_block(full_message) { expected_dom != actual_dom }
|
||||
end
|
||||
end
|
||||
|
||||
# ensures that the passed record is valid by active record standards. returns the error messages if not
|
||||
def assert_valid(record)
|
||||
clean_backtrace do
|
||||
assert record.valid?, record.errors.full_messages.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
def clean_backtrace(&block)
|
||||
yield
|
||||
rescue AssertionFailedError => e
|
||||
path = File.expand_path(__FILE__)
|
||||
raise AssertionFailedError, e.message, e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ }
|
||||
end
|
||||
def clean_backtrace(&block)
|
||||
yield
|
||||
rescue Test::Unit::AssertionFailedError => e
|
||||
path = File.expand_path(__FILE__)
|
||||
raise Test::Unit::AssertionFailedError, e.message, e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require File.dirname(__FILE__) + '/assertions/response_assertions'
|
||||
require File.dirname(__FILE__) + '/assertions/selector_assertions'
|
||||
require File.dirname(__FILE__) + '/assertions/tag_assertions'
|
||||
require File.dirname(__FILE__) + '/assertions/dom_assertions'
|
||||
require File.dirname(__FILE__) + '/assertions/routing_assertions'
|
||||
require File.dirname(__FILE__) + '/assertions/model_assertions'
|
||||
require File.dirname(__FILE__) + '/assertions/deprecated_assertions'
|
||||
|
||||
module Test #:nodoc:
|
||||
module Unit #:nodoc:
|
||||
class TestCase #:nodoc:
|
||||
include ActionController::Assertions
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load diff
|
@ -8,11 +8,8 @@ module ActionController #:nodoc:
|
|||
base.extend(ClassMethods)
|
||||
|
||||
base.class_eval do
|
||||
alias_method :perform_action_without_benchmark, :perform_action
|
||||
alias_method :perform_action, :perform_action_with_benchmark
|
||||
|
||||
alias_method :render_without_benchmark, :render
|
||||
alias_method :render, :render_with_benchmark
|
||||
alias_method_chain :perform_action, :benchmark
|
||||
alias_method_chain :render, :benchmark
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -68,7 +65,7 @@ module ActionController #:nodoc:
|
|||
else
|
||||
runtime = [Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001].max
|
||||
log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
|
||||
log_message << rendering_runtime(runtime) if @rendering_runtime
|
||||
log_message << rendering_runtime(runtime) if defined?(@rendering_runtime)
|
||||
log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
log_message << " | #{headers["Status"]}"
|
||||
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'fileutils'
|
||||
require 'uri'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
# Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
|
||||
|
@ -117,24 +118,24 @@ module ActionController #:nodoc:
|
|||
return unless perform_caching
|
||||
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 })))
|
||||
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 })))
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true)))
|
||||
end
|
||||
end
|
||||
|
||||
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of @response.body is used
|
||||
# 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 current +options+ for this action is used. Example:
|
||||
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
|
||||
def cache_page(content = nil, options = {})
|
||||
return unless perform_caching && caching_allowed
|
||||
self.class.cache_page(content || @response.body, url_for(options.merge({ :only_path => true, :skip_relative_url_root => true })))
|
||||
self.class.cache_page(content || response.body, url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format])))
|
||||
end
|
||||
|
||||
private
|
||||
def caching_allowed
|
||||
!@request.post? && @response.headers['Status'] && @response.headers['Status'].to_i < 400
|
||||
request.get? && response.headers['Status'].to_i == 200
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -155,9 +156,12 @@ module ActionController #:nodoc:
|
|||
# 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>.
|
||||
module Actions
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.send(:attr_accessor, :rendered_action_cache)
|
||||
end
|
||||
|
@ -173,22 +177,24 @@ module ActionController #:nodoc:
|
|||
return unless perform_caching
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
expire_fragment(url_for(options.merge({ :action => action })).split("://").last)
|
||||
expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action })))
|
||||
end
|
||||
else
|
||||
expire_fragment(url_for(options).split("://").last)
|
||||
expire_fragment(ActionCachePath.path_for(self, options))
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCacheFilter #:nodoc:
|
||||
def initialize(*actions)
|
||||
def initialize(*actions, &block)
|
||||
@actions = actions
|
||||
end
|
||||
|
||||
def before(controller)
|
||||
return unless @actions.include?(controller.action_name.intern)
|
||||
if cache = controller.read_fragment(controller.url_for.split("://").last)
|
||||
action_cache_path = ActionCachePath.new(controller)
|
||||
if cache = controller.read_fragment(action_cache_path.path)
|
||||
controller.rendered_action_cache = true
|
||||
set_content_type!(action_cache_path)
|
||||
controller.send(:render_text, cache)
|
||||
false
|
||||
end
|
||||
|
@ -196,8 +202,60 @@ module ActionController #:nodoc:
|
|||
|
||||
def after(controller)
|
||||
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache
|
||||
controller.write_fragment(controller.url_for.split("://").last, controller.response.body)
|
||||
controller.write_fragment(ActionCachePath.path_for(controller), controller.response.body)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_content_type!(action_cache_path)
|
||||
if extention = action_cache_path.extension
|
||||
content_type = Mime::EXTENSION_LOOKUP[extention]
|
||||
action_cache_path.controller.response.content_type = content_type.to_s
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ActionCachePath
|
||||
attr_reader :controller, :options
|
||||
|
||||
class << self
|
||||
def path_for(*args, &block)
|
||||
new(*args).path
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(controller, options = {})
|
||||
@controller = controller
|
||||
@options = options
|
||||
end
|
||||
|
||||
def path
|
||||
return @path if @path
|
||||
@path = controller.url_for(options).split('://').last
|
||||
normalize!
|
||||
add_extension!
|
||||
URI.unescape(@path)
|
||||
end
|
||||
|
||||
def extension
|
||||
@extension ||= extract_extension(controller.request.path)
|
||||
end
|
||||
|
||||
private
|
||||
def normalize!
|
||||
@path << 'index' if @path.last == '/'
|
||||
end
|
||||
|
||||
def add_extension!
|
||||
@path << ".#{extension}" if extension
|
||||
end
|
||||
|
||||
def extract_extension(file_path)
|
||||
# Don't want just what comes after the last '.' to accomodate multi part extensions
|
||||
# such as tar.gz.
|
||||
file_path[/^[^.]+\.(.+)$/, 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -208,7 +266,7 @@ module ActionController #:nodoc:
|
|||
# <b>Hello <%= @name %></b>
|
||||
# <% cache do %>
|
||||
# All the topics in the system:
|
||||
# <%= render_collection_of_partials "topic", Topic.find_all %>
|
||||
# <%= render :partial => "topic", :collection => Topic.find(:all) %>
|
||||
# <% end %>
|
||||
#
|
||||
# This cache will bind to the name of action that called it. So you would be able to invalidate it using
|
||||
|
@ -246,8 +304,7 @@ module ActionController #:nodoc:
|
|||
# ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost"
|
||||
# ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter")
|
||||
module Fragments
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
@@fragment_cache_store = MemoryStore.new
|
||||
cattr_reader :fragment_cache_store
|
||||
|
@ -306,7 +363,12 @@ module ActionController #:nodoc:
|
|||
# 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: 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 not supported on caches which can't iterate over
|
||||
# all keys, such as memcached.
|
||||
def expire_fragment(name, options = nil)
|
||||
return unless perform_caching
|
||||
|
||||
|
@ -327,6 +389,7 @@ module ActionController #:nodoc:
|
|||
def expire_matched_fragments(matcher = /.*/, options = nil) #:nodoc:
|
||||
expire_fragment(matcher, options)
|
||||
end
|
||||
deprecate :expire_matched_fragments => :expire_fragment
|
||||
|
||||
|
||||
class UnthreadedMemoryStore #:nodoc:
|
||||
|
@ -430,7 +493,7 @@ module ActionController #:nodoc:
|
|||
if f =~ matcher
|
||||
begin
|
||||
File.delete(f)
|
||||
rescue Object => e
|
||||
rescue SystemCallError => e
|
||||
# If there's no cache, then there's nothing to complain about
|
||||
end
|
||||
end
|
||||
|
@ -493,8 +556,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# In the example above, four actions are cached and three actions are responsible for expiring those caches.
|
||||
module Sweeping
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
|
@ -503,8 +565,7 @@ module ActionController #:nodoc:
|
|||
return unless perform_caching
|
||||
configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {}
|
||||
sweepers.each do |sweeper|
|
||||
observer(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)
|
||||
|
@ -523,7 +584,7 @@ module ActionController #:nodoc:
|
|||
|
||||
# ActiveRecord::Observer will mark this class as reloadable even though it should not be.
|
||||
# However, subclasses of ActionController::Caching::Sweeper should be Reloadable
|
||||
include Reloadable::Subclasses
|
||||
include Reloadable::Deprecated
|
||||
|
||||
def before(controller)
|
||||
self.controller = controller
|
||||
|
|
|
@ -27,13 +27,6 @@ class CGI #:nodoc:
|
|||
def request_parameters
|
||||
CGIMethods.parse_request_parameters(params, env_table)
|
||||
end
|
||||
|
||||
def redirect(where)
|
||||
header({
|
||||
"Status" => "302 Moved",
|
||||
"location" => "#{where}"
|
||||
})
|
||||
end
|
||||
|
||||
def session(parameters = nil)
|
||||
parameters = {} if parameters.nil?
|
||||
|
|
|
@ -1,217 +1,211 @@
|
|||
require 'cgi'
|
||||
require 'action_controller/vendor/xml_simple'
|
||||
require 'action_controller/vendor/xml_node'
|
||||
require 'strscan'
|
||||
|
||||
# Static methods for parsing the query and request parameters that can be used in
|
||||
# a CGI extension class or testing in isolation.
|
||||
class CGIMethods #:nodoc:
|
||||
public
|
||||
# Returns a hash with the pairs from the query string. The implicit hash construction that is done in
|
||||
# parse_request_params is not done here.
|
||||
def CGIMethods.parse_query_parameters(query_string)
|
||||
parsed_params = {}
|
||||
|
||||
query_string.split(/[&;]/).each { |p|
|
||||
# Ignore repeated delimiters.
|
||||
next if p.empty?
|
||||
class << self
|
||||
# DEPRECATED: Use parse_form_encoded_parameters
|
||||
def parse_query_parameters(query_string)
|
||||
pairs = query_string.split('&').collect do |chunk|
|
||||
next if chunk.empty?
|
||||
key, value = chunk.split('=', 2)
|
||||
next if key.empty?
|
||||
value = (value.nil? || value.empty?) ? nil : CGI.unescape(value)
|
||||
[ CGI.unescape(key), value ]
|
||||
end.compact
|
||||
|
||||
k, v = p.split('=',2)
|
||||
v = nil if (v && v.empty?)
|
||||
|
||||
k = CGI.unescape(k) if k
|
||||
v = CGI.unescape(v) if v
|
||||
|
||||
unless k.include?(?[)
|
||||
parsed_params[k] = v
|
||||
else
|
||||
keys = split_key(k)
|
||||
last_key = keys.pop
|
||||
last_key = keys.pop if (use_array = last_key.empty?)
|
||||
parent = keys.inject(parsed_params) {|h, k| h[k] ||= {}}
|
||||
|
||||
if use_array then (parent[last_key] ||= []) << v
|
||||
else parent[last_key] = v
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
parsed_params
|
||||
FormEncodedPairParser.new(pairs).result
|
||||
end
|
||||
|
||||
# Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" /
|
||||
# "Somewhere cool!" are translated into a full hash hierarchy, like
|
||||
# { "customer" => { "address" => { "street" => "Somewhere cool!" } } }
|
||||
def CGIMethods.parse_request_parameters(params)
|
||||
parsed_params = {}
|
||||
# DEPRECATED: Use parse_form_encoded_parameters
|
||||
def parse_request_parameters(params)
|
||||
parser = FormEncodedPairParser.new
|
||||
|
||||
for key, value in params
|
||||
value = [value] if key =~ /.*\[\]$/
|
||||
unless key.include?('[')
|
||||
# much faster to test for the most common case first (GET)
|
||||
# and avoid the call to build_deep_hash
|
||||
parsed_params[key] = get_typed_value(value[0])
|
||||
else
|
||||
build_deep_hash(get_typed_value(value[0]), parsed_params, get_levels(key))
|
||||
params = params.dup
|
||||
until params.empty?
|
||||
for key, value in params
|
||||
if key.blank?
|
||||
params.delete key
|
||||
elsif !key.include?('[')
|
||||
# much faster to test for the most common case first (GET)
|
||||
# and avoid the call to build_deep_hash
|
||||
parser.result[key] = get_typed_value(value[0])
|
||||
params.delete key
|
||||
elsif value.is_a?(Array)
|
||||
parser.parse(key, get_typed_value(value.shift))
|
||||
params.delete key if value.empty?
|
||||
else
|
||||
raise TypeError, "Expected array, found #{value.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
parsed_params
|
||||
parser.result
|
||||
end
|
||||
|
||||
def self.parse_formatted_request_parameters(mime_type, raw_post_data)
|
||||
params = case strategy = ActionController::Base.param_parsers[mime_type]
|
||||
def parse_formatted_request_parameters(mime_type, raw_post_data)
|
||||
case strategy = ActionController::Base.param_parsers[mime_type]
|
||||
when Proc
|
||||
strategy.call(raw_post_data)
|
||||
when :xml_simple
|
||||
raw_post_data.blank? ? nil :
|
||||
typecast_xml_value(XmlSimple.xml_in(raw_post_data,
|
||||
'forcearray' => false,
|
||||
'forcecontent' => true,
|
||||
'keeproot' => true,
|
||||
'contentkey' => '__content__'))
|
||||
raw_post_data.blank? ? {} : Hash.from_xml(raw_post_data)
|
||||
when :yaml
|
||||
YAML.load(raw_post_data)
|
||||
when :xml_node
|
||||
node = XmlNode.from_xml(raw_post_data)
|
||||
{ node.node_name => node }
|
||||
end
|
||||
|
||||
dasherize_keys(params || {})
|
||||
rescue Object => e
|
||||
rescue Exception => e # YAML, XML or Ruby code block errors
|
||||
{ "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace,
|
||||
"raw_post_data" => raw_post_data, "format" => mime_type }
|
||||
end
|
||||
|
||||
def self.typecast_xml_value(value)
|
||||
case value
|
||||
when Hash
|
||||
if value.has_key?("__content__")
|
||||
content = translate_xml_entities(value["__content__"])
|
||||
case value["type"]
|
||||
when "integer" then content.to_i
|
||||
when "boolean" then content == "true"
|
||||
when "datetime" then Time.parse(content)
|
||||
when "date" then Date.parse(content)
|
||||
else content
|
||||
end
|
||||
else
|
||||
value.empty? ? nil : value.inject({}) do |h,(k,v)|
|
||||
h[k] = typecast_xml_value(v)
|
||||
h
|
||||
end
|
||||
end
|
||||
when Array
|
||||
value.map! { |i| typecast_xml_value(i) }
|
||||
case value.length
|
||||
when 0 then nil
|
||||
when 1 then value.first
|
||||
else value
|
||||
end
|
||||
else
|
||||
raise "can't typecast #{value.inspect}"
|
||||
end
|
||||
end
|
||||
private
|
||||
def get_typed_value(value)
|
||||
case value
|
||||
when String
|
||||
value
|
||||
when NilClass
|
||||
''
|
||||
when Array
|
||||
value.map { |v| get_typed_value(v) }
|
||||
else
|
||||
# Uploaded file provides content type and filename.
|
||||
if value.respond_to?(:content_type) &&
|
||||
!value.content_type.blank? &&
|
||||
!value.original_filename.blank?
|
||||
unless value.respond_to?(:full_original_filename)
|
||||
class << value
|
||||
alias_method :full_original_filename, :original_filename
|
||||
|
||||
private
|
||||
|
||||
def self.translate_xml_entities(value)
|
||||
value.gsub(/</, "<").
|
||||
gsub(/>/, ">").
|
||||
gsub(/"/, '"').
|
||||
gsub(/'/, "'").
|
||||
gsub(/&/, "&")
|
||||
end
|
||||
|
||||
def self.dasherize_keys(params)
|
||||
case params.class.to_s
|
||||
when "Hash"
|
||||
params.inject({}) do |h,(k,v)|
|
||||
h[k.to_s.tr("-", "_")] = dasherize_keys(v)
|
||||
h
|
||||
end
|
||||
when "Array"
|
||||
params.map { |v| dasherize_keys(v) }
|
||||
else
|
||||
params
|
||||
end
|
||||
end
|
||||
|
||||
# Splits the given key into several pieces. Example keys are 'name', 'person[name]',
|
||||
# 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned.
|
||||
# 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', '']
|
||||
def CGIMethods.split_key(key)
|
||||
if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key
|
||||
keys = [$1]
|
||||
|
||||
keys.concat($2[1..-2].split(']['))
|
||||
keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings
|
||||
|
||||
keys
|
||||
else
|
||||
[key]
|
||||
end
|
||||
end
|
||||
|
||||
def CGIMethods.get_typed_value(value)
|
||||
# test most frequent case first
|
||||
if value.is_a?(String)
|
||||
value
|
||||
elsif value.respond_to?(:content_type) && ! value.content_type.blank?
|
||||
# Uploaded file
|
||||
unless value.respond_to?(:full_original_filename)
|
||||
class << value
|
||||
alias_method :full_original_filename, :original_filename
|
||||
|
||||
# Take the basename of the upload's original filename.
|
||||
# This handles the full Windows paths given by Internet Explorer
|
||||
# (and perhaps other broken user agents) without affecting
|
||||
# those which give the lone filename.
|
||||
# The Windows regexp is adapted from Perl's File::Basename.
|
||||
def original_filename
|
||||
if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
|
||||
md.captures.first
|
||||
else
|
||||
File.basename full_original_filename
|
||||
# Take the basename of the upload's original filename.
|
||||
# This handles the full Windows paths given by Internet Explorer
|
||||
# (and perhaps other broken user agents) without affecting
|
||||
# those which give the lone filename.
|
||||
# The Windows regexp is adapted from Perl's File::Basename.
|
||||
def original_filename
|
||||
if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
|
||||
md.captures.first
|
||||
else
|
||||
File.basename full_original_filename
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Return the same value after overriding original_filename.
|
||||
value
|
||||
|
||||
# Multipart values may have content type, but no filename.
|
||||
elsif value.respond_to?(:read)
|
||||
result = value.read
|
||||
value.rewind
|
||||
result
|
||||
|
||||
# Unknown value, neither string nor multipart.
|
||||
else
|
||||
raise "Unknown form value: #{value.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FormEncodedPairParser < StringScanner #:nodoc:
|
||||
attr_reader :top, :parent, :result
|
||||
|
||||
def initialize(pairs = [])
|
||||
super('')
|
||||
@result = {}
|
||||
pairs.each { |key, value| parse(key, value) }
|
||||
end
|
||||
|
||||
KEY_REGEXP = %r{([^\[\]=&]+)}
|
||||
BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
|
||||
|
||||
# Parse the query string
|
||||
def parse(key, value)
|
||||
self.string = key
|
||||
@top, @parent = result, nil
|
||||
|
||||
# First scan the bare key
|
||||
key = scan(KEY_REGEXP) or return
|
||||
key = post_key_check(key)
|
||||
|
||||
# Then scan as many nestings as present
|
||||
until eos?
|
||||
r = scan(BRACKETED_KEY_REGEXP) or return
|
||||
key = self[1]
|
||||
key = post_key_check(key)
|
||||
end
|
||||
|
||||
bind(key, value)
|
||||
end
|
||||
|
||||
private
|
||||
# After we see a key, we must look ahead to determine our next action. Cases:
|
||||
#
|
||||
# [] follows the key. Then the value must be an array.
|
||||
# = follows the key. (A value comes next)
|
||||
# & or the end of string follows the key. Then the key is a flag.
|
||||
# otherwise, a hash follows the key.
|
||||
def post_key_check(key)
|
||||
if scan(/\[\]/) # a[b][] indicates that b is an array
|
||||
container(key, Array)
|
||||
nil
|
||||
elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
|
||||
container(key, Hash)
|
||||
nil
|
||||
else # End of key? We do nothing.
|
||||
key
|
||||
end
|
||||
end
|
||||
|
||||
# Add a container to the stack.
|
||||
#
|
||||
def container(key, klass)
|
||||
type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
|
||||
value = bind(key, klass.new)
|
||||
type_conflict! klass, value unless value.is_a?(klass)
|
||||
push(value)
|
||||
end
|
||||
|
||||
# Push a value onto the 'stack', which is actually only the top 2 items.
|
||||
def push(value)
|
||||
@parent, @top = @top, value
|
||||
end
|
||||
|
||||
# Bind a key (which may be nil for items in an array) to the provided value.
|
||||
def bind(key, value)
|
||||
if top.is_a? Array
|
||||
if key
|
||||
if top[-1].is_a?(Hash) && ! top[-1].key?(key)
|
||||
top[-1][key] = value
|
||||
else
|
||||
top << {key => value}.with_indifferent_access
|
||||
push top.last
|
||||
end
|
||||
else
|
||||
top << value
|
||||
end
|
||||
elsif top.is_a? Hash
|
||||
key = CGI.unescape(key)
|
||||
parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
|
||||
return top[key] ||= value
|
||||
else
|
||||
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
|
||||
end
|
||||
|
||||
# Return the same value after overriding original_filename.
|
||||
value
|
||||
|
||||
elsif value.respond_to?(:read)
|
||||
# Value as part of a multipart request
|
||||
value.read
|
||||
elsif value.class == Array
|
||||
value.collect { |v| CGIMethods.get_typed_value(v) }
|
||||
else
|
||||
# other value (neither string nor a multipart request)
|
||||
value.to_s
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/
|
||||
def CGIMethods.get_levels(key)
|
||||
all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a
|
||||
if main.nil?
|
||||
[]
|
||||
elsif trailing
|
||||
[key]
|
||||
elsif bracketed
|
||||
[main] + bracketed.slice(1...-1).split('][')
|
||||
else
|
||||
[main]
|
||||
end
|
||||
end
|
||||
|
||||
def CGIMethods.build_deep_hash(value, hash, levels)
|
||||
if levels.length == 0
|
||||
value
|
||||
elsif hash.nil?
|
||||
{ levels.first => CGIMethods.build_deep_hash(value, nil, levels[1..-1]) }
|
||||
else
|
||||
hash.update({ levels.first => CGIMethods.build_deep_hash(value, hash[levels.first], levels[1..-1]) })
|
||||
|
||||
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 passing Array and Hash based paramters qs[]=value&qs[key]=value. "
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,27 +1,48 @@
|
|||
class CGI #:nodoc:
|
||||
# Add @request.env['RAW_POST_DATA'] for the vegans.
|
||||
module QueryExtension
|
||||
# Initialize the data from the query.
|
||||
#
|
||||
# Handles multipart forms (in particular, forms that involve file uploads).
|
||||
# Reads query parameters in the @params field, and cookies into @cookies.
|
||||
def initialize_query()
|
||||
def initialize_query
|
||||
@cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
|
||||
|
||||
#fix some strange request environments
|
||||
# Fix some strange request environments.
|
||||
if method = env_table['REQUEST_METHOD']
|
||||
method = method.to_s.downcase.intern
|
||||
else
|
||||
method = :get
|
||||
end
|
||||
|
||||
if method == :post && (boundary = multipart_form_boundary)
|
||||
@multipart = true
|
||||
@params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH']))
|
||||
else
|
||||
@multipart = false
|
||||
@params = CGI::parse(read_query_params(method) || "")
|
||||
# POST assumes missing Content-Type is application/x-www-form-urlencoded.
|
||||
content_type = env_table['CONTENT_TYPE']
|
||||
if content_type.blank? && method == :post
|
||||
content_type = 'application/x-www-form-urlencoded'
|
||||
end
|
||||
|
||||
# Force content length to zero if missing.
|
||||
content_length = env_table['CONTENT_LENGTH'].to_i
|
||||
|
||||
# Set multipart to false by default.
|
||||
@multipart = false
|
||||
|
||||
# POST and PUT may have params in entity body. If content type is
|
||||
# missing for POST, assume urlencoded. If content type is missing
|
||||
# for PUT, don't assume anything and don't parse the parameters:
|
||||
# it's likely binary data.
|
||||
#
|
||||
# The other HTTP methods have their params in the query string.
|
||||
if method == :post || method == :put
|
||||
if boundary = extract_multipart_form_boundary(content_type)
|
||||
@multipart = true
|
||||
@params = read_multipart(boundary, content_length)
|
||||
elsif content_type.blank? || content_type !~ %r{application/x-www-form-urlencoded}i
|
||||
read_params(method, content_length)
|
||||
@params = {}
|
||||
end
|
||||
end
|
||||
|
||||
@params ||= CGI.parse(read_params(method, content_length))
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -29,16 +50,16 @@ class CGI #:nodoc:
|
|||
MULTIPART_FORM_BOUNDARY_RE = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n #"
|
||||
end
|
||||
|
||||
def multipart_form_boundary
|
||||
MULTIPART_FORM_BOUNDARY_RE.match(env_table['CONTENT_TYPE']).to_a.pop
|
||||
def extract_multipart_form_boundary(content_type)
|
||||
MULTIPART_FORM_BOUNDARY_RE.match(content_type).to_a.pop
|
||||
end
|
||||
|
||||
if defined? MOD_RUBY
|
||||
def read_params_from_query
|
||||
def read_query
|
||||
Apache::request.args || ''
|
||||
end
|
||||
else
|
||||
def read_params_from_query
|
||||
def read_query
|
||||
# fixes CGI querystring parsing for lighttpd
|
||||
env_qs = env_table['QUERY_STRING']
|
||||
if env_qs.blank? && !(uri = env_table['REQUEST_URI']).blank?
|
||||
|
@ -49,25 +70,25 @@ class CGI #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
def read_params_from_post
|
||||
def read_body(content_length)
|
||||
stdinput.binmode if stdinput.respond_to?(:binmode)
|
||||
content = stdinput.read(Integer(env_table['CONTENT_LENGTH'])) || ''
|
||||
# fix for Safari Ajax postings that always append \000
|
||||
content = stdinput.read(content_length) || ''
|
||||
# Fix for Safari Ajax postings that always append \000
|
||||
content.chop! if content[-1] == 0
|
||||
content.gsub! /&_=$/, ''
|
||||
content.gsub!(/&_=$/, '')
|
||||
env_table['RAW_POST_DATA'] = content.freeze
|
||||
end
|
||||
|
||||
def read_query_params(method)
|
||||
def read_params(method, content_length)
|
||||
case method
|
||||
when :get
|
||||
read_params_from_query
|
||||
read_query
|
||||
when :post, :put
|
||||
read_params_from_post
|
||||
read_body(content_length)
|
||||
when :cmd
|
||||
read_from_cmdline
|
||||
else # when :head, :delete, :options
|
||||
read_params_from_query
|
||||
else # :head, :delete, :options, :trace, :connect
|
||||
read_query
|
||||
end
|
||||
end
|
||||
end # module QueryExtension
|
||||
|
|
|
@ -8,13 +8,13 @@ module ActionController #:nodoc:
|
|||
# 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
|
||||
# (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
|
||||
# (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
|
||||
# lib/action_controller/session.
|
||||
# * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
|
||||
# * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ parameter
|
||||
# of the request, or automatically generated for a new session.
|
||||
# * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
|
||||
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
|
||||
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
|
||||
# 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.
|
||||
|
@ -22,10 +22,10 @@ module ActionController #:nodoc:
|
|||
# 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.
|
||||
def self.process_cgi(cgi = CGI.new, session_options = {})
|
||||
def self.process_cgi(cgi = CGI.new, session_options = {})
|
||||
new.process_cgi(cgi, session_options)
|
||||
end
|
||||
|
||||
|
||||
def process_cgi(cgi, session_options = {}) #:nodoc:
|
||||
process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
|
||||
end
|
||||
|
@ -51,7 +51,7 @@ module ActionController #:nodoc:
|
|||
if (qs = @cgi.query_string) && !qs.empty?
|
||||
qs
|
||||
elsif uri = @env['REQUEST_URI']
|
||||
parts = uri.split('?')
|
||||
parts = uri.split('?')
|
||||
parts.shift
|
||||
parts.join('?')
|
||||
else
|
||||
|
@ -60,7 +60,8 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def query_parameters
|
||||
(qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs)
|
||||
@query_parameters ||=
|
||||
(qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs)
|
||||
end
|
||||
|
||||
def request_parameters
|
||||
|
@ -71,7 +72,7 @@ module ActionController #:nodoc:
|
|||
CGIMethods.parse_request_parameters(@cgi.params)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def cookies
|
||||
@cgi.cookies.freeze
|
||||
end
|
||||
|
@ -101,15 +102,26 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def session
|
||||
unless @session
|
||||
unless defined?(@session)
|
||||
if @session_options == false
|
||||
@session = Hash.new
|
||||
else
|
||||
stale_session_check! do
|
||||
if session_options_with_string_keys['new_session'] == true
|
||||
@session = new_session
|
||||
else
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
case value = session_options_with_string_keys['new_session']
|
||||
when true
|
||||
@session = new_session
|
||||
when false
|
||||
begin
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
# CGI::Session raises ArgumentError if 'new_session' == false
|
||||
# and no session cookie or query param is present.
|
||||
rescue ArgumentError
|
||||
@session = Hash.new
|
||||
end
|
||||
when nil
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
else
|
||||
raise ArgumentError, "Invalid new_session option: #{value}"
|
||||
end
|
||||
@session['__valid_session']
|
||||
end
|
||||
|
@ -119,7 +131,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def reset_session
|
||||
@session.delete if CGI::Session === @session
|
||||
@session.delete if defined?(@session) && @session.is_a?(CGI::Session)
|
||||
@session = new_session
|
||||
end
|
||||
|
||||
|
@ -141,11 +153,11 @@ module ActionController #:nodoc:
|
|||
def stale_session_check!
|
||||
yield
|
||||
rescue ArgumentError => argument_error
|
||||
if argument_error.message =~ %r{undefined class/module (\w+)}
|
||||
if argument_error.message =~ %r{undefined class/module ([\w:]+)}
|
||||
begin
|
||||
Module.const_missing($1)
|
||||
rescue LoadError, NameError => const_error
|
||||
raise ActionController::SessionRestoreError, <<end_msg
|
||||
raise ActionController::SessionRestoreError, <<-end_msg
|
||||
Session contains objects whose class definition isn\'t available.
|
||||
Remember to require the classes for all objects kept in the session.
|
||||
(Original exception: #{const_error.message} [#{const_error.class}])
|
||||
|
@ -159,7 +171,7 @@ end_msg
|
|||
end
|
||||
|
||||
def session_options_with_string_keys
|
||||
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).inject({}) { |options, (k,v)| options[k.to_s] = v; options }
|
||||
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -170,38 +182,49 @@ end_msg
|
|||
end
|
||||
|
||||
def out(output = $stdout)
|
||||
convert_content_type!(@headers)
|
||||
convert_content_type!
|
||||
set_content_length!
|
||||
|
||||
output.binmode if output.respond_to?(:binmode)
|
||||
output.sync = false if output.respond_to?(:sync=)
|
||||
|
||||
|
||||
begin
|
||||
output.write(@cgi.header(@headers))
|
||||
|
||||
if @cgi.send(:env_table)['REQUEST_METHOD'] == 'HEAD'
|
||||
return
|
||||
elsif @body.respond_to?(:call)
|
||||
# Flush the output now in case the @body Proc uses
|
||||
# #syswrite.
|
||||
output.flush if output.respond_to?(:flush)
|
||||
@body.call(self, output)
|
||||
else
|
||||
output.write(@body)
|
||||
end
|
||||
|
||||
output.flush if output.respond_to?(:flush)
|
||||
rescue Errno::EPIPE => e
|
||||
# lost connection to the FCGI process -- ignore the output, then
|
||||
rescue Errno::EPIPE, Errno::ECONNRESET
|
||||
# lost connection to parent process, ignore output
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def convert_content_type!(headers)
|
||||
if header = headers.delete("Content-Type")
|
||||
headers["type"] = header
|
||||
def convert_content_type!
|
||||
if content_type = @headers.delete("Content-Type")
|
||||
@headers["type"] = content_type
|
||||
end
|
||||
if header = headers.delete("Content-type")
|
||||
headers["type"] = header
|
||||
if content_type = @headers.delete("Content-type")
|
||||
@headers["type"] = content_type
|
||||
end
|
||||
if header = headers.delete("content-type")
|
||||
headers["type"] = header
|
||||
if content_type = @headers.delete("content-type")
|
||||
@headers["type"] = content_type
|
||||
end
|
||||
end
|
||||
|
||||
# Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice
|
||||
# for, say, a 2GB streaming file.
|
||||
def set_content_length!
|
||||
@headers["Content-Length"] = @body.size unless @body.respond_to?(:call)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,14 +50,9 @@ module ActionController #:nodoc:
|
|||
base.send :attr_accessor, :parent_controller
|
||||
|
||||
base.class_eval do
|
||||
alias_method :process_cleanup_without_components, :process_cleanup
|
||||
alias_method :process_cleanup, :process_cleanup_with_components
|
||||
|
||||
alias_method :set_session_options_without_components, :set_session_options
|
||||
alias_method :set_session_options, :set_session_options_with_components
|
||||
|
||||
alias_method :flash_without_components, :flash
|
||||
alias_method :flash, :flash_with_components
|
||||
alias_method_chain :process_cleanup, :components
|
||||
alias_method_chain :set_session_options, :components
|
||||
alias_method_chain :flash, :components
|
||||
|
||||
alias_method :component_request?, :parent_controller
|
||||
end
|
||||
|
@ -80,11 +75,13 @@ module ActionController #:nodoc:
|
|||
# will also use /code/weblog/components as template root
|
||||
# and find templates in /code/weblog/components/admin/parties/users/
|
||||
def uses_component_template_root
|
||||
path_of_calling_controller = File.dirname(caller[0].split(/:\d+:/).first)
|
||||
path_of_controller_root = path_of_calling_controller.sub(/#{controller_path.split("/")[0..-2]}$/, "") # " (for ruby-mode)
|
||||
path_of_calling_controller = File.dirname(caller[1].split(/:\d+:/, 2).first)
|
||||
path_of_controller_root = path_of_calling_controller.sub(/#{Regexp.escape(File.dirname(controller_path))}$/, "")
|
||||
|
||||
self.template_root = path_of_controller_root
|
||||
end
|
||||
|
||||
deprecate :uses_component_template_root => 'Components are deprecated and will be removed in Rails 2.0.'
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
|
@ -116,27 +113,26 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def flash_with_components(refresh = false) #:nodoc:
|
||||
if @flash.nil? || refresh
|
||||
@flash =
|
||||
if @parent_controller
|
||||
if !defined?(@_flash) || refresh
|
||||
@_flash =
|
||||
if defined?(@parent_controller)
|
||||
@parent_controller.flash
|
||||
else
|
||||
flash_without_components
|
||||
end
|
||||
end
|
||||
|
||||
@flash
|
||||
@_flash
|
||||
end
|
||||
|
||||
private
|
||||
def component_response(options, reuse_response)
|
||||
klass = component_class(options)
|
||||
request = request_for_component(klass.controller_name, options)
|
||||
response = reuse_response ? @response : @response.dup
|
||||
new_response = reuse_response ? response : response.dup
|
||||
|
||||
klass.process_with_components(request, response, self)
|
||||
klass.process_with_components(request, new_response, self)
|
||||
end
|
||||
|
||||
|
||||
# determine the controller class for the component request
|
||||
def component_class(options)
|
||||
if controller = options[:controller]
|
||||
|
@ -145,22 +141,22 @@ module ActionController #:nodoc:
|
|||
self.class
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Create a new request object based on the current request.
|
||||
# The new request inherits the session from the current request,
|
||||
# bypassing any session options set for the component controller's class
|
||||
def request_for_component(controller_name, options)
|
||||
request = @request.dup
|
||||
request.session = @request.session
|
||||
|
||||
request.instance_variable_set(
|
||||
new_request = request.dup
|
||||
new_request.session = request.session
|
||||
|
||||
new_request.instance_variable_set(
|
||||
:@parameters,
|
||||
(options[:params] || {}).with_indifferent_access.update(
|
||||
"controller" => controller_name, "action" => options[:action], "id" => options[:id]
|
||||
)
|
||||
)
|
||||
|
||||
request
|
||||
|
||||
new_request
|
||||
end
|
||||
|
||||
def component_logging(options)
|
||||
|
|
|
@ -4,13 +4,14 @@ module ActionController #:nodoc:
|
|||
# itself back -- just the value it holds). Examples for writing:
|
||||
#
|
||||
# cookies[:user_name] = "david" # => Will set a simple session cookie
|
||||
# cookies[:login] = { :value => "XJ-122", :expires => Time.now + 360} # => Will set 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
|
||||
#
|
||||
#
|
||||
# Example for deleting:
|
||||
#
|
||||
# cookies.delete :user_name
|
||||
|
@ -32,13 +33,13 @@ module ActionController #:nodoc:
|
|||
|
||||
# Deprecated cookie writer method
|
||||
def cookie(*options)
|
||||
@response.headers["cookie"] << CGI::Cookie.new(*options)
|
||||
response.headers['cookie'] << CGI::Cookie.new(*options)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class CookieJar < Hash #:nodoc:
|
||||
def initialize(controller)
|
||||
@controller, @cookies = controller, controller.instance_variable_get("@cookies")
|
||||
@controller, @cookies = controller, controller.request.cookies
|
||||
super()
|
||||
update(@cookies)
|
||||
end
|
||||
|
@ -48,7 +49,7 @@ module ActionController #:nodoc:
|
|||
def [](name)
|
||||
@cookies[name.to_s].value.first if @cookies[name.to_s] && @cookies[name.to_s].respond_to?(:value)
|
||||
end
|
||||
|
||||
|
||||
def []=(name, options)
|
||||
if options.is_a?(Hash)
|
||||
options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
|
||||
|
@ -56,10 +57,10 @@ module ActionController #:nodoc:
|
|||
else
|
||||
options = { "name" => name.to_s, "value" => options }
|
||||
end
|
||||
|
||||
|
||||
set_cookie(options)
|
||||
end
|
||||
|
||||
|
||||
# Removes the cookie on the client machine by setting the value to an empty string
|
||||
# and setting its expiration date into the past
|
||||
def delete(name)
|
||||
|
|
|
@ -5,19 +5,14 @@ module ActionController #:nodoc:
|
|||
base.send(:include, ActionController::Filters::InstanceMethods)
|
||||
end
|
||||
|
||||
# Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
|
||||
# authentication, caching, or auditing before the intended action is performed. Or to do localization or output
|
||||
# compression after the action has been performed.
|
||||
#
|
||||
# Filters have access to the request, response, and all the instance variables set by other filters in the chain
|
||||
# or by the action (in the case of after filters). Additionally, it's possible for a pre-processing <tt>before_filter</tt>
|
||||
# to halt the processing before the intended action is processed by returning false or performing a redirect or render.
|
||||
# This is especially useful for filters like authentication where you're not interested in allowing the action to be
|
||||
# performed if the proper credentials are not in order.
|
||||
# Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
|
||||
# authentication, caching, or auditing before the intended action is performed. Or to do localization or output
|
||||
# compression after the action has been performed. Filters have access to the request, response, and all the instance
|
||||
# variables set by other filters in the chain or by the action (in the case of after filters).
|
||||
#
|
||||
# == Filter inheritance
|
||||
#
|
||||
# Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without
|
||||
# Controller inheritance hierarchies share filters downwards, but subclasses can also add or skip filters without
|
||||
# affecting the superclass. For example:
|
||||
#
|
||||
# class BankController < ActionController::Base
|
||||
|
@ -39,7 +34,7 @@ module ActionController #:nodoc:
|
|||
# end
|
||||
#
|
||||
# Now any actions performed on the BankController will have the audit method called before. On the VaultController,
|
||||
# first the audit method is called, then the verify_credentials method. If the audit method returns false, then
|
||||
# first the audit method is called, then the verify_credentials method. If the audit method returns false, then
|
||||
# verify_credentials and the intended action are never called.
|
||||
#
|
||||
# == Filter types
|
||||
|
@ -64,7 +59,7 @@ module ActionController #:nodoc:
|
|||
# The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
|
||||
# manipulate them as it sees fit.
|
||||
#
|
||||
# The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
|
||||
# The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
|
||||
# Or just as a quick test. It works like this:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
|
@ -76,6 +71,9 @@ module ActionController #:nodoc:
|
|||
# session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call
|
||||
# and returns 1 or -1 on arity will do (such as a Proc or an Method object).
|
||||
#
|
||||
# Please note that around_filters function a little differently than the normal before and after filters with regard to filter
|
||||
# types. Please see the section dedicated to around_filters below.
|
||||
#
|
||||
# == Filter chain ordering
|
||||
#
|
||||
# Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
|
||||
|
@ -83,14 +81,14 @@ module ActionController #:nodoc:
|
|||
# can use <tt>prepend_before_filter</tt> and <tt>prepend_after_filter</tt>. Filters added by these methods will be put at the
|
||||
# beginning of their respective chain and executed before the rest. For example:
|
||||
#
|
||||
# class ShoppingController
|
||||
# class ShoppingController < ActionController::Base
|
||||
# before_filter :verify_open_shop
|
||||
#
|
||||
# class CheckoutController
|
||||
# class CheckoutController < ShoppingController
|
||||
# prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
|
||||
#
|
||||
# The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
|
||||
# <tt>:verify_open_shop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop
|
||||
# <tt>:verify_open_shop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop
|
||||
# is open or not.
|
||||
#
|
||||
# You may pass multiple filter arguments of each type as well as a filter block.
|
||||
|
@ -98,250 +96,511 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# == Around filters
|
||||
#
|
||||
# In addition to the individual before and after filters, it's also possible to specify that a single object should handle
|
||||
# both the before and after call. That's especially useful when you need to keep state active between the before and after,
|
||||
# such as the example of a benchmark filter below:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# around_filter BenchmarkingFilter.new
|
||||
#
|
||||
# # Before this action is performed, BenchmarkingFilter#before(controller) is executed
|
||||
# def index
|
||||
# Around filters wrap an action, executing code both before and after.
|
||||
# They may be declared as method references, blocks, or objects responding
|
||||
# to #filter or to both #before and #after.
|
||||
#
|
||||
# To use a method as an around_filter, pass a symbol naming the Ruby method.
|
||||
# Yield (or block.call) within the method to run the action.
|
||||
#
|
||||
# around_filter :catch_exceptions
|
||||
#
|
||||
# private
|
||||
# def catch_exceptions
|
||||
# yield
|
||||
# rescue => exception
|
||||
# logger.debug "Caught exception! #{exception}"
|
||||
# raise
|
||||
# end
|
||||
# # After this action has been performed, BenchmarkingFilter#after(controller) is executed
|
||||
#
|
||||
# To use a block as an around_filter, pass a block taking as args both
|
||||
# the controller and the action block. You can't call yield directly from
|
||||
# an around_filter block; explicitly call the action block instead:
|
||||
#
|
||||
# around_filter do |controller, action|
|
||||
# logger.debug "before #{controller.action_name}"
|
||||
# action.call
|
||||
# logger.debug "after #{controller.action_name}"
|
||||
# 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:
|
||||
#
|
||||
# around_filter BenchmarkingFilter
|
||||
#
|
||||
# class BenchmarkingFilter
|
||||
# def initialize
|
||||
# @runtime
|
||||
# end
|
||||
#
|
||||
# def before
|
||||
# start_timer
|
||||
# end
|
||||
#
|
||||
# def after
|
||||
# stop_timer
|
||||
# report_result
|
||||
# def self.filter(controller, &block)
|
||||
# Benchmark.measure(&block)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# With before and after methods:
|
||||
#
|
||||
# around_filter Authorizer.new
|
||||
#
|
||||
# class Authorizer
|
||||
# # This will run before the action. Returning false aborts the action.
|
||||
# def before(controller)
|
||||
# if user.authorized?
|
||||
# return true
|
||||
# else
|
||||
# redirect_to login_url
|
||||
# return false
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # This will run after the action if and only if before returned true.
|
||||
# def after(controller)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# If the filter has before and after methods, the before method will be
|
||||
# called before the action. If before returns false, the filter chain is
|
||||
# halted and after will not be run. See Filter Chain Halting below for
|
||||
# an example.
|
||||
#
|
||||
# == Filter chain skipping
|
||||
#
|
||||
# Some times its convenient to specify a filter chain in a superclass that'll hold true for the majority of the
|
||||
# subclasses, but not necessarily all of them. The subclasses that behave in exception can then specify which filters
|
||||
# they would like to be relieved of. Examples
|
||||
# Declaring a filter on a base class conveniently applies to its subclasses,
|
||||
# but sometimes a subclass should skip some of its superclass' filters:
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# before_filter :authenticate
|
||||
# around_filter :catch_exceptions
|
||||
# end
|
||||
#
|
||||
# class WeblogController < ApplicationController
|
||||
# # will run the :authenticate filter
|
||||
# # Will run the :authenticate and :catch_exceptions filters.
|
||||
# end
|
||||
#
|
||||
# class SignupController < ApplicationController
|
||||
# # will not run the :authenticate filter
|
||||
# # Skip :authenticate, run :catch_exceptions.
|
||||
# skip_before_filter :authenticate
|
||||
# end
|
||||
#
|
||||
# class ProjectsController < ApplicationController
|
||||
# # Skip :catch_exceptions, run :authenticate.
|
||||
# skip_filter :catch_exceptions
|
||||
# end
|
||||
#
|
||||
# class ClientsController < ApplicationController
|
||||
# # Skip :catch_exceptions and :authenticate unless action is index.
|
||||
# skip_filter :catch_exceptions, :authenticate, :except => :index
|
||||
# end
|
||||
#
|
||||
# == Filter conditions
|
||||
#
|
||||
# Filters can be limited to run for only specific actions. This can be expressed either by listing the actions to
|
||||
# exclude or the actions to include when executing the filter. Available conditions are +:only+ or +:except+, both
|
||||
# of which accept an arbitrary number of method references. For example:
|
||||
# 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]).
|
||||
#
|
||||
# class Journal < ActionController::Base
|
||||
# # only require authentication if the current action is edit or delete
|
||||
# before_filter :authorize, :only => [ :edit, :delete ]
|
||||
#
|
||||
# # Require authentication for edit and delete.
|
||||
# before_filter :authorize, :only => [:edit, :delete]
|
||||
#
|
||||
# # Passing options to a filter with a block.
|
||||
# around_filter(:except => :index) do |controller, action_block|
|
||||
# results = Profiler.run(&action_block)
|
||||
# controller.response.sub! "</body>", "#{results}</body>"
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def authorize
|
||||
# # redirect to login unless authenticated
|
||||
# # Redirect to login unless authenticated.
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# When setting conditions on inline method (proc) filters the condition must come first and be placed in parentheses.
|
||||
#
|
||||
# class UserPreferences < ActionController::Base
|
||||
# before_filter(:except => :new) { # some proc ... }
|
||||
# # ...
|
||||
# end
|
||||
# == Filter Chain Halting
|
||||
#
|
||||
# <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
|
||||
# before controller action is run. This is useful, for example, to deny
|
||||
# access to unauthenticated users or to redirect from http to https.
|
||||
# Simply return false from the filter or call render or redirect.
|
||||
#
|
||||
# Around filters halt the request unless the action block is called.
|
||||
# Given these filters
|
||||
# after_filter :after
|
||||
# around_filter :around
|
||||
# before_filter :before
|
||||
#
|
||||
# The filter chain will look like:
|
||||
#
|
||||
# ...
|
||||
# . \
|
||||
# . #around (code before yield)
|
||||
# . . \
|
||||
# . . #before (actual filter code is run)
|
||||
# . . . \
|
||||
# . . . execute controller action
|
||||
# . . . /
|
||||
# . . ...
|
||||
# . . /
|
||||
# . #around (code after yield)
|
||||
# . /
|
||||
# #after (actual filter code is run)
|
||||
#
|
||||
# If #around returns before yielding, only #after will be run. The #before
|
||||
# filter and controller action will not be run. If #before returns false,
|
||||
# the second half of #around and all of #after will still run but the
|
||||
# action will not.
|
||||
module ClassMethods
|
||||
# The passed <tt>filters</tt> will be appended to the array of filters that's run _before_ actions
|
||||
# on this controller are performed.
|
||||
# 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)
|
||||
conditions = extract_conditions!(filters)
|
||||
filters << block if block_given?
|
||||
add_action_conditions(filters, conditions)
|
||||
append_filter_to_chain('before', filters)
|
||||
append_filter_to_chain(filters, :before, &block)
|
||||
end
|
||||
|
||||
# The passed <tt>filters</tt> will be prepended to the array of filters that's run _before_ actions
|
||||
# on this controller are performed.
|
||||
# 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)
|
||||
conditions = extract_conditions!(filters)
|
||||
filters << block if block_given?
|
||||
add_action_conditions(filters, conditions)
|
||||
prepend_filter_to_chain('before', filters)
|
||||
prepend_filter_to_chain(filters, :before, &block)
|
||||
end
|
||||
|
||||
# Short-hand for append_before_filter since that's the most common of the two.
|
||||
# Shorthand for append_before_filter since it's the most common.
|
||||
alias :before_filter :append_before_filter
|
||||
|
||||
# The passed <tt>filters</tt> will be appended to the array of filters that's run _after_ actions
|
||||
# on this controller are performed.
|
||||
|
||||
# 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)
|
||||
conditions = extract_conditions!(filters)
|
||||
filters << block if block_given?
|
||||
add_action_conditions(filters, conditions)
|
||||
append_filter_to_chain('after', filters)
|
||||
prepend_filter_to_chain(filters, :after, &block)
|
||||
end
|
||||
|
||||
# The passed <tt>filters</tt> will be prepended to the array of filters that's run _after_ actions
|
||||
# on this controller are performed.
|
||||
# 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)
|
||||
conditions = extract_conditions!(filters)
|
||||
filters << block if block_given?
|
||||
add_action_conditions(filters, conditions)
|
||||
prepend_filter_to_chain("after", filters)
|
||||
append_filter_to_chain(filters, :after, &block)
|
||||
end
|
||||
|
||||
# Short-hand for append_after_filter since that's the most common of the two.
|
||||
# Shorthand for append_after_filter since it's the most common.
|
||||
alias :after_filter :append_after_filter
|
||||
|
||||
# The passed <tt>filters</tt> will have their +before+ method appended to the array of filters that's run both before actions
|
||||
# on this controller are performed and have their +after+ method prepended to the after actions. The filter objects must all
|
||||
# respond to both +before+ and +after+. So if you do append_around_filter A.new, B.new, the callstack will look like:
|
||||
|
||||
|
||||
# If you append_around_filter A.new, B.new, the filter chain looks like
|
||||
#
|
||||
# B#before
|
||||
# A#before
|
||||
# # run the action
|
||||
# A#after
|
||||
# B#after
|
||||
def append_around_filter(*filters)
|
||||
conditions = extract_conditions!(filters)
|
||||
for filter in filters.flatten
|
||||
ensure_filter_responds_to_before_and_after(filter)
|
||||
append_before_filter(conditions || {}) { |c| filter.before(c) }
|
||||
prepend_after_filter(conditions || {}) { |c| filter.after(c) }
|
||||
#
|
||||
# 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
|
||||
end
|
||||
end
|
||||
|
||||
# The passed <tt>filters</tt> will have their +before+ method prepended to the array of filters that's run both before actions
|
||||
# on this controller are performed and have their +after+ method appended to the after actions. The filter objects must all
|
||||
# respond to both +before+ and +after+. So if you do prepend_around_filter A.new, B.new, the callstack will look like:
|
||||
# If you prepend_around_filter A.new, B.new, the filter chain looks like:
|
||||
#
|
||||
# A#before
|
||||
# B#before
|
||||
# # run the action
|
||||
# B#after
|
||||
# A#after
|
||||
def prepend_around_filter(*filters)
|
||||
for filter in filters.flatten
|
||||
ensure_filter_responds_to_before_and_after(filter)
|
||||
prepend_before_filter { |c| filter.before(c) }
|
||||
append_after_filter { |c| filter.after(c) }
|
||||
#
|
||||
# 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
|
||||
end
|
||||
end
|
||||
|
||||
# Short-hand for append_around_filter since that's the most common of the two.
|
||||
# Shorthand for append_around_filter since it's the most common.
|
||||
alias :around_filter :append_around_filter
|
||||
|
||||
# Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
|
||||
|
||||
# Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
|
||||
# filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
|
||||
# of many sub-controllers need a different hierarchy.
|
||||
#
|
||||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# 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)
|
||||
if conditions = extract_conditions!(filters)
|
||||
remove_contradicting_conditions!(filters, conditions)
|
||||
conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
|
||||
add_action_conditions(filters, conditions)
|
||||
else
|
||||
for filter in filters.flatten
|
||||
write_inheritable_attribute("before_filters", read_inheritable_attribute("before_filters") - [ filter ])
|
||||
end
|
||||
end
|
||||
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
|
||||
# Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
|
||||
# filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
|
||||
# of many sub-controllers need a different hierarchy.
|
||||
#
|
||||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# 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)
|
||||
if conditions = extract_conditions!(filters)
|
||||
remove_contradicting_conditions!(filters, conditions)
|
||||
conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
|
||||
add_action_conditions(filters, conditions)
|
||||
else
|
||||
for filter in filters.flatten
|
||||
write_inheritable_attribute("after_filters", read_inheritable_attribute("after_filters") - [ filter ])
|
||||
end
|
||||
end
|
||||
skip_filter_in_chain(*filters, &:after?)
|
||||
end
|
||||
|
||||
|
||||
# Removes the specified filters from the filter chain. This only works for method reference (symbol)
|
||||
# filters, not procs. This method is different from skip_after_filter and skip_before_filter in that
|
||||
# it will match any before, after or yielding around filter.
|
||||
#
|
||||
# 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)
|
||||
end
|
||||
|
||||
# Returns an array of Filter objects for this controller.
|
||||
def filter_chain
|
||||
read_inheritable_attribute("filter_chain") || []
|
||||
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:
|
||||
@before_filters ||= read_inheritable_attribute("before_filters") || []
|
||||
filter_chain.select(&:before?).map(&:filter)
|
||||
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:
|
||||
@after_filters ||= read_inheritable_attribute("after_filters") || []
|
||||
filter_chain.select(&:after?).map(&:filter)
|
||||
end
|
||||
|
||||
|
||||
# Returns a mapping between filters and the actions that may run them.
|
||||
def included_actions #:nodoc:
|
||||
@included_actions ||= read_inheritable_attribute("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") || {}
|
||||
read_inheritable_attribute("excluded_actions") || {}
|
||||
end
|
||||
|
||||
private
|
||||
def append_filter_to_chain(condition, filters)
|
||||
write_inheritable_array("#{condition}_filters", filters)
|
||||
|
||||
# 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:
|
||||
if (ia = included_actions[filter]) && !ia.empty?
|
||||
!ia.include?(action)
|
||||
else
|
||||
(excluded_actions[filter] || []).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 prepend_filter_to_chain(condition, filters)
|
||||
old_filters = read_inheritable_attribute("#{condition}_filters") || []
|
||||
write_inheritable_attribute("#{condition}_filters", filters + old_filters)
|
||||
def before?
|
||||
false
|
||||
end
|
||||
|
||||
def ensure_filter_responds_to_before_and_after(filter)
|
||||
unless filter.respond_to?(:before) && filter.respond_to?(:after)
|
||||
raise ActionControllerError, "Filter object must respond to both before and after"
|
||||
def after?
|
||||
false
|
||||
end
|
||||
|
||||
def around?
|
||||
true
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
raise(ActionControllerError, 'No filter type: Nothing to do here.')
|
||||
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
|
||||
|
||||
def around?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class BeforeFilterProxy < FilterProxy #:nodoc:
|
||||
def before?
|
||||
true
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
if false == @filter.call(controller) # must only stop if equal to false. only filters returning false are halted.
|
||||
controller.halt_filter_chain(@filter, :returned_false)
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AfterFilterProxy < FilterProxy #:nodoc:
|
||||
def after?
|
||||
true
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
yield
|
||||
@filter.call(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
|
||||
|
||||
protected
|
||||
def append_filter_to_chain(filters, position = :around, &block)
|
||||
write_inheritable_array('filter_chain', create_filters(filters, position, &block) )
|
||||
end
|
||||
|
||||
def prepend_filter_to_chain(filters, position = :around, &block)
|
||||
write_inheritable_attribute('filter_chain', create_filters(filters, position, &block) + filter_chain)
|
||||
end
|
||||
|
||||
def create_filters(filters, position, &block) #:nodoc:
|
||||
filters, conditions = extract_conditions(filters, &block)
|
||||
filters.map! { |filter| find_or_create_filter(filter,position) }
|
||||
update_conditions(filters, conditions)
|
||||
filters
|
||||
end
|
||||
|
||||
def find_or_create_filter(filter,position)
|
||||
if found_filter = find_filter(filter) { |f| f.send("#{position}?") }
|
||||
found_filter
|
||||
else
|
||||
f = class_for_filter(filter).new(filter)
|
||||
# apply proxy to filter if necessary
|
||||
case position
|
||||
when :before
|
||||
BeforeFilterProxy.new(f)
|
||||
when :after
|
||||
AfterFilterProxy.new(f)
|
||||
else
|
||||
f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def extract_conditions!(filters)
|
||||
return nil unless filters.last.is_a? Hash
|
||||
filters.pop
|
||||
# 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) #: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
|
||||
else
|
||||
raise(ActionControllerError, 'A filters must be a Symbol, Proc, Method, or object responding to filter.')
|
||||
end
|
||||
end
|
||||
|
||||
def add_action_conditions(filters, conditions)
|
||||
return unless conditions
|
||||
included, excluded = conditions[:only], conditions[:except]
|
||||
write_inheritable_hash('included_actions', condition_hash(filters, included)) && return if included
|
||||
write_inheritable_hash('excluded_actions', condition_hash(filters, excluded)) if excluded
|
||||
def extract_conditions(*filters, &block) #:nodoc:
|
||||
filters.flatten!
|
||||
conditions = filters.last.is_a?(Hash) ? filters.pop : {}
|
||||
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]))
|
||||
else
|
||||
write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except])) if conditions[:except]
|
||||
end
|
||||
end
|
||||
|
||||
def condition_hash(filters, *actions)
|
||||
filters.inject({}) {|hash, filter| hash.merge(filter => actions.flatten.map {|action| action.to_s})}
|
||||
actions = actions.flatten.map(&:to_s)
|
||||
filters.inject({}) { |h,f| h.update( f => (actions.blank? ? nil : actions)) }
|
||||
end
|
||||
|
||||
def remove_contradicting_conditions!(filters, conditions)
|
||||
return unless conditions[:only]
|
||||
filters.each do |filter|
|
||||
next unless included_actions_for_filter = (read_inheritable_attribute('included_actions') || {})[filter]
|
||||
[*conditions[:only]].each do |conditional_action|
|
||||
conditional_action = conditional_action.to_s
|
||||
included_actions_for_filter.delete(conditional_action) if included_actions_for_filter.include?(conditional_action)
|
||||
|
||||
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(included_actions) do |hash,filter|
|
||||
ia = (hash[filter] || []) - actions
|
||||
ia.blank? ? 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|
|
||||
unless filter.before(controller) == false
|
||||
begin
|
||||
action.call
|
||||
ensure
|
||||
filter.after(controller)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -350,26 +609,14 @@ module ActionController #:nodoc:
|
|||
module InstanceMethods # :nodoc:
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
alias_method :perform_action_without_filters, :perform_action
|
||||
alias_method :perform_action, :perform_action_with_filters
|
||||
|
||||
alias_method :process_without_filters, :process
|
||||
alias_method :process, :process_with_filters
|
||||
|
||||
alias_method :process_cleanup_without_filters, :process_cleanup
|
||||
alias_method :process_cleanup, :process_cleanup_with_filters
|
||||
alias_method_chain :perform_action, :filters
|
||||
alias_method_chain :process, :filters
|
||||
alias_method_chain :process_cleanup, :filters
|
||||
end
|
||||
end
|
||||
|
||||
def perform_action_with_filters
|
||||
before_action_result = before_action
|
||||
|
||||
unless before_action_result == false || performed?
|
||||
perform_action_without_filters
|
||||
after_action
|
||||
end
|
||||
|
||||
@before_filter_chain_aborted = (before_action_result == false)
|
||||
call_filter(self.class.filter_chain, 0)
|
||||
end
|
||||
|
||||
def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
|
@ -377,61 +624,37 @@ module ActionController #:nodoc:
|
|||
process_without_filters(request, response, method, *arguments)
|
||||
end
|
||||
|
||||
# Calls all the defined before-filter filters, which are added by using "before_filter :method".
|
||||
# If any of the filters return false, no more filters will be executed and the action is aborted.
|
||||
def before_action #:doc:
|
||||
call_filters(self.class.before_filters)
|
||||
def filter_chain
|
||||
self.class.filter_chain
|
||||
end
|
||||
|
||||
# Calls all the defined after-filter filters, which are added by using "after_filter :method".
|
||||
# If any of the filters return false, no more filters will be executed.
|
||||
def after_action #:doc:
|
||||
call_filters(self.class.after_filters)
|
||||
def call_filter(chain, index)
|
||||
return (performed? || perform_action_without_filters) if index >= chain.size
|
||||
filter = chain[index]
|
||||
return call_filter(chain, index.next) if self.class.filter_excluded_from_action?(filter,action_name)
|
||||
|
||||
halted = false
|
||||
filter.call(self) do
|
||||
halted = call_filter(chain, index.next)
|
||||
end
|
||||
halt_filter_chain(filter.filter, :no_yield) if halted == false unless @before_filter_chain_aborted
|
||||
halted
|
||||
end
|
||||
|
||||
|
||||
def halt_filter_chain(filter, reason)
|
||||
if logger
|
||||
case reason
|
||||
when :no_yield
|
||||
logger.info "Filter chain halted as [#{filter.inspect}] did not yield."
|
||||
when :returned_false
|
||||
logger.info "Filter chain halted as [#{filter.inspect}] returned false."
|
||||
end
|
||||
end
|
||||
@before_filter_chain_aborted = true
|
||||
return false
|
||||
end
|
||||
|
||||
private
|
||||
def call_filters(filters)
|
||||
filters.each do |filter|
|
||||
next if action_exempted?(filter)
|
||||
|
||||
filter_result = case
|
||||
when filter.is_a?(Symbol)
|
||||
self.send(filter)
|
||||
when filter_block?(filter)
|
||||
filter.call(self)
|
||||
when filter_class?(filter)
|
||||
filter.filter(self)
|
||||
else
|
||||
raise(
|
||||
ActionControllerError,
|
||||
'Filters need to be either a symbol, proc/method, or class implementing a static filter method'
|
||||
)
|
||||
end
|
||||
|
||||
if filter_result == false
|
||||
logger.info "Filter chain halted as [#{filter}] returned false" if logger
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def filter_block?(filter)
|
||||
filter.respond_to?('call') && (filter.arity == 1 || filter.arity == -1)
|
||||
end
|
||||
|
||||
def filter_class?(filter)
|
||||
filter.respond_to?('filter')
|
||||
end
|
||||
|
||||
def action_exempted?(filter)
|
||||
case
|
||||
when ia = self.class.included_actions[filter]
|
||||
!ia.include?(action_name)
|
||||
when ea = self.class.excluded_actions[filter]
|
||||
ea.include?(action_name)
|
||||
end
|
||||
end
|
||||
|
||||
def process_cleanup_with_filters
|
||||
if @before_filter_chain_aborted
|
||||
close_session
|
||||
|
|
|
@ -17,7 +17,7 @@ module ActionController #:nodoc:
|
|||
# end
|
||||
#
|
||||
# display.rhtml
|
||||
# <% if @flash[:notice] %><div class="notice"><%= @flash[:notice] %></div><% end %>
|
||||
# <% if flash[:notice] %><div class="notice"><%= flash[:notice] %></div><% end %>
|
||||
#
|
||||
# This example just places a string in the flash, but you can put any object in there. And of course, you can put as many
|
||||
# as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
|
||||
|
@ -28,11 +28,9 @@ module ActionController #:nodoc:
|
|||
base.send :include, InstanceMethods
|
||||
|
||||
base.class_eval do
|
||||
alias_method :assign_shortcuts_without_flash, :assign_shortcuts
|
||||
alias_method :assign_shortcuts, :assign_shortcuts_with_flash
|
||||
|
||||
alias_method :process_cleanup_without_flash, :process_cleanup
|
||||
alias_method :process_cleanup, :process_cleanup_with_flash
|
||||
alias_method_chain :assign_shortcuts, :flash
|
||||
alias_method_chain :process_cleanup, :flash
|
||||
alias_method_chain :reset_session, :flash
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -94,7 +92,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# flash.keep # keeps the entire flash
|
||||
# flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
|
||||
def keep(k=nil)
|
||||
def keep(k = nil)
|
||||
use(k, false)
|
||||
end
|
||||
|
||||
|
@ -102,7 +100,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# flash.keep # keep entire flash available for the next action
|
||||
# flash.discard(:warning) # discard the "warning" entry (it'll still be available for the current action)
|
||||
def discard(k=nil)
|
||||
def discard(k = nil)
|
||||
use(k)
|
||||
end
|
||||
|
||||
|
@ -118,6 +116,7 @@ module ActionController #:nodoc:
|
|||
@used.delete(k)
|
||||
end
|
||||
end
|
||||
|
||||
(@used.keys - keys).each{|k| @used.delete k } # clean up after keys that could have been left over by calling reject! or shift on the flash
|
||||
end
|
||||
|
||||
|
@ -143,36 +142,41 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def process_cleanup_with_flash
|
||||
flash.sweep if @session
|
||||
flash.sweep if @_session
|
||||
process_cleanup_without_flash
|
||||
end
|
||||
|
||||
def reset_session_with_flash
|
||||
reset_session_without_flash
|
||||
remove_instance_variable(:@_flash)
|
||||
flash(:refresh)
|
||||
end
|
||||
|
||||
protected
|
||||
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or
|
||||
# <tt>flash["notice"] = "hello"</tt> to put a new one.
|
||||
# Note that if sessions are disabled only flash.now will work.
|
||||
def flash(refresh = false) #:doc:
|
||||
if @flash.nil? || refresh
|
||||
@flash =
|
||||
if @session.is_a?(Hash)
|
||||
# @session is a Hash, if sessions are disabled
|
||||
# we don't put the flash in the session in this case
|
||||
if !defined?(@_flash) || refresh
|
||||
@_flash =
|
||||
if session.is_a?(Hash)
|
||||
# don't put flash in session if disabled
|
||||
FlashHash.new
|
||||
else
|
||||
# otherwise, @session is a CGI::Session or a TestSession
|
||||
# otherwise, session is a CGI::Session or a TestSession
|
||||
# so make sure it gets retrieved from/saved to session storage after request processing
|
||||
@session["flash"] ||= FlashHash.new
|
||||
session["flash"] ||= FlashHash.new
|
||||
end
|
||||
end
|
||||
|
||||
@flash
|
||||
|
||||
@_flash
|
||||
end
|
||||
|
||||
# deprecated. use <tt>flash.keep</tt> instead
|
||||
def keep_flash #:doc:
|
||||
warn 'keep_flash is deprecated; use flash.keep instead.'
|
||||
ActiveSupport::Deprecation.warn 'keep_flash is deprecated; use flash.keep instead.', caller
|
||||
flash.keep
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
module ActionController #:nodoc:
|
||||
module Helpers #:nodoc:
|
||||
def self.append_features(base)
|
||||
super
|
||||
|
||||
def self.included(base)
|
||||
# Initialize the base module to aggregate its helpers.
|
||||
base.class_inheritable_accessor :master_helper_module
|
||||
base.master_helper_module = Module.new
|
||||
|
@ -13,8 +11,7 @@ module ActionController #:nodoc:
|
|||
base.class_eval do
|
||||
# Wrap inherited to create a new master helper module for subclasses.
|
||||
class << self
|
||||
alias_method :inherited_without_helper, :inherited
|
||||
alias_method :inherited, :inherited_with_helper
|
||||
alias_method_chain :inherited, :helper
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require 'dispatcher'
|
||||
require 'stringio'
|
||||
require 'uri'
|
||||
require 'action_controller/test_process'
|
||||
|
||||
module ActionController
|
||||
module Integration #:nodoc:
|
||||
|
@ -13,6 +14,7 @@ module ActionController
|
|||
# rather than instantiating Integration::Session directly.
|
||||
class Session
|
||||
include Test::Unit::Assertions
|
||||
include ActionController::Assertions
|
||||
include ActionController::TestProcess
|
||||
|
||||
# The integer HTTP status code of the last request.
|
||||
|
@ -73,11 +75,11 @@ module ActionController
|
|||
unless @named_routes_configured
|
||||
# install the named routes in this session instance.
|
||||
klass = class<<self; self; end
|
||||
Routing::NamedRoutes.install(klass)
|
||||
Routing::Routes.named_routes.install(klass)
|
||||
|
||||
# the helpers are made protected by default--we make them public for
|
||||
# easier access during testing and troubleshooting.
|
||||
klass.send(:public, *Routing::NamedRoutes::Helpers)
|
||||
klass.send(:public, *Routing::Routes.named_routes.helpers)
|
||||
@named_routes_configured = true
|
||||
end
|
||||
end
|
||||
|
@ -111,7 +113,7 @@ module ActionController
|
|||
# performed on the location header.
|
||||
def follow_redirect!
|
||||
raise "not a redirect! #{@status} #{@status_message}" unless redirect?
|
||||
get(interpret_uri(headers["location"].first))
|
||||
get(interpret_uri(headers['location'].first))
|
||||
status
|
||||
end
|
||||
|
||||
|
@ -143,19 +145,33 @@ module ActionController
|
|||
# (application/x-www-form-urlencoded or multipart/form-data). The headers
|
||||
# should be a hash. The keys will automatically be upcased, with the
|
||||
# prefix 'HTTP_' added if needed.
|
||||
#
|
||||
# You can also perform POST, PUT, DELETE, and HEAD requests with #post,
|
||||
# #put, #delete, and #head.
|
||||
def get(path, parameters=nil, headers=nil)
|
||||
process :get, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a POST request with the given parameters. The parameters may
|
||||
# be +nil+, a Hash, or a string that is appropriately encoded
|
||||
# (application/x-www-form-urlencoded or multipart/form-data). The headers
|
||||
# should be a hash. The keys will automatically be upcased, with the
|
||||
# prefix 'HTTP_' added if needed.
|
||||
# Performs a POST request with the given parameters. See get() for more details.
|
||||
def post(path, parameters=nil, headers=nil)
|
||||
process :post, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a PUT request with the given parameters. See get() for more details.
|
||||
def put(path, parameters=nil, headers=nil)
|
||||
process :put, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a DELETE request with the given parameters. See get() for more details.
|
||||
def delete(path, parameters=nil, headers=nil)
|
||||
process :delete, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a HEAD request with the given parameters. See get() for more details.
|
||||
def head(path, parameters=nil, headers=nil)
|
||||
process :head, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs an XMLHttpRequest request with the given parameters, mimicing
|
||||
# the request environment created by the Prototype library. The parameters
|
||||
# may be +nil+, a Hash, or a string that is appropriately encoded
|
||||
|
@ -163,7 +179,11 @@ module ActionController
|
|||
# should be a hash. The keys will automatically be upcased, with the
|
||||
# prefix 'HTTP_' added if needed.
|
||||
def xml_http_request(path, parameters=nil, headers=nil)
|
||||
headers = (headers || {}).merge("X-Requested-With" => "XMLHttpRequest")
|
||||
headers = (headers || {}).merge(
|
||||
"X-Requested-With" => "XMLHttpRequest",
|
||||
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
|
||||
)
|
||||
|
||||
post(path, parameters, headers)
|
||||
end
|
||||
|
||||
|
@ -174,7 +194,6 @@ module ActionController
|
|||
end
|
||||
|
||||
private
|
||||
|
||||
class MockCGI < CGI #:nodoc:
|
||||
attr_accessor :stdinput, :stdoutput, :env_table
|
||||
|
||||
|
@ -224,7 +243,7 @@ module ActionController
|
|||
|
||||
(headers || {}).each do |key, value|
|
||||
key = key.to_s.upcase.gsub(/-/, "_")
|
||||
key = "HTTP_#{key}" unless env.has_key?(key) || env =~ /^X|HTTP/
|
||||
key = "HTTP_#{key}" unless env.has_key?(key) || key =~ /^HTTP_/
|
||||
env[key] = value
|
||||
end
|
||||
|
||||
|
@ -247,6 +266,8 @@ module ActionController
|
|||
# tests.
|
||||
@response.extend(TestResponseBehavior)
|
||||
|
||||
@html_document = nil
|
||||
|
||||
parse_result
|
||||
return status
|
||||
end
|
||||
|
@ -317,9 +338,8 @@ module ActionController
|
|||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
class <<self
|
||||
alias_method :new_without_capture, :new
|
||||
alias_method :new, :new_with_capture
|
||||
class << self
|
||||
alias_method_chain :new, :capture
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -330,9 +350,11 @@ module ActionController
|
|||
def clear_last_instantiation!
|
||||
self.last_instantiation = nil
|
||||
end
|
||||
|
||||
|
||||
def new_with_capture(*args)
|
||||
self.last_instantiation ||= new_without_capture(*args)
|
||||
controller = new_without_capture(*args)
|
||||
self.last_instantiation ||= controller
|
||||
controller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -471,6 +493,8 @@ module ActionController
|
|||
%w(get post cookies assigns xml_http_request).each do |method|
|
||||
define_method(method) do |*args|
|
||||
reset! unless @integration_session
|
||||
# reset the html_document variable, but only for new get/post calls
|
||||
@html_document = nil unless %w(cookies assigns).include?(method)
|
||||
returning @integration_session.send(method, *args) do
|
||||
copy_session_variables!
|
||||
end
|
||||
|
|
|
@ -3,12 +3,13 @@ module ActionController #:nodoc:
|
|||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
# NOTE: Can't use alias_method_chain here because +render_without_layout+ is already
|
||||
# defined as a publicly exposed method
|
||||
alias_method :render_with_no_layout, :render
|
||||
alias_method :render, :render_with_a_layout
|
||||
|
||||
class << self
|
||||
alias_method :inherited_without_layout, :inherited
|
||||
alias_method :inherited, :inherited_with_layout
|
||||
alias_method_chain :inherited, :layout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -26,9 +27,9 @@ module ActionController #:nodoc:
|
|||
# With layouts, you can flip it around and have the common structure know where to insert changing content. This means
|
||||
# that the header and footer are only mentioned in one place, like this:
|
||||
#
|
||||
# <!-- The header part of this layout -->
|
||||
# // 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:
|
||||
#
|
||||
|
@ -37,9 +38,9 @@ module ActionController #:nodoc:
|
|||
# Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
|
||||
# like this:
|
||||
#
|
||||
# <!-- The header part of this layout -->
|
||||
# // The header part of this layout
|
||||
# hello world
|
||||
# <!-- The footer part of this layout -->
|
||||
# // The footer part of this layout -->
|
||||
#
|
||||
# == Accessing shared variables
|
||||
#
|
||||
|
@ -182,7 +183,6 @@ module ActionController #:nodoc:
|
|||
private
|
||||
def inherited_with_layout(child)
|
||||
inherited_without_layout(child)
|
||||
child.send :include, Reloadable
|
||||
layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '')
|
||||
child.layout(layout_match) unless layout_list.grep(%r{layouts/#{layout_match}\.[a-z][0-9a-z]*$}).empty?
|
||||
end
|
||||
|
@ -235,6 +235,8 @@ module ActionController #:nodoc:
|
|||
template_with_options = options.is_a?(Hash)
|
||||
|
||||
if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options, deprecated_layout))
|
||||
assert_existence_of_template_file(layout)
|
||||
|
||||
options = options.merge :layout => false if template_with_options
|
||||
logger.info("Rendering #{options} within #{layout}") if logger
|
||||
|
||||
|
@ -248,6 +250,7 @@ module ActionController #:nodoc:
|
|||
erase_render_results
|
||||
add_variables_to_assigns
|
||||
@template.instance_variable_set("@content_for_layout", content_for_layout)
|
||||
response.layout = layout
|
||||
render_text(@template.render_file(layout, true), deprecated_status)
|
||||
else
|
||||
render_with_no_layout(options, deprecated_status, &block)
|
||||
|
@ -263,7 +266,7 @@ module ActionController #:nodoc:
|
|||
|
||||
def candidate_for_layout?(options)
|
||||
(options.has_key?(:layout) && options[:layout] != false) ||
|
||||
options.values_at(:text, :xml, :file, :inline, :partial, :nothing).compact.empty? &&
|
||||
options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? &&
|
||||
!template_exempt_from_layout?(default_template_name(options[:action] || options[:template]))
|
||||
end
|
||||
|
||||
|
|
|
@ -4,11 +4,12 @@ module ActionController
|
|||
# backing.
|
||||
module Macros
|
||||
module AutoComplete #:nodoc:
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# DEPRECATION WARNING: This method will become a separate plugin when Rails 2.0 ships.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# # Controller
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
module ActionController
|
||||
module Macros
|
||||
module InPlaceEditing #:nodoc:
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# DEPRECATION WARNING: This method will become a separate plugin when Rails 2.0 ships.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# # Controller
|
||||
|
|
|
@ -8,18 +8,18 @@ module ActionController #:nodoc:
|
|||
# Without web-service support, an action which collects the data for displaying a list of people
|
||||
# might look something like this:
|
||||
#
|
||||
# def list
|
||||
# def index
|
||||
# @people = Person.find(:all)
|
||||
# end
|
||||
#
|
||||
# Here's the same action, with web-service support baked in:
|
||||
#
|
||||
# def list
|
||||
# def index
|
||||
# @people = Person.find(:all)
|
||||
#
|
||||
# respond_to do |wants|
|
||||
# wants.html
|
||||
# wants.xml { render :xml => @people.to_xml }
|
||||
# respond_to do |format|
|
||||
# format.html
|
||||
# format.xml { render :xml => @people.to_xml }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
|
@ -30,7 +30,7 @@ module ActionController #:nodoc:
|
|||
# Supposing you have an action that adds a new person, optionally creating their company
|
||||
# (by name) if it does not already exist, without web-services, it might look like this:
|
||||
#
|
||||
# def add
|
||||
# def create
|
||||
# @company = Company.find_or_create_by_name(params[:company][:name])
|
||||
# @person = @company.people.create(params[:person])
|
||||
#
|
||||
|
@ -39,15 +39,15 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# Here's the same action, with web-service support baked in:
|
||||
#
|
||||
# def add
|
||||
# def create
|
||||
# company = params[:person].delete(:company)
|
||||
# @company = Company.find_or_create_by_name(company[:name])
|
||||
# @person = @company.people.create(params[:person])
|
||||
#
|
||||
# respond_to do |wants|
|
||||
# wants.html { redirect_to(person_list_url) }
|
||||
# wants.js
|
||||
# wants.xml { render :xml => @person.to_xml(:include => @company) }
|
||||
# respond_to do |format|
|
||||
# format.html { redirect_to(person_list_url) }
|
||||
# format.js
|
||||
# format.xml { render :xml => @person.to_xml(:include => @company) }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
|
@ -97,9 +97,8 @@ module ActionController #:nodoc:
|
|||
# environment.rb as follows.
|
||||
#
|
||||
# Mime::Type.register "image/jpg", :jpg
|
||||
#
|
||||
def respond_to(*types, &block)
|
||||
raise ArgumentError, "respond_to takes either types or a block, never bot" unless types.any? ^ block
|
||||
raise ArgumentError, "respond_to takes either types or a block, never both" unless types.any? ^ block
|
||||
block ||= lambda { |responder| types.each { |type| responder.send(type) } }
|
||||
responder = Responder.new(block.binding)
|
||||
block.call(responder)
|
||||
|
@ -108,15 +107,19 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
class Responder #:nodoc:
|
||||
DEFAULT_BLOCKS = {
|
||||
:html => 'Proc.new { render }',
|
||||
:js => 'Proc.new { render :action => "#{action_name}.rjs" }',
|
||||
:xml => 'Proc.new { render :action => "#{action_name}.rxml" }'
|
||||
}
|
||||
DEFAULT_BLOCKS = [:html, :js, :xml].inject({}) do |blocks, ext|
|
||||
template_extension = (ext == :html ? '' : ".r#{ext}")
|
||||
blocks.update ext => %(Proc.new { render :action => "\#{action_name}#{template_extension}", :content_type => Mime::#{ext.to_s.upcase} })
|
||||
end
|
||||
|
||||
def initialize(block_binding)
|
||||
@block_binding = block_binding
|
||||
@mime_type_priority = eval("request.accepts", block_binding)
|
||||
@mime_type_priority = eval(
|
||||
"(params[:format] && Mime::EXTENSION_LOOKUP[params[:format]]) ? " +
|
||||
"[ Mime::EXTENSION_LOOKUP[params[:format]] ] : request.accepts",
|
||||
block_binding
|
||||
)
|
||||
|
||||
@order = []
|
||||
@responses = {}
|
||||
end
|
||||
|
@ -127,24 +130,33 @@ module ActionController #:nodoc:
|
|||
@order << mime_type
|
||||
|
||||
if block_given?
|
||||
@responses[mime_type] = block
|
||||
else
|
||||
@responses[mime_type] = eval(DEFAULT_BLOCKS[mime_type.to_sym], @block_binding)
|
||||
end
|
||||
end
|
||||
|
||||
for mime_type in %w( all html js xml rss atom yaml )
|
||||
eval <<-EOT
|
||||
def #{mime_type}(&block)
|
||||
custom(Mime::#{mime_type.upcase}, &block)
|
||||
@responses[mime_type] = Proc.new do
|
||||
eval "response.content_type = '#{mime_type.to_s}'", @block_binding
|
||||
block.call
|
||||
end
|
||||
EOT
|
||||
else
|
||||
if source = DEFAULT_BLOCKS[mime_type.to_sym]
|
||||
@responses[mime_type] = eval(source, @block_binding)
|
||||
else
|
||||
raise ActionController::RenderError, "Expected a block but none was given for custom mime handler #{mime_type}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def any(*args, &block)
|
||||
args.each { |type| send(type, &block) }
|
||||
end
|
||||
|
||||
def method_missing(symbol, &block)
|
||||
mime_constant = symbol.to_s.upcase
|
||||
|
||||
if Mime::SET.include?(Mime.const_get(mime_constant))
|
||||
custom(Mime.const_get(mime_constant), &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def respond
|
||||
for priority in @mime_type_priority
|
||||
if priority == Mime::ALL
|
||||
|
|
|
@ -1,5 +1,18 @@
|
|||
module Mime
|
||||
class Type #:nodoc:
|
||||
# Encapsulates the notion of a mime type. Can be used at render time, for example, with:
|
||||
#
|
||||
# class PostsController < ActionController::Base
|
||||
# def show
|
||||
# @post = Post.find(params[:id])
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html
|
||||
# format.ics { render :text => post.to_ics, :mime_type => Mime::Type["text/calendar"] }
|
||||
# format.xml { render :xml => @people.to_xml }
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
class Type
|
||||
# A simple helper class used in parsing the accept header
|
||||
class AcceptItem #:nodoc:
|
||||
attr_accessor :order, :name, :q
|
||||
|
@ -31,14 +44,20 @@ module Mime
|
|||
LOOKUP[string]
|
||||
end
|
||||
|
||||
def register(string, symbol, synonyms = [])
|
||||
Mime.send :const_set, symbol.to_s.upcase, Type.new(string, symbol, synonyms)
|
||||
SET << Mime.send(:const_get, symbol.to_s.upcase)
|
||||
LOOKUP[string] = EXTENSION_LOOKUP[symbol.to_s] = SET.last
|
||||
end
|
||||
|
||||
def parse(accept_header)
|
||||
# keep track of creation order to keep the subsequent sort stable
|
||||
index = 0
|
||||
list = accept_header.split(/,/).
|
||||
map! { |i| AcceptItem.new(index += 1, *i.split(/;\s*q=/)) }.sort!
|
||||
list = accept_header.split(/,/).map! do |i|
|
||||
AcceptItem.new(index += 1, *i.split(/;\s*q=/))
|
||||
end.sort!
|
||||
|
||||
# Take care of the broken text/xml entry by renaming or deleting it
|
||||
|
||||
text_xml = list.index("text/xml")
|
||||
app_xml = list.index("application/xml")
|
||||
|
||||
|
@ -112,31 +131,70 @@ module Mime
|
|||
end
|
||||
|
||||
ALL = Type.new "*/*", :all
|
||||
TEXT = Type.new "text/plain", :text
|
||||
HTML = Type.new "text/html", :html, %w( application/xhtml+xml )
|
||||
JS = Type.new "text/javascript", :js, %w( application/javascript application/x-javascript )
|
||||
ICS = Type.new "text/calendar", :ics
|
||||
CSV = Type.new "text/csv", :csv
|
||||
XML = Type.new "application/xml", :xml, %w( text/xml application/x-xml )
|
||||
RSS = Type.new "application/rss+xml", :rss
|
||||
ATOM = Type.new "application/atom+xml", :atom
|
||||
YAML = Type.new "application/x-yaml", :yaml, %w( text/yaml )
|
||||
JSON = Type.new "application/json", :json, %w( text/x-json )
|
||||
|
||||
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) }
|
||||
SET = [ ALL, TEXT, HTML, JS, ICS, XML, RSS, ATOM, YAML, JSON ]
|
||||
|
||||
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k == "" }
|
||||
|
||||
LOOKUP["*/*"] = ALL
|
||||
|
||||
LOOKUP["text/plain"] = TEXT
|
||||
|
||||
LOOKUP["text/html"] = HTML
|
||||
LOOKUP["application/xhtml+xml"] = HTML
|
||||
|
||||
LOOKUP["application/xml"] = XML
|
||||
LOOKUP["text/xml"] = XML
|
||||
LOOKUP["application/x-xml"] = XML
|
||||
|
||||
LOOKUP["text/javascript"] = JS
|
||||
LOOKUP["application/javascript"] = JS
|
||||
LOOKUP["application/x-javascript"] = JS
|
||||
|
||||
LOOKUP["text/calendar"] = ICS
|
||||
|
||||
LOOKUP["text/csv"] = CSV
|
||||
|
||||
LOOKUP["application/xml"] = XML
|
||||
LOOKUP["text/xml"] = XML
|
||||
LOOKUP["application/x-xml"] = XML
|
||||
|
||||
LOOKUP["text/yaml"] = YAML
|
||||
LOOKUP["application/x-yaml"] = YAML
|
||||
|
||||
LOOKUP["application/rss+xml"] = RSS
|
||||
LOOKUP["application/atom+xml"] = ATOM
|
||||
end
|
||||
|
||||
LOOKUP["application/json"] = JSON
|
||||
LOOKUP["text/x-json"] = JSON
|
||||
|
||||
|
||||
EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k == "" }
|
||||
|
||||
EXTENSION_LOOKUP["html"] = HTML
|
||||
EXTENSION_LOOKUP["xhtml"] = HTML
|
||||
|
||||
EXTENSION_LOOKUP["txt"] = TEXT
|
||||
|
||||
EXTENSION_LOOKUP["xml"] = XML
|
||||
|
||||
EXTENSION_LOOKUP["js"] = JS
|
||||
|
||||
EXTENSION_LOOKUP["ics"] = ICS
|
||||
|
||||
EXTENSION_LOOKUP["csv"] = CSV
|
||||
|
||||
EXTENSION_LOOKUP["yml"] = YAML
|
||||
EXTENSION_LOOKUP["yaml"] = YAML
|
||||
|
||||
EXTENSION_LOOKUP["rss"] = RSS
|
||||
EXTENSION_LOOKUP["atom"] = ATOM
|
||||
|
||||
EXTENSION_LOOKUP["json"] = JSON
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
module ActionController
|
||||
# === Action Pack pagination for Active Record collections
|
||||
#
|
||||
# DEPRECATION WARNING: Pagination will be separated into its own plugin with Rails 2.0.
|
||||
#
|
||||
# The Pagination module aids in the process of paging large collections of
|
||||
# Active Record objects. It offers macro-style automatic fetching of your
|
||||
# model for multiple views, or explicit fetching for single actions. And if
|
||||
|
@ -104,8 +106,7 @@ module ActionController
|
|||
# ClassMethods#paginate.
|
||||
#
|
||||
# +options+ are:
|
||||
# <tt>:singular_name</tt>:: the singular name to use, if it can't be inferred by
|
||||
# singularizing the collection name
|
||||
# <tt>:singular_name</tt>:: the singular name to use, if it can't be inferred by singularizing the collection name
|
||||
# <tt>:class_name</tt>:: the class name to use, if it can't be inferred by
|
||||
# camelizing the singular name
|
||||
# <tt>:per_page</tt>:: the maximum number of items to include in a
|
||||
|
@ -192,7 +193,7 @@ module ActionController
|
|||
|
||||
def paginator_and_collection_for(collection_id, options) #:nodoc:
|
||||
klass = options[:class_name].constantize
|
||||
page = @params[options[:parameter]]
|
||||
page = params[options[:parameter]]
|
||||
count = count_collection_for_pagination(klass, options)
|
||||
paginator = Paginator.new(self, count, options[:per_page], page)
|
||||
collection = find_collection_for_pagination(klass, options, paginator)
|
||||
|
|
|
@ -13,12 +13,18 @@ module ActionController
|
|||
@parameters ||= request_parameters.update(query_parameters).update(path_parameters).with_indifferent_access
|
||||
end
|
||||
|
||||
# Returns the HTTP request method as a lowercase symbol (:get, for example)
|
||||
# Returns the HTTP request method as a lowercase symbol (:get, for example). Note, HEAD is returned as :get
|
||||
# since the two are supposedly to be functionaly equivilent for all purposes except that HEAD won't return a response
|
||||
# body (which Rails also takes care of elsewhere).
|
||||
def method
|
||||
@request_method ||= @env['REQUEST_METHOD'].downcase.to_sym
|
||||
@request_method ||= (!parameters[:_method].blank? && @env['REQUEST_METHOD'] == 'POST') ?
|
||||
parameters[:_method].to_s.downcase.to_sym :
|
||||
@env['REQUEST_METHOD'].downcase.to_sym
|
||||
|
||||
@request_method == :head ? :get : @request_method
|
||||
end
|
||||
|
||||
# Is this a GET request? Equivalent to request.method == :get
|
||||
# Is this a GET (or HEAD) request? Equivalent to request.method == :get
|
||||
def get?
|
||||
method == :get
|
||||
end
|
||||
|
@ -38,9 +44,10 @@ module ActionController
|
|||
method == :delete
|
||||
end
|
||||
|
||||
# Is this a HEAD request? Equivalent to request.method == :head
|
||||
# Is this a HEAD request? HEAD is mapped as :get for request.method, so here we ask the
|
||||
# REQUEST_METHOD header directly. Thus, for head, both get? and head? will return true.
|
||||
def head?
|
||||
method == :head
|
||||
@env['REQUEST_METHOD'].downcase.to_sym == :head
|
||||
end
|
||||
|
||||
# Determine whether the body of a HTTP call is URL-encoded (default)
|
||||
|
@ -128,19 +135,21 @@ module ActionController
|
|||
@env['RAW_POST_DATA']
|
||||
end
|
||||
|
||||
# Returns the request URI correctly, taking into account the idiosyncracies
|
||||
# of the various servers.
|
||||
# Return the request URI, accounting for server idiosyncracies.
|
||||
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
|
||||
def request_uri
|
||||
if uri = @env['REQUEST_URI']
|
||||
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri # Remove domain, which webrick puts into the request_uri.
|
||||
else # REQUEST_URI is blank under IIS - get this from PATH_INFO and SCRIPT_NAME
|
||||
# Remove domain, which webrick puts into the request_uri.
|
||||
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
|
||||
else
|
||||
# Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
|
||||
script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
|
||||
uri = @env['PATH_INFO']
|
||||
uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil?
|
||||
unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
|
||||
uri << '?' << env_qs
|
||||
end
|
||||
uri
|
||||
@env['REQUEST_URI'] = uri
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -159,11 +168,10 @@ module ActionController
|
|||
path = (uri = request_uri) ? uri.split('?').first : ''
|
||||
|
||||
# Cut off the path to the installation directory if given
|
||||
root = relative_url_root
|
||||
path[0, root.length] = '' if root
|
||||
path || ''
|
||||
path.sub!(%r/^#{relative_url_root}/, '')
|
||||
path || ''
|
||||
end
|
||||
|
||||
|
||||
# Returns the path minus the web server relative installation directory.
|
||||
# This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
|
||||
# It can be automatically extracted for Apache setups. If the server is not
|
||||
|
|
|
@ -6,12 +6,10 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# You can tailor the rescuing behavior and appearance by overwriting the following two stub methods.
|
||||
module Rescue
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
alias_method :perform_action_without_rescue, :perform_action
|
||||
alias_method :perform_action, :perform_action_with_rescue
|
||||
alias_method_chain :perform_action, :rescue
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -36,23 +34,26 @@ module ActionController #:nodoc:
|
|||
|
||||
# Overwrite to implement custom logging of errors. By default logs as fatal.
|
||||
def log_error(exception) #:doc:
|
||||
if ActionView::TemplateError === exception
|
||||
logger.fatal(exception.to_s)
|
||||
else
|
||||
logger.fatal(
|
||||
"\n\n#{exception.class} (#{exception.message}):\n " +
|
||||
clean_backtrace(exception).join("\n ") +
|
||||
"\n\n"
|
||||
)
|
||||
ActiveSupport::Deprecation.silence do
|
||||
if ActionView::TemplateError === exception
|
||||
logger.fatal(exception.to_s)
|
||||
else
|
||||
logger.fatal(
|
||||
"\n\n#{exception.class} (#{exception.message}):\n " +
|
||||
clean_backtrace(exception).join("\n ") +
|
||||
"\n\n"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>).
|
||||
def rescue_action_in_public(exception) #:doc:
|
||||
case exception
|
||||
when RoutingError, UnknownAction then
|
||||
when RoutingError, UnknownAction
|
||||
render_text(IO.read(File.join(RAILS_ROOT, 'public', '404.html')), "404 Not Found")
|
||||
else render_text "<html><body><h1>Application error (Rails)</h1></body></html>"
|
||||
else
|
||||
render_text(IO.read(File.join(RAILS_ROOT, 'public', '500.html')), "500 Internal Error")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -60,19 +61,19 @@ module ActionController #:nodoc:
|
|||
# the remote IP being 127.0.0.1. For example, this could include the IP of the developer machine when debugging
|
||||
# remotely.
|
||||
def local_request? #:doc:
|
||||
[@request.remote_addr, @request.remote_ip] == ["127.0.0.1"] * 2
|
||||
[request.remote_addr, request.remote_ip] == ["127.0.0.1"] * 2
|
||||
end
|
||||
|
||||
# Renders a detailed diagnostics screen on action exceptions.
|
||||
def rescue_action_locally(exception)
|
||||
add_variables_to_assigns
|
||||
@template.instance_variable_set("@exception", exception)
|
||||
@template.instance_variable_set("@rescues_path", File.dirname(__FILE__) + "/templates/rescues/")
|
||||
@template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
|
||||
@template.send(:assign_variables_from_controller)
|
||||
|
||||
@template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
|
||||
|
||||
@headers["Content-Type"] = "text/html"
|
||||
response.content_type = Mime::HTML
|
||||
render_file(rescues_path("layout"), response_code_for_rescue(exception))
|
||||
end
|
||||
|
||||
|
@ -80,8 +81,8 @@ module ActionController #:nodoc:
|
|||
def perform_action_with_rescue #:nodoc:
|
||||
begin
|
||||
perform_action_without_rescue
|
||||
rescue Object => exception
|
||||
if defined?(Breakpoint) && @params["BP-RETRY"]
|
||||
rescue Exception => exception # errors from action performed
|
||||
if defined?(Breakpoint) && params["BP-RETRY"]
|
||||
msg = exception.backtrace.first
|
||||
if md = /^(.+?):(\d+)(?::in `(.+)')?$/.match(msg) then
|
||||
origin_file, origin_line = md[1], md[2].to_i
|
||||
|
@ -89,7 +90,7 @@ module ActionController #:nodoc:
|
|||
set_trace_func(lambda do |type, file, line, method, context, klass|
|
||||
if file == origin_file and line == origin_line then
|
||||
set_trace_func(nil)
|
||||
@params["BP-RETRY"] = false
|
||||
params["BP-RETRY"] = false
|
||||
|
||||
callstack = caller
|
||||
callstack.slice!(0) if callstack.first["rescue.rb"]
|
||||
|
@ -127,8 +128,10 @@ module ActionController #:nodoc:
|
|||
|
||||
def response_code_for_rescue(exception)
|
||||
case exception
|
||||
when UnknownAction, RoutingError then "404 Page Not Found"
|
||||
else "500 Internal Error"
|
||||
when UnknownAction, RoutingError
|
||||
"404 Page Not Found"
|
||||
else
|
||||
"500 Internal Error"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,15 +1,33 @@
|
|||
module ActionController
|
||||
class AbstractResponse #:nodoc:
|
||||
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
|
||||
attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params
|
||||
attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
|
||||
|
||||
def initialize
|
||||
@body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
|
||||
end
|
||||
|
||||
def content_type=(mime_type)
|
||||
@headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type
|
||||
end
|
||||
|
||||
def content_type
|
||||
content_type = String(@headers["Content-Type"]).split(";")[0]
|
||||
content_type.blank? ? nil : content_type
|
||||
end
|
||||
|
||||
def charset=(encoding)
|
||||
@headers["Content-Type"] = "#{content_type || "text/html"}; charset=#{encoding}"
|
||||
end
|
||||
|
||||
def charset
|
||||
charset = String(@headers["Content-Type"]).split(";")[1]
|
||||
charset.blank? ? nil : charset.strip.split("=")[1]
|
||||
end
|
||||
|
||||
def redirect(to_url, permanently = false)
|
||||
@headers["Status"] = "302 Found" unless @headers["Status"] == "301 Moved Permanently"
|
||||
@headers["location"] = to_url
|
||||
@headers["Location"] = to_url
|
||||
|
||||
@body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
|
||||
end
|
||||
|
|
1824
vendor/rails/actionpack/lib/action_controller/routing.rb
vendored
1824
vendor/rails/actionpack/lib/action_controller/routing.rb
vendored
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,6 @@
|
|||
module ActionController
|
||||
module Scaffolding # :nodoc:
|
||||
def self.append_features(base)
|
||||
super
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
|
@ -25,25 +24,25 @@ module ActionController
|
|||
# end
|
||||
#
|
||||
# def list
|
||||
# @entries = Entry.find_all
|
||||
# @entries = Entry.find(:all)
|
||||
# render_scaffold "list"
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def show
|
||||
# @entry = Entry.find(params[:id])
|
||||
# render_scaffold
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def destroy
|
||||
# Entry.find(params[:id]).destroy
|
||||
# redirect_to :action => "list"
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def new
|
||||
# @entry = Entry.new
|
||||
# render_scaffold
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def create
|
||||
# @entry = Entry.new(params[:entry])
|
||||
# if @entry.save
|
||||
|
@ -53,16 +52,16 @@ module ActionController
|
|||
# render_scaffold('new')
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def edit
|
||||
# @entry = Entry.find(params[:id])
|
||||
# render_scaffold
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def update
|
||||
# @entry = Entry.find(params[:id])
|
||||
# @entry.attributes = params[:entry]
|
||||
#
|
||||
#
|
||||
# if @entry.save
|
||||
# flash[:notice] = "Entry was successfully updated"
|
||||
# redirect_to :action => "show", :id => @entry
|
||||
|
@ -72,17 +71,17 @@ module ActionController
|
|||
# end
|
||||
# end
|
||||
#
|
||||
# The <tt>render_scaffold</tt> method will first check to see if you've made your own template (like "weblog/show.rhtml" for
|
||||
# the show action) and if not, then render the generic template for that action. This gives you the possibility of using the
|
||||
# scaffold while you're building your specific application. Start out with a totally generic setup, then replace one template
|
||||
# The <tt>render_scaffold</tt> method will first check to see if you've made your own template (like "weblog/show.rhtml" for
|
||||
# the show action) and if not, then render the generic template for that action. This gives you the possibility of using the
|
||||
# scaffold while you're building your specific application. Start out with a totally generic setup, then replace one template
|
||||
# and one action at a time while relying on the rest of the scaffolded templates and actions.
|
||||
module ClassMethods
|
||||
# Adds a swath of generic CRUD actions to the controller. The +model_id+ is automatically converted into a class name unless
|
||||
# one is specifically provide through <tt>options[:class_name]</tt>. So <tt>scaffold :post</tt> would use Post as the class
|
||||
# and @post/@posts for the instance variables.
|
||||
#
|
||||
#
|
||||
# It's possible to use more than one scaffold in a single controller by specifying <tt>options[:suffix] = true</tt>. This will
|
||||
# make <tt>scaffold :post, :suffix => true</tt> use method names like list_post, show_post, and create_post
|
||||
# make <tt>scaffold :post, :suffix => true</tt> use method names like list_post, show_post, and create_post
|
||||
# instead of just list, show, and post. If suffix is used, then no index method is added.
|
||||
def scaffold(model_id, options = {})
|
||||
options.assert_valid_keys(:class_name, :suffix)
|
||||
|
@ -99,13 +98,13 @@ module ActionController
|
|||
end
|
||||
end_eval
|
||||
end
|
||||
|
||||
|
||||
module_eval <<-"end_eval", __FILE__, __LINE__
|
||||
|
||||
|
||||
verify :method => :post, :only => [ :destroy#{suffix}, :create#{suffix}, :update#{suffix} ],
|
||||
:redirect_to => { :action => :list#{suffix} }
|
||||
|
||||
|
||||
|
||||
|
||||
def list#{suffix}
|
||||
@#{singular_name}_pages, @#{plural_name} = paginate :#{plural_name}, :per_page => 10
|
||||
render#{suffix}_scaffold "list#{suffix}"
|
||||
|
@ -115,17 +114,17 @@ module ActionController
|
|||
@#{singular_name} = #{class_name}.find(params[:id])
|
||||
render#{suffix}_scaffold
|
||||
end
|
||||
|
||||
|
||||
def destroy#{suffix}
|
||||
#{class_name}.find(params[:id]).destroy
|
||||
redirect_to :action => "list#{suffix}"
|
||||
end
|
||||
|
||||
|
||||
def new#{suffix}
|
||||
@#{singular_name} = #{class_name}.new
|
||||
render#{suffix}_scaffold
|
||||
end
|
||||
|
||||
|
||||
def create#{suffix}
|
||||
@#{singular_name} = #{class_name}.new(params[:#{singular_name}])
|
||||
if @#{singular_name}.save
|
||||
|
@ -135,12 +134,12 @@ module ActionController
|
|||
render#{suffix}_scaffold('new')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def edit#{suffix}
|
||||
@#{singular_name} = #{class_name}.find(params[:id])
|
||||
render#{suffix}_scaffold
|
||||
end
|
||||
|
||||
|
||||
def update#{suffix}
|
||||
@#{singular_name} = #{class_name}.find(params[:id])
|
||||
@#{singular_name}.attributes = params[:#{singular_name}]
|
||||
|
@ -152,14 +151,14 @@ module ActionController
|
|||
render#{suffix}_scaffold('edit')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def render#{suffix}_scaffold(action=nil)
|
||||
action ||= caller_method_name(caller)
|
||||
# logger.info ("testing template:" + "\#{self.class.controller_path}/\#{action}") if logger
|
||||
|
||||
|
||||
if template_exists?("\#{self.class.controller_path}/\#{action}")
|
||||
render_action(action)
|
||||
render :action => action
|
||||
else
|
||||
@scaffold_class = #{class_name}
|
||||
@scaffold_singular_name, @scaffold_plural_name = "#{singular_name}", "#{plural_name}"
|
||||
|
@ -169,9 +168,9 @@ module ActionController
|
|||
@template.instance_variable_set("@content_for_layout", @template.render_file(scaffold_path(action.sub(/#{suffix}$/, "")), false))
|
||||
|
||||
if !active_layout.nil?
|
||||
render_file(active_layout, nil, true)
|
||||
render :file => active_layout, :use_full_path => true
|
||||
else
|
||||
render_file(scaffold_path("layout"))
|
||||
render :file => scaffold_path('layout')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -179,12 +178,12 @@ module ActionController
|
|||
def scaffold_path(template_name)
|
||||
File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
|
||||
end
|
||||
|
||||
|
||||
def caller_method_name(caller)
|
||||
caller.first.scan(/`(.*)'/).first.first # ' ruby-mode
|
||||
end
|
||||
end_eval
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,6 +5,8 @@ require 'base64'
|
|||
|
||||
class CGI
|
||||
class Session
|
||||
attr_reader :data
|
||||
|
||||
# Return this session's underlying Session instance. Useful for the DB-backed session stores.
|
||||
def model
|
||||
@dbman.model if @dbman
|
||||
|
|
|
@ -26,6 +26,10 @@ class CGI #:nodoc:all
|
|||
def delete
|
||||
@@session_data.delete(@session_id)
|
||||
end
|
||||
|
||||
def data
|
||||
@@session_data[@session_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -93,6 +93,10 @@ begin
|
|||
end
|
||||
@session_data = {}
|
||||
end
|
||||
|
||||
def data
|
||||
@session_data
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,12 +8,9 @@ module ActionController #:nodoc:
|
|||
module SessionManagement #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
|
||||
base.send :alias_method, :process_without_session_management_support, :process
|
||||
base.send :alias_method, :process, :process_with_session_management_support
|
||||
|
||||
base.send :alias_method, :process_cleanup_without_session_management_support, :process_cleanup
|
||||
base.send :alias_method, :process_cleanup, :process_cleanup_with_session_management_support
|
||||
|
||||
base.send :alias_method_chain, :process, :session_management_support
|
||||
base.send :alias_method_chain, :process_cleanup, :session_management_support
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
@ -123,16 +120,16 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def process_cleanup_with_session_management_support
|
||||
process_cleanup_without_session_management_support
|
||||
clear_persistent_model_associations
|
||||
process_cleanup_without_session_management_support
|
||||
end
|
||||
|
||||
# Clear cached associations in session data so they don't overflow
|
||||
# the database field. Only applies to ActiveRecordStore since there
|
||||
# is not a standard way to iterate over session data.
|
||||
def clear_persistent_model_associations #:doc:
|
||||
if defined?(@session) && @session.instance_variables.include?('@data')
|
||||
session_data = @session.instance_variable_get('@data')
|
||||
if defined?(@_session) && @_session.respond_to?(:data)
|
||||
session_data = @_session.data
|
||||
|
||||
if session_data && session_data.respond_to?(:each_value)
|
||||
session_data.each_value do |obj|
|
||||
|
|
|
@ -69,17 +69,8 @@ module ActionController #:nodoc:
|
|||
logger.info "Streaming file #{path}" unless logger.nil?
|
||||
len = options[:buffer_size] || 4096
|
||||
File.open(path, 'rb') do |file|
|
||||
if output.respond_to?(:syswrite)
|
||||
begin
|
||||
while true
|
||||
output.syswrite(file.sysread(len))
|
||||
end
|
||||
rescue EOFError
|
||||
end
|
||||
else
|
||||
while buf = file.read(len)
|
||||
output.write(buf)
|
||||
end
|
||||
while buf = file.read(len)
|
||||
output.write(buf)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
@ -97,8 +88,8 @@ module ActionController #:nodoc:
|
|||
# * <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>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
||||
# 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
|
||||
|
@ -125,10 +116,10 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
disposition = options[:disposition].dup || 'attachment'
|
||||
|
||||
|
||||
disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
|
||||
|
||||
@headers.update(
|
||||
headers.update(
|
||||
'Content-Length' => options[:length],
|
||||
'Content-Type' => options[:type].strip, # fixes a problem with extra '\r' with some browsers
|
||||
'Content-Disposition' => disposition,
|
||||
|
@ -141,7 +132,7 @@ module ActionController #:nodoc:
|
|||
# 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'
|
||||
headers['Cache-Control'] = 'private' if headers['Cache-Control'] == 'no-cache'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
<% if false %>
|
||||
<br /><br />
|
||||
<% begin %>
|
||||
<%= form_tag(@request.request_uri, "method" => @request.method) %>
|
||||
<%= form_tag(request.request_uri, "method" => request.method) %>
|
||||
<input type="hidden" name="BP-RETRY" value="1" />
|
||||
|
||||
<% for key, values in @params %>
|
||||
<% for key, values in params %>
|
||||
<% next if key == "BP-RETRY" %>
|
||||
<% for value in Array(values) %>
|
||||
<input type="hidden" name="<%= key %>" value="<%= value %>" />
|
||||
|
@ -26,7 +26,7 @@
|
|||
<% end %>
|
||||
|
||||
<%
|
||||
request_parameters_without_action = @request.parameters.clone
|
||||
request_parameters_without_action = request.parameters.clone
|
||||
request_parameters_without_action.delete("action")
|
||||
request_parameters_without_action.delete("controller")
|
||||
|
||||
|
@ -37,8 +37,8 @@
|
|||
<p><b>Parameters</b>: <%=h request_dump == "{}" ? "None" : request_dump %></p>
|
||||
|
||||
<p><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></p>
|
||||
<div id="session_dump" style="display:none"><%= debug(@request.session.instance_variable_get("@data")) %></div>
|
||||
<div id="session_dump" style="display:none"><%= debug(request.session.instance_variable_get("@data")) %></div>
|
||||
|
||||
|
||||
<h2 style="margin-top: 30px">Response</h2>
|
||||
<b>Headers</b>: <%=h @response.headers.inspect.gsub(/,/, ",\n") %><br/>
|
||||
<b>Headers</b>: <%=h response ? response.headers.inspect.gsub(/,/, ",\n") : "None" %><br/>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<h1>
|
||||
<%=h @exception.class.to_s %>
|
||||
<% if @request.parameters['controller'] %>
|
||||
in <%=h @request.parameters['controller'].humanize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
|
||||
<% if request.parameters['controller'] %>
|
||||
in <%=h request.parameters['controller'].humanize %>Controller<% if request.parameters['action'] %>#<%=h request.parameters['action'] %><% end %>
|
||||
<% end %>
|
||||
</h1>
|
||||
<pre><%=h @exception.clean_message %></pre>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<h1>Routing Error</h1>
|
||||
<p><pre><%=h @exception.message %></pre></p>
|
||||
<% unless @exception.failures.empty? %><p>
|
||||
<h2>Failure reasons:</h2>
|
||||
<ol>
|
||||
<% @exception.failures.each do |route, reason| %>
|
||||
<li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
|
||||
<h2>Failure reasons:</h2>
|
||||
<ol>
|
||||
<% @exception.failures.each do |route, reason| %>
|
||||
<li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
|
||||
<% end %>
|
||||
</ol>
|
||||
</p><% end %>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<h1>
|
||||
<%=h @exception.original_exception.class.to_s %> in
|
||||
<%=h @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%=h @request.parameters["action"] %>
|
||||
<%=h request.parameters["controller"].capitalize if request.parameters["controller"]%>#<%=h request.parameters["action"] %>
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<% end %>
|
||||
<td><%= link_to "Show", :action => "show#{@scaffold_suffix}", :id => entry %></td>
|
||||
<td><%= link_to "Edit", :action => "edit#{@scaffold_suffix}", :id => entry %></td>
|
||||
<td><%= link_to "Destroy", {:action => "destroy#{@scaffold_suffix}", :id => entry}, { :confirm => "Are you sure?", :post => true} %></td>
|
||||
<td><%= link_to "Destroy", {:action => "destroy#{@scaffold_suffix}", :id => entry}, { :confirm => "Are you sure?", :method => :post } %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require File.dirname(__FILE__) + '/assertions'
|
||||
require File.dirname(__FILE__) + '/deprecated_assertions'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class Base
|
||||
|
@ -18,8 +17,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
alias_method :process_without_test, :process
|
||||
alias_method :process, :process_with_test
|
||||
alias_method_chain :process, :test
|
||||
end
|
||||
|
||||
class TestRequest < AbstractRequest #:nodoc:
|
||||
|
@ -39,8 +37,8 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def reset_session
|
||||
@session = {}
|
||||
end
|
||||
@session = TestSession.new
|
||||
end
|
||||
|
||||
def raw_post
|
||||
if raw_post = env['RAW_POST_DATA']
|
||||
|
@ -79,6 +77,10 @@ module ActionController #:nodoc:
|
|||
@path = uri.split("?").first
|
||||
end
|
||||
|
||||
def accept=(mime_types)
|
||||
@env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
|
||||
end
|
||||
|
||||
def remote_addr=(addr)
|
||||
@env['REMOTE_ADDR'] = addr
|
||||
end
|
||||
|
@ -103,7 +105,7 @@ module ActionController #:nodoc:
|
|||
if value.is_a? Fixnum
|
||||
value = value.to_s
|
||||
elsif value.is_a? Array
|
||||
value = ActionController::Routing::PathComponent::Result.new(value)
|
||||
value = ActionController::Routing::PathSegment::Result.new(value)
|
||||
end
|
||||
|
||||
if extra_keys.include?(key.to_sym)
|
||||
|
@ -112,6 +114,7 @@ module ActionController #:nodoc:
|
|||
path_parameters[key.to_s] = value
|
||||
end
|
||||
end
|
||||
@parameters = nil # reset TestRequest#parameters to use the new path_parameters
|
||||
end
|
||||
|
||||
def recycle!
|
||||
|
@ -176,7 +179,7 @@ module ActionController #:nodoc:
|
|||
|
||||
# returns the redirection location or nil
|
||||
def redirect_url
|
||||
redirect? ? headers['location'] : nil
|
||||
headers['Location']
|
||||
end
|
||||
|
||||
# does the redirect location match this regexp pattern?
|
||||
|
@ -272,27 +275,40 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
class TestSession #:nodoc:
|
||||
def initialize(attributes = {})
|
||||
attr_accessor :session_id
|
||||
|
||||
def initialize(attributes = nil)
|
||||
@session_id = ''
|
||||
@attributes = attributes
|
||||
@saved_attributes = nil
|
||||
end
|
||||
|
||||
def data
|
||||
@attributes ||= @saved_attributes || {}
|
||||
end
|
||||
|
||||
def [](key)
|
||||
@attributes[key]
|
||||
data[key.to_s]
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@attributes[key] = value
|
||||
data[key.to_s] = value
|
||||
end
|
||||
|
||||
def session_id
|
||||
""
|
||||
def update
|
||||
@saved_attributes = @attributes
|
||||
end
|
||||
|
||||
def update() end
|
||||
def close() end
|
||||
def delete() @attributes = {} end
|
||||
def delete
|
||||
@attributes = nil
|
||||
end
|
||||
|
||||
def close
|
||||
update
|
||||
delete
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Essentially generates a modified Tempfile object similar to the object
|
||||
# you'd get from the standard library CGI module in a multipart
|
||||
# request. This means you can use an ActionController::TestUploadedFile
|
||||
|
@ -301,6 +317,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# Usage example, within a functional test:
|
||||
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
|
||||
require 'tempfile'
|
||||
class TestUploadedFile
|
||||
# The filename, *not* including the path, of the "uploaded" file
|
||||
attr_reader :original_filename
|
||||
|
@ -309,7 +326,7 @@ module ActionController #:nodoc:
|
|||
attr_reader :content_type
|
||||
|
||||
def initialize(path, content_type = 'text/plain')
|
||||
raise "file does not exist" unless File.exist?(path)
|
||||
raise "#{path} file does not exist" unless File.exist?(path)
|
||||
@content_type = content_type
|
||||
@original_filename = path.sub(/^.*#{File::SEPARATOR}([^#{File::SEPARATOR}]+)$/) { $1 }
|
||||
@tempfile = Tempfile.new(@original_filename)
|
||||
|
@ -333,7 +350,7 @@ module ActionController #:nodoc:
|
|||
%w( get post put delete head ).each do |method|
|
||||
base.class_eval <<-EOV, __FILE__, __LINE__
|
||||
def #{method}(action, parameters = nil, session = nil, flash = nil)
|
||||
@request.env['REQUEST_METHOD'] = "#{method.upcase}" if @request
|
||||
@request.env['REQUEST_METHOD'] = "#{method.upcase}" if defined?(@request)
|
||||
process(action, parameters, session, flash)
|
||||
end
|
||||
EOV
|
||||
|
@ -344,8 +361,10 @@ module ActionController #:nodoc:
|
|||
def process(action, parameters = nil, session = nil, flash = nil)
|
||||
# Sanity check for required instance variables so we can give an
|
||||
# understandable error message.
|
||||
%w(controller request response).each do |iv_name|
|
||||
raise "@#{iv_name} is nil: make sure you set it in your test's setup method." if instance_variable_get("@#{iv_name}").nil?
|
||||
%w(@controller @request @response).each do |iv_name|
|
||||
if !instance_variables.include?(iv_name) || instance_variable_get(iv_name).nil?
|
||||
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
|
||||
end
|
||||
end
|
||||
|
||||
@request.recycle!
|
||||
|
@ -374,8 +393,9 @@ module ActionController #:nodoc:
|
|||
alias xhr :xml_http_request
|
||||
|
||||
def follow_redirect
|
||||
if @response.redirected_to[:controller]
|
||||
raise "Can't follow redirects outside of current controller (#{@response.redirected_to[:controller]})"
|
||||
redirected_controller = @response.redirected_to[:controller]
|
||||
if redirected_controller && redirected_controller != @controller.controller_name
|
||||
raise "Can't follow redirects outside of current controller (from #{@controller.controller_name} to #{redirected_controller})"
|
||||
end
|
||||
|
||||
get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys)
|
||||
|
@ -428,7 +448,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def method_missing(selector, *args)
|
||||
return @controller.send(selector, *args) if ActionController::Routing::NamedRoutes::Helpers.include?(selector)
|
||||
return @controller.send(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
|
||||
return super
|
||||
end
|
||||
|
||||
|
@ -448,13 +468,15 @@ module ActionController #:nodoc:
|
|||
# The new instance is yielded to the passed block. Typically the block
|
||||
# will create some routes using map.draw { map.connect ... }:
|
||||
#
|
||||
# with_routing do |set|
|
||||
# set.draw { set.connect ':controller/:id/:action' }
|
||||
# assert_equal(
|
||||
# ['/content/10/show', {}],
|
||||
# set.generate(:controller => 'content', :id => 10, :action => 'show')
|
||||
# )
|
||||
# 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,13 +1,71 @@
|
|||
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.
|
||||
mattr_accessor :default_url_options
|
||||
self.default_url_options = {}
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
ActionController::Routing::Routes.named_routes.install base
|
||||
base.mattr_accessor :default_url_options
|
||||
base.default_url_options ||= default_url_options
|
||||
end
|
||||
|
||||
# Generate a url with the provided options. The following special options may
|
||||
# effect the constructed url:
|
||||
#
|
||||
# * :host Specifies the host the link should be targetted at. This option
|
||||
# must be provided either explicitly, or via default_url_options.
|
||||
# * :protocol The protocol to connect to. Defaults to 'http'
|
||||
# * :port Optionally specify the port to connect to.
|
||||
#
|
||||
def url_for(options)
|
||||
options = self.class.default_url_options.merge(options)
|
||||
|
||||
url = ''
|
||||
unless options.delete :only_path
|
||||
url << (options.delete(:protocol) || 'http')
|
||||
url << '://'
|
||||
|
||||
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 }
|
||||
end
|
||||
url << Routing::Routes.generate(options, {})
|
||||
return 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, :trailing_slash, :skip_relative_url_root]
|
||||
def initialize(request, parameters)
|
||||
@request, @parameters = request, parameters
|
||||
end
|
||||
|
||||
def rewrite(options = {})
|
||||
def rewrite(options = {})
|
||||
rewrite_url(rewrite_path(options), options)
|
||||
end
|
||||
|
||||
|
@ -41,34 +99,10 @@ module ActionController
|
|||
options.update(overwrite)
|
||||
end
|
||||
RESERVED_OPTIONS.each {|k| options.delete k}
|
||||
path, extra_keys = Routing::Routes.generate(options.dup, @request) # Warning: Routes will mutate and violate the options hash
|
||||
|
||||
path << build_query_string(options, extra_keys) unless extra_keys.empty?
|
||||
|
||||
path
|
||||
end
|
||||
|
||||
# Returns a query string with escaped keys and values from the passed hash. If the passed hash contains an "id" it'll
|
||||
# be added as a path element instead of a regular parameter pair.
|
||||
def build_query_string(hash, only_keys = nil)
|
||||
elements = []
|
||||
query_string = ""
|
||||
|
||||
only_keys ||= hash.keys
|
||||
|
||||
only_keys.each do |key|
|
||||
value = hash[key]
|
||||
key = CGI.escape key.to_s
|
||||
if value.class == Array
|
||||
key << '[]'
|
||||
else
|
||||
value = [ value ]
|
||||
end
|
||||
value.each { |val| elements << "#{key}=#{Routing.extract_parameter_value(val)}" }
|
||||
end
|
||||
|
||||
query_string << ("?" + elements.join("&")) unless elements.empty?
|
||||
query_string
|
||||
# Generates the query string, too
|
||||
Routing::Routes.generate(options, @request.symbolized_path_parameters)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require File.dirname(__FILE__) + '/tokenizer'
|
||||
require File.dirname(__FILE__) + '/node'
|
||||
require File.dirname(__FILE__) + '/selector'
|
||||
|
||||
module HTML #:nodoc:
|
||||
|
||||
|
|
|
@ -92,7 +92,6 @@ module HTML #:nodoc:
|
|||
# returns non +nil+. Returns the result of the #find call that succeeded.
|
||||
def find(conditions)
|
||||
conditions = validate_conditions(conditions)
|
||||
|
||||
@children.each do |child|
|
||||
node = child.find(conditions)
|
||||
return node if node
|
||||
|
@ -152,11 +151,11 @@ module HTML #:nodoc:
|
|||
|
||||
if scanner.skip(/!\[CDATA\[/)
|
||||
scanner.scan_until(/\]\]>/)
|
||||
return CDATA.new(parent, line, pos, scanner.pre_match)
|
||||
return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
|
||||
end
|
||||
|
||||
closing = ( scanner.scan(/\//) ? :close : nil )
|
||||
return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:]+/)
|
||||
return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:-]+/)
|
||||
name.downcase!
|
||||
|
||||
unless closing
|
||||
|
@ -239,7 +238,7 @@ module HTML #:nodoc:
|
|||
def match(conditions)
|
||||
case conditions
|
||||
when String
|
||||
@content.index(conditions)
|
||||
@content == conditions
|
||||
when Regexp
|
||||
@content =~ conditions
|
||||
when Hash
|
||||
|
@ -316,7 +315,7 @@ module HTML #:nodoc:
|
|||
s = "<#{@name}"
|
||||
@attributes.each do |k,v|
|
||||
s << " #{k}"
|
||||
s << "='#{v.gsub(/'/,"\\\\'")}'" if String === v
|
||||
s << "=\"#{v}\"" if String === v
|
||||
end
|
||||
s << " /" if @closing == :self
|
||||
s << ">"
|
||||
|
@ -410,7 +409,6 @@ module HTML #:nodoc:
|
|||
# :child => /hello world/ }
|
||||
def match(conditions)
|
||||
conditions = validate_conditions(conditions)
|
||||
|
||||
# check content of child nodes
|
||||
if conditions[:content]
|
||||
if children.empty?
|
||||
|
@ -455,7 +453,6 @@ module HTML #:nodoc:
|
|||
# count children
|
||||
if opts = conditions[:children]
|
||||
matches = children.select do |c|
|
||||
c.match(/./) or
|
||||
(c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
module ActionController #:nodoc:
|
||||
module Verification #:nodoc:
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
|
@ -18,19 +17,26 @@ module ActionController #:nodoc:
|
|||
# Usage:
|
||||
#
|
||||
# class GlobalController < ActionController::Base
|
||||
# # prevent the #update_settings action from being invoked unless
|
||||
# # the 'admin_privileges' request parameter exists.
|
||||
# # Prevent the #update_settings action from being invoked unless
|
||||
# # the 'admin_privileges' request parameter exists. The
|
||||
# # settings action will be redirected to in current controller
|
||||
# # if verification fails.
|
||||
# verify :params => "admin_privileges", :only => :update_post,
|
||||
# :redirect_to => { :action => "settings" }
|
||||
#
|
||||
# # disallow a post from being updated if there was no information
|
||||
# # Disallow a post from being updated if there was no information
|
||||
# # submitted with the post, and if there is no active post in the
|
||||
# # session, and if there is no "note" key in the flash.
|
||||
# # session, and if there is no "note" key in the flash. The route
|
||||
# # named category_url will be redirected to if verification fails.
|
||||
#
|
||||
# verify :params => "post", :session => "post", "flash" => "note",
|
||||
# :only => :update_post,
|
||||
# :add_flash => { "alert" => "Failed to create your message" },
|
||||
# :redirect_to => :category_url
|
||||
#
|
||||
# Note that these prerequisites are not business rules. They do not examine
|
||||
# the content of the session or the parameters. That level of validation should
|
||||
# be encapsulated by your domain model or helper methods in the controller.
|
||||
module ClassMethods
|
||||
# Verify the given actions so that if certain prerequisites are not met,
|
||||
# the user is redirected to a different action. The +options+ parameter
|
||||
|
@ -40,7 +46,7 @@ module ActionController #:nodoc:
|
|||
# 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 @session in order for the action(s) to be safely called.
|
||||
# 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
|
||||
|
@ -51,8 +57,12 @@ module ActionController #:nodoc:
|
|||
# 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.
|
||||
# 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
|
||||
|
@ -69,19 +79,20 @@ module ActionController #:nodoc:
|
|||
|
||||
def verify_action(options) #:nodoc:
|
||||
prereqs_invalid =
|
||||
[*options[:params] ].find { |v| @params[v].nil? } ||
|
||||
[*options[:session]].find { |v| @session[v].nil? } ||
|
||||
[*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 }
|
||||
[*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]
|
||||
unless performed?
|
||||
render(options[:render]) if options[:render]
|
||||
redirect_to(options[:redirect_to]) if options[:redirect_to]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue