Upgrade to Rails 2.0.2
Upgraded to Rails 2.0.2, except that we maintain vendor/rails/actionpack/lib/action_controller/routing.rb from Rail 1.2.6 (at least for now), so that Routes don't change. We still get to enjoy Rails's many new features. Also fixed a bug in Chunk-handling: disable WikiWord processing in tags (for real this time).
This commit is contained in:
parent
0f6889e09f
commit
6873fc8026
1083 changed files with 52810 additions and 41058 deletions
|
@ -1,4 +1,3 @@
|
|||
require 'test/unit'
|
||||
require 'test/unit/assertions'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
|
@ -9,7 +8,7 @@ module ActionController #:nodoc:
|
|||
# * 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
|
||||
|
@ -17,7 +16,7 @@ module ActionController #:nodoc:
|
|||
# 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.
|
||||
# appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
|
||||
# So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
|
||||
#
|
||||
# On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
|
||||
|
@ -39,44 +38,32 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# == 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:
|
||||
# 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
|
||||
%w(response selector tag dom routing model).each do |kind|
|
||||
require "action_controller/assertions/#{kind}_assertions"
|
||||
klass.module_eval { include const_get("#{kind.camelize}Assertions") }
|
||||
end
|
||||
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}/ }
|
||||
rescue Test::Unit::AssertionFailedError => error
|
||||
framework_path = Regexp.new(File.expand_path("#{File.dirname(__FILE__)}/assertions"))
|
||||
error.backtrace.reject! { |line| File.expand_path(line) =~ framework_path }
|
||||
raise
|
||||
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
|
||||
end
|
||||
|
|
|
@ -1,228 +0,0 @@
|
|||
require 'rexml/document'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
module Assertions #:nodoc:
|
||||
module DeprecatedAssertions #:nodoc:
|
||||
def assert_success(message=nil) #:nodoc:
|
||||
assert_response(:success, message)
|
||||
end
|
||||
deprecate :assert_success => "use assert_response(:success)"
|
||||
|
||||
def assert_redirect(message=nil) #:nodoc:
|
||||
assert_response(:redirect, message)
|
||||
end
|
||||
deprecate :assert_redirect => "use assert_response(:redirect)"
|
||||
|
||||
def assert_rendered_file(expected=nil, message=nil) #:nodoc:
|
||||
assert_template(expected, message)
|
||||
end
|
||||
deprecate :assert_rendered_file => :assert_template
|
||||
|
||||
# ensure that the session has an object with the specified name
|
||||
def assert_session_has(key=nil, message=nil) #:nodoc:
|
||||
msg = build_message(message, "<?> is not in the session <?>", key, @response.session)
|
||||
assert_block(msg) { @response.has_session_object?(key) }
|
||||
end
|
||||
deprecate :assert_session_has => "use assert(@response.has_session_object?(key))"
|
||||
|
||||
# ensure that the session has no object with the specified name
|
||||
def assert_session_has_no(key=nil, message=nil) #:nodoc:
|
||||
msg = build_message(message, "<?> is in the session <?>", key, @response.session)
|
||||
assert_block(msg) { !@response.has_session_object?(key) }
|
||||
end
|
||||
deprecate :assert_session_has_no => "use assert(!@response.has_session_object?(key))"
|
||||
|
||||
def assert_session_equal(expected = nil, key = nil, message = nil) #:nodoc:
|
||||
msg = build_message(message, "<?> expected in session['?'] but was <?>", expected, key, @response.session[key])
|
||||
assert_block(msg) { expected == @response.session[key] }
|
||||
end
|
||||
deprecate :assert_session_equal => "use assert_equal(expected, @response[key])"
|
||||
|
||||
# -- cookie assertions ---------------------------------------------------
|
||||
|
||||
def assert_no_cookie(key = nil, message = nil) #:nodoc:
|
||||
actual = @response.cookies[key]
|
||||
msg = build_message(message, "<?> not expected in cookies['?']", actual, key)
|
||||
assert_block(msg) { actual.nil? or actual.empty? }
|
||||
end
|
||||
deprecate :assert_no_cookie => "use assert(!@response.cookies.key?(key))"
|
||||
|
||||
def assert_cookie_equal(expected = nil, key = nil, message = nil) #:nodoc:
|
||||
actual = @response.cookies[key]
|
||||
actual = actual.first if actual
|
||||
msg = build_message(message, "<?> expected in cookies['?'] but was <?>", expected, key, actual)
|
||||
assert_block(msg) { expected == actual }
|
||||
end
|
||||
deprecate :assert_cookie_equal => "use assert(@response.cookies.key?(key))"
|
||||
|
||||
# -- flash assertions ---------------------------------------------------
|
||||
|
||||
# ensure that the flash has an object with the specified name
|
||||
def assert_flash_has(key=nil, message=nil) #:nodoc:
|
||||
msg = build_message(message, "<?> is not in the flash <?>", key, @response.flash)
|
||||
assert_block(msg) { @response.has_flash_object?(key) }
|
||||
end
|
||||
deprecate :assert_flash_has => "use assert(@response.has_flash_object?(key))"
|
||||
|
||||
# ensure that the flash has no object with the specified name
|
||||
def assert_flash_has_no(key=nil, message=nil) #:nodoc:
|
||||
msg = build_message(message, "<?> is in the flash <?>", key, @response.flash)
|
||||
assert_block(msg) { !@response.has_flash_object?(key) }
|
||||
end
|
||||
deprecate :assert_flash_has_no => "use assert(!@response.has_flash_object?(key))"
|
||||
|
||||
# ensure the flash exists
|
||||
def assert_flash_exists(message=nil) #:nodoc:
|
||||
msg = build_message(message, "the flash does not exist <?>", @response.session['flash'] )
|
||||
assert_block(msg) { @response.has_flash? }
|
||||
end
|
||||
deprecate :assert_flash_exists => "use assert(@response.has_flash?)"
|
||||
|
||||
# ensure the flash does not exist
|
||||
def assert_flash_not_exists(message=nil) #:nodoc:
|
||||
msg = build_message(message, "the flash exists <?>", @response.flash)
|
||||
assert_block(msg) { !@response.has_flash? }
|
||||
end
|
||||
deprecate :assert_flash_not_exists => "use assert(!@response.has_flash?)"
|
||||
|
||||
# ensure the flash is empty but existent
|
||||
def assert_flash_empty(message=nil) #:nodoc:
|
||||
msg = build_message(message, "the flash is not empty <?>", @response.flash)
|
||||
assert_block(msg) { !@response.has_flash_with_contents? }
|
||||
end
|
||||
deprecate :assert_flash_empty => "use assert(!@response.has_flash_with_contents?)"
|
||||
|
||||
# ensure the flash is not empty
|
||||
def assert_flash_not_empty(message=nil) #:nodoc:
|
||||
msg = build_message(message, "the flash is empty")
|
||||
assert_block(msg) { @response.has_flash_with_contents? }
|
||||
end
|
||||
deprecate :assert_flash_not_empty => "use assert(@response.has_flash_with_contents?)"
|
||||
|
||||
def assert_flash_equal(expected = nil, key = nil, message = nil) #:nodoc:
|
||||
msg = build_message(message, "<?> expected in flash['?'] but was <?>", expected, key, @response.flash[key])
|
||||
assert_block(msg) { expected == @response.flash[key] }
|
||||
end
|
||||
deprecate :assert_flash_equal => "use assert_equal(expected, @response.flash[key])"
|
||||
|
||||
|
||||
# ensure our redirection url is an exact match
|
||||
def assert_redirect_url(url=nil, message=nil) #:nodoc:
|
||||
assert_redirect(message)
|
||||
msg = build_message(message, "<?> is not the redirected location <?>", url, @response.redirect_url)
|
||||
assert_block(msg) { @response.redirect_url == url }
|
||||
end
|
||||
deprecate :assert_redirect_url => "use assert_equal(url, @response.redirect_url)"
|
||||
|
||||
# ensure our redirection url matches a pattern
|
||||
def assert_redirect_url_match(pattern=nil, message=nil) #:nodoc:
|
||||
assert_redirect(message)
|
||||
msg = build_message(message, "<?> was not found in the location: <?>", pattern, @response.redirect_url)
|
||||
assert_block(msg) { @response.redirect_url_match?(pattern) }
|
||||
end
|
||||
deprecate :assert_redirect_url_match => "use assert(@response.redirect_url_match?(pattern))"
|
||||
|
||||
|
||||
# -- template assertions ------------------------------------------------
|
||||
|
||||
# ensure that a template object with the given name exists
|
||||
def assert_template_has(key=nil, message=nil) #:nodoc:
|
||||
msg = build_message(message, "<?> is not a template object", key )
|
||||
assert_block(msg) { @response.has_template_object?(key) }
|
||||
end
|
||||
deprecate :assert_template_has => "use assert(@response.has_template_object?(key))"
|
||||
|
||||
# ensure that a template object with the given name does not exist
|
||||
def assert_template_has_no(key=nil,message=nil) #:nodoc:
|
||||
msg = build_message(message, "<?> is a template object <?>", key, @response.template_objects[key])
|
||||
assert_block(msg) { !@response.has_template_object?(key) }
|
||||
end
|
||||
deprecate :assert_template_has_no => "use assert(!@response.has_template_object?(key))"
|
||||
|
||||
# ensures that the object assigned to the template on +key+ is equal to +expected+ object.
|
||||
def assert_template_equal(expected = nil, key = nil, message = nil) #:nodoc:
|
||||
msg = build_message(message, "<?> expected in assigns['?'] but was <?>", expected, key, @response.template.assigns[key.to_s])
|
||||
assert_block(msg) { expected == @response.template.assigns[key.to_s] }
|
||||
end
|
||||
alias_method :assert_assigned_equal, :assert_template_equal
|
||||
deprecate :assert_assigned_equal => "use assert_equal(expected, @response.template.assigns[key.to_s])"
|
||||
deprecate :assert_template_equal => "use assert_equal(expected, @response.template.assigns[key.to_s])"
|
||||
|
||||
# Asserts that the template returns the +expected+ string or array based on the XPath +expression+.
|
||||
# This will only work if the template rendered a valid XML document.
|
||||
def assert_template_xpath_match(expression=nil, expected=nil, message=nil) #:nodoc:
|
||||
xml, matches = REXML::Document.new(@response.body), []
|
||||
xml.elements.each(expression) { |e| matches << e.text }
|
||||
if matches.empty? then
|
||||
msg = build_message(message, "<?> not found in document", expression)
|
||||
flunk(msg)
|
||||
return
|
||||
elsif matches.length < 2 then
|
||||
matches = matches.first
|
||||
end
|
||||
|
||||
msg = build_message(message, "<?> found <?>, not <?>", expression, matches, expected)
|
||||
assert_block(msg) { matches == expected }
|
||||
end
|
||||
deprecate :assert_template_xpath_match => "you should use assert_tag, instead"
|
||||
|
||||
# Assert the template object with the given name is an Active Record descendant and is valid.
|
||||
def assert_valid_record(key = nil, message = nil) #:nodoc:
|
||||
record = find_record_in_template(key)
|
||||
msg = build_message(message, "Active Record is invalid <?>)", record.errors.full_messages)
|
||||
assert_block(msg) { record.valid? }
|
||||
end
|
||||
deprecate :assert_valid_record => "use assert(assigns(key).valid?)"
|
||||
|
||||
# Assert the template object with the given name is an Active Record descendant and is invalid.
|
||||
def assert_invalid_record(key = nil, message = nil) #:nodoc:
|
||||
record = find_record_in_template(key)
|
||||
msg = build_message(message, "Active Record is valid)")
|
||||
assert_block(msg) { !record.valid? }
|
||||
end
|
||||
deprecate :assert_invalid_record => "use assert(!assigns(key).valid?)"
|
||||
|
||||
# Assert the template object with the given name is an Active Record descendant and the specified column(s) are valid.
|
||||
def assert_valid_column_on_record(key = nil, columns = "", message = nil) #:nodoc:
|
||||
record = find_record_in_template(key)
|
||||
record.send(:validate)
|
||||
|
||||
cols = glue_columns(columns)
|
||||
cols.delete_if { |col| !record.errors.invalid?(col) }
|
||||
msg = build_message(message, "Active Record has invalid columns <?>)", cols.join(",") )
|
||||
assert_block(msg) { cols.empty? }
|
||||
end
|
||||
deprecate :assert_valid_column_on_record => "use assert(!record.errors.invalid?(column)) instead"
|
||||
|
||||
# Assert the template object with the given name is an Active Record descendant and the specified column(s) are invalid.
|
||||
def assert_invalid_column_on_record(key = nil, columns = "", message = nil) #:nodoc:
|
||||
record = find_record_in_template(key)
|
||||
record.send(:validate)
|
||||
|
||||
cols = glue_columns(columns)
|
||||
cols.delete_if { |col| record.errors.invalid?(col) }
|
||||
msg = build_message(message, "Active Record has valid columns <?>)", cols.join(",") )
|
||||
assert_block(msg) { cols.empty? }
|
||||
end
|
||||
deprecate :assert_invalid_column_on_record => "use assert(record.errors.invalid?(column)) instead"
|
||||
|
||||
private
|
||||
def glue_columns(columns)
|
||||
cols = []
|
||||
cols << columns if columns.class == String
|
||||
cols += columns if columns.class == Array
|
||||
cols
|
||||
end
|
||||
|
||||
def find_record_in_template(key = nil)
|
||||
assert_not_nil assigns(key)
|
||||
record = @response.template_objects[key]
|
||||
|
||||
assert_not_nil(record)
|
||||
assert_kind_of ActiveRecord::Base, record
|
||||
|
||||
return record
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,24 +2,38 @@ module ActionController
|
|||
module Assertions
|
||||
module DomAssertions
|
||||
# Test two HTML strings for equivalency (e.g., identical up to reordering of attributes)
|
||||
def assert_dom_equal(expected, actual, message="")
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that the referenced method generates the appropriate HTML string
|
||||
# assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
|
||||
#
|
||||
def assert_dom_equal(expected, actual, message = "")
|
||||
clean_backtrace do
|
||||
expected_dom = HTML::Document.new(expected).root
|
||||
actual_dom = HTML::Document.new(actual).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
|
||||
|
||||
# The negated form of +assert_dom_equivalent+.
|
||||
def assert_dom_not_equal(expected, actual, message="")
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that the referenced method does not generate the specified HTML string
|
||||
# assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
|
||||
#
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,13 @@ module ActionController
|
|||
module Assertions
|
||||
module ModelAssertions
|
||||
# Ensures that the passed record is valid by ActiveRecord standards and returns any error messages if it is not.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that a newly created record is valid
|
||||
# model = Model.new
|
||||
# assert_valid(model)
|
||||
#
|
||||
def assert_valid(record)
|
||||
clean_backtrace do
|
||||
assert record.valid?, record.errors.full_messages.join("\n")
|
||||
|
@ -9,4 +16,4 @@ module ActionController
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,19 +1,29 @@
|
|||
require 'rexml/document'
|
||||
require File.dirname(__FILE__) + "/../vendor/html-scanner/html/document"
|
||||
require 'html/document'
|
||||
|
||||
module ActionController
|
||||
module Assertions
|
||||
# A small suite of assertions that test responses from Rails applications.
|
||||
module ResponseAssertions
|
||||
# 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
|
||||
# * <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 number like assert_response(501)
|
||||
# or its symbolic equivalent assert_response(:not_implemented).
|
||||
# See ActionController::StatusCodes for a full list.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that the response was a redirection
|
||||
# assert_response :redirect
|
||||
#
|
||||
# # assert that the response code was status code 401 (unauthorized)
|
||||
# assert_response 401
|
||||
#
|
||||
def assert_response(type, message = nil)
|
||||
clean_backtrace do
|
||||
if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
|
||||
|
@ -28,9 +38,18 @@ module ActionController
|
|||
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.
|
||||
# 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.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that the redirection was to the "index" action on the WeblogController
|
||||
# assert_redirected_to :controller => "weblog", :action => "index"
|
||||
#
|
||||
# # assert that the redirection was to the named route login_url
|
||||
# assert_redirected_to login_url
|
||||
#
|
||||
def assert_redirected_to(options = {}, message=nil)
|
||||
clean_backtrace do
|
||||
assert_response(:redirect, message)
|
||||
|
@ -79,10 +98,8 @@ module ActionController
|
|||
url[key] = value
|
||||
end
|
||||
|
||||
|
||||
@response_diff = url[:expected].diff(url[:actual]) if url[:actual]
|
||||
msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is <?>), difference: <?>",
|
||||
url[:actual], @response_diff)
|
||||
@response_diff = url[:actual].diff(url[:expected]) if url[:actual]
|
||||
msg = build_message(message, "expected a redirect to <?>, found one to <?>, a difference of <?> ", url[:expected], url[:actual], @response_diff)
|
||||
|
||||
assert_block(msg) do
|
||||
url[:expected].keys.all? do |k|
|
||||
|
@ -106,6 +123,12 @@ module ActionController
|
|||
end
|
||||
|
||||
# Asserts that the request was rendered with the appropriate template file.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # assert that the "new" view template was rendered
|
||||
# assert_template "new"
|
||||
#
|
||||
def assert_template(expected = nil, message=nil)
|
||||
clean_backtrace do
|
||||
rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
|
||||
|
|
|
@ -1,25 +1,41 @@
|
|||
module ActionController
|
||||
module Assertions
|
||||
# Suite of assertions to test routes generated by Rails and the handling of requests made to them.
|
||||
module RoutingAssertions
|
||||
# Asserts that the routing of the given path was handled correctly and that the parsed options match.
|
||||
# Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
|
||||
# match +path+. Basically, it asserts that Rails recognizes the route given by +expected_options+.
|
||||
#
|
||||
# assert_recognizes({:controller => 'items', :action => 'index'}, 'items') # check the default action
|
||||
# assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list') # check a specific action
|
||||
# assert_recognizes({:controller => 'items', :action => 'list', :id => '1'}, 'items/list/1') # check an action with a parameter
|
||||
#
|
||||
# Pass a hash in the second argument to specify the request method. This is useful for routes
|
||||
# Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
|
||||
# requiring a specific HTTP method. The hash should contain a :path with the incoming request path
|
||||
# and a :method containing the required HTTP verb.
|
||||
#
|
||||
# # assert that POSTing to /items will call the create action on ItemsController
|
||||
# assert_recognizes({:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post})
|
||||
#
|
||||
# You can also pass in "extras" with a hash containing URL parameters that would normally be in the query string. This can be used
|
||||
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
|
||||
# to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
|
||||
# extras argument, appending the query string on the path directly will not work. For example:
|
||||
#
|
||||
# # assert that a path of '/items/list/1?view=print' returns the correct options
|
||||
# assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
|
||||
#
|
||||
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Check the default route (i.e., the index action)
|
||||
# assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
|
||||
#
|
||||
# # Test a specific action
|
||||
# assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list')
|
||||
#
|
||||
# # Test an action with a parameter
|
||||
# assert_recognizes({:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1')
|
||||
#
|
||||
# # Test a custom route
|
||||
# assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1')
|
||||
#
|
||||
# # Check a Simply RESTful generated route
|
||||
# assert_recognizes(list_items_url, 'items/list')
|
||||
def assert_recognizes(expected_options, path, extras={}, message=nil)
|
||||
if path.is_a? Hash
|
||||
request_method = path[:method]
|
||||
|
@ -43,12 +59,24 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
# Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes.
|
||||
# For example:
|
||||
# Asserts that the provided options can be used to generate the provided path. This is the inverse of #assert_recognizes.
|
||||
# The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in
|
||||
# a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
|
||||
#
|
||||
# The +defaults+ parameter is unused.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Asserts that the default action is generated for a route with no action
|
||||
# assert_generates("/items", :controller => "items", :action => "index")
|
||||
#
|
||||
# # Tests that the list action is properly routed
|
||||
# assert_generates("/items/list", :controller => "items", :action => "list")
|
||||
#
|
||||
# # Tests the generation of a route with a parameter
|
||||
# assert_generates("/items/list/1", { :controller => "items", :action => "list", :id => "1" })
|
||||
#
|
||||
# # Asserts that the generated route gives us our custom route
|
||||
# assert_generates "changesets/12", { :controller => 'scm', :action => 'show_diff', :revision => "12" }
|
||||
def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
|
||||
clean_backtrace do
|
||||
expected_path = "/#{expected_path}" unless expected_path[0] == ?/
|
||||
|
@ -67,9 +95,25 @@ module ActionController
|
|||
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. This
|
||||
# essentially combines assert_recognizes and assert_generates into one step.
|
||||
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
|
||||
# <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines #assert_recognizes
|
||||
# and #assert_generates into one step.
|
||||
#
|
||||
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
|
||||
# +message+ parameter allows you to specify a custom error message to display upon failure.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Assert a basic route: a controller with the default action (index)
|
||||
# assert_routing('/home', :controller => 'home', :action => 'index')
|
||||
#
|
||||
# # Test a route generated with a specific controller, action, and parameter (id)
|
||||
# assert_routing('/entries/show/23', :controller => 'entries', :action => 'show', id => 23)
|
||||
#
|
||||
# # Assert a basic route (controller + default action), with an error message if it fails
|
||||
# assert_routing('/store', { :controller => 'store', :action => 'index' }, {}, {}, 'Route for store index not generated properly')
|
||||
#
|
||||
# # Tests a route, providing a defaults hash
|
||||
# assert_routing 'controller/action/9', {:id => "9", :item => "square"}, {:controller => "controller", :action => "action"}, {}, {:item => "square"}
|
||||
def assert_routing(path, options, defaults={}, extras={}, message=nil)
|
||||
assert_recognizes(options, path, extras, message)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#++
|
||||
|
||||
require 'rexml/document'
|
||||
require File.dirname(__FILE__) + "/../vendor/html-scanner/html/document"
|
||||
require 'html/document'
|
||||
|
||||
module ActionController
|
||||
module Assertions
|
||||
|
@ -13,15 +13,13 @@ module ActionController
|
|||
end
|
||||
|
||||
# Adds the #assert_select method for use in Rails functional
|
||||
# test cases.
|
||||
#
|
||||
# Use #assert_select to make assertions on the response HTML of a controller
|
||||
# test cases, which can be used to make assertions on the response HTML of a controller
|
||||
# action. You can also call #assert_select within another #assert_select to
|
||||
# make assertions on elements selected by the enclosing assertion.
|
||||
#
|
||||
# Use #css_select to select elements without making an assertions, either
|
||||
# from the response HTML or elements selected by the enclosing assertion.
|
||||
#
|
||||
#
|
||||
# In addition to HTML responses, you can make the following assertions:
|
||||
# * #assert_select_rjs -- Assertions on HTML content of RJS update and
|
||||
# insertion operations.
|
||||
|
@ -29,7 +27,7 @@ module ActionController
|
|||
# for example for dealing with feed item descriptions.
|
||||
# * #assert_select_email -- Assertions on the HTML body of an e-mail.
|
||||
#
|
||||
# Also see HTML::Selector for learning how to use selectors.
|
||||
# Also see HTML::Selector to learn how to use selectors.
|
||||
module SelectorAssertions
|
||||
# :call-seq:
|
||||
# css_select(selector) => array
|
||||
|
@ -49,12 +47,26 @@ module ActionController
|
|||
# The selector may be a CSS selector expression (+String+), an expression
|
||||
# with substitution values (+Array+) or an HTML::Selector object.
|
||||
#
|
||||
# For example:
|
||||
# ==== Examples
|
||||
# # Selects all div tags
|
||||
# divs = css_select("div")
|
||||
#
|
||||
# # Selects all paragraph tags and does something interesting
|
||||
# pars = css_select("p")
|
||||
# pars.each do |par|
|
||||
# # Do something fun with paragraphs here...
|
||||
# end
|
||||
#
|
||||
# # Selects all list items in unordered lists
|
||||
# items = css_select("ul>li")
|
||||
#
|
||||
# # Selects all form tags and then all inputs inside the form
|
||||
# forms = css_select("form")
|
||||
# forms.each do |form|
|
||||
# inputs = css_select(form, "input")
|
||||
# ...
|
||||
# end
|
||||
#
|
||||
def css_select(*args)
|
||||
# See assert_select to understand what's going on here.
|
||||
arg = args.shift
|
||||
|
@ -66,6 +78,7 @@ module ActionController
|
|||
raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?"
|
||||
elsif @selected
|
||||
matches = []
|
||||
|
||||
@selected.each do |selected|
|
||||
subset = css_select(selected, HTML::Selector.new(arg.dup, args.dup))
|
||||
subset.each do |match|
|
||||
|
@ -105,12 +118,13 @@ module ActionController
|
|||
# response HTML. Calling #assert_select inside an #assert_select block will
|
||||
# run the assertion for each element selected by the enclosing assertion.
|
||||
#
|
||||
# For example:
|
||||
# ==== Example
|
||||
# assert_select "ol>li" do |elements|
|
||||
# elements.each do |element|
|
||||
# assert_select element, "li"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Or for short:
|
||||
# assert_select "ol>li" do
|
||||
# assert_select "li"
|
||||
|
@ -148,7 +162,7 @@ module ActionController
|
|||
# If the method is called with a block, once all equality tests are
|
||||
# evaluated the block is called with an array of all matched elements.
|
||||
#
|
||||
# === Examples
|
||||
# ==== Examples
|
||||
#
|
||||
# # At least one form element
|
||||
# assert_select "form"
|
||||
|
@ -196,7 +210,7 @@ module ActionController
|
|||
# Otherwise just operate on the response document.
|
||||
root = response_from_page_or_rjs
|
||||
end
|
||||
|
||||
|
||||
# First or second argument is the selector: string and we pass
|
||||
# all remaining arguments. Array and we pass the argument. Also
|
||||
# accepts selector itself.
|
||||
|
@ -209,7 +223,7 @@ module ActionController
|
|||
selector = arg
|
||||
else raise ArgumentError, "Expecting a selector as the first argument"
|
||||
end
|
||||
|
||||
|
||||
# Next argument is used for equality tests.
|
||||
equals = {}
|
||||
case arg = args.shift
|
||||
|
@ -277,14 +291,10 @@ module ActionController
|
|||
# found one but expecting two.
|
||||
message ||= content_mismatch if matches.empty?
|
||||
# Test minimum/maximum occurrence.
|
||||
if equals[:minimum]
|
||||
assert matches.size >= equals[:minimum], message ||
|
||||
"Expected at least #{equals[:minimum]} elements, found #{matches.size}."
|
||||
end
|
||||
if equals[:maximum]
|
||||
assert matches.size <= equals[:maximum], message ||
|
||||
"Expected at most #{equals[:maximum]} elements, found #{matches.size}."
|
||||
end
|
||||
min, max = equals[:minimum], equals[:maximum]
|
||||
message = message || %(Expected #{count_description(min, max)} matching "#{selector.to_s}", found #{matches.size}.)
|
||||
assert matches.size >= min, message if min
|
||||
assert matches.size <= max, message if max
|
||||
|
||||
# If a block is given call that block. Set @selected to allow
|
||||
# nested assert_select, which can be nested several levels deep.
|
||||
|
@ -300,7 +310,19 @@ module ActionController
|
|||
# Returns all matches elements.
|
||||
matches
|
||||
end
|
||||
|
||||
|
||||
def count_description(min, max) #:nodoc:
|
||||
pluralize = lambda {|word, quantity| word << (quantity == 1 ? '' : 's')}
|
||||
|
||||
if min && max && (max != min)
|
||||
"between #{min} and #{max} elements"
|
||||
elsif min && !(min == 1 && max == 1)
|
||||
"at least #{min} #{pluralize['element', min]}"
|
||||
elsif max
|
||||
"at most #{max} #{pluralize['element', max]}"
|
||||
end
|
||||
end
|
||||
|
||||
# :call-seq:
|
||||
# assert_select_rjs(id?) { |elements| ... }
|
||||
# assert_select_rjs(statement, id?) { |elements| ... }
|
||||
|
@ -317,12 +339,17 @@ module ActionController
|
|||
# that update or insert an element with that identifier.
|
||||
#
|
||||
# Use the first argument to narrow down assertions to only statements
|
||||
# of that type. Possible values are +:replace+, +:replace_html+ and
|
||||
# +:insert_html+.
|
||||
# of that type. Possible values are <tt>:replace</tt>, <tt>:replace_html</tt>,
|
||||
# <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tt> and
|
||||
# <tt>:insert_html</tt>.
|
||||
#
|
||||
# Use the argument +:insert+ followed by an insertion position to narrow
|
||||
# Use the argument <tt>:insert</tt> followed by an insertion position to narrow
|
||||
# down the assertion to only statements that insert elements in that
|
||||
# position. Possible values are +:top+, +:bottom+, +:before+ and +:after+.
|
||||
# position. Possible values are <tt>:top</tt>, <tt>:bottom</tt>, <tt>:before</tt>
|
||||
# and <tt>:after</tt>.
|
||||
#
|
||||
# Using the <tt>:remove</tt> statement, you will be able to pass a block, but it will
|
||||
# be ignored as there is no HTML passed for this statement.
|
||||
#
|
||||
# === Using blocks
|
||||
#
|
||||
|
@ -339,7 +366,7 @@ module ActionController
|
|||
# but without distinguishing whether the content is returned in an HTML
|
||||
# or JavaScript.
|
||||
#
|
||||
# === Examples
|
||||
# ==== Examples
|
||||
#
|
||||
# # Replacing the element foo.
|
||||
# # page.replace 'foo', ...
|
||||
|
@ -352,6 +379,9 @@ module ActionController
|
|||
# # Inserting into the element bar, top position.
|
||||
# assert_select_rjs :insert, :top, "bar"
|
||||
#
|
||||
# # Remove the element bar
|
||||
# assert_select_rjs :remove, "bar"
|
||||
#
|
||||
# # Changing the element foo, with an image.
|
||||
# assert_select_rjs "foo" do
|
||||
# assert_select "img[src=/images/logo.gif""
|
||||
|
@ -373,6 +403,7 @@ module ActionController
|
|||
# any RJS statement.
|
||||
if arg.is_a?(Symbol)
|
||||
rjs_type = arg
|
||||
|
||||
if rjs_type == :insert
|
||||
arg = args.shift
|
||||
insertion = "insert_#{arg}".to_sym
|
||||
|
@ -400,20 +431,29 @@ module ActionController
|
|||
case rjs_type
|
||||
when :chained_replace, :chained_replace_html
|
||||
Regexp.new("\\$\\(\"#{id}\"\\)#{statement}\\(#{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE)
|
||||
when :remove, :show, :hide, :toggle
|
||||
Regexp.new("#{statement}\\(\"#{id}\"\\)")
|
||||
else
|
||||
Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE)
|
||||
end
|
||||
|
||||
# Duplicate the body since the next step involves destroying it.
|
||||
matches = nil
|
||||
@response.body.gsub(pattern) do |match|
|
||||
html = unescape_rjs($2)
|
||||
matches ||= []
|
||||
matches.concat HTML::Document.new(html).root.children.select { |n| n.tag? }
|
||||
""
|
||||
case rjs_type
|
||||
when :remove, :show, :hide, :toggle
|
||||
matches = @response.body.match(pattern)
|
||||
else
|
||||
@response.body.gsub(pattern) do |match|
|
||||
html = unescape_rjs($2)
|
||||
matches ||= []
|
||||
matches.concat HTML::Document.new(html).root.children.select { |n| n.tag? }
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
if matches
|
||||
if block_given?
|
||||
assert_block("") { true } # to count the assertion
|
||||
if block_given? && !([:remove, :show, :hide, :toggle].include? rjs_type)
|
||||
begin
|
||||
in_scope, @selected = @selected, matches
|
||||
yield matches
|
||||
|
@ -441,8 +481,20 @@ module ActionController
|
|||
# The content of each element is un-encoded, and wrapped in the root
|
||||
# element +encoded+. It then calls the block with all un-encoded elements.
|
||||
#
|
||||
# === Example
|
||||
# ==== Examples
|
||||
# # Selects all bold tags from within the title of an ATOM feed's entries (perhaps to nab a section name prefix)
|
||||
# assert_select_feed :atom, 1.0 do
|
||||
# # Select each entry item and then the title item
|
||||
# assert_select "entry>title" do
|
||||
# # Run assertions on the encoded title elements
|
||||
# assert_select_encoded do
|
||||
# assert_select "b"
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# # Selects all paragraph tags from within the description of an RSS feed
|
||||
# assert_select_feed :rss, 2.0 do
|
||||
# # Select description element of each feed item.
|
||||
# assert_select "channel>item>description" do
|
||||
|
@ -493,11 +545,19 @@ module ActionController
|
|||
# You must enable deliveries for this assertion to work, use:
|
||||
# ActionMailer::Base.perform_deliveries = true
|
||||
#
|
||||
# === Example
|
||||
# ==== Examples
|
||||
#
|
||||
# assert_select_email do
|
||||
# assert_select "h1", "Email alert"
|
||||
# end
|
||||
#
|
||||
# assert_select_email do
|
||||
# items = assert_select "ol>li"
|
||||
# items.each do
|
||||
# # Work with items here...
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# assert_select_email do
|
||||
# assert_select "h1", "Email alert"
|
||||
# end
|
||||
def assert_select_email(&block)
|
||||
deliveries = ActionMailer::Base.deliveries
|
||||
assert !deliveries.empty?, "No e-mail in delivery list"
|
||||
|
@ -519,6 +579,10 @@ module ActionController
|
|||
:replace_html => /Element\.update/,
|
||||
:chained_replace => /\.replace/,
|
||||
:chained_replace_html => /\.update/,
|
||||
:remove => /Element\.remove/,
|
||||
:show => /Element\.show/,
|
||||
:hide => /Element\.hide/,
|
||||
:toggle => /Element\.toggle/
|
||||
}
|
||||
RJS_INSERTIONS = [:top, :bottom, :before, :after]
|
||||
RJS_INSERTIONS.each do |insertion|
|
||||
|
@ -537,10 +601,12 @@ module ActionController
|
|||
# #assert_select and #css_select call this to obtain the content in the HTML
|
||||
# page, or from all the RJS statements, depending on the type of response.
|
||||
def response_from_page_or_rjs()
|
||||
content_type = @response.headers["Content-Type"]
|
||||
content_type = @response.content_type
|
||||
|
||||
if content_type && content_type =~ /text\/javascript/
|
||||
body = @response.body.dup
|
||||
root = HTML::Node.new(nil)
|
||||
|
||||
while true
|
||||
next if body.sub!(RJS_PATTERN_EVERYTHING) do |match|
|
||||
html = unescape_rjs($3)
|
||||
|
@ -550,6 +616,7 @@ module ActionController
|
|||
end
|
||||
break
|
||||
end
|
||||
|
||||
root
|
||||
else
|
||||
html_document.root
|
||||
|
@ -560,6 +627,7 @@ module ActionController
|
|||
def unescape_rjs(rjs_string)
|
||||
# RJS encodes double quotes and line breaks.
|
||||
unescaped= rjs_string.gsub('\"', '"')
|
||||
unescaped.gsub!(/\\\//, '/')
|
||||
unescaped.gsub!('\n', "\n")
|
||||
unescaped.gsub!('\076', '>')
|
||||
unescaped.gsub!('\074', '<')
|
||||
|
@ -567,7 +635,6 @@ module ActionController
|
|||
unescaped.gsub!(RJS_PATTERN_UNICODE_ESCAPED_CHAR) {|u| [$1.hex].pack('U*')}
|
||||
unescaped
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
require 'rexml/document'
|
||||
require File.dirname(__FILE__) + "/../vendor/html-scanner/html/document"
|
||||
require 'html/document'
|
||||
|
||||
module ActionController
|
||||
module Assertions
|
||||
# Pair of assertions to testing elements in the HTML output of the response.
|
||||
module TagAssertions
|
||||
# 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
|
||||
|
@ -37,8 +38,8 @@ module ActionController
|
|||
# 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.
|
||||
# given value. This will not match HTML tags in the body of a
|
||||
# tag--only text.
|
||||
#
|
||||
# Conditions are matched using the following algorithm:
|
||||
#
|
||||
|
@ -48,39 +49,39 @@ module ActionController
|
|||
# * if the condition is +true+, the value must not be +nil+.
|
||||
# * if the condition is +false+ or +nil+, the value must be +nil+.
|
||||
#
|
||||
# Usage:
|
||||
# === Examples
|
||||
#
|
||||
# # assert that there is a "span" tag
|
||||
# # Assert that there is a "span" tag
|
||||
# assert_tag :tag => "span"
|
||||
#
|
||||
# # assert that there is a "span" tag with id="x"
|
||||
# # 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 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 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 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 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 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)
|
||||
# # 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
|
||||
# # 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
|
||||
# # 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",
|
||||
|
@ -90,7 +91,7 @@ module ActionController
|
|||
# :descendant => { :tag => "span",
|
||||
# :child => /hello world/ }
|
||||
#
|
||||
# <strong>Please note</strong: #assert_tag and #assert_no_tag only work
|
||||
# <b>Please note</b>: #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
|
||||
|
@ -105,6 +106,18 @@ module ActionController
|
|||
|
||||
# Identical to #assert_tag, but asserts that a matching tag does _not_
|
||||
# exist. (See #assert_tag for a full discussion of the syntax.)
|
||||
#
|
||||
# === Examples
|
||||
# # Assert that there is not a "div" containing a "p"
|
||||
# assert_no_tag :tag => "div", :descendant => { :tag => "p" }
|
||||
#
|
||||
# # Assert that an unordered list is empty
|
||||
# assert_no_tag :tag => "ul", :descendant => { :tag => "li" }
|
||||
#
|
||||
# # Assert that there is not a "p" tag with between 1 to 3 "img" tags
|
||||
# # as immediate children
|
||||
# assert_no_tag :tag => "p",
|
||||
# :children => { :count => 1..3, :only => { :tag => "img" } }
|
||||
def assert_no_tag(*opts)
|
||||
clean_backtrace do
|
||||
opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
|
||||
|
|
|
@ -11,10 +11,16 @@ require 'set'
|
|||
module ActionController #:nodoc:
|
||||
class ActionControllerError < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
class SessionRestoreError < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
class MissingTemplate < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
class RenderError < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
class RoutingError < ActionControllerError #:nodoc:
|
||||
attr_reader :failures
|
||||
def initialize(message, failures=[])
|
||||
|
@ -22,14 +28,39 @@ module ActionController #:nodoc:
|
|||
@failures = failures
|
||||
end
|
||||
end
|
||||
|
||||
class MethodNotAllowed < ActionControllerError #:nodoc:
|
||||
attr_reader :allowed_methods
|
||||
|
||||
def initialize(*allowed_methods)
|
||||
super("Only #{allowed_methods.to_sentence} requests are allowed.")
|
||||
@allowed_methods = allowed_methods
|
||||
end
|
||||
|
||||
def allowed_methods_header
|
||||
allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', '
|
||||
end
|
||||
|
||||
def handle_response!(response)
|
||||
response.headers['Allow'] ||= allowed_methods_header
|
||||
end
|
||||
end
|
||||
|
||||
class NotImplemented < MethodNotAllowed #:nodoc:
|
||||
end
|
||||
|
||||
class UnknownController < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
class UnknownAction < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
class MissingFile < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
class RenderError < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
class SessionOverflowError < ActionControllerError #:nodoc:
|
||||
DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
|
||||
|
||||
|
@ -37,13 +68,15 @@ module ActionController #:nodoc:
|
|||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
end
|
||||
|
||||
class DoubleRenderError < ActionControllerError #:nodoc:
|
||||
DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and only once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\". Finally, note that to cause a before filter to halt execution of the rest of the filter chain, the filter must return false, explicitly, so \"render(...) and return false\"."
|
||||
DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"."
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
end
|
||||
|
||||
class RedirectBackError < ActionControllerError #:nodoc:
|
||||
DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].'
|
||||
|
||||
|
@ -52,6 +85,9 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
class UnknownHttpMethod < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
# Action Controllers are the core of a web request in Rails. They are made up of one or more actions that are executed
|
||||
# on request and then either render a template or redirect to another action. An action is defined as a public method
|
||||
# on the controller, which will automatically be made accessible to the web-server through Rails Routes.
|
||||
|
@ -71,7 +107,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action
|
||||
# after executing code in the action. For example, the +index+ action of the +GuestBookController+ would render the
|
||||
# template <tt>app/views/guestbook/index.rhtml</tt> by default after populating the <tt>@entries</tt> instance variable.
|
||||
# template <tt>app/views/guestbook/index.erb</tt> by default after populating the <tt>@entries</tt> instance variable.
|
||||
#
|
||||
# Unlike index, the sign action will not render a template. After performing its main purpose (creating a
|
||||
# new entry in the guest book), it initiates a redirect instead. This redirect works by returning an external
|
||||
|
@ -128,17 +164,26 @@ module ActionController #:nodoc:
|
|||
# For removing objects from the session, you can either assign a single key to nil, like <tt>session[:person] = nil</tt>, or you can
|
||||
# remove the entire session with reset_session.
|
||||
#
|
||||
# By default, sessions are stored on the file system in <tt>RAILS_ROOT/tmp/sessions</tt>. Any object can be placed in the session
|
||||
# (as long as it can be Marshalled). But remember that 1000 active sessions each storing a 50kb object could lead to a 50MB store on the filesystem.
|
||||
# In other words, think carefully about size and caching before resorting to the use of the session on the filesystem.
|
||||
# Sessions are stored in a browser cookie that's cryptographically signed, but unencrypted, by default. This prevents
|
||||
# the user from tampering with the session but also allows him to see its contents.
|
||||
#
|
||||
# An alternative to storing sessions on disk is to use ActiveRecordStore to store sessions in your database, which can solve problems
|
||||
# caused by storing sessions in the file system and may speed up your application. To use ActiveRecordStore, uncomment the line:
|
||||
# Do not put secret information in session!
|
||||
#
|
||||
# Other options for session storage are:
|
||||
#
|
||||
# ActiveRecordStore: sessions are stored in your database, which works better than PStore with multiple app servers and,
|
||||
# unlike CookieStore, hides your session contents from the user. To use ActiveRecordStore, set
|
||||
#
|
||||
# config.action_controller.session_store = :active_record_store
|
||||
#
|
||||
# in your <tt>environment.rb</tt> and run <tt>rake db:sessions:create</tt>.
|
||||
#
|
||||
# MemCacheStore: sessions are stored as entries in your memcached cache. Set the session store type in <tt>environment.rb</tt>:
|
||||
#
|
||||
# config.action_controller.session_store = :mem_cache_store
|
||||
#
|
||||
# This assumes that memcached has been installed and configured properly. See the MemCacheStore docs for more information.
|
||||
#
|
||||
# == Responses
|
||||
#
|
||||
# Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response
|
||||
|
@ -192,7 +237,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# == Calling multiple redirects or renders
|
||||
#
|
||||
# An action should conclude with a single render or redirect. Attempting to try to do either again will result in a DoubleRenderError:
|
||||
# An action may contain only a single render or a single redirect. Attempting to try to do either again will result in a DoubleRenderError:
|
||||
#
|
||||
# def do_something
|
||||
# redirect_to :action => "elsewhere"
|
||||
|
@ -209,7 +254,6 @@ module ActionController #:nodoc:
|
|||
class Base
|
||||
DEFAULT_RENDER_STATUS_CODE = "200 OK"
|
||||
|
||||
include Reloadable::Deprecated
|
||||
include StatusCodes
|
||||
|
||||
# Determines whether the view has access to controller internals @request, @response, @session, and @template.
|
||||
|
@ -249,7 +293,7 @@ module ActionController #:nodoc:
|
|||
# The param_parsers hash lets you register handlers which will process the http body and add parameters to the
|
||||
# <tt>params</tt> hash. These handlers are invoked for post and put requests.
|
||||
#
|
||||
# By default application/xml is enabled. A XmlSimple class with the same param name as the root will be instanciated
|
||||
# By default application/xml is enabled. A XmlSimple class with the same param name as the root will be instantiated
|
||||
# in the <tt>params</tt>. This allows XML requests to mask themselves as regular form submissions, so you can have one
|
||||
# action serve both regular forms and web service requests.
|
||||
#
|
||||
|
@ -271,17 +315,15 @@ module ActionController #:nodoc:
|
|||
# A YAML parser is also available and can be turned on with:
|
||||
#
|
||||
# ActionController::Base.param_parsers[Mime::YAML] = :yaml
|
||||
@@param_parsers = { Mime::XML => :xml_simple }
|
||||
@@param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
|
||||
Mime::URL_ENCODED_FORM => :url_encoded_form,
|
||||
Mime::XML => :xml_simple }
|
||||
cattr_accessor :param_parsers
|
||||
|
||||
# Controls the default charset for all renders.
|
||||
@@default_charset = "utf-8"
|
||||
cattr_accessor :default_charset
|
||||
|
||||
# Template root determines the base from which template references will be made. So a call to render("test/template")
|
||||
# will be converted to "#{template_root}/test/template.rhtml".
|
||||
class_inheritable_accessor :template_root
|
||||
|
||||
|
||||
# The logger is used for generating information on the action run-time (including benchmarking) if available.
|
||||
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
|
||||
cattr_accessor :logger
|
||||
|
@ -295,6 +337,18 @@ module ActionController #:nodoc:
|
|||
# Controls the resource action separator
|
||||
@@resource_action_separator = "/"
|
||||
cattr_accessor :resource_action_separator
|
||||
|
||||
# Sets the token parameter name for RequestForgery. Calling #protect_from_forgery sets it to :authenticity_token by default
|
||||
cattr_accessor :request_forgery_protection_token
|
||||
|
||||
# Indicates whether or not optimise the generated named
|
||||
# route helper methods
|
||||
cattr_accessor :optimise_named_routes
|
||||
self.optimise_named_routes = true
|
||||
|
||||
# Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode.
|
||||
class_inheritable_accessor :allow_forgery_protection
|
||||
self.allow_forgery_protection = true
|
||||
|
||||
# Holds the request object that's primarily used to get environment variables through access like
|
||||
# <tt>request.env["REQUEST_URI"]</tt>.
|
||||
|
@ -353,19 +407,57 @@ module ActionController #:nodoc:
|
|||
# By default, all methods defined in ActionController::Base and included modules are hidden.
|
||||
# More methods can be hidden using <tt>hide_actions</tt>.
|
||||
def hidden_actions
|
||||
write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods) unless read_inheritable_attribute(:hidden_actions)
|
||||
unless read_inheritable_attribute(:hidden_actions)
|
||||
write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map(&:to_s))
|
||||
end
|
||||
|
||||
read_inheritable_attribute(:hidden_actions)
|
||||
end
|
||||
|
||||
# Hide each of the given methods from being callable as actions.
|
||||
def hide_action(*names)
|
||||
write_inheritable_attribute(:hidden_actions, hidden_actions | names.collect { |n| n.to_s })
|
||||
write_inheritable_attribute(:hidden_actions, hidden_actions | names.map(&:to_s))
|
||||
end
|
||||
|
||||
# Replace sensitive paramater data from the request log.
|
||||
# Filters paramaters that have any of the arguments as a substring.
|
||||
## View load paths determine the bases from which template references can be made. So a call to
|
||||
## render("test/template") will be looked up in the view load paths array and the closest match will be
|
||||
## returned.
|
||||
def view_paths
|
||||
@view_paths || superclass.view_paths
|
||||
end
|
||||
|
||||
def view_paths=(value)
|
||||
@view_paths = value
|
||||
end
|
||||
|
||||
# Adds a view_path to the front of the view_paths array.
|
||||
# If the current class has no view paths, copy them from
|
||||
# the superclass. This change will be visible for all future requests.
|
||||
#
|
||||
# ArticleController.prepend_view_path("views/default")
|
||||
# ArticleController.prepend_view_path(["views/default", "views/custom"])
|
||||
#
|
||||
def prepend_view_path(path)
|
||||
@view_paths = superclass.view_paths.dup if @view_paths.nil?
|
||||
view_paths.unshift(*path)
|
||||
end
|
||||
|
||||
# Adds a view_path to the end of the view_paths array.
|
||||
# If the current class has no view paths, copy them from
|
||||
# the superclass. This change will be visible for all future requests.
|
||||
#
|
||||
# ArticleController.append_view_path("views/default")
|
||||
# ArticleController.append_view_path(["views/default", "views/custom"])
|
||||
#
|
||||
def append_view_path(path)
|
||||
@view_paths = superclass.view_paths.dup if @view_paths.nil?
|
||||
view_paths.push(*path)
|
||||
end
|
||||
|
||||
# Replace sensitive parameter data from the request log.
|
||||
# Filters parameters that have any of the arguments as a substring.
|
||||
# Looks in all subhashes of the param hash for keys to filter.
|
||||
# If a block is given, each key and value of the paramater hash and all
|
||||
# If a block is given, each key and value of the parameter hash and all
|
||||
# subhashes is passed to it, the value or key
|
||||
# can be replaced using String#replace or similar method.
|
||||
#
|
||||
|
@ -412,13 +504,10 @@ module ActionController #:nodoc:
|
|||
|
||||
# Don't render layouts for templates with the given extensions.
|
||||
def exempt_from_layout(*extensions)
|
||||
@@exempt_from_layout.merge extensions.collect { |extension|
|
||||
if extension.is_a?(Regexp)
|
||||
extension
|
||||
else
|
||||
/\.#{Regexp.escape(extension.to_s)}$/
|
||||
end
|
||||
}
|
||||
regexps = extensions.collect do |extension|
|
||||
extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
|
||||
end
|
||||
@@exempt_from_layout.merge regexps
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -435,6 +524,9 @@ module ActionController #:nodoc:
|
|||
send(method, *arguments)
|
||||
|
||||
assign_default_content_type_and_charset
|
||||
|
||||
response.request = request
|
||||
response.prepare! unless component_request?
|
||||
response
|
||||
ensure
|
||||
process_cleanup
|
||||
|
@ -452,18 +544,25 @@ module ActionController #:nodoc:
|
|||
# * <tt>:only_path</tt> -- if true, returns the relative URL (omitting the protocol, host name, and port) (<tt>false</tt> by default)
|
||||
# * <tt>:trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this
|
||||
# is currently not recommended since it breaks caching.
|
||||
# * <tt>:host</tt> -- overrides the default (current) host if provided
|
||||
# * <tt>:protocol</tt> -- overrides the default (current) protocol if provided
|
||||
# * <tt>:host</tt> -- overrides the default (current) host if provided.
|
||||
# * <tt>:protocol</tt> -- overrides the default (current) protocol if provided.
|
||||
# * <tt>:port</tt> -- optionally specify the port to connect to.
|
||||
# * <tt>:user</tt> -- Inline HTTP authentication (only plucked out if :password is also present).
|
||||
# * <tt>:password</tt> -- Inline HTTP authentication (only plucked out if :user is also present).
|
||||
# * <tt>:skip_relative_url_root</tt> -- if true, the url is not constructed using the relative_url_root of the request so the path
|
||||
# will include the web server relative installation directory.
|
||||
#
|
||||
# The URL is generated from the remaining keys in the hash. A URL contains two key parts: the <base> and a query string.
|
||||
# Routes composes a query string as the key/value pairs not included in the <base>.
|
||||
#
|
||||
# The default Routes setup supports a typical Rails path of "controller/action/id" where action and id are optional, with
|
||||
# action defaulting to 'index' when not given. Here are some typical url_for statements and their corresponding URLs:
|
||||
#
|
||||
# url_for :controller => 'posts', :action => 'recent' # => 'proto://host.com/posts/recent'
|
||||
# url_for :controller => 'posts', :action => 'index' # => 'proto://host.com/posts'
|
||||
# url_for :controller => 'posts', :action => 'show', :id => 10 # => 'proto://host.com/posts/show/10'
|
||||
#
|
||||
# url_for :controller => 'posts', :action => 'recent' # => 'proto://host.com/posts/recent'
|
||||
# url_for :controller => 'posts', :action => 'index' # => 'proto://host.com/posts'
|
||||
# url_for :controller => 'posts', :action => 'index', :port=>'8033' # => 'proto://host.com:8033/posts'
|
||||
# url_for :controller => 'posts', :action => 'show', :id => 10 # => 'proto://host.com/posts/show/10'
|
||||
# url_for :controller => 'posts', :user => 'd', :password => '123' # => 'proto://d:123@host.com/posts'
|
||||
#
|
||||
# When generating a new URL, missing values may be filled in from the current request's parameters. For example,
|
||||
# <tt>url_for :action => 'some_action'</tt> will retain the current controller, as expected. This behavior extends to
|
||||
|
@ -491,9 +590,9 @@ module ActionController #:nodoc:
|
|||
# However, you might ask why the action from the current request, 'contacts', isn't carried over into the new URL. The
|
||||
# answer has to do with the order in which the parameters appear in the generated path. In a nutshell, since the
|
||||
# value that appears in the slot for <tt>:first</tt> is not equal to default value for <tt>:first</tt> we stop using
|
||||
# defaults. On it's own, this rule can account for much of the typical Rails URL behavior.
|
||||
# defaults. On its own, this rule can account for much of the typical Rails URL behavior.
|
||||
#
|
||||
# Although a convienence, defaults can occasionaly get in your way. In some cases a default persists longer than desired.
|
||||
# Although a convenience, defaults can occasionally get in your way. In some cases a default persists longer than desired.
|
||||
# The default may be cleared by adding <tt>:name => nil</tt> to <tt>url_for</tt>'s options.
|
||||
# This is often required when writing form helpers, since the defaults in play may vary greatly depending upon where the
|
||||
# helper is used from. The following line will redirect to PostController's default action, regardless of the page it is
|
||||
|
@ -509,22 +608,14 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# This takes the current URL as is and only exchanges the action. In contrast, <tt>url_for :action => 'print'</tt>
|
||||
# would have slashed-off the path components after the changed action.
|
||||
def url_for(options = {}, *parameters_for_method_reference) #:doc:
|
||||
case options
|
||||
def url_for(options = nil) #:doc:
|
||||
case options || {}
|
||||
when String
|
||||
options
|
||||
|
||||
when Symbol
|
||||
ActiveSupport::Deprecation.warn(
|
||||
"You called url_for(:#{options}), which is a deprecated API call. Instead you should use the named " +
|
||||
"route directly, like #{options}(). Using symbols and parameters with url_for will be removed from Rails 2.0.",
|
||||
caller
|
||||
)
|
||||
|
||||
send(options, *parameters_for_method_reference)
|
||||
|
||||
when Hash
|
||||
@url.rewrite(rewrite_options(options))
|
||||
else
|
||||
polymorphic_url(options)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -543,11 +634,41 @@ module ActionController #:nodoc:
|
|||
self.class.controller_path
|
||||
end
|
||||
|
||||
# Test whether the session is enabled for this request.
|
||||
def session_enabled?
|
||||
request.session_options && request.session_options[:disabled] != false
|
||||
end
|
||||
|
||||
self.view_paths = []
|
||||
|
||||
# View load paths for controller.
|
||||
def view_paths
|
||||
(@template || self.class).view_paths
|
||||
end
|
||||
|
||||
def view_paths=(value)
|
||||
(@template || self.class).view_paths = value
|
||||
end
|
||||
|
||||
# Adds a view_path to the front of the view_paths array.
|
||||
# This change affects the current request only.
|
||||
#
|
||||
# self.prepend_view_path("views/default")
|
||||
# self.prepend_view_path(["views/default", "views/custom"])
|
||||
#
|
||||
def prepend_view_path(path)
|
||||
(@template || self.class).prepend_view_path(path)
|
||||
end
|
||||
|
||||
# Adds a view_path to the end of the view_paths array.
|
||||
# This change affects the current request only.
|
||||
#
|
||||
# self.append_view_path("views/default")
|
||||
# self.append_view_path(["views/default", "views/custom"])
|
||||
#
|
||||
def append_view_path(path)
|
||||
(@template || self.class).append_view_path(path)
|
||||
end
|
||||
|
||||
protected
|
||||
# Renders the content that will be returned to the browser as the response body.
|
||||
#
|
||||
|
@ -567,10 +688,6 @@ module ActionController #:nodoc:
|
|||
# # but with a custom layout
|
||||
# render :action => "long_goal", :layout => "spectacular"
|
||||
#
|
||||
# _Deprecation_ _notice_: This used to have the signatures <tt>render_action("action", status = 200)</tt>,
|
||||
# <tt>render_without_layout("controller/action", status = 200)</tt>, and
|
||||
# <tt>render_with_layout("controller/action", status = 200, layout)</tt>.
|
||||
#
|
||||
# === Rendering partials
|
||||
#
|
||||
# Partial rendering in a controller is most commonly used together with Ajax calls that only update one or a few elements on a page
|
||||
|
@ -581,6 +698,10 @@ module ActionController #:nodoc:
|
|||
# # Renders the same partial with a local variable.
|
||||
# render :partial => "person", :locals => { :name => "david" }
|
||||
#
|
||||
# # Renders the partial, making @new_person available through
|
||||
# # the local variable 'person'
|
||||
# render :partial => "person", :object => @new_person
|
||||
#
|
||||
# # Renders a collection of the same partial by making each element
|
||||
# # of @winners available through the local variable "person" as it
|
||||
# # builds the complete response.
|
||||
|
@ -602,16 +723,19 @@ module ActionController #:nodoc:
|
|||
# Note that the partial filename must also be a valid Ruby variable name,
|
||||
# so e.g. 2005 and register-user are invalid.
|
||||
#
|
||||
# _Deprecation_ _notice_: This used to have the signatures
|
||||
# <tt>render_partial(partial_path = default_template_name, object = nil, local_assigns = {})</tt> and
|
||||
# <tt>render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = {})</tt>.
|
||||
#
|
||||
# == Automatic etagging
|
||||
#
|
||||
# Rendering will automatically insert the etag header on 200 OK responses. The etag is calculated using MD5 of the
|
||||
# response body. If a request comes in that has a matching etag, the response will be changed to a 304 Not Modified
|
||||
# and the response body will be set to an empty string. No etag header will be inserted if it's already set.
|
||||
#
|
||||
# === Rendering a template
|
||||
#
|
||||
# Template rendering works just like action rendering except that it takes a path relative to the template root.
|
||||
# The current layout is automatically applied.
|
||||
#
|
||||
# # Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.rhtml)
|
||||
# # Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.erb)
|
||||
# render :template => "weblog/show"
|
||||
#
|
||||
# === Rendering a file
|
||||
|
@ -620,18 +744,16 @@ module ActionController #:nodoc:
|
|||
# is assumed to be absolute, and the current layout is not applied.
|
||||
#
|
||||
# # Renders the template located at the absolute filesystem path
|
||||
# render :file => "/path/to/some/template.rhtml"
|
||||
# render :file => "c:/path/to/some/template.rhtml"
|
||||
# render :file => "/path/to/some/template.erb"
|
||||
# render :file => "c:/path/to/some/template.erb"
|
||||
#
|
||||
# # Renders a template within the current layout, and with a 404 status code
|
||||
# render :file => "/path/to/some/template.rhtml", :layout => true, :status => 404
|
||||
# render :file => "c:/path/to/some/template.rhtml", :layout => true, :status => 404
|
||||
# render :file => "/path/to/some/template.erb", :layout => true, :status => 404
|
||||
# render :file => "c:/path/to/some/template.erb", :layout => true, :status => 404
|
||||
#
|
||||
# # Renders a template relative to the template root and chooses the proper file extension
|
||||
# render :file => "some/template", :use_full_path => true
|
||||
#
|
||||
# _Deprecation_ _notice_: This used to have the signature <tt>render_file(path, status = 200)</tt>
|
||||
#
|
||||
# === Rendering text
|
||||
#
|
||||
# Rendering of text is usually used for tests or for rendering prepared content, such as a cache. By default, text
|
||||
|
@ -644,11 +766,11 @@ module ActionController #:nodoc:
|
|||
# render :text => "Explosion!", :status => 500
|
||||
#
|
||||
# # Renders the clear text "Hi there!" within the current active layout (if one exists)
|
||||
# render :text => "Explosion!", :layout => true
|
||||
# render :text => "Hi there!", :layout => true
|
||||
#
|
||||
# # Renders the clear text "Hi there!" within the layout
|
||||
# # placed in "app/views/layouts/special.r(html|xml)"
|
||||
# render :text => "Explosion!", :layout => "special"
|
||||
# render :text => "Hi there!", :layout => "special"
|
||||
#
|
||||
# The :text option can also accept a Proc object, which can be used to manually control the page generation. This should
|
||||
# generally be avoided, as it violates the separation between code and content, and because almost everything that can be
|
||||
|
@ -657,20 +779,24 @@ module ActionController #:nodoc:
|
|||
# # Renders "Hello from code!"
|
||||
# render :text => proc { |response, output| output.write("Hello from code!") }
|
||||
#
|
||||
# _Deprecation_ _notice_: This used to have the signature <tt>render_text("text", status = 200)</tt>
|
||||
#
|
||||
# === Rendering JSON
|
||||
#
|
||||
# Rendering JSON sets the content type to text/x-json and optionally wraps the JSON in a callback. It is expected
|
||||
# that the response will be eval'd for use as a data structure.
|
||||
# Rendering JSON sets the content type to application/json and optionally wraps the JSON in a callback. It is expected
|
||||
# that the response will be parsed (or eval'd) for use as a data structure.
|
||||
#
|
||||
# # Renders '{name: "David"}'
|
||||
# # Renders '{"name": "David"}'
|
||||
# render :json => {:name => "David"}.to_json
|
||||
#
|
||||
# Sometimes the result isn't handled directly by a script (such as when the request comes from a SCRIPT tag),
|
||||
# so the callback option is provided for these cases.
|
||||
# It's not necessary to call <tt>to_json</tt> on the object you want to render, since <tt>render</tt> will
|
||||
# automatically do that for you:
|
||||
#
|
||||
# # Renders 'show({name: "David"})'
|
||||
# # Also renders '{"name": "David"}'
|
||||
# render :json => {:name => "David"}
|
||||
#
|
||||
# Sometimes the result isn't handled directly by a script (such as when the request comes from a SCRIPT tag),
|
||||
# so the <tt>:callback</tt> option is provided for these cases.
|
||||
#
|
||||
# # Renders 'show({"name": "David"})'
|
||||
# render :json => {:name => "David"}.to_json, :callback => 'show'
|
||||
#
|
||||
# === Rendering an inline template
|
||||
|
@ -683,13 +809,11 @@ module ActionController #:nodoc:
|
|||
# render :inline => "<%= 'hello, ' * 3 + 'again' %>"
|
||||
#
|
||||
# # Renders "<p>Good seeing you!</p>" using Builder
|
||||
# render :inline => "xml.p { 'Good seeing you!' }", :type => :rxml
|
||||
# render :inline => "xml.p { 'Good seeing you!' }", :type => :builder
|
||||
#
|
||||
# # Renders "hello david"
|
||||
# render :inline => "<%= 'hello ' + name %>", :locals => { :name => "david" }
|
||||
#
|
||||
# _Deprecation_ _notice_: This used to have the signature <tt>render_template(template, status = 200, type = :rhtml)</tt>
|
||||
#
|
||||
# === Rendering inline JavaScriptGenerator page updates
|
||||
#
|
||||
# In addition to rendering JavaScriptGenerator page updates with Ajax in RJS templates (see ActionView::Base for details),
|
||||
|
@ -700,35 +824,21 @@ module ActionController #:nodoc:
|
|||
# page.visual_effect :highlight, 'user_list'
|
||||
# end
|
||||
#
|
||||
# === Rendering nothing
|
||||
# === Rendering with status and location headers
|
||||
#
|
||||
# Rendering nothing is often convenient in combination with Ajax calls that perform their effect client-side or
|
||||
# when you just want to communicate a status code. Due to a bug in Safari, nothing actually means a single space.
|
||||
# All renders take the :status and :location options and turn them into headers. They can even be used together:
|
||||
#
|
||||
# # Renders an empty response with status code 200
|
||||
# render :nothing => true
|
||||
#
|
||||
# # Renders an empty response with status code 401 (access denied)
|
||||
# render :nothing => true, :status => 401
|
||||
def render(options = nil, deprecated_status = nil, &block) #:doc:
|
||||
# render :xml => post.to_xml, :status => :created, :location => post_url(post)
|
||||
def render(options = nil, &block) #:doc:
|
||||
raise DoubleRenderError, "Can only render or redirect once per action" if performed?
|
||||
|
||||
if options.nil?
|
||||
return render_file(default_template_name, deprecated_status, true)
|
||||
return render_for_file(default_template_name, nil, true)
|
||||
else
|
||||
# Backwards compatibility
|
||||
unless options.is_a?(Hash)
|
||||
if options == :update
|
||||
options = { :update => true }
|
||||
else
|
||||
ActiveSupport::Deprecation.warn(
|
||||
"You called render('#{options}'), which is a deprecated API call. Instead you use " +
|
||||
"render :file => #{options}. Calling render with just a string will be removed from Rails 2.0.",
|
||||
caller
|
||||
)
|
||||
|
||||
return render_file(options, deprecated_status, true)
|
||||
end
|
||||
if options == :update
|
||||
options = { :update => true }
|
||||
elsif !options.is_a?(Hash)
|
||||
raise RenderError, "You called render with invalid options : #{options}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -736,52 +846,72 @@ module ActionController #:nodoc:
|
|||
response.content_type = content_type.to_s
|
||||
end
|
||||
|
||||
if location = options[:location]
|
||||
response.headers["Location"] = url_for(location)
|
||||
end
|
||||
|
||||
if text = options[:text]
|
||||
render_text(text, options[:status])
|
||||
render_for_text(text, options[:status])
|
||||
|
||||
else
|
||||
if file = options[:file]
|
||||
render_file(file, options[:status], options[:use_full_path], options[:locals] || {})
|
||||
render_for_file(file, options[:status], options[:use_full_path], options[:locals] || {})
|
||||
|
||||
elsif template = options[:template]
|
||||
render_file(template, options[:status], true)
|
||||
render_for_file(template, options[:status], true)
|
||||
|
||||
elsif inline = options[:inline]
|
||||
render_template(inline, options[:status], options[:type], options[:locals] || {})
|
||||
add_variables_to_assigns
|
||||
render_for_text(@template.render_template(options[:type], inline, nil, options[:locals] || {}), options[:status])
|
||||
|
||||
elsif action_name = options[:action]
|
||||
ActiveSupport::Deprecation.silence do
|
||||
render_action(action_name, options[:status], options[:layout])
|
||||
end
|
||||
template = default_template_name(action_name.to_s)
|
||||
if options[:layout] && !template_exempt_from_layout?(template)
|
||||
render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true)
|
||||
else
|
||||
render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true)
|
||||
end
|
||||
|
||||
elsif xml = options[:xml]
|
||||
render_xml(xml, options[:status])
|
||||
response.content_type ||= Mime::XML
|
||||
render_for_text(xml.respond_to?(:to_xml) ? xml.to_xml : xml, options[:status])
|
||||
|
||||
elsif json = options[:json]
|
||||
render_json(json, options[:callback], options[:status])
|
||||
json = json.to_json unless json.is_a?(String)
|
||||
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
|
||||
response.content_type ||= Mime::JSON
|
||||
render_for_text(json, options[:status])
|
||||
|
||||
elsif partial = options[:partial]
|
||||
partial = default_template_name if partial == true
|
||||
add_variables_to_assigns
|
||||
|
||||
if collection = options[:collection]
|
||||
render_partial_collection(partial, collection, options[:spacer_template], options[:locals], options[:status])
|
||||
render_for_text(
|
||||
@template.send!(:render_partial_collection, partial, collection,
|
||||
options[:spacer_template], options[:locals]), options[:status]
|
||||
)
|
||||
else
|
||||
render_partial(partial, ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals], options[:status])
|
||||
render_for_text(
|
||||
@template.send!(:render_partial, partial,
|
||||
ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]), options[:status]
|
||||
)
|
||||
end
|
||||
|
||||
elsif options[:update]
|
||||
add_variables_to_assigns
|
||||
@template.send :evaluate_assigns
|
||||
@template.send! :evaluate_assigns
|
||||
|
||||
generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
|
||||
render_javascript(generator.to_s)
|
||||
response.content_type = Mime::JS
|
||||
render_for_text(generator.to_s)
|
||||
|
||||
elsif options[:nothing]
|
||||
# Safari doesn't pass the headers of the return if the response is zero length
|
||||
render_text(" ", options[:status])
|
||||
render_for_text(" ", options[:status])
|
||||
|
||||
else
|
||||
render_file(default_template_name, options[:status], true)
|
||||
|
||||
render_for_file(default_template_name, options[:status], true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -789,87 +919,13 @@ module ActionController #:nodoc:
|
|||
# Renders according to the same rules as <tt>render</tt>, but returns the result in a string instead
|
||||
# of sending it as the response body to the browser.
|
||||
def render_to_string(options = nil, &block) #:doc:
|
||||
ActiveSupport::Deprecation.silence { render(options, &block) }
|
||||
render(options, &block)
|
||||
ensure
|
||||
erase_render_results
|
||||
forget_variables_added_to_assigns
|
||||
reset_variables_added_to_assigns
|
||||
end
|
||||
|
||||
def render_action(action_name, status = nil, with_layout = true) #:nodoc:
|
||||
template = default_template_name(action_name.to_s)
|
||||
if with_layout && !template_exempt_from_layout?(template)
|
||||
render_with_layout(:file => template, :status => status, :use_full_path => true, :layout => true)
|
||||
else
|
||||
render_without_layout(:file => template, :status => status, :use_full_path => true)
|
||||
end
|
||||
end
|
||||
|
||||
def render_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc:
|
||||
add_variables_to_assigns
|
||||
assert_existence_of_template_file(template_path) if use_full_path
|
||||
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
|
||||
render_text(@template.render_file(template_path, use_full_path, locals), status)
|
||||
end
|
||||
|
||||
def render_template(template, status = nil, type = :rhtml, local_assigns = {}) #:nodoc:
|
||||
add_variables_to_assigns
|
||||
render_text(@template.render_template(type, template, nil, local_assigns), status)
|
||||
end
|
||||
|
||||
def render_text(text = nil, status = nil, append_response = false) #:nodoc:
|
||||
@performed_render = true
|
||||
|
||||
response.headers['Status'] = interpret_status(status || DEFAULT_RENDER_STATUS_CODE)
|
||||
|
||||
if append_response
|
||||
response.body ||= ''
|
||||
response.body << text
|
||||
else
|
||||
response.body = text
|
||||
end
|
||||
end
|
||||
|
||||
def render_javascript(javascript, status = nil, append_response = true) #:nodoc:
|
||||
response.content_type = Mime::JS
|
||||
render_text(javascript, status, append_response)
|
||||
end
|
||||
|
||||
def render_xml(xml, status = nil) #:nodoc:
|
||||
response.content_type = Mime::XML
|
||||
render_text(xml, status)
|
||||
end
|
||||
|
||||
def render_json(json, callback = nil, status = nil) #:nodoc:
|
||||
json = "#{callback}(#{json})" unless callback.blank?
|
||||
|
||||
response.content_type = Mime::JSON
|
||||
render_text(json, status)
|
||||
end
|
||||
|
||||
def render_nothing(status = nil) #:nodoc:
|
||||
render_text(' ', status)
|
||||
end
|
||||
|
||||
def render_partial(partial_path = default_template_name, object = nil, local_assigns = nil, status = nil) #:nodoc:
|
||||
add_variables_to_assigns
|
||||
render_text(@template.render_partial(partial_path, object, local_assigns), status)
|
||||
end
|
||||
|
||||
def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = nil, status = nil) #:nodoc:
|
||||
add_variables_to_assigns
|
||||
render_text(@template.render_partial_collection(partial_name, collection, partial_spacer_template, local_assigns), status)
|
||||
end
|
||||
|
||||
def render_with_layout(template_name = default_template_name, status = nil, layout = nil) #:nodoc:
|
||||
render_with_a_layout(template_name, status, layout)
|
||||
end
|
||||
|
||||
def render_without_layout(template_name = default_template_name, status = nil) #:nodoc:
|
||||
render_with_no_layout(template_name, status)
|
||||
end
|
||||
|
||||
|
||||
# Return a response that has no content (merely headers). The options
|
||||
# argument is interpreted to be a hash of header names and values.
|
||||
# This allows you to easily return a response that consists only of
|
||||
|
@ -887,19 +943,9 @@ module ActionController #:nodoc:
|
|||
raise ArgumentError, "too many arguments to head"
|
||||
elsif args.empty?
|
||||
raise ArgumentError, "too few arguments to head"
|
||||
elsif args.length == 2
|
||||
status = args.shift
|
||||
options = args.shift
|
||||
elsif args.first.is_a?(Hash)
|
||||
options = args.first
|
||||
else
|
||||
status = args.first
|
||||
options = {}
|
||||
end
|
||||
|
||||
raise ArgumentError, "head requires an options hash" if !options.is_a?(Hash)
|
||||
|
||||
status = interpret_status(status || options.delete(:status) || :ok)
|
||||
options = args.extract_options!
|
||||
status = interpret_status(args.shift || options.delete(:status) || :ok)
|
||||
|
||||
options.each do |key, value|
|
||||
headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s
|
||||
|
@ -956,47 +1002,62 @@ module ActionController #:nodoc:
|
|||
|
||||
# Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
|
||||
#
|
||||
# * <tt>Hash</tt>: The URL will be generated by calling url_for with the +options+.
|
||||
# * <tt>String starting with protocol:// (like http://)</tt>: Is passed straight through as the target for redirection.
|
||||
# * <tt>String not containing a protocol</tt>: The current protocol and host is prepended to the string.
|
||||
# * <tt>:back</tt>: Back to the page that issued the request. Useful for forms that are triggered from multiple places.
|
||||
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
|
||||
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
|
||||
# * <tt>String starting with protocol:// (like http://)</tt> - Is passed straight through as the target for redirection.
|
||||
# * <tt>String not containing a protocol</tt> - The current protocol and host is prepended to the string.
|
||||
# * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
|
||||
# Short-hand for redirect_to(request.env["HTTP_REFERER"])
|
||||
#
|
||||
# Examples:
|
||||
# redirect_to :action => "show", :id => 5
|
||||
# redirect_to post
|
||||
# redirect_to "http://www.rubyonrails.org"
|
||||
# redirect_to "/images/screenshot.jpg"
|
||||
# redirect_to articles_url
|
||||
# redirect_to :back
|
||||
#
|
||||
# The redirection happens as a "302 Moved" header.
|
||||
# The redirection happens as a "302 Moved" header unless otherwise specified.
|
||||
#
|
||||
# Examples:
|
||||
# redirect_to post_url(@post), :status=>:found
|
||||
# redirect_to :action=>'atom', :status=>:moved_permanently
|
||||
# redirect_to post_url(@post), :status=>301
|
||||
# redirect_to :action=>'atom', :status=>302
|
||||
#
|
||||
# When using <tt>redirect_to :back</tt>, if there is no referrer,
|
||||
# RedirectBackError will be raised. You may specify some fallback
|
||||
# behavior for this case by rescueing RedirectBackError.
|
||||
def redirect_to(options = {}, *parameters_for_method_reference) #:doc:
|
||||
# behavior for this case by rescuing RedirectBackError.
|
||||
def redirect_to(options = {}, response_status = {}) #:doc:
|
||||
|
||||
if options.is_a?(Hash) && options[:status]
|
||||
status = options.delete(:status)
|
||||
elsif response_status[:status]
|
||||
status = response_status[:status]
|
||||
else
|
||||
status = 302
|
||||
end
|
||||
|
||||
case options
|
||||
when %r{^\w+://.*}
|
||||
raise DoubleRenderError if performed?
|
||||
logger.info("Redirected to #{options}") if logger
|
||||
response.redirect(options)
|
||||
logger.info("Redirected to #{options}") if logger && logger.info?
|
||||
response.redirect(options, interpret_status(status))
|
||||
response.redirected_to = options
|
||||
@performed_redirect = true
|
||||
|
||||
when String
|
||||
redirect_to(request.protocol + request.host_with_port + options)
|
||||
redirect_to(request.protocol + request.host_with_port + options, :status=>status)
|
||||
|
||||
when :back
|
||||
request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"]) : raise(RedirectBackError)
|
||||
request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"], :status=>status) : raise(RedirectBackError)
|
||||
|
||||
when Hash
|
||||
redirect_to(url_for(options), :status=>status)
|
||||
response.redirected_to = options
|
||||
|
||||
else
|
||||
if parameters_for_method_reference.empty?
|
||||
redirect_to(url_for(options))
|
||||
response.redirected_to = options
|
||||
else
|
||||
# TOOD: Deprecate me!
|
||||
redirect_to(url_for(options, *parameters_for_method_reference))
|
||||
response.redirected_to, response.redirected_to_method_params = options, parameters_for_method_reference
|
||||
end
|
||||
redirect_to(url_for(options), :status=>status)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1030,23 +1091,35 @@ module ActionController #:nodoc:
|
|||
response.session = @_session
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def self.view_class
|
||||
@view_class ||=
|
||||
# create a new class based on the default template class and include helper methods
|
||||
returning Class.new(ActionView::Base) do |view_class|
|
||||
view_class.send(:include, master_helper_module)
|
||||
end
|
||||
def render_for_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc:
|
||||
add_variables_to_assigns
|
||||
assert_existence_of_template_file(template_path) if use_full_path
|
||||
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
|
||||
render_for_text(@template.render_file(template_path, use_full_path, locals), status)
|
||||
end
|
||||
|
||||
def self.view_root
|
||||
@view_root ||= template_root
|
||||
end
|
||||
def render_for_text(text = nil, status = nil, append_response = false) #:nodoc:
|
||||
@performed_render = true
|
||||
|
||||
response.headers['Status'] = interpret_status(status || DEFAULT_RENDER_STATUS_CODE)
|
||||
|
||||
if append_response
|
||||
response.body ||= ''
|
||||
response.body << text.to_s
|
||||
else
|
||||
response.body = text.is_a?(Proc) ? text : text.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def initialize_template_class(response)
|
||||
raise "You must assign a template class through ActionController.template_class= before processing a request" unless @@template_class
|
||||
unless @@template_class
|
||||
raise "You must assign a template class through ActionController.template_class= before processing a request"
|
||||
end
|
||||
|
||||
response.template = self.class.view_class.new(self.class.view_root, {}, self)
|
||||
response.template = ActionView::Base.new(view_paths, {}, self)
|
||||
response.template.extend self.class.master_helper_module
|
||||
response.redirected_to = nil
|
||||
@performed_render = @performed_redirect = false
|
||||
end
|
||||
|
@ -1062,26 +1135,6 @@ module ActionController #:nodoc:
|
|||
@assigns = @_response.template.assigns
|
||||
|
||||
@_headers = @_response.headers
|
||||
|
||||
assign_deprecated_shortcuts(request, response)
|
||||
end
|
||||
|
||||
|
||||
# TODO: assigns cookies headers params request response template
|
||||
DEPRECATED_INSTANCE_VARIABLES = %w(cookies flash headers params request response session)
|
||||
|
||||
# Gone after 1.2.
|
||||
def assign_deprecated_shortcuts(request, response)
|
||||
DEPRECATED_INSTANCE_VARIABLES.each do |method|
|
||||
var = "@#{method}"
|
||||
if instance_variables.include?(var)
|
||||
value = instance_variable_get(var)
|
||||
unless ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy === value
|
||||
raise "Deprecating #{var}, but it's already set to #{value.inspect}! Use the #{method}= writer method instead of setting #{var} directly."
|
||||
end
|
||||
end
|
||||
instance_variable_set var, ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, method)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize_current_url
|
||||
|
@ -1089,22 +1142,26 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def log_processing
|
||||
if logger
|
||||
if logger && logger.info?
|
||||
logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]"
|
||||
logger.info " Session ID: #{@_session.session_id}" if @_session and @_session.respond_to?(:session_id)
|
||||
logger.info " Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(params).inspect : params.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def default_render #:nodoc:
|
||||
render
|
||||
end
|
||||
|
||||
def perform_action
|
||||
if self.class.action_methods.include?(action_name)
|
||||
send(action_name)
|
||||
render unless performed?
|
||||
default_render unless performed?
|
||||
elsif respond_to? :method_missing
|
||||
send(:method_missing, action_name)
|
||||
render unless performed?
|
||||
method_missing action_name
|
||||
default_render unless performed?
|
||||
elsif template_exists? && template_public?
|
||||
render
|
||||
default_render
|
||||
else
|
||||
raise UnknownAction, "No action responded to #{action_name}", caller
|
||||
end
|
||||
|
@ -1132,7 +1189,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def self.action_methods
|
||||
@action_methods ||= Set.new(public_instance_methods - hidden_actions)
|
||||
@action_methods ||= Set.new(public_instance_methods.map(&:to_s)) - hidden_actions
|
||||
end
|
||||
|
||||
def add_variables_to_assigns
|
||||
|
@ -1160,7 +1217,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def add_class_variables_to_assigns
|
||||
%w(template_root logger template_class ignore_missing_templates).each do |cvar|
|
||||
%w(view_paths logger template_class ignore_missing_templates).each do |cvar|
|
||||
@assigns[cvar] = self.send(cvar)
|
||||
end
|
||||
end
|
||||
|
@ -1199,16 +1256,17 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def template_exempt_from_layout?(template_name = default_template_name)
|
||||
extension = @template.pick_template_extension(template_name) rescue nil
|
||||
extension = @template && @template.pick_template_extension(template_name)
|
||||
name_with_extension = !template_name.include?('.') && extension ? "#{template_name}.#{extension}" : template_name
|
||||
extension == :rjs || @@exempt_from_layout.any? { |ext| name_with_extension =~ ext }
|
||||
@@exempt_from_layout.any? { |ext| name_with_extension =~ ext }
|
||||
end
|
||||
|
||||
def assert_existence_of_template_file(template_name)
|
||||
unless template_exists?(template_name) || ignore_missing_templates
|
||||
full_template_path = @template.send(:full_template_path, template_name, 'rhtml')
|
||||
full_template_path = template_name.include?('.') ? template_name : "#{template_name}.#{@template.template_format}.erb"
|
||||
display_paths = view_paths.join(':')
|
||||
template_type = (template_name =~ /layouts/i) ? 'layout' : 'template'
|
||||
raise(MissingTemplate, "Missing #{template_type} #{full_template_path}")
|
||||
raise(MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -40,50 +40,55 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
def render_with_benchmark(options = nil, deprecated_status = nil, &block)
|
||||
unless logger
|
||||
render_without_benchmark(options, deprecated_status, &block)
|
||||
else
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
protected
|
||||
def render_with_benchmark(options = nil, deprecated_status = nil, &block)
|
||||
unless logger
|
||||
render_without_benchmark(options, &block)
|
||||
else
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
|
||||
render_output = nil
|
||||
@rendering_runtime = Benchmark::measure{ render_output = render_without_benchmark(options, deprecated_status, &block) }.real
|
||||
render_output = nil
|
||||
@rendering_runtime = Benchmark::measure{ render_output = render_without_benchmark(options, &block) }.real
|
||||
|
||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
@db_rt_before_render = db_runtime
|
||||
@db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
|
||||
@rendering_runtime -= @db_rt_after_render
|
||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
@db_rt_before_render = db_runtime
|
||||
@db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
|
||||
@rendering_runtime -= @db_rt_after_render
|
||||
end
|
||||
|
||||
render_output
|
||||
end
|
||||
end
|
||||
|
||||
render_output
|
||||
end
|
||||
end
|
||||
|
||||
def perform_action_with_benchmark
|
||||
unless logger
|
||||
perform_action_without_benchmark
|
||||
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 defined?(@rendering_runtime)
|
||||
log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
log_message << " | #{headers["Status"]}"
|
||||
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
||||
logger.info(log_message)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def perform_action_with_benchmark
|
||||
unless logger
|
||||
perform_action_without_benchmark
|
||||
else
|
||||
runtime = [ Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001 ].max
|
||||
|
||||
log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
|
||||
log_message << rendering_runtime(runtime) if defined?(@rendering_runtime)
|
||||
log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
log_message << " | #{headers["Status"]}"
|
||||
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
||||
|
||||
logger.info(log_message)
|
||||
response.headers["X-Runtime"] = sprintf("%.5f", runtime)
|
||||
end
|
||||
end
|
||||
|
||||
def rendering_runtime(runtime)
|
||||
" | Rendering: #{sprintf("%.5f", @rendering_runtime)} (#{sprintf("%d", (@rendering_runtime * 100) / runtime)}%)"
|
||||
percentage = @rendering_runtime * 100 / runtime
|
||||
" | Rendering: %.5f (%d%%)" % [@rendering_runtime, percentage.to_i]
|
||||
end
|
||||
|
||||
def active_record_runtime(runtime)
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
||||
db_runtime += @db_rt_before_render if @db_rt_before_render
|
||||
db_runtime += @db_rt_after_render if @db_rt_after_render
|
||||
db_percentage = (db_runtime * 100) / runtime
|
||||
" | DB: #{sprintf("%.5f", db_runtime)} (#{sprintf("%d", db_percentage)}%)"
|
||||
db_percentage = db_runtime * 100 / runtime
|
||||
" | DB: %.5f (%d%%)" % [db_runtime, db_percentage.to_i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,9 +11,13 @@ module ActionController #:nodoc:
|
|||
# Note: To turn off all caching and sweeping, set Base.perform_caching = false.
|
||||
module Caching
|
||||
def self.included(base) #:nodoc:
|
||||
base.send(:include, Pages, Actions, Fragments, Sweeping)
|
||||
|
||||
base.class_eval do
|
||||
include Pages, Actions, Fragments
|
||||
|
||||
if defined? ActiveRecord
|
||||
include Sweeping, SqlCache
|
||||
end
|
||||
|
||||
@@perform_caching = true
|
||||
cattr_accessor :perform_caching
|
||||
end
|
||||
|
@ -77,7 +81,7 @@ module ActionController #:nodoc:
|
|||
return unless perform_caching
|
||||
|
||||
benchmark "Expired page: #{page_cache_file(path)}" do
|
||||
File.delete(page_cache_path(path)) if File.exists?(page_cache_path(path))
|
||||
File.delete(page_cache_path(path)) if File.exist?(page_cache_path(path))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -96,14 +100,13 @@ module ActionController #:nodoc:
|
|||
# matches the triggering url.
|
||||
def caches_page(*actions)
|
||||
return unless perform_caching
|
||||
actions.each do |action|
|
||||
class_eval "after_filter { |c| c.cache_page if c.action_name == '#{action}' }"
|
||||
end
|
||||
actions = actions.map(&:to_s)
|
||||
after_filter { |c| c.cache_page if actions.include?(c.action_name) }
|
||||
end
|
||||
|
||||
private
|
||||
def page_cache_file(path)
|
||||
name = ((path.empty? || path == "/") ? "/index" : URI.unescape(path))
|
||||
name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/'))
|
||||
name << page_cache_extension unless (name.split('/').last || name).include? '.'
|
||||
return name
|
||||
end
|
||||
|
@ -117,21 +120,36 @@ module ActionController #:nodoc:
|
|||
# expire_page :controller => "lists", :action => "show"
|
||||
def expire_page(options = {})
|
||||
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)))
|
||||
|
||||
if options.is_a?(Hash)
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action)))
|
||||
end
|
||||
else
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true)))
|
||||
end
|
||||
else
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true)))
|
||||
self.class.expire_page(options)
|
||||
end
|
||||
end
|
||||
|
||||
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used
|
||||
# If no options are provided, the current +options+ for this action is used. Example:
|
||||
# If no options are provided, the requested url is used. Example:
|
||||
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
|
||||
def cache_page(content = nil, options = {})
|
||||
def cache_page(content = nil, options = nil)
|
||||
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, :format => params[:format])))
|
||||
|
||||
path = case options
|
||||
when Hash
|
||||
url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format]))
|
||||
when String
|
||||
options
|
||||
else
|
||||
request.path
|
||||
end
|
||||
|
||||
self.class.cache_page(content || response.body, path)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -161,17 +179,26 @@ module ActionController #:nodoc:
|
|||
# Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt>
|
||||
# are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same
|
||||
# as <tt>:action => 'list', :format => :xml</tt>.
|
||||
#
|
||||
# You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy
|
||||
# for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# before_filter :authenticate, :except => :public
|
||||
# caches_page :public
|
||||
# caches_action :show, :cache_path => { :project => 1 }
|
||||
# caches_action :show, :cache_path => Proc.new { |controller|
|
||||
# controller.params[:user_id] ?
|
||||
# controller.send(:user_list_url, c.params[:user_id], c.params[:id]) :
|
||||
# controller.send(:list_url, c.params[:id]) }
|
||||
# end
|
||||
module Actions
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
attr_accessor :rendered_action_cache, :action_cache_path
|
||||
alias_method_chain :protected_instance_variables, :action_caching
|
||||
end
|
||||
end
|
||||
|
||||
def protected_instance_variables_with_action_caching
|
||||
protected_instance_variables_without_action_caching + %w(@action_cache_path)
|
||||
base.class_eval do
|
||||
attr_accessor :rendered_action_cache, :action_cache_path
|
||||
alias_method_chain :protected_instance_variables, :action_caching
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
@ -179,12 +206,14 @@ module ActionController #:nodoc:
|
|||
# See ActionController::Caching::Actions for details.
|
||||
def caches_action(*actions)
|
||||
return unless perform_caching
|
||||
action_cache_filter = ActionCacheFilter.new(*actions)
|
||||
before_filter action_cache_filter
|
||||
after_filter action_cache_filter
|
||||
around_filter(ActionCacheFilter.new(*actions))
|
||||
end
|
||||
end
|
||||
|
||||
def protected_instance_variables_with_action_caching
|
||||
protected_instance_variables_without_action_caching + %w(@action_cache_path)
|
||||
end
|
||||
|
||||
def expire_action(options = {})
|
||||
return unless perform_caching
|
||||
if options[:action].is_a?(Array)
|
||||
|
@ -197,17 +226,18 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
class ActionCacheFilter #:nodoc:
|
||||
def initialize(*actions)
|
||||
def initialize(*actions, &block)
|
||||
@options = actions.extract_options!
|
||||
@actions = Set.new actions
|
||||
end
|
||||
|
||||
def before(controller)
|
||||
return unless @actions.include?(controller.action_name.to_sym)
|
||||
cache_path = ActionCachePath.new(controller, {})
|
||||
return unless @actions.include?(controller.action_name.intern)
|
||||
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options))
|
||||
if cache = controller.read_fragment(cache_path.path)
|
||||
controller.rendered_action_cache = true
|
||||
set_content_type!(controller, cache_path.extension)
|
||||
controller.send(:render_text, cache)
|
||||
controller.send!(:render_for_text, cache)
|
||||
false
|
||||
else
|
||||
controller.action_cache_path = cache_path
|
||||
|
@ -215,15 +245,22 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def after(controller)
|
||||
return if !@actions.include?(controller.action_name.to_sym) || controller.rendered_action_cache
|
||||
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache || !caching_allowed(controller)
|
||||
controller.write_fragment(controller.action_cache_path.path, controller.response.body)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def set_content_type!(controller, extension)
|
||||
controller.response.content_type = Mime::EXTENSION_LOOKUP[extension].to_s if extension
|
||||
controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension
|
||||
end
|
||||
|
||||
def path_options_for(controller, options)
|
||||
((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {}
|
||||
end
|
||||
|
||||
def caching_allowed(controller)
|
||||
controller.request.get? && controller.response.headers['Status'].to_i == 200
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ActionCachePath
|
||||
|
@ -253,7 +290,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def extract_extension(file_path)
|
||||
# Don't want just what comes after the last '.' to accomodate multi part extensions
|
||||
# Don't want just what comes after the last '.' to accommodate multi part extensions
|
||||
# such as tar.gz.
|
||||
file_path[/^[^.]+\.(.+)$/, 1]
|
||||
end
|
||||
|
@ -270,25 +307,28 @@ module ActionController #:nodoc:
|
|||
# <%= 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
|
||||
# <tt>expire_fragment(:controller => "topics", :action => "list")</tt> -- if that was the controller/action used. This is not too helpful
|
||||
# if you need to cache multiple fragments per action or if the action itself is cached using <tt>caches_action</tt>. So instead we should
|
||||
# qualify the name of the action used with something like:
|
||||
# This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would
|
||||
# be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>.
|
||||
#
|
||||
# This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using
|
||||
# <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like:
|
||||
#
|
||||
# <% cache(:action => "list", :action_suffix => "all_topics") do %>
|
||||
#
|
||||
# That would result in a name such as "/topics/list/all_topics", which wouldn't conflict with any action cache and neither with another
|
||||
# fragment using a different suffix. Note that the URL doesn't have to really exist or be callable. We're just using the url_for system
|
||||
# to generate unique cache names that we can refer to later for expirations. The expiration call for this example would be
|
||||
# <tt>expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")</tt>.
|
||||
# That would result in a name such as "/topics/list/all_topics", avoiding conflicts with the action cache and with any fragments that use a
|
||||
# different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique
|
||||
# cache names that we can refer to when we need to expire the cache.
|
||||
#
|
||||
# The expiration call for this example is:
|
||||
#
|
||||
# expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
|
||||
#
|
||||
# == Fragment stores
|
||||
#
|
||||
# In order to use the fragment caching, you need to designate where the caches should be stored. This is done by assigning a fragment store
|
||||
# of which there are four different kinds:
|
||||
# By default, cached fragments are stored in memory. The available store options are:
|
||||
#
|
||||
# * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and shares the fragments for
|
||||
# all the web server processes running off the same application directory.
|
||||
# * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and allows all
|
||||
# processes running from the same application directory to access the cached content.
|
||||
# * MemoryStore: Keeps the fragments in memory, which is fine for WEBrick and for FCGI (if you don't care that each FCGI process holds its
|
||||
# own fragment store). It's not suitable for CGI as the process is thrown away at the end of each request. It can potentially also take
|
||||
# up a lot of memory since each process keeps all the caches in memory.
|
||||
|
@ -310,6 +350,7 @@ module ActionController #:nodoc:
|
|||
@@fragment_cache_store = MemoryStore.new
|
||||
cattr_reader :fragment_cache_store
|
||||
|
||||
# Defines the storage option for cached fragments
|
||||
def self.fragment_cache_store=(store_option)
|
||||
store, *parameters = *([ store_option ].flatten)
|
||||
@@fragment_cache_store = if store.is_a?(Symbol)
|
||||
|
@ -323,6 +364,9 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Given a name (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,
|
||||
# writing, or expiring a cached fragment. If the name is a hash, the generated name is the return
|
||||
# value of url_for on that hash (without the protocol).
|
||||
def fragment_cache_key(name)
|
||||
name.is_a?(Hash) ? url_for(name).split("://").last : name
|
||||
end
|
||||
|
@ -331,7 +375,7 @@ module ActionController #:nodoc:
|
|||
def cache_erb_fragment(block, name = {}, options = nil)
|
||||
unless perform_caching then block.call; return end
|
||||
|
||||
buffer = eval("_erbout", block.binding)
|
||||
buffer = eval(ActionView::Base.erb_variable, block.binding)
|
||||
|
||||
if cache = read_fragment(name, options)
|
||||
buffer.concat(cache)
|
||||
|
@ -342,6 +386,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Writes <tt>content</tt> to the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
||||
def write_fragment(name, content, options = nil)
|
||||
return unless perform_caching
|
||||
|
||||
|
@ -352,6 +397,7 @@ module ActionController #:nodoc:
|
|||
content
|
||||
end
|
||||
|
||||
# Reads a cached fragment from the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
||||
def read_fragment(name, options = nil)
|
||||
return unless perform_caching
|
||||
|
||||
|
@ -368,8 +414,8 @@ module ActionController #:nodoc:
|
|||
# %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.
|
||||
# Regexp expiration is only supported on caches that can iterate over
|
||||
# all keys (unlike memcached).
|
||||
def expire_fragment(name, options = nil)
|
||||
return unless perform_caching
|
||||
|
||||
|
@ -386,12 +432,6 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Deprecated -- just call expire_fragment with a regular expression
|
||||
def expire_matched_fragments(matcher = /.*/, options = nil) #:nodoc:
|
||||
expire_fragment(matcher, options)
|
||||
end
|
||||
deprecate :expire_matched_fragments => :expire_fragment
|
||||
|
||||
|
||||
class UnthreadedMemoryStore #:nodoc:
|
||||
def initialize #:nodoc:
|
||||
|
@ -438,7 +478,7 @@ module ActionController #:nodoc:
|
|||
super
|
||||
if ActionController::Base.allow_concurrency
|
||||
@mutex = Mutex.new
|
||||
MemoryStore.send(:include, ThreadSafety)
|
||||
MemoryStore.module_eval { include ThreadSafety }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -453,6 +493,8 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
begin
|
||||
require_library_or_gem 'memcache'
|
||||
class MemCacheStore < MemoryStore #:nodoc:
|
||||
attr_reader :addresses
|
||||
|
||||
|
@ -464,6 +506,9 @@ module ActionController #:nodoc:
|
|||
@data = MemCache.new(*addresses)
|
||||
end
|
||||
end
|
||||
rescue LoadError
|
||||
# MemCache wasn't available so neither can the store be
|
||||
end
|
||||
|
||||
class UnthreadedFileStore #:nodoc:
|
||||
attr_reader :cache_path
|
||||
|
@ -507,7 +552,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def ensure_cache_path(path)
|
||||
FileUtils.makedirs(path) unless File.exists?(path)
|
||||
FileUtils.makedirs(path) unless File.exist?(path)
|
||||
end
|
||||
|
||||
def search_dir(dir, &callback)
|
||||
|
@ -528,7 +573,7 @@ module ActionController #:nodoc:
|
|||
super(cache_path)
|
||||
if ActionController::Base.allow_concurrency
|
||||
@mutex = Mutex.new
|
||||
FileStore.send(:include, ThreadSafety)
|
||||
FileStore.module_eval { include ThreadSafety }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -564,7 +609,7 @@ module ActionController #:nodoc:
|
|||
module ClassMethods #:nodoc:
|
||||
def cache_sweeper(*sweepers)
|
||||
return unless perform_caching
|
||||
configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {}
|
||||
configuration = sweepers.extract_options!
|
||||
sweepers.each do |sweeper|
|
||||
ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
|
||||
sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance
|
||||
|
@ -583,10 +628,6 @@ module ActionController #:nodoc:
|
|||
class Sweeper < ActiveRecord::Observer #:nodoc:
|
||||
attr_accessor :controller
|
||||
|
||||
# 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::Deprecated
|
||||
|
||||
def before(controller)
|
||||
self.controller = controller
|
||||
callback(:before)
|
||||
|
@ -598,20 +639,45 @@ module ActionController #:nodoc:
|
|||
self.controller = nil
|
||||
end
|
||||
|
||||
protected
|
||||
# gets the action cache path for the given options.
|
||||
def action_path_for(options)
|
||||
ActionController::Caching::Actions::ActionCachePath.path_for(controller, options)
|
||||
end
|
||||
|
||||
# Retrieve instance variables set in the controller.
|
||||
def assigns(key)
|
||||
controller.instance_variable_get("@#{key}")
|
||||
end
|
||||
|
||||
private
|
||||
def callback(timing)
|
||||
controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
|
||||
action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
|
||||
|
||||
send(controller_callback_method_name) if respond_to?(controller_callback_method_name)
|
||||
send(action_callback_method_name) if respond_to?(action_callback_method_name)
|
||||
send!(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
|
||||
send!(action_callback_method_name) if respond_to?(action_callback_method_name, true)
|
||||
end
|
||||
|
||||
def method_missing(method, *arguments)
|
||||
return if @controller.nil?
|
||||
@controller.send(method, *arguments)
|
||||
@controller.send!(method, *arguments)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module SqlCache
|
||||
def self.included(base) #:nodoc:
|
||||
if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache)
|
||||
base.alias_method_chain :perform_action, :caching
|
||||
end
|
||||
end
|
||||
|
||||
def perform_action_with_caching
|
||||
ActiveRecord::Base.cache do
|
||||
perform_action_without_caching
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
16
vendor/rails/actionpack/lib/action_controller/cgi_ext.rb
vendored
Normal file
16
vendor/rails/actionpack/lib/action_controller/cgi_ext.rb
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
require 'action_controller/cgi_ext/stdinput'
|
||||
require 'action_controller/cgi_ext/query_extension'
|
||||
require 'action_controller/cgi_ext/cookie'
|
||||
require 'action_controller/cgi_ext/session'
|
||||
|
||||
class CGI #:nodoc:
|
||||
include ActionController::CgiExt::Stdinput
|
||||
|
||||
class << self
|
||||
alias :escapeHTML_fail_on_nil :escapeHTML
|
||||
|
||||
def escapeHTML(string)
|
||||
escapeHTML_fail_on_nil(string) unless string.nil?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,36 +0,0 @@
|
|||
require 'cgi'
|
||||
require 'cgi/session'
|
||||
require 'cgi/session/pstore'
|
||||
require 'action_controller/cgi_ext/cgi_methods'
|
||||
|
||||
# Wrapper around the CGIMethods that have been secluded to allow testing without
|
||||
# an instantiated CGI object
|
||||
class CGI #:nodoc:
|
||||
class << self
|
||||
alias :escapeHTML_fail_on_nil :escapeHTML
|
||||
|
||||
def escapeHTML(string)
|
||||
escapeHTML_fail_on_nil(string) unless string.nil?
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a parameter hash including values from both the request (POST/GET)
|
||||
# and the query string with the latter taking precedence.
|
||||
def parameters
|
||||
request_parameters.update(query_parameters)
|
||||
end
|
||||
|
||||
def query_parameters
|
||||
CGIMethods.parse_query_parameters(query_string)
|
||||
end
|
||||
|
||||
def request_parameters
|
||||
CGIMethods.parse_request_parameters(params, env_table)
|
||||
end
|
||||
|
||||
def session(parameters = nil)
|
||||
parameters = {} if parameters.nil?
|
||||
parameters['database_manager'] = CGI::Session::PStore
|
||||
CGI::Session.new(self, parameters)
|
||||
end
|
||||
end
|
|
@ -1,211 +0,0 @@
|
|||
require 'cgi'
|
||||
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:
|
||||
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
|
||||
|
||||
FormEncodedPairParser.new(pairs).result
|
||||
end
|
||||
|
||||
# DEPRECATED: Use parse_form_encoded_parameters
|
||||
def parse_request_parameters(params)
|
||||
parser = FormEncodedPairParser.new
|
||||
|
||||
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
|
||||
|
||||
parser.result
|
||||
end
|
||||
|
||||
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? ? {} : 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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# 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 value
|
||||
end
|
||||
|
||||
def type_conflict!(klass, value)
|
||||
raise TypeError,
|
||||
"Conflicting types for parameter containers. " +
|
||||
"Expected an instance of #{klass}, but found an instance of #{value.class}. " +
|
||||
"This can be caused by passing Array and Hash based paramters qs[]=value&qs[key]=value. "
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,10 +1,11 @@
|
|||
CGI.module_eval { remove_const "Cookie" }
|
||||
|
||||
# TODO: document how this differs from stdlib CGI::Cookie
|
||||
class CGI #:nodoc:
|
||||
# This is a cookie class that fixes the performance problems with the default one that ships with 1.8.1 and below.
|
||||
# It replaces the inheritance on SimpleDelegator with DelegateClass(Array) following the suggestion from Matz on
|
||||
# http://groups.google.com/groups?th=e3a4e68ba042f842&seekm=c3sioe%241qvm%241%40news.cybercity.dk#link14
|
||||
class Cookie < DelegateClass(Array)
|
||||
attr_accessor :name, :value, :path, :domain, :expires
|
||||
attr_reader :secure, :http_only
|
||||
|
||||
# Create a new CGI::Cookie object.
|
||||
#
|
||||
# The contents of the cookie can be specified as a +name+ and one
|
||||
|
@ -19,9 +20,11 @@ class CGI #:nodoc:
|
|||
# domain:: the domain for which this cookie applies.
|
||||
# expires:: the time at which this cookie expires, as a +Time+ object.
|
||||
# secure:: whether this cookie is a secure cookie or not (default to
|
||||
# false). Secure cookies are only transmitted to HTTPS
|
||||
# false). Secure cookies are only transmitted to HTTPS
|
||||
# servers.
|
||||
#
|
||||
# http_only:: whether this cookie can be accessed by client side scripts (e.g. document.cookie) or only over HTTP
|
||||
# More details: http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx
|
||||
# Defaults to false.
|
||||
# These keywords correspond to attributes of the cookie object.
|
||||
def initialize(name = '', *value)
|
||||
if name.kind_of?(String)
|
||||
|
@ -30,6 +33,7 @@ class CGI #:nodoc:
|
|||
@domain = nil
|
||||
@expires = nil
|
||||
@secure = false
|
||||
@http_only = false
|
||||
@path = nil
|
||||
else
|
||||
@name = name['name']
|
||||
|
@ -37,12 +41,11 @@ class CGI #:nodoc:
|
|||
@domain = name['domain']
|
||||
@expires = name['expires']
|
||||
@secure = name['secure'] || false
|
||||
@http_only = name['http_only'] || false
|
||||
@path = name['path']
|
||||
end
|
||||
|
||||
unless @name
|
||||
raise ArgumentError, "`name' required"
|
||||
end
|
||||
|
||||
raise ArgumentError, "`name' required" unless @name
|
||||
|
||||
# simple support for IE
|
||||
unless @path
|
||||
|
@ -53,48 +56,26 @@ class CGI #:nodoc:
|
|||
super(@value)
|
||||
end
|
||||
|
||||
def __setobj__(obj)
|
||||
@_dc_obj = obj
|
||||
# Set whether the Cookie is a secure cookie or not.
|
||||
def secure=(val)
|
||||
@secure = val == true
|
||||
end
|
||||
|
||||
attr_accessor("name", "value", "path", "domain", "expires")
|
||||
attr_reader("secure")
|
||||
|
||||
# Set whether the Cookie is a secure cookie or not.
|
||||
#
|
||||
# +val+ must be a boolean.
|
||||
def secure=(val)
|
||||
@secure = val if val == true or val == false
|
||||
@secure
|
||||
# Set whether the Cookie is an HTTP only cookie or not.
|
||||
def http_only=(val)
|
||||
@http_only = val == true
|
||||
end
|
||||
|
||||
# Convert the Cookie to its string representation.
|
||||
def to_s
|
||||
buf = ""
|
||||
buf = ''
|
||||
buf << @name << '='
|
||||
|
||||
if @value.kind_of?(String)
|
||||
buf << CGI::escape(@value)
|
||||
else
|
||||
buf << @value.collect{|v| CGI::escape(v) }.join("&")
|
||||
end
|
||||
|
||||
if @domain
|
||||
buf << '; domain=' << @domain
|
||||
end
|
||||
|
||||
if @path
|
||||
buf << '; path=' << @path
|
||||
end
|
||||
|
||||
if @expires
|
||||
buf << '; expires=' << CGI::rfc1123_date(@expires)
|
||||
end
|
||||
|
||||
if @secure == true
|
||||
buf << '; secure'
|
||||
end
|
||||
|
||||
buf << (@value.kind_of?(String) ? CGI::escape(@value) : @value.collect{|v| CGI::escape(v) }.join("&"))
|
||||
buf << '; domain=' << @domain if @domain
|
||||
buf << '; path=' << @path if @path
|
||||
buf << '; expires=' << CGI::rfc1123_date(@expires) if @expires
|
||||
buf << '; secure' if @secure
|
||||
buf << '; HttpOnly' if @http_only
|
||||
buf
|
||||
end
|
||||
|
||||
|
@ -108,7 +89,7 @@ class CGI #:nodoc:
|
|||
cookies = Hash.new([])
|
||||
|
||||
if raw_cookie
|
||||
raw_cookie.split(/; ?/).each do |pairs|
|
||||
raw_cookie.split(/[;,]\s?/).each do |pairs|
|
||||
name, values = pairs.split('=',2)
|
||||
next unless name and values
|
||||
name = CGI::unescape(name)
|
|
@ -1,30 +0,0 @@
|
|||
# CGI::Session::PStore.initialize requires 'digest/md5' on every call.
|
||||
# This makes sense when spawning processes per request, but is
|
||||
# unnecessarily expensive when serving requests from a long-lived
|
||||
# process.
|
||||
require 'cgi/session'
|
||||
require 'cgi/session/pstore'
|
||||
require 'digest/md5'
|
||||
|
||||
class CGI::Session::PStore #:nodoc:
|
||||
def initialize(session, option={})
|
||||
dir = option['tmpdir'] || Dir::tmpdir
|
||||
prefix = option['prefix'] || ''
|
||||
id = session.session_id
|
||||
md5 = Digest::MD5.hexdigest(id)[0,16]
|
||||
path = dir+"/"+prefix+md5
|
||||
path.untaint
|
||||
if File::exist?(path)
|
||||
@hash = nil
|
||||
else
|
||||
unless session.new_session
|
||||
raise CGI::Session::NoSession, "uninitialized session"
|
||||
end
|
||||
@hash = {}
|
||||
end
|
||||
@p = ::PStore.new(path)
|
||||
@p.transaction do |p|
|
||||
File.chmod(0600, p.path)
|
||||
end
|
||||
end
|
||||
end
|
22
vendor/rails/actionpack/lib/action_controller/cgi_ext/query_extension.rb
vendored
Normal file
22
vendor/rails/actionpack/lib/action_controller/cgi_ext/query_extension.rb
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
require 'cgi'
|
||||
|
||||
class CGI #:nodoc:
|
||||
module QueryExtension
|
||||
# Remove the old initialize_query method before redefining it.
|
||||
remove_method :initialize_query
|
||||
|
||||
# Neuter CGI parameter parsing.
|
||||
def initialize_query
|
||||
# Fix some strange request environments.
|
||||
env_table['REQUEST_METHOD'] ||= 'GET'
|
||||
|
||||
# POST assumes missing Content-Type is application/x-www-form-urlencoded.
|
||||
if env_table['CONTENT_TYPE'].blank? && env_table['REQUEST_METHOD'] == 'POST'
|
||||
env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
|
||||
end
|
||||
|
||||
@cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
|
||||
@params = {}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,95 +0,0 @@
|
|||
class CGI #:nodoc:
|
||||
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
|
||||
@cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
|
||||
|
||||
# Fix some strange request environments.
|
||||
if method = env_table['REQUEST_METHOD']
|
||||
method = method.to_s.downcase.intern
|
||||
else
|
||||
method = :get
|
||||
end
|
||||
|
||||
# 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
|
||||
unless defined?(MULTIPART_FORM_BOUNDARY_RE)
|
||||
MULTIPART_FORM_BOUNDARY_RE = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n #"
|
||||
end
|
||||
|
||||
def extract_multipart_form_boundary(content_type)
|
||||
MULTIPART_FORM_BOUNDARY_RE.match(content_type).to_a.pop
|
||||
end
|
||||
|
||||
if defined? MOD_RUBY
|
||||
def read_query
|
||||
Apache::request.args || ''
|
||||
end
|
||||
else
|
||||
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?
|
||||
uri.split('?', 2)[1] || ''
|
||||
else
|
||||
env_qs || ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def read_body(content_length)
|
||||
stdinput.binmode if stdinput.respond_to?(:binmode)
|
||||
content = stdinput.read(content_length) || ''
|
||||
# Fix for Safari Ajax postings that always append \000
|
||||
content.chop! if content[-1] == 0
|
||||
content.gsub!(/&_=$/, '')
|
||||
env_table['RAW_POST_DATA'] = content.freeze
|
||||
end
|
||||
|
||||
def read_params(method, content_length)
|
||||
case method
|
||||
when :get
|
||||
read_query
|
||||
when :post, :put
|
||||
read_body(content_length)
|
||||
when :cmd
|
||||
read_from_cmdline
|
||||
else # :head, :delete, :options, :trace, :connect
|
||||
read_query
|
||||
end
|
||||
end
|
||||
end # module QueryExtension
|
||||
end
|
73
vendor/rails/actionpack/lib/action_controller/cgi_ext/session.rb
vendored
Normal file
73
vendor/rails/actionpack/lib/action_controller/cgi_ext/session.rb
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
require 'digest/md5'
|
||||
require 'cgi/session'
|
||||
require 'cgi/session/pstore'
|
||||
|
||||
class CGI #:nodoc:
|
||||
# * Expose the CGI instance to session stores.
|
||||
# * Don't require 'digest/md5' whenever a new session id is generated.
|
||||
class Session #:nodoc:
|
||||
begin
|
||||
require 'securerandom'
|
||||
|
||||
# Generate a 32-character unique id using SecureRandom.
|
||||
# This is used to generate session ids but may be reused elsewhere.
|
||||
def self.generate_unique_id(constant = nil)
|
||||
SecureRandom.hex(16)
|
||||
end
|
||||
rescue LoadError
|
||||
# Generate an 32-character unique id based on a hash of the current time,
|
||||
# a random number, the process id, and a constant string. This is used
|
||||
# to generate session ids but may be reused elsewhere.
|
||||
def self.generate_unique_id(constant = 'foobar')
|
||||
md5 = Digest::MD5.new
|
||||
now = Time.now
|
||||
md5 << now.to_s
|
||||
md5 << String(now.usec)
|
||||
md5 << String(rand(0))
|
||||
md5 << String($$)
|
||||
md5 << constant
|
||||
md5.hexdigest
|
||||
end
|
||||
end
|
||||
|
||||
# Make the CGI instance available to session stores.
|
||||
attr_reader :cgi
|
||||
attr_reader :dbman
|
||||
alias_method :initialize_without_cgi_reader, :initialize
|
||||
def initialize(cgi, options = {})
|
||||
@cgi = cgi
|
||||
initialize_without_cgi_reader(cgi, options)
|
||||
end
|
||||
|
||||
private
|
||||
# Create a new session id.
|
||||
def create_new_id
|
||||
@new_session = true
|
||||
self.class.generate_unique_id
|
||||
end
|
||||
|
||||
# * Don't require 'digest/md5' whenever a new session is started.
|
||||
class PStore #:nodoc:
|
||||
def initialize(session, option={})
|
||||
dir = option['tmpdir'] || Dir::tmpdir
|
||||
prefix = option['prefix'] || ''
|
||||
id = session.session_id
|
||||
md5 = Digest::MD5.hexdigest(id)[0,16]
|
||||
path = dir+"/"+prefix+md5
|
||||
path.untaint
|
||||
if File::exist?(path)
|
||||
@hash = nil
|
||||
else
|
||||
unless session.new_session
|
||||
raise CGI::Session::NoSession, "uninitialized session"
|
||||
end
|
||||
@hash = {}
|
||||
end
|
||||
@p = ::PStore.new(path)
|
||||
@p.transaction do |p|
|
||||
File.chmod(0600, p.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,30 +0,0 @@
|
|||
# CGI::Session#create_new_id requires 'digest/md5' on every call. This makes
|
||||
# sense when spawning processes per request, but is unnecessarily expensive
|
||||
# when serving requests from a long-lived process.
|
||||
#
|
||||
# http://railsexpress.de/blog/articles/2005/11/22/speeding-up-the-creation-of-new-sessions
|
||||
require 'cgi/session'
|
||||
require 'digest/md5'
|
||||
|
||||
class CGI
|
||||
class Session #:nodoc:
|
||||
private
|
||||
# Create a new session id.
|
||||
#
|
||||
# The session id is an MD5 hash based upon the time,
|
||||
# a random number, and a constant string. This routine
|
||||
# is used internally for automatically generated
|
||||
# session ids.
|
||||
def create_new_id
|
||||
md5 = Digest::MD5::new
|
||||
now = Time::now
|
||||
md5.update(now.to_s)
|
||||
md5.update(String(now.usec))
|
||||
md5.update(String(rand(0)))
|
||||
md5.update(String($$))
|
||||
md5.update('foobar')
|
||||
@new_session = true
|
||||
md5.hexdigest
|
||||
end
|
||||
end
|
||||
end
|
23
vendor/rails/actionpack/lib/action_controller/cgi_ext/stdinput.rb
vendored
Normal file
23
vendor/rails/actionpack/lib/action_controller/cgi_ext/stdinput.rb
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
require 'cgi'
|
||||
|
||||
module ActionController
|
||||
module CgiExt
|
||||
# Publicize the CGI's internal input stream so we can lazy-read
|
||||
# request.body. Make it writable so we don't have to play $stdin games.
|
||||
module Stdinput
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
remove_method :stdinput
|
||||
attr_accessor :stdinput
|
||||
end
|
||||
|
||||
base.alias_method_chain :initialize, :stdinput
|
||||
end
|
||||
|
||||
def initialize_with_stdinput(type = nil, stdinput = $stdin)
|
||||
@stdinput = stdinput
|
||||
initialize_without_stdinput(type || 'query')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +1,5 @@
|
|||
require 'action_controller/cgi_ext/cgi_ext'
|
||||
require 'action_controller/cgi_ext/cookie_performance_fix'
|
||||
require 'action_controller/cgi_ext/raw_post_data_fix'
|
||||
require 'action_controller/cgi_ext/session_performance_fix'
|
||||
require 'action_controller/cgi_ext/pstore_performance_fix'
|
||||
require 'action_controller/cgi_ext'
|
||||
require 'action_controller/session/cookie_store'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class Base
|
||||
|
@ -36,55 +33,56 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
class CgiRequest < AbstractRequest #:nodoc:
|
||||
attr_accessor :cgi, :session_options, :cookie_only
|
||||
attr_accessor :cgi, :session_options
|
||||
class SessionFixationAttempt < StandardError; end #:nodoc:
|
||||
|
||||
DEFAULT_SESSION_OPTIONS = {
|
||||
:database_manager => CGI::Session::PStore,
|
||||
:prefix => "ruby_sess.",
|
||||
:session_path => "/",
|
||||
:database_manager => CGI::Session::CookieStore, # store data in cookie
|
||||
:prefix => "ruby_sess.", # prefix session file names
|
||||
:session_path => "/", # available to all paths in app
|
||||
:session_key => "_session_id",
|
||||
:cookie_only => true
|
||||
} unless const_defined?(:DEFAULT_SESSION_OPTIONS)
|
||||
|
||||
def initialize(cgi, session_options = {})
|
||||
@cgi = cgi
|
||||
@session_options = session_options
|
||||
@env = @cgi.send(:env_table)
|
||||
@cookie_only = session_options.delete :cookie_only
|
||||
@env = @cgi.send!(:env_table)
|
||||
super()
|
||||
end
|
||||
|
||||
def query_string
|
||||
if (qs = @cgi.query_string) && !qs.empty?
|
||||
qs = @cgi.query_string if @cgi.respond_to?(:query_string)
|
||||
if !qs.blank?
|
||||
qs
|
||||
elsif uri = @env['REQUEST_URI']
|
||||
parts = uri.split('?')
|
||||
parts.shift
|
||||
parts.join('?')
|
||||
else
|
||||
@env['QUERY_STRING'] || ''
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# The request body is an IO input stream. If the RAW_POST_DATA environment
|
||||
# variable is already set, wrap it in a StringIO.
|
||||
def body
|
||||
if raw_post = env['RAW_POST_DATA']
|
||||
StringIO.new(raw_post)
|
||||
else
|
||||
@cgi.stdinput
|
||||
end
|
||||
end
|
||||
|
||||
def query_parameters
|
||||
@query_parameters ||=
|
||||
(qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs)
|
||||
@query_parameters ||= self.class.parse_query_parameters(query_string)
|
||||
end
|
||||
|
||||
def request_parameters
|
||||
@request_parameters ||=
|
||||
if ActionController::Base.param_parsers.has_key?(content_type)
|
||||
CGIMethods.parse_formatted_request_parameters(content_type, @env['RAW_POST_DATA'])
|
||||
else
|
||||
CGIMethods.parse_request_parameters(@cgi.params)
|
||||
end
|
||||
@request_parameters ||= parse_formatted_request_parameters
|
||||
end
|
||||
|
||||
def cookies
|
||||
@cgi.cookies.freeze
|
||||
end
|
||||
|
||||
def host_with_port
|
||||
def host_with_port_without_standard_port_handling
|
||||
if forwarded = env["HTTP_X_FORWARDED_HOST"]
|
||||
forwarded.split(/,\s?/).last
|
||||
elsif http_host = env['HTTP_HOST']
|
||||
|
@ -97,11 +95,11 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def host
|
||||
host_with_port[/^[^:]+/]
|
||||
host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
|
||||
end
|
||||
|
||||
def port
|
||||
if host_with_port =~ /:(\d+)$/
|
||||
if host_with_port_without_standard_port_handling =~ /:(\d+)$/
|
||||
$1.to_i
|
||||
else
|
||||
standard_port
|
||||
|
@ -114,7 +112,7 @@ module ActionController #:nodoc:
|
|||
@session = Hash.new
|
||||
else
|
||||
stale_session_check! do
|
||||
if @cookie_only && request_parameters[session_options_with_string_keys['session_key']]
|
||||
if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
|
||||
raise SessionFixationAttempt
|
||||
end
|
||||
case value = session_options_with_string_keys['new_session']
|
||||
|
@ -146,7 +144,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def method_missing(method_id, *arguments)
|
||||
@cgi.send(method_id, *arguments) rescue super
|
||||
@cgi.send!(method_id, *arguments) rescue super
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -160,12 +158,17 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
def cookie_only?
|
||||
session_options_with_string_keys['cookie_only']
|
||||
end
|
||||
|
||||
def stale_session_check!
|
||||
yield
|
||||
rescue ArgumentError => argument_error
|
||||
if argument_error.message =~ %r{undefined class/module ([\w:]+)}
|
||||
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
||||
begin
|
||||
Module.const_missing($1)
|
||||
# Note that the regexp does not allow $1 to end with a ':'
|
||||
$1.constantize
|
||||
rescue LoadError, NameError => const_error
|
||||
raise ActionController::SessionRestoreError, <<-end_msg
|
||||
Session contains objects whose class definition isn\'t available.
|
||||
|
@ -192,16 +195,13 @@ end_msg
|
|||
end
|
||||
|
||||
def out(output = $stdout)
|
||||
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'
|
||||
if @cgi.send!(:env_table)['REQUEST_METHOD'] == 'HEAD'
|
||||
return
|
||||
elsif @body.respond_to?(:call)
|
||||
# Flush the output now in case the @body Proc uses
|
||||
|
@ -217,24 +217,5 @@ end_msg
|
|||
# lost connection to parent process, ignore output
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def convert_content_type!
|
||||
if content_type = @headers.delete("Content-Type")
|
||||
@headers["type"] = content_type
|
||||
end
|
||||
if content_type = @headers.delete("Content-type")
|
||||
@headers["type"] = content_type
|
||||
end
|
||||
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
|
||||
|
|
|
@ -1,235 +0,0 @@
|
|||
module ActionController
|
||||
module CodeGeneration #:nodoc:
|
||||
class GenerationError < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
class Source #:nodoc:
|
||||
attr_reader :lines, :indentation_level
|
||||
IndentationString = ' '
|
||||
def initialize
|
||||
@lines, @indentation_level = [], 0
|
||||
end
|
||||
def line(line)
|
||||
@lines << (IndentationString * @indentation_level + line)
|
||||
end
|
||||
alias :<< :line
|
||||
|
||||
def indent
|
||||
@indentation_level += 1
|
||||
yield
|
||||
ensure
|
||||
@indentation_level -= 1
|
||||
end
|
||||
|
||||
def to_s() lines.join("\n") end
|
||||
end
|
||||
|
||||
class CodeGenerator #:nodoc:
|
||||
attr_accessor :source, :locals
|
||||
def initialize(source = nil)
|
||||
@locals = []
|
||||
@source = source || Source.new
|
||||
end
|
||||
|
||||
BeginKeywords = %w(if unless begin until while def).collect {|kw| kw.to_sym}
|
||||
ResumeKeywords = %w(elsif else rescue).collect {|kw| kw.to_sym}
|
||||
Keywords = BeginKeywords + ResumeKeywords
|
||||
|
||||
def method_missing(keyword, *text)
|
||||
if Keywords.include? keyword
|
||||
if ResumeKeywords.include? keyword
|
||||
raise GenerationError, "Can only resume with #{keyword} immediately after an end" unless source.lines.last =~ /^\s*end\s*$/
|
||||
source.lines.pop # Remove the 'end'
|
||||
end
|
||||
|
||||
line "#{keyword} #{text.join ' '}"
|
||||
begin source.indent { yield(self.dup) }
|
||||
ensure line 'end'
|
||||
end
|
||||
else
|
||||
super(keyword, *text)
|
||||
end
|
||||
end
|
||||
|
||||
def line(*args) self.source.line(*args) end
|
||||
alias :<< :line
|
||||
def indent(*args, &block) source(*args, &block) end
|
||||
def to_s() source.to_s end
|
||||
|
||||
def share_locals_with(other)
|
||||
other.locals = self.locals = (other.locals | locals)
|
||||
end
|
||||
|
||||
FieldsToDuplicate = [:locals]
|
||||
def dup
|
||||
copy = self.class.new(source)
|
||||
self.class::FieldsToDuplicate.each do |sym|
|
||||
value = self.send(sym)
|
||||
value = value.dup unless value.nil? || value.is_a?(Numeric)
|
||||
copy.send("#{sym}=", value)
|
||||
end
|
||||
return copy
|
||||
end
|
||||
end
|
||||
|
||||
class RecognitionGenerator < CodeGenerator #:nodoc:
|
||||
Attributes = [:after, :before, :current, :results, :constants, :depth, :move_ahead, :finish_statement]
|
||||
attr_accessor(*Attributes)
|
||||
FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes
|
||||
|
||||
def initialize(*args)
|
||||
super(*args)
|
||||
@after, @before = [], []
|
||||
@current = nil
|
||||
@results, @constants = {}, {}
|
||||
@depth = 0
|
||||
@move_ahead = nil
|
||||
@finish_statement = Proc.new {|hash_expr| hash_expr}
|
||||
end
|
||||
|
||||
def if_next_matches(string, &block)
|
||||
test = Routing.test_condition(next_segment(true), string)
|
||||
self.if(test, &block)
|
||||
end
|
||||
|
||||
def move_forward(places = 1)
|
||||
dup = self.dup
|
||||
dup.depth += 1
|
||||
dup.move_ahead = places
|
||||
yield dup
|
||||
end
|
||||
|
||||
def next_segment(assign_inline = false, default = nil)
|
||||
if locals.include?(segment_name)
|
||||
code = segment_name
|
||||
else
|
||||
code = "#{segment_name} = #{path_name}[#{index_name}]"
|
||||
if assign_inline
|
||||
code = "(#{code})"
|
||||
else
|
||||
line(code)
|
||||
code = segment_name
|
||||
end
|
||||
|
||||
locals << segment_name
|
||||
end
|
||||
code = "(#{code} || #{default.inspect})" if default
|
||||
|
||||
return code.to_s
|
||||
end
|
||||
|
||||
def segment_name() "segment#{depth}".to_sym end
|
||||
def path_name() :path end
|
||||
def index_name
|
||||
move_ahead, @move_ahead = @move_ahead, nil
|
||||
move_ahead ? "index += #{move_ahead}" : 'index'
|
||||
end
|
||||
|
||||
def continue
|
||||
dup = self.dup
|
||||
dup.before << dup.current
|
||||
dup.current = dup.after.shift
|
||||
dup.go
|
||||
end
|
||||
|
||||
def go
|
||||
if current then current.write_recognition(self)
|
||||
else self.finish
|
||||
end
|
||||
end
|
||||
|
||||
def result(key, expression, delay = false)
|
||||
unless delay
|
||||
line "#{key}_value = #{expression}"
|
||||
expression = "#{key}_value"
|
||||
end
|
||||
results[key] = expression
|
||||
end
|
||||
def constant_result(key, object)
|
||||
constants[key] = object
|
||||
end
|
||||
|
||||
def finish(ensure_traversal_finished = true)
|
||||
pairs = []
|
||||
(results.keys + constants.keys).uniq.each do |key|
|
||||
pairs << "#{key.to_s.inspect} => #{results[key] ? results[key] : constants[key].inspect}"
|
||||
end
|
||||
hash_expr = "{#{pairs.join(', ')}}"
|
||||
|
||||
statement = finish_statement.call(hash_expr)
|
||||
if ensure_traversal_finished then self.if("! #{next_segment(true)}") {|gp| gp << statement}
|
||||
else self << statement
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class GenerationGenerator < CodeGenerator #:nodoc:
|
||||
Attributes = [:after, :before, :current, :segments]
|
||||
attr_accessor(*Attributes)
|
||||
FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes
|
||||
|
||||
def initialize(*args)
|
||||
super(*args)
|
||||
@after, @before = [], []
|
||||
@current = nil
|
||||
@segments = []
|
||||
end
|
||||
|
||||
def hash_name() 'hash' end
|
||||
def local_name(key) "#{key}_value" end
|
||||
|
||||
def hash_value(key, assign = true, default = nil)
|
||||
if locals.include?(local_name(key)) then code = local_name(key)
|
||||
else
|
||||
code = "hash[#{key.to_sym.inspect}]"
|
||||
if assign
|
||||
code = "(#{local_name(key)} = #{code})"
|
||||
locals << local_name(key)
|
||||
end
|
||||
end
|
||||
code = "(#{code} || (#{default.inspect}))" if default
|
||||
return code
|
||||
end
|
||||
|
||||
def expire_for_keys(*keys)
|
||||
return if keys.empty?
|
||||
conds = keys.collect {|key| "expire_on[#{key.to_sym.inspect}]"}
|
||||
line "not_expired, #{hash_name} = false, options if not_expired && #{conds.join(' && ')}"
|
||||
end
|
||||
|
||||
def add_segment(*segments)
|
||||
d = dup
|
||||
d.segments.concat segments
|
||||
yield d
|
||||
end
|
||||
|
||||
def go
|
||||
if current then current.write_generation(self)
|
||||
else self.finish
|
||||
end
|
||||
end
|
||||
|
||||
def continue
|
||||
d = dup
|
||||
d.before << d.current
|
||||
d.current = d.after.shift
|
||||
d.go
|
||||
end
|
||||
|
||||
def finish
|
||||
line %("/#{segments.join('/')}")
|
||||
end
|
||||
|
||||
def check_conditions(conditions)
|
||||
tests = []
|
||||
generator = nil
|
||||
conditions.each do |key, condition|
|
||||
tests << (generator || self).hash_value(key, true) if condition.is_a? Regexp
|
||||
tests << Routing.test_condition((generator || self).hash_value(key, false), condition)
|
||||
generator = self.dup unless generator
|
||||
end
|
||||
return tests.join(' && ')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -17,44 +17,44 @@ module ActionController #:nodoc:
|
|||
# end
|
||||
#
|
||||
# The same can be done in a view to do a partial rendering:
|
||||
#
|
||||
# Let's see a greeting:
|
||||
#
|
||||
# Let's see a greeting:
|
||||
# <%= render_component :controller => "greeter", :action => "hello_world" %>
|
||||
#
|
||||
# It is also possible to specify the controller as a class constant, bypassing the inflector
|
||||
# code to compute the controller class at runtime:
|
||||
#
|
||||
#
|
||||
# <%= render_component :controller => GreeterController, :action => "hello_world" %>
|
||||
#
|
||||
# == When to use components
|
||||
#
|
||||
# Components should be used with care. They're significantly slower than simply splitting reusable parts into partials and
|
||||
# conceptually more complicated. Don't use components as a way of separating concerns inside a single application. Instead,
|
||||
# reserve components to those rare cases where you truly have reusable view and controller elements that can be employed
|
||||
# reserve components to those rare cases where you truly have reusable view and controller elements that can be employed
|
||||
# across many applications at once.
|
||||
#
|
||||
# So to repeat: Components are a special-purpose approach that can often be replaced with better use of partials and filters.
|
||||
module Components
|
||||
def self.included(base) #:nodoc:
|
||||
base.send :include, InstanceMethods
|
||||
base.extend(ClassMethods)
|
||||
|
||||
base.helper do
|
||||
def render_component(options)
|
||||
@controller.send(:render_component_as_string, options)
|
||||
end
|
||||
end
|
||||
|
||||
# If this controller was instantiated to process a component request,
|
||||
# +parent_controller+ points to the instantiator of this controller.
|
||||
base.send :attr_accessor, :parent_controller
|
||||
|
||||
base.class_eval do
|
||||
include InstanceMethods
|
||||
extend ClassMethods
|
||||
|
||||
helper do
|
||||
def render_component(options)
|
||||
@controller.send!(:render_component_as_string, options)
|
||||
end
|
||||
end
|
||||
|
||||
# If this controller was instantiated to process a component request,
|
||||
# +parent_controller+ points to the instantiator of this controller.
|
||||
attr_accessor :parent_controller
|
||||
|
||||
alias_method_chain :process_cleanup, :components
|
||||
alias_method_chain :set_session_options, :components
|
||||
alias_method_chain :flash, :components
|
||||
|
||||
alias_method :component_request?, :parent_controller
|
||||
alias_method :component_request?, :parent_controller
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -65,23 +65,6 @@ module ActionController #:nodoc:
|
|||
controller.parent_controller = parent_controller
|
||||
controller.process(request, response)
|
||||
end
|
||||
|
||||
# Set the template root to be one directory behind the root dir of the controller. Examples:
|
||||
# /code/weblog/components/admin/users_controller.rb with Admin::UsersController
|
||||
# will use /code/weblog/components as template root
|
||||
# and find templates in /code/weblog/components/admin/users/
|
||||
#
|
||||
# /code/weblog/components/admin/parties/users_controller.rb with Admin::Parties::UsersController
|
||||
# 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[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
|
||||
|
@ -90,12 +73,12 @@ module ActionController #:nodoc:
|
|||
flash.discard if component_request?
|
||||
process_without_components(request, response, method, *arguments)
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
# Renders the component specified as the response for the current method
|
||||
def render_component(options) #:doc:
|
||||
component_logging(options) do
|
||||
render_text(component_response(options, true).body, response.headers["Status"])
|
||||
render_for_text(component_response(options, true).body, response.headers["Status"])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -23,18 +23,19 @@ module ActionController #:nodoc:
|
|||
# * <tt>domain</tt> - the domain for which this cookie applies.
|
||||
# * <tt>expires</tt> - the time at which this cookie expires, as a +Time+ object.
|
||||
# * <tt>secure</tt> - whether this cookie is a secure cookie or not (default to false).
|
||||
# Secure cookies are only transmitted to HTTPS servers.
|
||||
# Secure cookies are only transmitted to HTTPS servers.
|
||||
# * <tt>http_only</tt> - whether this cookie is accessible via scripting or only HTTP (defaults to false).
|
||||
|
||||
module Cookies
|
||||
def self.included(base)
|
||||
base.helper_method :cookies
|
||||
end
|
||||
|
||||
protected
|
||||
# Returns the cookie container, which operates as described above.
|
||||
def cookies
|
||||
CookieJar.new(self)
|
||||
end
|
||||
|
||||
# Deprecated cookie writer method
|
||||
def cookie(*options)
|
||||
response.headers['cookie'] << CGI::Cookie.new(*options)
|
||||
end
|
||||
end
|
||||
|
||||
class CookieJar < Hash #:nodoc:
|
||||
|
@ -44,10 +45,13 @@ module ActionController #:nodoc:
|
|||
update(@cookies)
|
||||
end
|
||||
|
||||
# Returns the value of the cookie by +name+ -- or nil if no such cookie exists. You set new cookies using either the cookie method
|
||||
# or cookies[]= (for simple name/value cookies without options).
|
||||
# Returns the value of the cookie by +name+ -- or nil if no such cookie exists. You set new cookies using cookies[]=
|
||||
# (for simple name/value cookies without options).
|
||||
def [](name)
|
||||
@cookies[name.to_s].value.first if @cookies[name.to_s] && @cookies[name.to_s].respond_to?(:value)
|
||||
cookie = @cookies[name.to_s]
|
||||
if cookie && cookie.respond_to?(:value)
|
||||
cookie.size > 1 ? cookie.value : cookie.value[0]
|
||||
end
|
||||
end
|
||||
|
||||
def []=(name, options)
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
module ActionController #:nodoc:
|
||||
module Dependencies #:nodoc:
|
||||
def self.append_features(base)
|
||||
super
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# Dependencies control what classes are needed for the controller to run its course. This is an alternative to doing explicit
|
||||
# +require+ statements that bring a number of benefits. It's more succinct, communicates what type of dependency we're talking about,
|
||||
# can trigger special behavior (as in the case of +observer+), and enables Rails to be clever about reloading in cached environments
|
||||
# like FCGI. Example:
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# model :account, :company, :person, :project, :category
|
||||
# helper :access_control
|
||||
# service :notifications, :billings
|
||||
# observer :project_change_observer
|
||||
# end
|
||||
#
|
||||
# Please note that a controller like ApplicationController will automatically attempt to require_dependency on a model of its
|
||||
# singuralized name and a helper of its name. If nothing is found, no error is raised. This is especially useful for concrete
|
||||
# controllers like PostController:
|
||||
#
|
||||
# class PostController < ApplicationController
|
||||
# # model :post (already required)
|
||||
# # helper :post (already required)
|
||||
# end
|
||||
#
|
||||
# Also note, that if the models follow the pattern of just 1 class per file in the form of MyClass => my_class.rb, then these
|
||||
# classes don't have to be required as Active Support will auto-require them.
|
||||
module ClassMethods #:nodoc:
|
||||
# Specifies a variable number of models that this controller depends on. Models are normally Active Record classes or a similar
|
||||
# backend for modelling entity classes.
|
||||
def model(*models)
|
||||
require_dependencies(:model, models)
|
||||
depend_on(:model, models)
|
||||
end
|
||||
|
||||
# Specifies a variable number of services that this controller depends on. Services are normally singletons or factories, like
|
||||
# Action Mailer service or a Payment Gateway service.
|
||||
def service(*services)
|
||||
require_dependencies(:service, services)
|
||||
depend_on(:service, services)
|
||||
end
|
||||
|
||||
# Specifies a variable number of observers that are to govern when this controller is handling actions. The observers will
|
||||
# automatically have .instance called on them to make them active on assignment.
|
||||
def observer(*observers)
|
||||
require_dependencies(:observer, observers)
|
||||
depend_on(:observer, observers)
|
||||
instantiate_observers(observers)
|
||||
end
|
||||
|
||||
# Returns an array of symbols that specify the dependencies on a given layer. For the example at the top, calling
|
||||
# <tt>ApplicationController.dependencies_on(:model)</tt> would return <tt>[:account, :company, :person, :project, :category]</tt>
|
||||
def dependencies_on(layer)
|
||||
read_inheritable_attribute("#{layer}_dependencies")
|
||||
end
|
||||
|
||||
def depend_on(layer, dependencies) #:nodoc:
|
||||
write_inheritable_array("#{layer}_dependencies", dependencies)
|
||||
end
|
||||
|
||||
private
|
||||
def instantiate_observers(observers)
|
||||
observers.flatten.each { |observer| Object.const_get(Inflector.classify(observer.to_s)).instance }
|
||||
end
|
||||
|
||||
def require_dependencies(layer, dependencies)
|
||||
dependencies.flatten.each do |dependency|
|
||||
begin
|
||||
require_dependency(dependency.to_s)
|
||||
rescue LoadError => e
|
||||
raise LoadError.new("Missing #{layer} #{dependency}.rb").copy_blame!(e)
|
||||
rescue Object => exception
|
||||
exception.blame_file! "=> #{layer} #{dependency}.rb"
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,204 +0,0 @@
|
|||
require 'test/unit'
|
||||
require 'test/unit/assertions'
|
||||
require 'rexml/document'
|
||||
|
||||
module Test #:nodoc:
|
||||
module Unit #:nodoc:
|
||||
module Assertions
|
||||
def assert_success(message=nil) #:nodoc:
|
||||
assert_response(:success, message)
|
||||
end
|
||||
|
||||
def assert_redirect(message=nil) #:nodoc:
|
||||
assert_response(:redirect, message)
|
||||
end
|
||||
|
||||
def assert_rendered_file(expected=nil, message=nil) #:nodoc:
|
||||
assert_template(expected, message)
|
||||
end
|
||||
|
||||
# ensure that the session has an object with the specified name
|
||||
def assert_session_has(key=nil, message=nil) #:nodoc:
|
||||
msg = build_message(message, "<?> is not in the session <?>", key, @response.session)
|
||||
assert_block(msg) { @response.has_session_object?(key) }
|
||||
end
|
||||
|
||||
# ensure that the session has no object with the specified name
|
||||
def assert_session_has_no(key=nil, message=nil) #:nodoc:
|
||||
msg = build_message(message, "<?> is in the session <?>", key, @response.session)
|
||||
assert_block(msg) { !@response.has_session_object?(key) }
|
||||
end
|
||||
|
||||
def assert_session_equal(expected = nil, key = nil, message = nil) #:nodoc:
|
||||
msg = build_message(message, "<?> expected in session['?'] but was <?>", expected, key, @response.session[key])
|
||||
assert_block(msg) { expected == @response.session[key] }
|
||||
end
|
||||
|
||||
# -- cookie assertions ---------------------------------------------------
|
||||
|
||||
def assert_no_cookie(key = nil, message = nil) #:nodoc:
|
||||
actual = @response.cookies[key]
|
||||
msg = build_message(message, "<?> not expected in cookies['?']", actual, key)
|
||||
assert_block(msg) { actual.nil? or actual.empty? }
|
||||
end
|
||||
|
||||
def assert_cookie_equal(expected = nil, key = nil, message = nil) #:nodoc:
|
||||
actual = @response.cookies[key]
|
||||
actual = actual.first if actual
|
||||
msg = build_message(message, "<?> expected in cookies['?'] but was <?>", expected, key, actual)
|
||||
assert_block(msg) { expected == actual }
|
||||
end
|
||||
|
||||
# -- flash assertions ---------------------------------------------------
|
||||
|
||||
# ensure that the flash has an object with the specified name
|
||||
def assert_flash_has(key=nil, message=nil) #:nodoc:
|
||||
msg = build_message(message, "<?> is not in the flash <?>", key, @response.flash)
|
||||
assert_block(msg) { @response.has_flash_object?(key) }
|
||||
end
|
||||
|
||||
# ensure that the flash has no object with the specified name
|
||||
def assert_flash_has_no(key=nil, message=nil) #:nodoc:
|
||||
msg = build_message(message, "<?> is in the flash <?>", key, @response.flash)
|
||||
assert_block(msg) { !@response.has_flash_object?(key) }
|
||||
end
|
||||
|
||||
# ensure the flash exists
|
||||
def assert_flash_exists(message=nil) #:nodoc:
|
||||
msg = build_message(message, "the flash does not exist <?>", @response.session['flash'] )
|
||||
assert_block(msg) { @response.has_flash? }
|
||||
end
|
||||
|
||||
# ensure the flash does not exist
|
||||
def assert_flash_not_exists(message=nil) #:nodoc:
|
||||
msg = build_message(message, "the flash exists <?>", @response.flash)
|
||||
assert_block(msg) { !@response.has_flash? }
|
||||
end
|
||||
|
||||
# ensure the flash is empty but existent
|
||||
def assert_flash_empty(message=nil) #:nodoc:
|
||||
msg = build_message(message, "the flash is not empty <?>", @response.flash)
|
||||
assert_block(msg) { !@response.has_flash_with_contents? }
|
||||
end
|
||||
|
||||
# ensure the flash is not empty
|
||||
def assert_flash_not_empty(message=nil) #:nodoc:
|
||||
msg = build_message(message, "the flash is empty")
|
||||
assert_block(msg) { @response.has_flash_with_contents? }
|
||||
end
|
||||
|
||||
def assert_flash_equal(expected = nil, key = nil, message = nil) #:nodoc:
|
||||
msg = build_message(message, "<?> expected in flash['?'] but was <?>", expected, key, @response.flash[key])
|
||||
assert_block(msg) { expected == @response.flash[key] }
|
||||
end
|
||||
|
||||
|
||||
# ensure our redirection url is an exact match
|
||||
def assert_redirect_url(url=nil, message=nil) #:nodoc:
|
||||
assert_redirect(message)
|
||||
msg = build_message(message, "<?> is not the redirected location <?>", url, @response.redirect_url)
|
||||
assert_block(msg) { @response.redirect_url == url }
|
||||
end
|
||||
|
||||
# ensure our redirection url matches a pattern
|
||||
def assert_redirect_url_match(pattern=nil, message=nil) #:nodoc:
|
||||
assert_redirect(message)
|
||||
msg = build_message(message, "<?> was not found in the location: <?>", pattern, @response.redirect_url)
|
||||
assert_block(msg) { @response.redirect_url_match?(pattern) }
|
||||
end
|
||||
|
||||
|
||||
# -- template assertions ------------------------------------------------
|
||||
|
||||
# ensure that a template object with the given name exists
|
||||
def assert_template_has(key=nil, message=nil) #:nodoc:
|
||||
msg = build_message(message, "<?> is not a template object", key )
|
||||
assert_block(msg) { @response.has_template_object?(key) }
|
||||
end
|
||||
|
||||
# ensure that a template object with the given name does not exist
|
||||
def assert_template_has_no(key=nil,message=nil) #:nodoc:
|
||||
msg = build_message(message, "<?> is a template object <?>", key, @response.template_objects[key])
|
||||
assert_block(msg) { !@response.has_template_object?(key) }
|
||||
end
|
||||
|
||||
# ensures that the object assigned to the template on +key+ is equal to +expected+ object.
|
||||
def assert_template_equal(expected = nil, key = nil, message = nil) #:nodoc:
|
||||
msg = build_message(message, "<?> expected in assigns['?'] but was <?>", expected, key, @response.template.assigns[key.to_s])
|
||||
assert_block(msg) { expected == @response.template.assigns[key.to_s] }
|
||||
end
|
||||
alias_method :assert_assigned_equal, :assert_template_equal
|
||||
|
||||
# Asserts that the template returns the +expected+ string or array based on the XPath +expression+.
|
||||
# This will only work if the template rendered a valid XML document.
|
||||
def assert_template_xpath_match(expression=nil, expected=nil, message=nil) #:nodoc:
|
||||
xml, matches = REXML::Document.new(@response.body), []
|
||||
xml.elements.each(expression) { |e| matches << e.text }
|
||||
if matches.empty? then
|
||||
msg = build_message(message, "<?> not found in document", expression)
|
||||
flunk(msg)
|
||||
return
|
||||
elsif matches.length < 2 then
|
||||
matches = matches.first
|
||||
end
|
||||
|
||||
msg = build_message(message, "<?> found <?>, not <?>", expression, matches, expected)
|
||||
assert_block(msg) { matches == expected }
|
||||
end
|
||||
|
||||
# Assert the template object with the given name is an Active Record descendant and is valid.
|
||||
def assert_valid_record(key = nil, message = nil) #:nodoc:
|
||||
record = find_record_in_template(key)
|
||||
msg = build_message(message, "Active Record is invalid <?>)", record.errors.full_messages)
|
||||
assert_block(msg) { record.valid? }
|
||||
end
|
||||
|
||||
# Assert the template object with the given name is an Active Record descendant and is invalid.
|
||||
def assert_invalid_record(key = nil, message = nil) #:nodoc:
|
||||
record = find_record_in_template(key)
|
||||
msg = build_message(message, "Active Record is valid)")
|
||||
assert_block(msg) { !record.valid? }
|
||||
end
|
||||
|
||||
# Assert the template object with the given name is an Active Record descendant and the specified column(s) are valid.
|
||||
def assert_valid_column_on_record(key = nil, columns = "", message = nil) #:nodoc:
|
||||
record = find_record_in_template(key)
|
||||
record.send(:validate)
|
||||
|
||||
cols = glue_columns(columns)
|
||||
cols.delete_if { |col| !record.errors.invalid?(col) }
|
||||
msg = build_message(message, "Active Record has invalid columns <?>)", cols.join(",") )
|
||||
assert_block(msg) { cols.empty? }
|
||||
end
|
||||
|
||||
# Assert the template object with the given name is an Active Record descendant and the specified column(s) are invalid.
|
||||
def assert_invalid_column_on_record(key = nil, columns = "", message = nil) #:nodoc:
|
||||
record = find_record_in_template(key)
|
||||
record.send(:validate)
|
||||
|
||||
cols = glue_columns(columns)
|
||||
cols.delete_if { |col| record.errors.invalid?(col) }
|
||||
msg = build_message(message, "Active Record has valid columns <?>)", cols.join(",") )
|
||||
assert_block(msg) { cols.empty? }
|
||||
end
|
||||
|
||||
private
|
||||
def glue_columns(columns)
|
||||
cols = []
|
||||
cols << columns if columns.class == String
|
||||
cols += columns if columns.class == Array
|
||||
cols
|
||||
end
|
||||
|
||||
def find_record_in_template(key = nil)
|
||||
assert_template_has(key)
|
||||
record = @response.template_objects[key]
|
||||
|
||||
assert_not_nil(record)
|
||||
assert_kind_of ActiveRecord::Base, record
|
||||
|
||||
return record
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,65 +0,0 @@
|
|||
module ActionController #:nodoc:
|
||||
module Dependencies #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# Deprecated module. The responsibility of loading dependencies belong with Active Support now.
|
||||
module ClassMethods #:nodoc:
|
||||
# Specifies a variable number of models that this controller depends on. Models are normally Active Record classes or a similar
|
||||
# backend for modelling entity classes.
|
||||
def model(*models)
|
||||
require_dependencies(:model, models)
|
||||
depend_on(:model, models)
|
||||
end
|
||||
deprecate :model
|
||||
|
||||
# Specifies a variable number of services that this controller depends on. Services are normally singletons or factories, like
|
||||
# Action Mailer service or a Payment Gateway service.
|
||||
def service(*services)
|
||||
require_dependencies(:service, services)
|
||||
depend_on(:service, services)
|
||||
end
|
||||
deprecate :service
|
||||
|
||||
# Specifies a variable number of observers that are to govern when this controller is handling actions. The observers will
|
||||
# automatically have .instance called on them to make them active on assignment.
|
||||
def observer(*observers)
|
||||
require_dependencies(:observer, observers)
|
||||
depend_on(:observer, observers)
|
||||
instantiate_observers(observers)
|
||||
end
|
||||
deprecate :observer
|
||||
|
||||
# Returns an array of symbols that specify the dependencies on a given layer. For the example at the top, calling
|
||||
# <tt>ApplicationController.dependencies_on(:model)</tt> would return <tt>[:account, :company, :person, :project, :category]</tt>
|
||||
def dependencies_on(layer)
|
||||
read_inheritable_attribute("#{layer}_dependencies")
|
||||
end
|
||||
deprecate :dependencies_on
|
||||
|
||||
def depend_on(layer, dependencies) #:nodoc:
|
||||
write_inheritable_array("#{layer}_dependencies", dependencies)
|
||||
end
|
||||
deprecate :depend_on
|
||||
|
||||
private
|
||||
def instantiate_observers(observers)
|
||||
observers.flatten.each { |observer| Object.const_get(Inflector.classify(observer.to_s)).instance }
|
||||
end
|
||||
|
||||
def require_dependencies(layer, dependencies)
|
||||
dependencies.flatten.each do |dependency|
|
||||
begin
|
||||
require_dependency(dependency.to_s)
|
||||
rescue LoadError => e
|
||||
raise LoadError.new("Missing #{layer} #{dependency}.rb").copy_blame!(e)
|
||||
rescue Exception => exception # error from loaded file
|
||||
exception.blame_file! "=> #{layer} #{dependency}.rb"
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,17 +0,0 @@
|
|||
module ActionController
|
||||
class Base
|
||||
protected
|
||||
# Deprecated in favor of calling redirect_to directly with the path.
|
||||
def redirect_to_path(path) #:nodoc:
|
||||
redirect_to(path)
|
||||
end
|
||||
|
||||
# Deprecated in favor of calling redirect_to directly with the url. If the resource has moved permanently, it's possible to pass
|
||||
# true as the second parameter and the browser will get "301 Moved Permanently" instead of "302 Found". This can also be done through
|
||||
# just setting the headers["Status"] to "301 Moved Permanently" before using the redirect_to.
|
||||
def redirect_to_url(url, permanently = false) #:nodoc:
|
||||
headers["Status"] = "301 Moved Permanently" if permanently
|
||||
redirect_to(url)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,34 +0,0 @@
|
|||
module ActionController
|
||||
class AbstractRequest
|
||||
# Determine whether the body of a HTTP call is URL-encoded (default)
|
||||
# or matches one of the registered param_parsers.
|
||||
#
|
||||
# For backward compatibility, the post format is extracted from the
|
||||
# X-Post-Data-Format HTTP header if present.
|
||||
def post_format
|
||||
case content_type.to_s
|
||||
when 'application/xml'
|
||||
:xml
|
||||
when 'application/x-yaml'
|
||||
:yaml
|
||||
else
|
||||
:url_encoded
|
||||
end
|
||||
end
|
||||
|
||||
# Is this a POST request formatted as XML or YAML?
|
||||
def formatted_post?
|
||||
post? && (post_format == :yaml || post_format == :xml)
|
||||
end
|
||||
|
||||
# Is this a POST request formatted as XML?
|
||||
def xml_post?
|
||||
post? && post_format == :xml
|
||||
end
|
||||
|
||||
# Is this a POST request formatted as YAML?
|
||||
def yaml_post?
|
||||
post? && post_format == :yaml
|
||||
end
|
||||
end
|
||||
end
|
195
vendor/rails/actionpack/lib/action_controller/dispatcher.rb
vendored
Normal file
195
vendor/rails/actionpack/lib/action_controller/dispatcher.rb
vendored
Normal file
|
@ -0,0 +1,195 @@
|
|||
module ActionController
|
||||
# Dispatches requests to the appropriate controller and takes care of
|
||||
# reloading the app after each request when Dependencies.load? is true.
|
||||
class Dispatcher
|
||||
class << self
|
||||
# Backward-compatible class method takes CGI-specific args. Deprecated
|
||||
# in favor of Dispatcher.new(output, request, response).dispatch.
|
||||
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
|
||||
new(output).dispatch_cgi(cgi, session_options)
|
||||
end
|
||||
|
||||
# Declare a block to be called before each dispatch.
|
||||
# Run in the order declared.
|
||||
def before_dispatch(*method_names, &block)
|
||||
callbacks[:before].concat method_names
|
||||
callbacks[:before] << block if block_given?
|
||||
end
|
||||
|
||||
# Declare a block to be called after each dispatch.
|
||||
# Run in reverse of the order declared.
|
||||
def after_dispatch(*method_names, &block)
|
||||
callbacks[:after].concat method_names
|
||||
callbacks[:after] << block if block_given?
|
||||
end
|
||||
|
||||
# Add a preparation callback. Preparation callbacks are run before every
|
||||
# request in development mode, and before the first request in production
|
||||
# mode.
|
||||
#
|
||||
# An optional identifier may be supplied for the callback. If provided,
|
||||
# to_prepare may be called again with the same identifier to replace the
|
||||
# existing callback. Passing an identifier is a suggested practice if the
|
||||
# code adding a preparation block may be reloaded.
|
||||
def to_prepare(identifier = nil, &block)
|
||||
# Already registered: update the existing callback
|
||||
if identifier
|
||||
if callback = callbacks[:prepare].assoc(identifier)
|
||||
callback[1] = block
|
||||
else
|
||||
callbacks[:prepare] << [identifier, block]
|
||||
end
|
||||
else
|
||||
callbacks[:prepare] << block
|
||||
end
|
||||
end
|
||||
|
||||
# If the block raises, send status code as a last-ditch response.
|
||||
def failsafe_response(fallback_output, status, originating_exception = nil)
|
||||
yield
|
||||
rescue Exception => exception
|
||||
begin
|
||||
log_failsafe_exception(status, originating_exception || exception)
|
||||
body = failsafe_response_body(status)
|
||||
fallback_output.write "Status: #{status}\r\nContent-Type: text/html\r\n\r\n#{body}"
|
||||
nil
|
||||
rescue Exception => failsafe_error # Logger or IO errors
|
||||
$stderr.puts "Error during failsafe response: #{failsafe_error}"
|
||||
$stderr.puts "(originally #{originating_exception})" if originating_exception
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def failsafe_response_body(status)
|
||||
error_path = "#{error_file_path}/#{status.to_s[0..3]}.html"
|
||||
|
||||
if File.exist?(error_path)
|
||||
File.read(error_path)
|
||||
else
|
||||
"<html><body><h1>#{status}</h1></body></html>"
|
||||
end
|
||||
end
|
||||
|
||||
def log_failsafe_exception(status, exception)
|
||||
message = "/!\\ FAILSAFE /!\\ #{Time.now}\n Status: #{status}\n"
|
||||
message << " #{exception}\n #{exception.backtrace.join("\n ")}" if exception
|
||||
failsafe_logger.fatal message
|
||||
end
|
||||
|
||||
def failsafe_logger
|
||||
if defined?(::RAILS_DEFAULT_LOGGER) && !::RAILS_DEFAULT_LOGGER.nil?
|
||||
::RAILS_DEFAULT_LOGGER
|
||||
else
|
||||
Logger.new($stderr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cattr_accessor :error_file_path
|
||||
self.error_file_path = "#{::RAILS_ROOT}/public" if defined? ::RAILS_ROOT
|
||||
|
||||
cattr_accessor :callbacks
|
||||
self.callbacks = Hash.new { |h, k| h[k] = [] }
|
||||
|
||||
cattr_accessor :unprepared
|
||||
self.unprepared = true
|
||||
|
||||
|
||||
before_dispatch :reload_application
|
||||
before_dispatch :prepare_application
|
||||
after_dispatch :flush_logger
|
||||
after_dispatch :cleanup_application
|
||||
|
||||
if defined? ActiveRecord
|
||||
to_prepare :activerecord_instantiate_observers do
|
||||
ActiveRecord::Base.instantiate_observers
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(output, request = nil, response = nil)
|
||||
@output, @request, @response = output, request, response
|
||||
end
|
||||
|
||||
def dispatch
|
||||
run_callbacks :before
|
||||
handle_request
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
ensure
|
||||
run_callbacks :after, :reverse_each
|
||||
end
|
||||
|
||||
def dispatch_cgi(cgi, session_options)
|
||||
if cgi ||= self.class.failsafe_response(@output, '400 Bad Request') { CGI.new }
|
||||
@request = CgiRequest.new(cgi, session_options)
|
||||
@response = CgiResponse.new(cgi)
|
||||
dispatch
|
||||
end
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
end
|
||||
|
||||
def reload_application
|
||||
if Dependencies.load?
|
||||
Routing::Routes.reload
|
||||
self.unprepared = true
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_application(force = false)
|
||||
begin
|
||||
require_dependency 'application' unless defined?(::ApplicationController)
|
||||
rescue LoadError => error
|
||||
raise unless error.message =~ /application\.rb/
|
||||
end
|
||||
|
||||
ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord)
|
||||
|
||||
if unprepared || force
|
||||
run_callbacks :prepare
|
||||
self.unprepared = false
|
||||
end
|
||||
end
|
||||
|
||||
# Cleanup the application by clearing out loaded classes so they can
|
||||
# be reloaded on the next request without restarting the server.
|
||||
def cleanup_application(force = false)
|
||||
if Dependencies.load? || force
|
||||
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
|
||||
Dependencies.clear
|
||||
ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
|
||||
end
|
||||
end
|
||||
|
||||
def flush_logger
|
||||
RAILS_DEFAULT_LOGGER.flush if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush)
|
||||
end
|
||||
|
||||
protected
|
||||
def handle_request
|
||||
@controller = Routing::Routes.recognize(@request)
|
||||
@controller.process(@request, @response).out(@output)
|
||||
end
|
||||
|
||||
def run_callbacks(kind, enumerator = :each)
|
||||
callbacks[kind].send!(enumerator) do |callback|
|
||||
case callback
|
||||
when Proc; callback.call(self)
|
||||
when String, Symbol; send!(callback)
|
||||
when Array; callback[1].call(self)
|
||||
else raise ArgumentError, "Unrecognized callback #{callback.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def failsafe_rescue(exception)
|
||||
self.class.failsafe_response(@output, '500 Internal Server Error', exception) do
|
||||
if @controller ||= defined?(::ApplicationController) ? ::ApplicationController : Base
|
||||
@controller.process_with_exception(@request, @response, exception).out(@output)
|
||||
else
|
||||
raise exception
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,11 +1,13 @@
|
|||
module ActionController #:nodoc:
|
||||
module Filters #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.send(:include, ActionController::Filters::InstanceMethods)
|
||||
base.class_eval do
|
||||
extend ClassMethods
|
||||
include ActionController::Filters::InstanceMethods
|
||||
end
|
||||
end
|
||||
|
||||
# Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
|
||||
# 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).
|
||||
|
@ -34,7 +36,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 renders or redirects, then
|
||||
# verify_credentials and the intended action are never called.
|
||||
#
|
||||
# == Filter types
|
||||
|
@ -63,7 +65,7 @@ module ActionController #:nodoc:
|
|||
# Or just as a quick test. It works like this:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# before_filter { |controller| false if controller.params["stop_action"] }
|
||||
# before_filter { |controller| head(400) if controller.params["stop_action"] }
|
||||
# end
|
||||
#
|
||||
# As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables.
|
||||
|
@ -88,7 +90,7 @@ module ActionController #:nodoc:
|
|||
# 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 renders or redirects, 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.
|
||||
|
@ -140,23 +142,20 @@ module ActionController #:nodoc:
|
|||
# around_filter Authorizer.new
|
||||
#
|
||||
# class Authorizer
|
||||
# # This will run before the action. Returning false aborts the action.
|
||||
# # This will run before the action. Redirecting aborts the action.
|
||||
# def before(controller)
|
||||
# if user.authorized?
|
||||
# return true
|
||||
# else
|
||||
# redirect_to login_url
|
||||
# return false
|
||||
# unless user.authorized?
|
||||
# redirect_to(login_url)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # This will run after the action if and only if before returned true.
|
||||
# # This will run after the action if and only if before did not render or redirect.
|
||||
# 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
|
||||
# called before the action. If before renders or redirects, the filter chain is
|
||||
# halted and after will not be run. See Filter Chain Halting below for
|
||||
# an example.
|
||||
#
|
||||
|
@ -216,8 +215,8 @@ module ActionController #:nodoc:
|
|||
# <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
|
||||
# before a 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.
|
||||
# After filters will not be executed if the filter chain is halted.
|
||||
# Simply call render or redirect. After filters will not be executed if the filter
|
||||
# chain is halted.
|
||||
#
|
||||
# Around filters halt the request unless the action block is called.
|
||||
# Given these filters
|
||||
|
@ -242,9 +241,9 @@ module ActionController #:nodoc:
|
|||
# #after (actual filter code is run, unless the around filter does not yield)
|
||||
#
|
||||
# If #around returns before yielding, #after will still not be run. The #before
|
||||
# filter and controller action will not be run. If #before returns false,
|
||||
# filter and controller action will not be run. If #before renders or redirects,
|
||||
# the second half of #around and will still run but #after and the
|
||||
# action will not. If #around does not yield, #after will not be run.
|
||||
# action will not. If #around fails to yield, #after will not be run.
|
||||
module ClassMethods
|
||||
# The passed <tt>filters</tt> will be appended to the filter_chain and
|
||||
# will execute before the action on this controller is performed.
|
||||
|
@ -439,8 +438,9 @@ module ActionController #:nodoc:
|
|||
|
||||
def run(controller)
|
||||
# only filters returning false are halted.
|
||||
if false == @filter.call(controller)
|
||||
controller.send :halt_filter_chain, @filter, :returned_false
|
||||
@filter.call(controller)
|
||||
if controller.send!(:performed?)
|
||||
controller.send!(:halt_filter_chain, @filter, :rendered_or_redirected)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -466,7 +466,7 @@ module ActionController #:nodoc:
|
|||
|
||||
class SymbolFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
controller.send(@filter, &block)
|
||||
controller.send!(@filter, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -601,7 +601,7 @@ module ActionController #:nodoc:
|
|||
|
||||
def extract_conditions(*filters, &block) #:nodoc:
|
||||
filters.flatten!
|
||||
conditions = filters.last.is_a?(Hash) ? filters.pop : {}
|
||||
conditions = filters.extract_options!
|
||||
filters << block if block_given?
|
||||
return filters, conditions
|
||||
end
|
||||
|
@ -655,8 +655,10 @@ module ActionController #:nodoc:
|
|||
def proxy_before_and_after_filter(filter) #:nodoc:
|
||||
return filter unless filter_responds_to_before_and_after(filter)
|
||||
Proc.new do |controller, action|
|
||||
if filter.before(controller) == false
|
||||
controller.send :halt_filter_chain, filter, :returned_false
|
||||
filter.before(controller)
|
||||
|
||||
if controller.send!(:performed?)
|
||||
controller.send!(:halt_filter_chain, filter, :rendered_or_redirected)
|
||||
else
|
||||
begin
|
||||
action.call
|
||||
|
@ -673,7 +675,6 @@ module ActionController #:nodoc:
|
|||
base.class_eval do
|
||||
alias_method_chain :perform_action, :filters
|
||||
alias_method_chain :process, :filters
|
||||
alias_method_chain :process_cleanup, :filters
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -709,6 +710,7 @@ module ActionController #:nodoc:
|
|||
while chain[index]
|
||||
filter, index = skip_excluded_filters(chain, index)
|
||||
break unless filter # end of call chain reached
|
||||
|
||||
case filter.type
|
||||
when :before
|
||||
filter.run(self) # invoke before filter
|
||||
|
@ -716,25 +718,31 @@ module ActionController #:nodoc:
|
|||
break if @before_filter_chain_aborted
|
||||
when :around
|
||||
yielded = false
|
||||
|
||||
filter.call(self) do
|
||||
yielded = true
|
||||
# all remaining before and around filters will be run in this call
|
||||
index = call_filters(chain, index.next, nesting.next)
|
||||
end
|
||||
|
||||
halt_filter_chain(filter, :did_not_yield) unless yielded
|
||||
|
||||
break
|
||||
else
|
||||
break # no before or around filters left
|
||||
end
|
||||
end
|
||||
|
||||
index
|
||||
end
|
||||
|
||||
def run_after_filters(chain, index)
|
||||
seen_after_filter = false
|
||||
|
||||
while chain[index]
|
||||
filter, index = skip_excluded_filters(chain, index)
|
||||
break unless filter # end of call chain reached
|
||||
|
||||
case filter.type
|
||||
when :after
|
||||
seen_after_filter = true
|
||||
|
@ -743,23 +751,16 @@ module ActionController #:nodoc:
|
|||
# implementation error or someone has mucked with the filter chain
|
||||
raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
|
||||
end
|
||||
|
||||
index = index.next
|
||||
end
|
||||
|
||||
index.next
|
||||
end
|
||||
|
||||
def halt_filter_chain(filter, reason)
|
||||
@before_filter_chain_aborted = true
|
||||
logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger
|
||||
false
|
||||
end
|
||||
|
||||
def process_cleanup_with_filters
|
||||
if @before_filter_chain_aborted
|
||||
close_session
|
||||
else
|
||||
process_cleanup_without_filters
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
module ActionController #:nodoc:
|
||||
# The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
|
||||
# to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create action
|
||||
# that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can then expose
|
||||
# the flash to its template. Actually, that exposure is automatically done. Example:
|
||||
# to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
|
||||
# action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
|
||||
# then expose the flash to its template. Actually, that exposure is automatically done. Example:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# def create
|
||||
|
@ -16,18 +16,17 @@ module ActionController #:nodoc:
|
|||
# end
|
||||
# end
|
||||
#
|
||||
# display.rhtml
|
||||
# display.erb
|
||||
# <% 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.
|
||||
# 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.
|
||||
#
|
||||
# See docs on the FlashHash class for more details about the flash.
|
||||
module Flash
|
||||
def self.included(base)
|
||||
base.send :include, InstanceMethods
|
||||
|
||||
base.class_eval do
|
||||
include InstanceMethods
|
||||
alias_method_chain :assign_shortcuts, :flash
|
||||
alias_method_chain :process_cleanup, :flash
|
||||
alias_method_chain :reset_session, :flash
|
||||
|
@ -63,7 +62,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def update(h) #:nodoc:
|
||||
h.keys.each{ |k| discard(k) }
|
||||
h.keys.each { |k| keep(k) }
|
||||
super
|
||||
end
|
||||
|
||||
|
@ -85,7 +84,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
|
||||
def now
|
||||
FlashNow.new self
|
||||
FlashNow.new(self)
|
||||
end
|
||||
|
||||
# Keeps either the entire current flash or a specific flash entry available for the next action:
|
||||
|
@ -96,10 +95,10 @@ module ActionController #:nodoc:
|
|||
use(k, false)
|
||||
end
|
||||
|
||||
# Marks the entire flash or a single flash entry to be discarded by the end of the current action
|
||||
# Marks the entire flash or a single flash entry to be discarded by the end of the current action:
|
||||
#
|
||||
# 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)
|
||||
# flash.discard # discard the entire flash at the end of the current action
|
||||
# flash.discard(:warning) # discard only the "warning" entry at the end of the current action
|
||||
def discard(k = nil)
|
||||
use(k)
|
||||
end
|
||||
|
@ -117,7 +116,8 @@ module ActionController #:nodoc:
|
|||
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
|
||||
# clean up after keys that could have been left over by calling reject! or shift on the flash
|
||||
(@used.keys - keys).each{ |k| @used.delete(k) }
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -130,29 +130,19 @@ module ActionController #:nodoc:
|
|||
unless k.nil?
|
||||
@used[k] = v
|
||||
else
|
||||
keys.each{|key| use key, v }
|
||||
keys.each{ |key| use(key, v) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods #:nodoc:
|
||||
def assign_shortcuts_with_flash(request, response) #:nodoc:
|
||||
assign_shortcuts_without_flash(request, response)
|
||||
flash(:refresh)
|
||||
end
|
||||
protected
|
||||
def reset_session_with_flash
|
||||
reset_session_without_flash
|
||||
remove_instance_variable(:@_flash)
|
||||
flash(:refresh)
|
||||
end
|
||||
|
||||
def process_cleanup_with_flash
|
||||
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.
|
||||
|
@ -172,10 +162,15 @@ module ActionController #:nodoc:
|
|||
@_flash
|
||||
end
|
||||
|
||||
# deprecated. use <tt>flash.keep</tt> instead
|
||||
def keep_flash #:doc:
|
||||
ActiveSupport::Deprecation.warn 'keep_flash is deprecated; use flash.keep instead.', caller
|
||||
flash.keep
|
||||
private
|
||||
def assign_shortcuts_with_flash(request, response) #:nodoc:
|
||||
assign_shortcuts_without_flash(request, response)
|
||||
flash(:refresh)
|
||||
end
|
||||
|
||||
def process_cleanup_with_flash
|
||||
flash.sweep if @_session
|
||||
process_cleanup_without_flash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# FIXME: helper { ... } is broken on Ruby 1.9
|
||||
module ActionController #:nodoc:
|
||||
module Helpers #:nodoc:
|
||||
HELPERS_DIR = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers")
|
||||
|
||||
def self.included(base)
|
||||
# Initialize the base module to aggregate its helpers.
|
||||
base.class_inheritable_accessor :master_helper_module
|
||||
|
@ -16,64 +19,120 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# The template helpers serve to relieve the templates from including the same inline code again and again. It's a
|
||||
# set of standardized methods for working with forms (FormHelper), dates (DateHelper), texts (TextHelper), and
|
||||
# Active Records (ActiveRecordHelper) that's available to all templates by default.
|
||||
# The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+,
|
||||
# +numbers+ and +ActiveRecord+ objects, to name a few. These helpers are available to all templates
|
||||
# by default.
|
||||
#
|
||||
# It's also really easy to make your own helpers and it's much encouraged to keep the template files free
|
||||
# from complicated logic. It's even encouraged to bundle common compositions of methods from other helpers
|
||||
# (often the common helpers) as they're used by the specific application.
|
||||
# In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to
|
||||
# extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will
|
||||
# include a helper whose name matches that of the controller, e.g., <tt>MyController</tt> will automatically
|
||||
# include <tt>MyHelper</tt>.
|
||||
#
|
||||
# module MyHelper
|
||||
# def hello_world() "hello world" end
|
||||
# Additional helpers can be specified using the +helper+ class method in <tt>ActionController::Base</tt> or any
|
||||
# controller which inherits from it.
|
||||
#
|
||||
# ==== Examples
|
||||
# The +to_s+ method from the +Time+ class can be wrapped in a helper method to display a custom message if
|
||||
# the Time object is blank:
|
||||
#
|
||||
# module FormattedTimeHelper
|
||||
# def format_time(time, format=:long, blank_message=" ")
|
||||
# time.blank? ? blank_message : time.to_s(format)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# MyHelper can now be included in a controller, like this:
|
||||
#
|
||||
# class MyController < ActionController::Base
|
||||
# helper :my_helper
|
||||
#
|
||||
# +FormattedTimeHelper+ can now be included in a controller, using the +helper+ class method:
|
||||
#
|
||||
# class EventsController < ActionController::Base
|
||||
# helper FormattedTimeHelper
|
||||
# def index
|
||||
# @events = Event.find(:all)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# ...and, same as above, used in any template rendered from MyController, like this:
|
||||
#
|
||||
# Let's hear what the helper has to say: <tt><%= hello_world %></tt>
|
||||
#
|
||||
# Then, in any view rendered by <tt>EventController</tt>, the <tt>format_time</tt> method can be called:
|
||||
#
|
||||
# <% @events.each do |event| -%>
|
||||
# <p>
|
||||
# <% format_time(event.time, :short, "N/A") %> | <%= event.name %>
|
||||
# </p>
|
||||
# <% end -%>
|
||||
#
|
||||
# Finally, assuming we have two event instances, one which has a time and one which does not,
|
||||
# the output might look like this:
|
||||
#
|
||||
# 23 Aug 11:30 | Carolina Railhawks Soccer Match
|
||||
# N/A | Carolina Railhaws Training Workshop
|
||||
#
|
||||
module ClassMethods
|
||||
# Makes all the (instance) methods in the helper module available to templates rendered through this controller.
|
||||
# See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
|
||||
# See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
|
||||
# available to the templates.
|
||||
def add_template_helper(helper_module) #:nodoc:
|
||||
master_helper_module.send(:include, helper_module)
|
||||
master_helper_module.module_eval { include helper_module }
|
||||
end
|
||||
|
||||
# Declare a helper:
|
||||
# helper :foo
|
||||
# requires 'foo_helper' and includes FooHelper in the template class.
|
||||
# helper FooHelper
|
||||
# includes FooHelper in the template class.
|
||||
# helper { def foo() "#{bar} is the very best" end }
|
||||
# evaluates the block in the template class, adding method #foo.
|
||||
# The +helper+ class method can take a series of helper module names, a block, or both.
|
||||
#
|
||||
# * <tt>*args</tt>: One or more +Modules+, +Strings+ or +Symbols+, or the special symbol <tt>:all</tt>.
|
||||
# * <tt>&block</tt>: A block defining helper methods.
|
||||
#
|
||||
# ==== Examples
|
||||
# When the argument is a +String+ or +Symbol+, the method will provide the "_helper" suffix, require the file
|
||||
# and include the module in the template class. The second form illustrates how to include custom helpers
|
||||
# when working with namespaced controllers, or other cases where the file containing the helper definition is not
|
||||
# in one of Rails' standard load paths:
|
||||
# helper :foo # => requires 'foo_helper' and includes FooHelper
|
||||
# helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper
|
||||
#
|
||||
# When the argument is a +Module+, it will be included directly in the template class.
|
||||
# helper FooHelper # => includes FooHelper
|
||||
#
|
||||
# When the argument is the symbol <tt>:all</tt>, the controller will include all helpers from
|
||||
# <tt>app/helpers/**/*.rb</tt> under +RAILS_ROOT+.
|
||||
# helper :all
|
||||
#
|
||||
# Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
|
||||
# to the template.
|
||||
# # One line
|
||||
# helper { def hello() "Hello, world!" end }
|
||||
# # Multi-line
|
||||
# helper do
|
||||
# def foo(bar)
|
||||
# "#{bar} is the very best"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of
|
||||
# +symbols+, +strings+, +modules+ and blocks.
|
||||
# helper(:three, BlindHelper) { def mice() 'mice' end }
|
||||
# does all three.
|
||||
#
|
||||
def helper(*args, &block)
|
||||
args.flatten.each do |arg|
|
||||
case arg
|
||||
when Module
|
||||
add_template_helper(arg)
|
||||
when :all
|
||||
helper(all_application_helpers)
|
||||
when String, Symbol
|
||||
file_name = arg.to_s.underscore + '_helper'
|
||||
class_name = file_name.camelize
|
||||
|
||||
|
||||
begin
|
||||
require_dependency(file_name)
|
||||
rescue LoadError => load_error
|
||||
requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
|
||||
msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
|
||||
raise LoadError.new(msg).copy_blame!(load_error)
|
||||
requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1]
|
||||
if requiree == file_name
|
||||
msg = "Missing helper file helpers/#{file_name}.rb"
|
||||
raise LoadError.new(msg).copy_blame!(load_error)
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
add_template_helper(class_name.constantize)
|
||||
else
|
||||
raise ArgumentError, 'helper expects String, Symbol, or Module argument'
|
||||
raise ArgumentError, "helper expects String, Symbol, or Module argument (was: #{args.inspect})"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -81,10 +140,14 @@ module ActionController #:nodoc:
|
|||
master_helper_module.module_eval(&block) if block_given?
|
||||
end
|
||||
|
||||
# Declare a controller method as a helper. For example,
|
||||
# helper_method :link_to
|
||||
# def link_to(name, options) ... end
|
||||
# makes the link_to controller method available in the view.
|
||||
# Declare a controller method as a helper. For example, the following
|
||||
# makes the +current_user+ controller method available to the view:
|
||||
# class ApplicationController < ActionController::Base
|
||||
# helper_method :current_user
|
||||
# def current_user
|
||||
# @current_user ||= User.find(session[:user])
|
||||
# end
|
||||
# end
|
||||
def helper_method(*methods)
|
||||
methods.flatten.each do |method|
|
||||
master_helper_module.module_eval <<-end_eval
|
||||
|
@ -95,37 +158,47 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Declare a controller attribute as a helper. For example,
|
||||
# Declares helper accessors for controller attributes. For example, the
|
||||
# following adds new +name+ and <tt>name=</tt> instance methods to a
|
||||
# controller and makes them available to the view:
|
||||
# helper_attr :name
|
||||
# attr_accessor :name
|
||||
# makes the name and name= controller methods available in the view.
|
||||
# The is a convenience wrapper for helper_method.
|
||||
def helper_attr(*attrs)
|
||||
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
private
|
||||
def default_helper_module!
|
||||
module_name = name.sub(/Controller$|$/, 'Helper')
|
||||
module_path = module_name.split('::').map { |m| m.underscore }.join('/')
|
||||
require_dependency module_path
|
||||
helper module_name.constantize
|
||||
rescue LoadError
|
||||
logger.debug("#{name}: missing default helper path #{module_path}") if logger
|
||||
rescue NameError
|
||||
logger.debug("#{name}: missing default helper module #{module_name}") if logger
|
||||
unless name.blank?
|
||||
module_name = name.sub(/Controller$|$/, 'Helper')
|
||||
module_path = module_name.split('::').map { |m| m.underscore }.join('/')
|
||||
require_dependency module_path
|
||||
helper module_name.constantize
|
||||
end
|
||||
rescue MissingSourceFile => e
|
||||
raise unless e.is_missing? module_path
|
||||
rescue NameError => e
|
||||
raise unless e.missing_name? module_name
|
||||
end
|
||||
|
||||
def inherited_with_helper(child)
|
||||
inherited_without_helper(child)
|
||||
|
||||
begin
|
||||
child.master_helper_module = Module.new
|
||||
child.master_helper_module.send :include, master_helper_module
|
||||
child.send :default_helper_module!
|
||||
child.master_helper_module.send! :include, master_helper_module
|
||||
child.send! :default_helper_module!
|
||||
rescue MissingSourceFile => e
|
||||
raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
|
||||
end
|
||||
end
|
||||
|
||||
# Extract helper names from files in app/helpers/**/*.rb
|
||||
def all_application_helpers
|
||||
extract = /^#{Regexp.quote(HELPERS_DIR)}\/?(.*)_helper.rb$/
|
||||
Dir["#{HELPERS_DIR}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
126
vendor/rails/actionpack/lib/action_controller/http_authentication.rb
vendored
Normal file
126
vendor/rails/actionpack/lib/action_controller/http_authentication.rb
vendored
Normal file
|
@ -0,0 +1,126 @@
|
|||
require 'base64'
|
||||
|
||||
module ActionController
|
||||
module HttpAuthentication
|
||||
# Makes it dead easy to do HTTP Basic authentication.
|
||||
#
|
||||
# Simple Basic example:
|
||||
#
|
||||
# class PostsController < ApplicationController
|
||||
# USER_NAME, PASSWORD = "dhh", "secret"
|
||||
#
|
||||
# before_filter :authenticate, :except => [ :index ]
|
||||
#
|
||||
# def index
|
||||
# render :text => "Everyone can see me!"
|
||||
# end
|
||||
#
|
||||
# def edit
|
||||
# render :text => "I'm only accessible if you know the password"
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def authenticate
|
||||
# authenticate_or_request_with_http_basic do |user_name, password|
|
||||
# user_name == USER_NAME && password == PASSWORD
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
|
||||
# the regular HTML interface is protected by a session approach:
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# before_filter :set_account, :authenticate
|
||||
#
|
||||
# protected
|
||||
# def set_account
|
||||
# @account = Account.find_by_url_name(request.subdomains.first)
|
||||
# end
|
||||
#
|
||||
# def authenticate
|
||||
# case request.format
|
||||
# when Mime::XML, Mime::ATOM
|
||||
# if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
|
||||
# @current_user = user
|
||||
# else
|
||||
# request_http_basic_authentication
|
||||
# end
|
||||
# else
|
||||
# if session_authenticated?
|
||||
# @current_user = @account.users.find(session[:authenticated][:user_id])
|
||||
# else
|
||||
# redirect_to(login_url) and return false
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# In your integration tests, you can do something like this:
|
||||
#
|
||||
# def test_access_granted_from_xml
|
||||
# get(
|
||||
# "/notes/1.xml", nil,
|
||||
# :authorization => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
|
||||
# )
|
||||
#
|
||||
# assert_equal 200, status
|
||||
# end
|
||||
#
|
||||
#
|
||||
# On shared hosts, Apache sometimes doesn't pass authentication headers to
|
||||
# FCGI instances. If your environment matches this description and you cannot
|
||||
# authenticate, try this rule in public/.htaccess (replace the plain one):
|
||||
#
|
||||
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
|
||||
module Basic
|
||||
extend self
|
||||
|
||||
module ControllerMethods
|
||||
def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
|
||||
authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
|
||||
end
|
||||
|
||||
def authenticate_with_http_basic(&login_procedure)
|
||||
HttpAuthentication::Basic.authenticate(self, &login_procedure)
|
||||
end
|
||||
|
||||
def request_http_basic_authentication(realm = "Application")
|
||||
HttpAuthentication::Basic.authentication_request(self, realm)
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate(controller, &login_procedure)
|
||||
unless authorization(controller.request).blank?
|
||||
login_procedure.call(*user_name_and_password(controller.request))
|
||||
end
|
||||
end
|
||||
|
||||
def user_name_and_password(request)
|
||||
decode_credentials(request).split(/:/, 2)
|
||||
end
|
||||
|
||||
def authorization(request)
|
||||
request.env['HTTP_AUTHORIZATION'] ||
|
||||
request.env['X-HTTP_AUTHORIZATION'] ||
|
||||
request.env['X_HTTP_AUTHORIZATION'] ||
|
||||
request.env['REDIRECT_X_HTTP_AUTHORIZATION']
|
||||
end
|
||||
|
||||
def decode_credentials(request)
|
||||
Base64.decode64(authorization(request).split.last || '')
|
||||
end
|
||||
|
||||
def encode_credentials(user_name, password)
|
||||
"Basic #{Base64.encode64("#{user_name}:#{password}")}"
|
||||
end
|
||||
|
||||
def authentication_request(controller, realm)
|
||||
controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
|
||||
controller.send! :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -51,7 +51,10 @@ module ActionController
|
|||
# A reference to the response instance used by the last request.
|
||||
attr_reader :response
|
||||
|
||||
# Create an initialize a new Session instance.
|
||||
# A running counter of the number of requests processed.
|
||||
attr_accessor :request_count
|
||||
|
||||
# Create and initialize a new +Session+ instance.
|
||||
def initialize
|
||||
reset!
|
||||
end
|
||||
|
@ -67,19 +70,20 @@ module ActionController
|
|||
@https = false
|
||||
@cookies = {}
|
||||
@controller = @request = @response = nil
|
||||
@request_count = 0
|
||||
|
||||
self.host = "www.example.com"
|
||||
self.remote_addr = "127.0.0.1"
|
||||
self.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
|
||||
|
||||
unless @named_routes_configured
|
||||
unless defined? @named_routes_configured
|
||||
# install the named routes in this session instance.
|
||||
klass = class<<self; self; end
|
||||
Routing::Routes.named_routes.install(klass)
|
||||
Routing::Routes.install_helpers(klass)
|
||||
|
||||
# the helpers are made protected by default--we make them public for
|
||||
# easier access during testing and troubleshooting.
|
||||
klass.send(:public, *Routing::Routes.named_routes.helpers)
|
||||
klass.module_eval { public *Routing::Routes.named_routes.helpers }
|
||||
@named_routes_configured = true
|
||||
end
|
||||
end
|
||||
|
@ -117,22 +121,38 @@ module ActionController
|
|||
status
|
||||
end
|
||||
|
||||
# Performs a GET request, following any subsequent redirect. Note that
|
||||
# the redirects are followed until the response is not a redirect--this
|
||||
# means you may run into an infinite loop if your redirect loops back to
|
||||
# itself.
|
||||
def get_via_redirect(path, args={})
|
||||
get path, args
|
||||
# Performs a request using the specified method, following any subsequent
|
||||
# redirect. Note that the redirects are followed until the response is
|
||||
# not a redirect--this means you may run into an infinite loop if your
|
||||
# redirect loops back to itself.
|
||||
def request_via_redirect(http_method, path, parameters = nil, headers = nil)
|
||||
send(http_method, path, parameters, headers)
|
||||
follow_redirect! while redirect?
|
||||
status
|
||||
end
|
||||
|
||||
# Performs a POST request, following any subsequent redirect. This is
|
||||
# vulnerable to infinite loops, the same as #get_via_redirect.
|
||||
def post_via_redirect(path, args={})
|
||||
post path, args
|
||||
follow_redirect! while redirect?
|
||||
status
|
||||
# Performs a GET request, following any subsequent redirect.
|
||||
# See #request_via_redirect() for more information.
|
||||
def get_via_redirect(path, parameters = nil, headers = nil)
|
||||
request_via_redirect(:get, path, parameters, headers)
|
||||
end
|
||||
|
||||
# Performs a POST request, following any subsequent redirect.
|
||||
# See #request_via_redirect() for more information.
|
||||
def post_via_redirect(path, parameters = nil, headers = nil)
|
||||
request_via_redirect(:post, path, parameters, headers)
|
||||
end
|
||||
|
||||
# Performs a PUT request, following any subsequent redirect.
|
||||
# See #request_via_redirect() for more information.
|
||||
def put_via_redirect(path, parameters = nil, headers = nil)
|
||||
request_via_redirect(:put, path, parameters, headers)
|
||||
end
|
||||
|
||||
# Performs a DELETE request, following any subsequent redirect.
|
||||
# See #request_via_redirect() for more information.
|
||||
def delete_via_redirect(path, parameters = nil, headers = nil)
|
||||
request_via_redirect(:delete, path, parameters, headers)
|
||||
end
|
||||
|
||||
# Returns +true+ if the last response was a redirect.
|
||||
|
@ -148,27 +168,27 @@ module ActionController
|
|||
#
|
||||
# You can also perform POST, PUT, DELETE, and HEAD requests with #post,
|
||||
# #put, #delete, and #head.
|
||||
def get(path, parameters=nil, headers=nil)
|
||||
def get(path, parameters = nil, headers = nil)
|
||||
process :get, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a POST request with the given parameters. See get() for more details.
|
||||
def post(path, parameters=nil, headers=nil)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
def head(path, parameters = nil, headers = nil)
|
||||
process :head, path, parameters, headers
|
||||
end
|
||||
|
||||
|
@ -179,19 +199,10 @@ module ActionController
|
|||
# parameters are +nil+, a hash, or a url-encoded or multipart string;
|
||||
# the headers are a hash. Keys are automatically upcased and prefixed
|
||||
# with 'HTTP_' if not already.
|
||||
#
|
||||
# This method used to omit the request_method parameter, assuming it
|
||||
# was :post. This was deprecated in Rails 1.2.4. Always pass the request
|
||||
# method as the first argument.
|
||||
def xml_http_request(request_method, path, parameters = nil, headers = nil)
|
||||
unless request_method.is_a?(Symbol)
|
||||
ActiveSupport::Deprecation.warn 'xml_http_request now takes the request_method (:get, :post, etc.) as the first argument. It used to assume :post, so add the :post argument to your existing method calls to silence this warning.'
|
||||
request_method, path, parameters, headers = :post, request_method, path, parameters
|
||||
end
|
||||
|
||||
headers ||= {}
|
||||
headers['X-Requested-With'] = 'XMLHttpRequest'
|
||||
headers['Accept'] = 'text/javascript, text/html, application/xml, text/xml, */*'
|
||||
headers['Accept'] ||= 'text/javascript, text/html, application/xml, text/xml, */*'
|
||||
|
||||
process(request_method, path, parameters, headers)
|
||||
end
|
||||
|
@ -204,15 +215,16 @@ module ActionController
|
|||
end
|
||||
|
||||
private
|
||||
class MockCGI < CGI #:nodoc:
|
||||
class StubCGI < CGI #:nodoc:
|
||||
attr_accessor :stdinput, :stdoutput, :env_table
|
||||
|
||||
def initialize(env, input=nil)
|
||||
def initialize(env, stdinput = nil)
|
||||
self.env_table = env
|
||||
self.stdinput = StringIO.new(input || "")
|
||||
self.stdoutput = StringIO.new
|
||||
|
||||
super()
|
||||
super
|
||||
|
||||
@stdinput = stdinput.is_a?(IO) ? stdinput : StringIO.new(stdinput || '')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -226,7 +238,7 @@ module ActionController
|
|||
end
|
||||
|
||||
# Performs the actual request.
|
||||
def process(method, path, parameters=nil, headers=nil)
|
||||
def process(method, path, parameters = nil, headers = nil)
|
||||
data = requestify(parameters)
|
||||
path = interpret_uri(path) if path =~ %r{://}
|
||||
path = "/#{path}" unless path[0] == ?/
|
||||
|
@ -258,14 +270,15 @@ module ActionController
|
|||
end
|
||||
|
||||
unless ActionController::Base.respond_to?(:clear_last_instantiation!)
|
||||
ActionController::Base.send(:include, ControllerCapture)
|
||||
ActionController::Base.module_eval { include ControllerCapture }
|
||||
end
|
||||
|
||||
ActionController::Base.clear_last_instantiation!
|
||||
|
||||
cgi = MockCGI.new(env, data)
|
||||
cgi = StubCGI.new(env, data)
|
||||
Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
|
||||
@result = cgi.stdoutput.string
|
||||
@request_count += 1
|
||||
|
||||
@controller = ActionController::Base.last_instantiation
|
||||
@request = @controller.request
|
||||
|
@ -294,7 +307,7 @@ module ActionController
|
|||
end
|
||||
|
||||
(@headers['set-cookie'] || [] ).each do |string|
|
||||
name, value = string.match(/^(.*?)=(.*?);/)[1,2]
|
||||
name, value = string.match(/^([^=]*)=([^;]*);/)[1,2]
|
||||
@cookies[name] = value
|
||||
end
|
||||
|
||||
|
@ -310,14 +323,14 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
# Get a temporarly URL writer object
|
||||
# Get a temporary URL writer object
|
||||
def generic_url_rewriter
|
||||
cgi = MockCGI.new('REQUEST_METHOD' => "GET",
|
||||
cgi = StubCGI.new('REQUEST_METHOD' => "GET",
|
||||
'QUERY_STRING' => "",
|
||||
"REQUEST_URI" => "/",
|
||||
"HTTP_HOST" => host,
|
||||
"SERVER_PORT" => https? ? "443" : "80",
|
||||
"HTTPS" => https? ? "on" : "off")
|
||||
"HTTPS" => https? ? "on" : "off")
|
||||
ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {})
|
||||
end
|
||||
|
||||
|
@ -339,7 +352,6 @@ module ActionController
|
|||
"#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# A module used to extend ActionController::Base, so that integration tests
|
||||
|
@ -368,6 +380,76 @@ module ActionController
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Runner
|
||||
# Reset the current session. This is useful for testing multiple sessions
|
||||
# in a single test case.
|
||||
def reset!
|
||||
@integration_session = open_session
|
||||
end
|
||||
|
||||
%w(get post put head delete cookies assigns
|
||||
xml_http_request get_via_redirect post_via_redirect).each do |method|
|
||||
define_method(method) do |*args|
|
||||
reset! unless @integration_session
|
||||
# reset the html_document variable, but only for new get/post calls
|
||||
@html_document = nil unless %w(cookies assigns).include?(method)
|
||||
returning @integration_session.send!(method, *args) do
|
||||
copy_session_variables!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Open a new session instance. If a block is given, the new session is
|
||||
# yielded to the block before being returned.
|
||||
#
|
||||
# session = open_session do |sess|
|
||||
# sess.extend(CustomAssertions)
|
||||
# end
|
||||
#
|
||||
# By default, a single session is automatically created for you, but you
|
||||
# can use this method to open multiple sessions that ought to be tested
|
||||
# simultaneously.
|
||||
def open_session
|
||||
session = Integration::Session.new
|
||||
|
||||
# delegate the fixture accessors back to the test instance
|
||||
extras = Module.new { attr_accessor :delegate, :test_result }
|
||||
if self.class.respond_to?(:fixture_table_names)
|
||||
self.class.fixture_table_names.each do |table_name|
|
||||
name = table_name.tr(".", "_")
|
||||
next unless respond_to?(name)
|
||||
extras.send!(:define_method, name) { |*args| delegate.send(name, *args) }
|
||||
end
|
||||
end
|
||||
|
||||
# delegate add_assertion to the test case
|
||||
extras.send!(:define_method, :add_assertion) { test_result.add_assertion }
|
||||
session.extend(extras)
|
||||
session.delegate = self
|
||||
session.test_result = @_result
|
||||
|
||||
yield session if block_given?
|
||||
session
|
||||
end
|
||||
|
||||
# Copy the instance variables from the current session instance into the
|
||||
# test instance.
|
||||
def copy_session_variables! #:nodoc:
|
||||
return unless @integration_session
|
||||
%w(controller response request).each do |var|
|
||||
instance_variable_set("@#{var}", @integration_session.send!(var))
|
||||
end
|
||||
end
|
||||
|
||||
# Delegate unhandled messages to the current session instance.
|
||||
def method_missing(sym, *args, &block)
|
||||
reset! unless @integration_session
|
||||
returning @integration_session.send!(sym, *args, &block) do
|
||||
copy_session_variables!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# An IntegrationTest is one that spans multiple controllers and actions,
|
||||
|
@ -446,6 +528,8 @@ module ActionController
|
|||
# end
|
||||
# end
|
||||
class IntegrationTest < Test::Unit::TestCase
|
||||
include Integration::Runner
|
||||
|
||||
# Work around a bug in test/unit caused by the default test being named
|
||||
# as a symbol (:default_test), which causes regex test filters
|
||||
# (like "ruby test.rb -n /foo/") to fail because =~ doesn't work on
|
||||
|
@ -493,70 +577,5 @@ module ActionController
|
|||
superclass.use_instantiated_fixtures
|
||||
end
|
||||
end
|
||||
|
||||
# Reset the current session. This is useful for testing multiple sessions
|
||||
# in a single test case.
|
||||
def reset!
|
||||
@integration_session = open_session
|
||||
end
|
||||
|
||||
%w(get post put head delete 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
|
||||
end
|
||||
end
|
||||
|
||||
# Open a new session instance. If a block is given, the new session is
|
||||
# yielded to the block before being returned.
|
||||
#
|
||||
# session = open_session do |sess|
|
||||
# sess.extend(CustomAssertions)
|
||||
# end
|
||||
#
|
||||
# By default, a single session is automatically created for you, but you
|
||||
# can use this method to open multiple sessions that ought to be tested
|
||||
# simultaneously.
|
||||
def open_session
|
||||
session = Integration::Session.new
|
||||
|
||||
# delegate the fixture accessors back to the test instance
|
||||
extras = Module.new { attr_accessor :delegate, :test_result }
|
||||
self.class.fixture_table_names.each do |table_name|
|
||||
name = table_name.tr(".", "_")
|
||||
next unless respond_to?(name)
|
||||
extras.send(:define_method, name) { |*args| delegate.send(name, *args) }
|
||||
end
|
||||
|
||||
# delegate add_assertion to the test case
|
||||
extras.send(:define_method, :add_assertion) { test_result.add_assertion }
|
||||
session.extend(extras)
|
||||
session.delegate = self
|
||||
session.test_result = @_result
|
||||
|
||||
yield session if block_given?
|
||||
session
|
||||
end
|
||||
|
||||
# Copy the instance variables from the current session instance into the
|
||||
# test instance.
|
||||
def copy_session_variables! #:nodoc:
|
||||
return unless @integration_session
|
||||
%w(controller response request).each do |var|
|
||||
instance_variable_set("@#{var}", @integration_session.send(var))
|
||||
end
|
||||
end
|
||||
|
||||
# Delegate unhandled messages to the current session instance.
|
||||
def method_missing(sym, *args, &block)
|
||||
reset! unless @integration_session
|
||||
returning @integration_session.send(sym, *args, &block) do
|
||||
copy_session_variables!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -64,13 +64,13 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically
|
||||
# set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named
|
||||
# <tt>app/views/layouts/weblog.rhtml</tt> or <tt>app/views/layouts/weblog.rxml</tt> exists then it will be automatically set as
|
||||
# the layout for your WeblogController. You can create a layout with the name <tt>application.rhtml</tt> or <tt>application.rxml</tt>
|
||||
# <tt>app/views/layouts/weblog.erb</tt> or <tt>app/views/layouts/weblog.builder</tt> exists then it will be automatically set as
|
||||
# the layout for your WeblogController. You can create a layout with the name <tt>application.erb</tt> or <tt>application.builder</tt>
|
||||
# and this will be set as the default controller if there is no layout with the same name as the current controller and there is
|
||||
# no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout.
|
||||
# assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.rhtml</tt>.
|
||||
# assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.erb</tt>.
|
||||
# Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set.
|
||||
# Explicitly setting the layout in a parent class, though, will not override the child class's layout assignement if the child
|
||||
# Explicitly setting the layout in a parent class, though, will not override the child class's layout assignment if the child
|
||||
# class has a layout with the same name.
|
||||
#
|
||||
# == Inheritance for layouts
|
||||
|
@ -124,7 +124,8 @@ module ActionController #:nodoc:
|
|||
# class WeblogController < ActionController::Base
|
||||
# layout "weblog_standard"
|
||||
#
|
||||
# If no directory is specified for the template name, the template will by default by looked for in +app/views/layouts/+.
|
||||
# If no directory is specified for the template name, the template will by default be looked for in +app/views/layouts/+.
|
||||
# Otherwise, it will be looked up relative to the template root.
|
||||
#
|
||||
# == Conditional layouts
|
||||
#
|
||||
|
@ -165,30 +166,39 @@ module ActionController #:nodoc:
|
|||
# variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
|
||||
module ClassMethods
|
||||
# If a layout is specified, all rendered actions will have their result rendered
|
||||
# when the layout<tt>yield</tt>'s. This layout can itself depend on instance variables assigned during action
|
||||
# when the layout <tt>yield</tt>s. This layout can itself depend on instance variables assigned during action
|
||||
# performance and have access to them as any normal template would.
|
||||
def layout(template_name, conditions = {})
|
||||
def layout(template_name, conditions = {}, auto = false)
|
||||
add_layout_conditions(conditions)
|
||||
write_inheritable_attribute "layout", template_name
|
||||
write_inheritable_attribute "auto_layout", auto
|
||||
end
|
||||
|
||||
def layout_conditions #:nodoc:
|
||||
@layout_conditions ||= read_inheritable_attribute("layout_conditions")
|
||||
end
|
||||
|
||||
def default_layout #:nodoc:
|
||||
@default_layout ||= read_inheritable_attribute("layout")
|
||||
def default_layout(format) #:nodoc:
|
||||
layout = read_inheritable_attribute("layout")
|
||||
return layout unless read_inheritable_attribute("auto_layout")
|
||||
@default_layout ||= {}
|
||||
@default_layout[format] ||= default_layout_with_format(format, layout)
|
||||
@default_layout[format]
|
||||
end
|
||||
|
||||
|
||||
def layout_list #:nodoc:
|
||||
view_paths.collect do |path|
|
||||
Dir["#{path}/layouts/**/*"]
|
||||
end.flatten
|
||||
end
|
||||
|
||||
private
|
||||
def inherited_with_layout(child)
|
||||
inherited_without_layout(child)
|
||||
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
|
||||
|
||||
def layout_list
|
||||
Dir.glob("#{template_root}/layouts/**/*")
|
||||
unless child.name.blank?
|
||||
layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '')
|
||||
child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty?
|
||||
end
|
||||
end
|
||||
|
||||
def add_layout_conditions(conditions)
|
||||
|
@ -204,6 +214,15 @@ module ActionController #:nodoc:
|
|||
h[dirname] = File.directory? dirname
|
||||
end
|
||||
end
|
||||
|
||||
def default_layout_with_format(format, layout)
|
||||
list = layout_list
|
||||
if list.grep(%r{layouts/#{layout}\.#{format}(\.[a-z][0-9a-z]*)+$}).empty?
|
||||
(!list.grep(%r{layouts/#{layout}\.([a-z][0-9a-z]*)+$}).empty? && format == :html) ? layout : nil
|
||||
else
|
||||
layout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
|
||||
|
@ -211,11 +230,10 @@ module ActionController #:nodoc:
|
|||
# object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
|
||||
# weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
|
||||
def active_layout(passed_layout = nil)
|
||||
layout = passed_layout || self.class.default_layout
|
||||
|
||||
layout = passed_layout || self.class.default_layout(response.template.template_format)
|
||||
active_layout = case layout
|
||||
when String then layout
|
||||
when Symbol then send(layout)
|
||||
when Symbol then send!(layout)
|
||||
when Proc then layout.call(self)
|
||||
end
|
||||
|
||||
|
@ -231,34 +249,30 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
def render_with_a_layout(options = nil, deprecated_status = nil, deprecated_layout = nil, &block) #:nodoc:
|
||||
template_with_options = options.is_a?(Hash)
|
||||
protected
|
||||
def render_with_a_layout(options = nil, &block) #:nodoc:
|
||||
template_with_options = options.is_a?(Hash)
|
||||
|
||||
if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options))
|
||||
assert_existence_of_template_file(layout)
|
||||
|
||||
if 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 template within #{layout}") if logger
|
||||
|
||||
options = options.merge :layout => false if template_with_options
|
||||
logger.info("Rendering #{options} within #{layout}") if logger
|
||||
|
||||
if template_with_options
|
||||
content_for_layout = render_with_no_layout(options, &block)
|
||||
deprecated_status = options[:status] || deprecated_status
|
||||
erase_render_results
|
||||
add_variables_to_assigns
|
||||
@template.instance_variable_set("@content_for_layout", content_for_layout)
|
||||
response.layout = layout
|
||||
status = template_with_options ? options[:status] : nil
|
||||
render_for_text(@template.render_file(layout, true), status)
|
||||
else
|
||||
content_for_layout = render_with_no_layout(options, deprecated_status, &block)
|
||||
render_with_no_layout(options, &block)
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def apply_layout?(template_with_options, options)
|
||||
return false if options == :update
|
||||
template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout?
|
||||
|
@ -266,14 +280,12 @@ module ActionController #:nodoc:
|
|||
|
||||
def candidate_for_layout?(options)
|
||||
(options.has_key?(:layout) && options[:layout] != false) ||
|
||||
options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? &&
|
||||
!template_exempt_from_layout?(default_template_name(options[:action] || options[:template]))
|
||||
options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? &&
|
||||
!template_exempt_from_layout?(options[:template] || default_template_name(options[:action]))
|
||||
end
|
||||
|
||||
def pick_layout(template_with_options, options, deprecated_layout)
|
||||
if deprecated_layout
|
||||
deprecated_layout
|
||||
elsif template_with_options
|
||||
def pick_layout(template_with_options, options)
|
||||
if template_with_options
|
||||
case layout = options[:layout]
|
||||
when FalseClass
|
||||
nil
|
||||
|
@ -305,9 +317,10 @@ module ActionController #:nodoc:
|
|||
# Does a layout directory for this class exist?
|
||||
# we cache this info in a class level hash
|
||||
def layout_directory?(layout_name)
|
||||
template_path = File.join(self.class.view_root, 'layouts', layout_name)
|
||||
dirname = File.dirname(template_path)
|
||||
self.class.send(:layout_directory_exists_cache)[dirname]
|
||||
view_paths.find do |path|
|
||||
next unless template_path = Dir[File.join(path, 'layouts', layout_name) + ".*"].first
|
||||
self.class.send!(:layout_directory_exists_cache)[File.dirname(template_path)]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
module ActionController
|
||||
# Macros are class-level calls that add pre-defined actions to the controller based on the parameters passed in.
|
||||
# Currently, they're used to bridge the JavaScript macros, like autocompletion and in-place editing, with the controller
|
||||
# backing.
|
||||
module Macros
|
||||
module AutoComplete #:nodoc:
|
||||
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
|
||||
# class BlogController < ApplicationController
|
||||
# auto_complete_for :post, :title
|
||||
# end
|
||||
#
|
||||
# # View
|
||||
# <%= text_field_with_auto_complete :post, title %>
|
||||
#
|
||||
# By default, auto_complete_for limits the results to 10 entries,
|
||||
# and sorts by the given field.
|
||||
#
|
||||
# auto_complete_for takes a third parameter, an options hash to
|
||||
# the find method used to search for the records:
|
||||
#
|
||||
# auto_complete_for :post, :title, :limit => 15, :order => 'created_at DESC'
|
||||
#
|
||||
# For help on defining text input fields with autocompletion,
|
||||
# see ActionView::Helpers::JavaScriptHelper.
|
||||
#
|
||||
# For more examples, see script.aculo.us:
|
||||
# * http://script.aculo.us/demos/ajax/autocompleter
|
||||
# * http://script.aculo.us/demos/ajax/autocompleter_customized
|
||||
module ClassMethods
|
||||
def auto_complete_for(object, method, options = {})
|
||||
define_method("auto_complete_for_#{object}_#{method}") do
|
||||
find_options = {
|
||||
:conditions => [ "LOWER(#{method}) LIKE ?", '%' + params[object][method].downcase + '%' ],
|
||||
:order => "#{method} ASC",
|
||||
:limit => 10 }.merge!(options)
|
||||
|
||||
@items = object.to_s.camelize.constantize.find(:all, find_options)
|
||||
|
||||
render :inline => "<%= auto_complete_result @items, '#{method}' %>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,33 +0,0 @@
|
|||
module ActionController
|
||||
module Macros
|
||||
module InPlaceEditing #:nodoc:
|
||||
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
|
||||
# class BlogController < ApplicationController
|
||||
# in_place_edit_for :post, :title
|
||||
# end
|
||||
#
|
||||
# # View
|
||||
# <%= in_place_editor_field :post, 'title' %>
|
||||
#
|
||||
# For help on defining an in place editor in the browser,
|
||||
# see ActionView::Helpers::JavaScriptHelper.
|
||||
module ClassMethods
|
||||
def in_place_edit_for(object, attribute, options = {})
|
||||
define_method("set_#{object}_#{attribute}") do
|
||||
@item = object.to_s.camelize.constantize.find(params[:id])
|
||||
@item.update_attribute(attribute, params[:value])
|
||||
render :text => @item.send(attribute).to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,9 @@
|
|||
module ActionController #:nodoc:
|
||||
module MimeResponds #:nodoc:
|
||||
def self.included(base)
|
||||
base.send(:include, ActionController::MimeResponds::InstanceMethods)
|
||||
base.module_eval do
|
||||
include ActionController::MimeResponds::InstanceMethods
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
|
@ -11,51 +13,51 @@ module ActionController #:nodoc:
|
|||
# def index
|
||||
# @people = Person.find(:all)
|
||||
# end
|
||||
#
|
||||
#
|
||||
# Here's the same action, with web-service support baked in:
|
||||
#
|
||||
#
|
||||
# def index
|
||||
# @people = Person.find(:all)
|
||||
#
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html
|
||||
# format.xml { render :xml => @people.to_xml }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# What that says is, "if the client wants HTML in response to this action, just respond as we
|
||||
# would have before, but if the client wants XML, return them the list of people in XML format."
|
||||
#
|
||||
# What that says is, "if the client wants HTML in response to this action, just respond as we
|
||||
# would have before, but if the client wants XML, return them the list of people in XML format."
|
||||
# (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
|
||||
#
|
||||
# Supposing you have an action that adds a new person, optionally creating their company
|
||||
#
|
||||
# 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 create
|
||||
# @company = Company.find_or_create_by_name(params[:company][:name])
|
||||
# @person = @company.people.create(params[:person])
|
||||
#
|
||||
#
|
||||
# redirect_to(person_list_url)
|
||||
# end
|
||||
#
|
||||
#
|
||||
# Here's the same action, with web-service support baked in:
|
||||
#
|
||||
#
|
||||
# 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 |format|
|
||||
# format.html { redirect_to(person_list_url) }
|
||||
# format.js
|
||||
# format.xml { render :xml => @person.to_xml(:include => @company) }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# If the client wants HTML, we just redirect them back to the person list. If they want Javascript
|
||||
# (wants.js), then it is an RJS request and we render the RJS template associated with this action.
|
||||
# Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
|
||||
# include the person’s company in the rendered XML, so you get something like this:
|
||||
#
|
||||
#
|
||||
# If the client wants HTML, we just redirect them back to the person list. If they want Javascript
|
||||
# (format.js), then it is an RJS request and we render the RJS template associated with this action.
|
||||
# Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
|
||||
# include the person's company in the rendered XML, so you get something like this:
|
||||
#
|
||||
# <person>
|
||||
# <id>...</id>
|
||||
# ...
|
||||
|
@ -65,60 +67,54 @@ module ActionController #:nodoc:
|
|||
# ...
|
||||
# </company>
|
||||
# </person>
|
||||
#
|
||||
#
|
||||
# Note, however, the extra bit at the top of that action:
|
||||
#
|
||||
#
|
||||
# company = params[:person].delete(:company)
|
||||
# @company = Company.find_or_create_by_name(company[:name])
|
||||
#
|
||||
# This is because the incoming XML document (if a web-service request is in process) can only contain a
|
||||
#
|
||||
# This is because the incoming XML document (if a web-service request is in process) can only contain a
|
||||
# single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
|
||||
#
|
||||
#
|
||||
# person[name]=...&person[company][name]=...&...
|
||||
#
|
||||
#
|
||||
# And, like this (xml-encoded):
|
||||
#
|
||||
#
|
||||
# <person>
|
||||
# <name>...</name>
|
||||
# <company>
|
||||
# <name>...</name>
|
||||
# </company>
|
||||
# </person>
|
||||
#
|
||||
# In other words, we make the request so that it operates on a single entity—a person. Then, in the action,
|
||||
# we extract the company data from the request, find or create the company, and then create the new person
|
||||
#
|
||||
# In other words, we make the request so that it operates on a single entity's person. Then, in the action,
|
||||
# we extract the company data from the request, find or create the company, and then create the new person
|
||||
# with the remaining data.
|
||||
#
|
||||
# Note that you can define your own XML parameter parser which would allow you to describe multiple entities
|
||||
# in a single request (i.e., by wrapping them all in a single root note), but if you just go with the flow
|
||||
#
|
||||
# Note that you can define your own XML parameter parser which would allow you to describe multiple entities
|
||||
# in a single request (i.e., by wrapping them all in a single root note), but if you just go with the flow
|
||||
# and accept Rails' defaults, life will be much easier.
|
||||
#
|
||||
#
|
||||
# If you need to use a MIME type which isn't supported by default, you can register your own handlers in
|
||||
# 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 both" unless types.any? ^ block
|
||||
block ||= lambda { |responder| types.each { |type| responder.send(type) } }
|
||||
responder = Responder.new(block.binding)
|
||||
responder = Responder.new(self)
|
||||
block.call(responder)
|
||||
responder.respond
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Responder #:nodoc:
|
||||
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(
|
||||
"(params[:format] && Mime::EXTENSION_LOOKUP[params[:format]]) ? " +
|
||||
"[ Mime::EXTENSION_LOOKUP[params[:format]] ] : request.accepts",
|
||||
block_binding
|
||||
)
|
||||
def initialize(controller)
|
||||
@controller = controller
|
||||
@request = controller.request
|
||||
@response = controller.response
|
||||
|
||||
@mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
|
||||
|
||||
@order = []
|
||||
@responses = {}
|
||||
|
@ -126,54 +122,47 @@ module ActionController #:nodoc:
|
|||
|
||||
def custom(mime_type, &block)
|
||||
mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
|
||||
|
||||
|
||||
@order << mime_type
|
||||
|
||||
if block_given?
|
||||
@responses[mime_type] = Proc.new do
|
||||
eval "response.content_type = '#{mime_type.to_s}'", @block_binding
|
||||
block.call
|
||||
end
|
||||
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
|
||||
|
||||
@responses[mime_type] = Proc.new do
|
||||
@response.template.template_format = mime_type.to_sym
|
||||
@response.content_type = mime_type.to_s
|
||||
block_given? ? block.call : @controller.send(:render, :action => @controller.action_name)
|
||||
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
|
||||
@responses[@order.first].call
|
||||
return
|
||||
else
|
||||
if priority === @order
|
||||
if @responses[priority]
|
||||
@responses[priority].call
|
||||
return # mime type match found, be happy and return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if @order.include?(Mime::ALL)
|
||||
@responses[Mime::ALL].call
|
||||
else
|
||||
eval 'render(:nothing => true, :status => "406 Not Acceptable")', @block_binding
|
||||
@controller.send :head, :not_acceptable
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
module Mime
|
||||
SET = []
|
||||
EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
|
||||
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
|
||||
|
||||
# Encapsulates the notion of a mime type. Can be used at render time, for example, with:
|
||||
#
|
||||
# class PostsController < ActionController::Base
|
||||
|
@ -20,7 +24,7 @@ module Mime
|
|||
def initialize(order, name, q=nil)
|
||||
@order = order
|
||||
@name = name.strip
|
||||
q ||= 0.0 if @name == "*/*" # default "*/*" to end of list
|
||||
q ||= 0.0 if @name == Mime::ALL # default wilcard match to end of list
|
||||
@q = ((q || 1.0).to_f * 100).to_i
|
||||
end
|
||||
|
||||
|
@ -44,22 +48,37 @@ 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
|
||||
def lookup_by_extension(extension)
|
||||
EXTENSION_LOOKUP[extension]
|
||||
end
|
||||
|
||||
# Registers an alias that's not used on mime type lookup, but can be referenced directly. Especially useful for
|
||||
# rendering different HTML versions depending on the user agent, like an iPhone.
|
||||
def register_alias(string, symbol, extension_synonyms = [])
|
||||
register(string, symbol, [], extension_synonyms, true)
|
||||
end
|
||||
|
||||
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
|
||||
Mime.instance_eval { const_set symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms) }
|
||||
|
||||
SET << Mime.const_get(symbol.to_s.upcase)
|
||||
|
||||
([string] + mime_type_synonyms).each { |string| LOOKUP[string] = SET.last } unless skip_lookup
|
||||
([symbol.to_s] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext] = 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! do |i|
|
||||
AcceptItem.new(index += 1, *i.split(/;\s*q=/))
|
||||
end.sort!
|
||||
list = []
|
||||
accept_header.split(/,/).each_with_index do |header, index|
|
||||
params = header.split(/;\s*q=/)
|
||||
list << AcceptItem.new(index, *params) unless params.empty?
|
||||
end
|
||||
list.sort!
|
||||
|
||||
# Take care of the broken text/xml entry by renaming or deleting it
|
||||
text_xml = list.index("text/xml")
|
||||
app_xml = list.index("application/xml")
|
||||
app_xml = list.index(Mime::XML.to_s)
|
||||
|
||||
if text_xml && app_xml
|
||||
# set the q value to the max of the two
|
||||
|
@ -73,9 +92,9 @@ module Mime
|
|||
|
||||
# delete text_xml from the list
|
||||
list.delete_at(text_xml)
|
||||
|
||||
|
||||
elsif text_xml
|
||||
list[text_xml].name = "application/xml"
|
||||
list[text_xml].name = Mime::XML.to_s
|
||||
end
|
||||
|
||||
# Look for more specific xml-based types and sort them ahead of app/xml
|
||||
|
@ -128,73 +147,17 @@ module Mime
|
|||
def ==(mime_type)
|
||||
(@synonyms + [ self ]).any? { |synonym| synonym.to_s == mime_type.to_s } if mime_type
|
||||
end
|
||||
|
||||
private
|
||||
def method_missing(method, *args)
|
||||
if method.to_s =~ /(\w+)\?$/
|
||||
mime_type = $1.downcase.to_sym
|
||||
mime_type == @symbol || (mime_type == :html && @symbol == :all)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
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 )
|
||||
|
||||
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["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
|
||||
|
||||
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
|
||||
|
||||
require 'action_controller/mime_types'
|
||||
|
|
20
vendor/rails/actionpack/lib/action_controller/mime_types.rb
vendored
Normal file
20
vendor/rails/actionpack/lib/action_controller/mime_types.rb
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Build list of Mime types for HTTP responses
|
||||
# http://www.iana.org/assignments/media-types/
|
||||
|
||||
Mime::Type.register "*/*", :all
|
||||
Mime::Type.register "text/plain", :text, [], %w(txt)
|
||||
Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
|
||||
Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )
|
||||
Mime::Type.register "text/css", :css
|
||||
Mime::Type.register "text/calendar", :ics
|
||||
Mime::Type.register "text/csv", :csv
|
||||
Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
|
||||
Mime::Type.register "application/rss+xml", :rss
|
||||
Mime::Type.register "application/atom+xml", :atom
|
||||
Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml )
|
||||
|
||||
Mime::Type.register "multipart/form-data", :multipart_form
|
||||
Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
|
||||
|
||||
# http://www.ietf.org/rfc/rfc4627.txt
|
||||
Mime::Type.register "application/json", :json, %w( text/x-json )
|
|
@ -1,408 +0,0 @@
|
|||
module ActionController
|
||||
# === Action Pack pagination for Active Record collections
|
||||
#
|
||||
# DEPRECATION WARNING: Pagination will be moved to a plugin in Rails 2.0.
|
||||
# Install the classic_pagination plugin for forward compatibility:
|
||||
# script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination
|
||||
#
|
||||
# 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
|
||||
# the magic isn't flexible enough for your needs, you can create your own
|
||||
# paginators with a minimal amount of code.
|
||||
#
|
||||
# The Pagination module can handle as much or as little as you wish. In the
|
||||
# controller, have it automatically query your model for pagination; or,
|
||||
# if you prefer, create Paginator objects yourself.
|
||||
#
|
||||
# Pagination is included automatically for all controllers.
|
||||
#
|
||||
# For help rendering pagination links, see
|
||||
# ActionView::Helpers::PaginationHelper.
|
||||
#
|
||||
# ==== Automatic pagination for every action in a controller
|
||||
#
|
||||
# class PersonController < ApplicationController
|
||||
# model :person
|
||||
#
|
||||
# paginate :people, :order => 'last_name, first_name',
|
||||
# :per_page => 20
|
||||
#
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# Each action in this controller now has access to a <tt>@people</tt>
|
||||
# instance variable, which is an ordered collection of model objects for the
|
||||
# current page (at most 20, sorted by last name and first name), and a
|
||||
# <tt>@person_pages</tt> Paginator instance. The current page is determined
|
||||
# by the <tt>params[:page]</tt> variable.
|
||||
#
|
||||
# ==== Pagination for a single action
|
||||
#
|
||||
# def list
|
||||
# @person_pages, @people =
|
||||
# paginate :people, :order => 'last_name, first_name'
|
||||
# end
|
||||
#
|
||||
# Like the previous example, but explicitly creates <tt>@person_pages</tt>
|
||||
# and <tt>@people</tt> for a single action, and uses the default of 10 items
|
||||
# per page.
|
||||
#
|
||||
# ==== Custom/"classic" pagination
|
||||
#
|
||||
# def list
|
||||
# @person_pages = Paginator.new self, Person.count, 10, params[:page]
|
||||
# @people = Person.find :all, :order => 'last_name, first_name',
|
||||
# :limit => @person_pages.items_per_page,
|
||||
# :offset => @person_pages.current.offset
|
||||
# end
|
||||
#
|
||||
# Explicitly creates the paginator from the previous example and uses
|
||||
# Paginator#to_sql to retrieve <tt>@people</tt> from the model.
|
||||
#
|
||||
module Pagination
|
||||
unless const_defined?(:OPTIONS)
|
||||
# A hash holding options for controllers using macro-style pagination
|
||||
OPTIONS = Hash.new
|
||||
|
||||
# The default options for pagination
|
||||
DEFAULT_OPTIONS = {
|
||||
:class_name => nil,
|
||||
:singular_name => nil,
|
||||
:per_page => 10,
|
||||
:conditions => nil,
|
||||
:order_by => nil,
|
||||
:order => nil,
|
||||
:join => nil,
|
||||
:joins => nil,
|
||||
:count => nil,
|
||||
:include => nil,
|
||||
:select => nil,
|
||||
:parameter => 'page'
|
||||
}
|
||||
end
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
super
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
def self.validate_options!(collection_id, options, in_action) #:nodoc:
|
||||
options.merge!(DEFAULT_OPTIONS) {|key, old, new| old}
|
||||
|
||||
valid_options = DEFAULT_OPTIONS.keys
|
||||
valid_options << :actions unless in_action
|
||||
|
||||
unknown_option_keys = options.keys - valid_options
|
||||
raise ActionController::ActionControllerError,
|
||||
"Unknown options: #{unknown_option_keys.join(', ')}" unless
|
||||
unknown_option_keys.empty?
|
||||
|
||||
options[:singular_name] ||= Inflector.singularize(collection_id.to_s)
|
||||
options[:class_name] ||= Inflector.camelize(options[:singular_name])
|
||||
end
|
||||
|
||||
# Returns a paginator and a collection of Active Record model instances
|
||||
# for the paginator's current page. This is designed to be used in a
|
||||
# single action; to automatically paginate multiple actions, consider
|
||||
# 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>: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
|
||||
# single page. Defaults to 10
|
||||
# <tt>:conditions</tt>:: optional conditions passed to Model.find(:all, *params) and
|
||||
# Model.count
|
||||
# <tt>:order</tt>:: optional order parameter passed to Model.find(:all, *params)
|
||||
# <tt>:order_by</tt>:: (deprecated, used :order) optional order parameter passed to Model.find(:all, *params)
|
||||
# <tt>:joins</tt>:: optional joins parameter passed to Model.find(:all, *params)
|
||||
# and Model.count
|
||||
# <tt>:join</tt>:: (deprecated, used :joins or :include) optional join parameter passed to Model.find(:all, *params)
|
||||
# and Model.count
|
||||
# <tt>:include</tt>:: optional eager loading parameter passed to Model.find(:all, *params)
|
||||
# and Model.count
|
||||
# <tt>:select</tt>:: :select parameter passed to Model.find(:all, *params)
|
||||
#
|
||||
# <tt>:count</tt>:: parameter passed as :select option to Model.count(*params)
|
||||
#
|
||||
def paginate(collection_id, options={})
|
||||
Pagination.validate_options!(collection_id, options, true)
|
||||
paginator_and_collection_for(collection_id, options)
|
||||
end
|
||||
|
||||
deprecate :paginate => 'Pagination is moving to a plugin in Rails 2.0: script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination'
|
||||
|
||||
# These methods become class methods on any controller
|
||||
module ClassMethods
|
||||
# Creates a +before_filter+ which automatically paginates an Active
|
||||
# Record model for all actions in a controller (or certain actions if
|
||||
# specified with the <tt>:actions</tt> option).
|
||||
#
|
||||
# +options+ are the same as PaginationHelper#paginate, with the addition
|
||||
# of:
|
||||
# <tt>:actions</tt>:: an array of actions for which the pagination is
|
||||
# active. Defaults to +nil+ (i.e., every action)
|
||||
def paginate(collection_id, options={})
|
||||
Pagination.validate_options!(collection_id, options, false)
|
||||
module_eval do
|
||||
before_filter :create_paginators_and_retrieve_collections
|
||||
OPTIONS[self] ||= Hash.new
|
||||
OPTIONS[self][collection_id] = options
|
||||
end
|
||||
end
|
||||
|
||||
deprecate :paginate => 'Pagination is moving to a plugin in Rails 2.0: script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination'
|
||||
end
|
||||
|
||||
def create_paginators_and_retrieve_collections #:nodoc:
|
||||
Pagination::OPTIONS[self.class].each do |collection_id, options|
|
||||
next unless options[:actions].include? action_name if
|
||||
options[:actions]
|
||||
|
||||
paginator, collection =
|
||||
paginator_and_collection_for(collection_id, options)
|
||||
|
||||
paginator_name = "@#{options[:singular_name]}_pages"
|
||||
self.instance_variable_set(paginator_name, paginator)
|
||||
|
||||
collection_name = "@#{collection_id.to_s}"
|
||||
self.instance_variable_set(collection_name, collection)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the total number of items in the collection to be paginated for
|
||||
# the +model+ and given +conditions+. Override this method to implement a
|
||||
# custom counter.
|
||||
def count_collection_for_pagination(model, options)
|
||||
model.count(:conditions => options[:conditions],
|
||||
:joins => options[:join] || options[:joins],
|
||||
:include => options[:include],
|
||||
:select => options[:count])
|
||||
end
|
||||
|
||||
# Returns a collection of items for the given +model+ and +options[conditions]+,
|
||||
# ordered by +options[order]+, for the current page in the given +paginator+.
|
||||
# Override this method to implement a custom finder.
|
||||
def find_collection_for_pagination(model, options, paginator)
|
||||
model.find(:all, :conditions => options[:conditions],
|
||||
:order => options[:order_by] || options[:order],
|
||||
:joins => options[:join] || options[:joins], :include => options[:include],
|
||||
:select => options[:select], :limit => options[:per_page],
|
||||
:offset => paginator.current.offset)
|
||||
end
|
||||
|
||||
protected :create_paginators_and_retrieve_collections,
|
||||
:count_collection_for_pagination,
|
||||
:find_collection_for_pagination
|
||||
|
||||
def paginator_and_collection_for(collection_id, options) #:nodoc:
|
||||
klass = options[:class_name].constantize
|
||||
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)
|
||||
|
||||
return paginator, collection
|
||||
end
|
||||
|
||||
private :paginator_and_collection_for
|
||||
|
||||
# A class representing a paginator for an Active Record collection.
|
||||
class Paginator
|
||||
include Enumerable
|
||||
|
||||
# Creates a new Paginator on the given +controller+ for a set of items
|
||||
# of size +item_count+ and having +items_per_page+ items per page.
|
||||
# Raises ArgumentError if items_per_page is out of bounds (i.e., less
|
||||
# than or equal to zero). The page CGI parameter for links defaults to
|
||||
# "page" and can be overridden with +page_parameter+.
|
||||
def initialize(controller, item_count, items_per_page, current_page=1)
|
||||
raise ArgumentError, 'must have at least one item per page' if
|
||||
items_per_page <= 0
|
||||
|
||||
@controller = controller
|
||||
@item_count = item_count || 0
|
||||
@items_per_page = items_per_page
|
||||
@pages = {}
|
||||
|
||||
self.current_page = current_page
|
||||
end
|
||||
attr_reader :controller, :item_count, :items_per_page
|
||||
|
||||
# Sets the current page number of this paginator. If +page+ is a Page
|
||||
# object, its +number+ attribute is used as the value; if the page does
|
||||
# not belong to this Paginator, an ArgumentError is raised.
|
||||
def current_page=(page)
|
||||
if page.is_a? Page
|
||||
raise ArgumentError, 'Page/Paginator mismatch' unless
|
||||
page.paginator == self
|
||||
end
|
||||
page = page.to_i
|
||||
@current_page_number = has_page_number?(page) ? page : 1
|
||||
end
|
||||
|
||||
# Returns a Page object representing this paginator's current page.
|
||||
def current_page
|
||||
@current_page ||= self[@current_page_number]
|
||||
end
|
||||
alias current :current_page
|
||||
|
||||
# Returns a new Page representing the first page in this paginator.
|
||||
def first_page
|
||||
@first_page ||= self[1]
|
||||
end
|
||||
alias first :first_page
|
||||
|
||||
# Returns a new Page representing the last page in this paginator.
|
||||
def last_page
|
||||
@last_page ||= self[page_count]
|
||||
end
|
||||
alias last :last_page
|
||||
|
||||
# Returns the number of pages in this paginator.
|
||||
def page_count
|
||||
@page_count ||= @item_count.zero? ? 1 :
|
||||
(q,r=@item_count.divmod(@items_per_page); r==0? q : q+1)
|
||||
end
|
||||
|
||||
alias length :page_count
|
||||
|
||||
# Returns true if this paginator contains the page of index +number+.
|
||||
def has_page_number?(number)
|
||||
number >= 1 and number <= page_count
|
||||
end
|
||||
|
||||
# Returns a new Page representing the page with the given index
|
||||
# +number+.
|
||||
def [](number)
|
||||
@pages[number] ||= Page.new(self, number)
|
||||
end
|
||||
|
||||
# Successively yields all the paginator's pages to the given block.
|
||||
def each(&block)
|
||||
page_count.times do |n|
|
||||
yield self[n+1]
|
||||
end
|
||||
end
|
||||
|
||||
# A class representing a single page in a paginator.
|
||||
class Page
|
||||
include Comparable
|
||||
|
||||
# Creates a new Page for the given +paginator+ with the index
|
||||
# +number+. If +number+ is not in the range of valid page numbers or
|
||||
# is not a number at all, it defaults to 1.
|
||||
def initialize(paginator, number)
|
||||
@paginator = paginator
|
||||
@number = number.to_i
|
||||
@number = 1 unless @paginator.has_page_number? @number
|
||||
end
|
||||
attr_reader :paginator, :number
|
||||
alias to_i :number
|
||||
|
||||
# Compares two Page objects and returns true when they represent the
|
||||
# same page (i.e., their paginators are the same and they have the
|
||||
# same page number).
|
||||
def ==(page)
|
||||
return false if page.nil?
|
||||
@paginator == page.paginator and
|
||||
@number == page.number
|
||||
end
|
||||
|
||||
# Compares two Page objects and returns -1 if the left-hand page comes
|
||||
# before the right-hand page, 0 if the pages are equal, and 1 if the
|
||||
# left-hand page comes after the right-hand page. Raises ArgumentError
|
||||
# if the pages do not belong to the same Paginator object.
|
||||
def <=>(page)
|
||||
raise ArgumentError unless @paginator == page.paginator
|
||||
@number <=> page.number
|
||||
end
|
||||
|
||||
# Returns the item offset for the first item in this page.
|
||||
def offset
|
||||
@paginator.items_per_page * (@number - 1)
|
||||
end
|
||||
|
||||
# Returns the number of the first item displayed.
|
||||
def first_item
|
||||
offset + 1
|
||||
end
|
||||
|
||||
# Returns the number of the last item displayed.
|
||||
def last_item
|
||||
[@paginator.items_per_page * @number, @paginator.item_count].min
|
||||
end
|
||||
|
||||
# Returns true if this page is the first page in the paginator.
|
||||
def first?
|
||||
self == @paginator.first
|
||||
end
|
||||
|
||||
# Returns true if this page is the last page in the paginator.
|
||||
def last?
|
||||
self == @paginator.last
|
||||
end
|
||||
|
||||
# Returns a new Page object representing the page just before this
|
||||
# page, or nil if this is the first page.
|
||||
def previous
|
||||
if first? then nil else @paginator[@number - 1] end
|
||||
end
|
||||
|
||||
# Returns a new Page object representing the page just after this
|
||||
# page, or nil if this is the last page.
|
||||
def next
|
||||
if last? then nil else @paginator[@number + 1] end
|
||||
end
|
||||
|
||||
# Returns a new Window object for this page with the specified
|
||||
# +padding+.
|
||||
def window(padding=2)
|
||||
Window.new(self, padding)
|
||||
end
|
||||
|
||||
# Returns the limit/offset array for this page.
|
||||
def to_sql
|
||||
[@paginator.items_per_page, offset]
|
||||
end
|
||||
|
||||
def to_param #:nodoc:
|
||||
@number.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# A class for representing ranges around a given page.
|
||||
class Window
|
||||
# Creates a new Window object for the given +page+ with the specified
|
||||
# +padding+.
|
||||
def initialize(page, padding=2)
|
||||
@paginator = page.paginator
|
||||
@page = page
|
||||
self.padding = padding
|
||||
end
|
||||
attr_reader :paginator, :page
|
||||
|
||||
# Sets the window's padding (the number of pages on either side of the
|
||||
# window page).
|
||||
def padding=(padding)
|
||||
@padding = padding < 0 ? 0 : padding
|
||||
# Find the beginning and end pages of the window
|
||||
@first = @paginator.has_page_number?(@page.number - @padding) ?
|
||||
@paginator[@page.number - @padding] : @paginator.first
|
||||
@last = @paginator.has_page_number?(@page.number + @padding) ?
|
||||
@paginator[@page.number + @padding] : @paginator.last
|
||||
end
|
||||
attr_reader :padding, :first, :last
|
||||
|
||||
# Returns an array of Page objects in the current window.
|
||||
def pages
|
||||
(@first.number..@last.number).to_a.collect! {|n| @paginator[n]}
|
||||
end
|
||||
alias to_a :pages
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
88
vendor/rails/actionpack/lib/action_controller/polymorphic_routes.rb
vendored
Normal file
88
vendor/rails/actionpack/lib/action_controller/polymorphic_routes.rb
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
module ActionController
|
||||
module PolymorphicRoutes
|
||||
def polymorphic_url(record_or_hash_or_array, options = {})
|
||||
record = extract_record(record_or_hash_or_array)
|
||||
|
||||
namespace = extract_namespace(record_or_hash_or_array)
|
||||
|
||||
args = case record_or_hash_or_array
|
||||
when Hash; [ record_or_hash_or_array ]
|
||||
when Array; record_or_hash_or_array.dup
|
||||
else [ record_or_hash_or_array ]
|
||||
end
|
||||
|
||||
inflection =
|
||||
case
|
||||
when options[:action] == "new"
|
||||
args.pop
|
||||
:singular
|
||||
when record.respond_to?(:new_record?) && record.new_record?
|
||||
args.pop
|
||||
:plural
|
||||
else
|
||||
:singular
|
||||
end
|
||||
|
||||
named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
|
||||
send!(named_route, *args)
|
||||
end
|
||||
|
||||
def polymorphic_path(record_or_hash_or_array)
|
||||
polymorphic_url(record_or_hash_or_array, :routing_type => :path)
|
||||
end
|
||||
|
||||
%w(edit new formatted).each do |action|
|
||||
module_eval <<-EOT, __FILE__, __LINE__
|
||||
def #{action}_polymorphic_url(record_or_hash)
|
||||
polymorphic_url(record_or_hash, :action => "#{action}")
|
||||
end
|
||||
|
||||
def #{action}_polymorphic_path(record_or_hash)
|
||||
polymorphic_url(record_or_hash, :action => "#{action}", :routing_type => :path)
|
||||
end
|
||||
EOT
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def action_prefix(options)
|
||||
options[:action] ? "#{options[:action]}_" : ""
|
||||
end
|
||||
|
||||
def routing_type(options)
|
||||
"#{options[:routing_type] || "url"}"
|
||||
end
|
||||
|
||||
def build_named_route_call(records, namespace, inflection, options = {})
|
||||
records = Array.new([extract_record(records)]) unless records.is_a?(Array)
|
||||
base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", records.pop)}_"
|
||||
|
||||
method_root = records.reverse.inject(base_segment) do |string, name|
|
||||
segment = "#{RecordIdentifier.send!("singular_class_name", name)}_"
|
||||
segment << string
|
||||
end
|
||||
|
||||
action_prefix(options) + namespace + method_root + routing_type(options)
|
||||
end
|
||||
|
||||
def extract_record(record_or_hash_or_array)
|
||||
case record_or_hash_or_array
|
||||
when Array; record_or_hash_or_array.last
|
||||
when Hash; record_or_hash_or_array[:id]
|
||||
else record_or_hash_or_array
|
||||
end
|
||||
end
|
||||
|
||||
def extract_namespace(record_or_hash_or_array)
|
||||
returning "" do |namespace|
|
||||
if record_or_hash_or_array.is_a?(Array)
|
||||
record_or_hash_or_array.delete_if do |record_or_namespace|
|
||||
if record_or_namespace.is_a?(String) || record_or_namespace.is_a?(Symbol)
|
||||
namespace << "#{record_or_namespace.to_s}_"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
91
vendor/rails/actionpack/lib/action_controller/record_identifier.rb
vendored
Normal file
91
vendor/rails/actionpack/lib/action_controller/record_identifier.rb
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
module ActionController
|
||||
# The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
|
||||
# Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate
|
||||
# the view actions to a higher logical level. Example:
|
||||
#
|
||||
# # routes
|
||||
# map.resources :posts
|
||||
#
|
||||
# # view
|
||||
# <% div_for(post) do %> <div id="post_45" class="post">
|
||||
# <%= post.body %> What a wonderful world!
|
||||
# <% end %> </div>
|
||||
#
|
||||
# # controller
|
||||
# def destroy
|
||||
# post = Post.find(params[:id])
|
||||
# post.destroy
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html { redirect_to(post) } # Calls polymorphic_url(post) which in turn calls post_url(post)
|
||||
# format.js do
|
||||
# # Calls: new Effect.fade('post_45');
|
||||
# render(:update) { |page| page[post].visual_effect(:fade) }
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know
|
||||
# that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
|
||||
# convention and allows you to write less code if you follow it.
|
||||
module RecordIdentifier
|
||||
extend self
|
||||
|
||||
# Returns plural/singular for a record or class. Example:
|
||||
#
|
||||
# partial_path(post) # => "posts/post"
|
||||
# partial_path(Person) # => "people/person"
|
||||
def partial_path(record_or_class)
|
||||
klass = class_from_record_or_class(record_or_class)
|
||||
"#{klass.name.tableize}/#{klass.name.demodulize.underscore}"
|
||||
end
|
||||
|
||||
# The DOM class convention is to use the singular form of an object or class. Examples:
|
||||
#
|
||||
# dom_class(post) # => "post"
|
||||
# dom_class(Person) # => "person"
|
||||
#
|
||||
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_class:
|
||||
#
|
||||
# dom_class(post, :edit) # => "edit_post"
|
||||
# dom_class(Person, :edit) # => "edit_person"
|
||||
def dom_class(record_or_class, prefix = nil)
|
||||
[ prefix, singular_class_name(record_or_class) ].compact * '_'
|
||||
end
|
||||
|
||||
# The DOM class convention is to use the singular form of an object or class with the id following an underscore.
|
||||
# If no id is found, prefix with "new_" instead. Examples:
|
||||
#
|
||||
# dom_class(Post.new(:id => 45)) # => "post_45"
|
||||
# dom_class(Post.new) # => "new_post"
|
||||
#
|
||||
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
|
||||
#
|
||||
# dom_class(Post.new(:id => 45), :edit) # => "edit_post_45"
|
||||
def dom_id(record, prefix = nil)
|
||||
prefix ||= 'new' unless record.id
|
||||
[ prefix, singular_class_name(record), record.id ].compact * '_'
|
||||
end
|
||||
|
||||
# Returns the plural class name of a record or class. Examples:
|
||||
#
|
||||
# plural_class_name(post) # => "posts"
|
||||
# plural_class_name(Highrise::Person) # => "highrise_people"
|
||||
def plural_class_name(record_or_class)
|
||||
singular_class_name(record_or_class).pluralize
|
||||
end
|
||||
|
||||
# Returns the singular class name of a record or class. Examples:
|
||||
#
|
||||
# singular_class_name(post) # => "post"
|
||||
# singular_class_name(Highrise::Person) # => "highrise_person"
|
||||
def singular_class_name(record_or_class)
|
||||
class_from_record_or_class(record_or_class).name.underscore.tr('/', '_')
|
||||
end
|
||||
|
||||
private
|
||||
def class_from_record_or_class(record_or_class)
|
||||
record_or_class.is_a?(Class) ? record_or_class : record_or_class.class
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,27 +1,38 @@
|
|||
require 'tempfile'
|
||||
require 'stringio'
|
||||
require 'strscan'
|
||||
|
||||
module ActionController
|
||||
# Subclassing AbstractRequest makes these methods available to the request objects used in production and testing,
|
||||
# CgiRequest and TestRequest
|
||||
# HTTP methods which are accepted by default.
|
||||
ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options ))
|
||||
|
||||
# CgiRequest and TestRequest provide concrete implementations.
|
||||
class AbstractRequest
|
||||
cattr_accessor :relative_url_root
|
||||
remove_method :relative_url_root
|
||||
|
||||
# Returns the hash of environment variables for this request,
|
||||
# The hash of environment variables for this request,
|
||||
# such as { 'RAILS_ENV' => 'production' }.
|
||||
attr_reader :env
|
||||
|
||||
# Returns both GET and POST parameters in a single hash.
|
||||
def parameters
|
||||
@parameters ||= request_parameters.update(query_parameters).update(path_parameters).with_indifferent_access
|
||||
# The true HTTP request method as a lowercase symbol, such as :get.
|
||||
# UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
|
||||
def request_method
|
||||
@request_method ||= begin
|
||||
method = ((@env['REQUEST_METHOD'] == 'POST' && !parameters[:_method].blank?) ? parameters[:_method].to_s : @env['REQUEST_METHOD']).downcase
|
||||
if ACCEPTED_HTTP_METHODS.include?(method)
|
||||
method.to_sym
|
||||
else
|
||||
raise UnknownHttpMethod, "#{method}, accepted HTTP methods are #{ACCEPTED_HTTP_METHODS.to_a.to_sentence}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# 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).
|
||||
# The HTTP request method as a lowercase symbol, such as :get.
|
||||
# Note, HEAD is returned as :get since the two are functionally
|
||||
# equivalent from the application's perspective.
|
||||
def method
|
||||
@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
|
||||
request_method == :head ? :get : request_method
|
||||
end
|
||||
|
||||
# Is this a GET (or HEAD) request? Equivalent to request.method == :get
|
||||
|
@ -31,63 +42,83 @@ module ActionController
|
|||
|
||||
# Is this a POST request? Equivalent to request.method == :post
|
||||
def post?
|
||||
method == :post
|
||||
request_method == :post
|
||||
end
|
||||
|
||||
# Is this a PUT request? Equivalent to request.method == :put
|
||||
def put?
|
||||
method == :put
|
||||
request_method == :put
|
||||
end
|
||||
|
||||
# Is this a DELETE request? Equivalent to request.method == :delete
|
||||
def delete?
|
||||
method == :delete
|
||||
request_method == :delete
|
||||
end
|
||||
|
||||
# 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.
|
||||
# Is this a HEAD request? request.method sees HEAD as :get, so check the
|
||||
# HTTP method directly.
|
||||
def head?
|
||||
@env['REQUEST_METHOD'].downcase.to_sym == :head
|
||||
request_method == :head
|
||||
end
|
||||
|
||||
# Determine whether the body of a HTTP call is URL-encoded (default)
|
||||
# or matches one of the registered param_parsers.
|
||||
def headers
|
||||
@env
|
||||
end
|
||||
|
||||
def content_length
|
||||
@content_length ||= env['CONTENT_LENGTH'].to_i
|
||||
end
|
||||
|
||||
# The MIME type of the HTTP request, such as Mime::XML.
|
||||
#
|
||||
# For backward compatibility, the post format is extracted from the
|
||||
# X-Post-Data-Format HTTP header if present.
|
||||
def content_type
|
||||
@content_type ||=
|
||||
begin
|
||||
content_type = @env['CONTENT_TYPE'].to_s.downcase
|
||||
|
||||
if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
|
||||
case x_post_format.to_s.downcase
|
||||
when 'yaml'
|
||||
content_type = 'application/x-yaml'
|
||||
when 'xml'
|
||||
content_type = 'application/xml'
|
||||
end
|
||||
end
|
||||
|
||||
Mime::Type.lookup(content_type)
|
||||
end
|
||||
@content_type ||= Mime::Type.lookup(content_type_without_parameters)
|
||||
end
|
||||
|
||||
# Returns the accepted MIME type for the request
|
||||
def accepts
|
||||
@accepts ||=
|
||||
if @env['HTTP_ACCEPT'].to_s.strip.empty?
|
||||
[ content_type, Mime::ALL ]
|
||||
[ content_type, Mime::ALL ].compact # make sure content_type being nil is not included
|
||||
else
|
||||
Mime::Type.parse(@env['HTTP_ACCEPT'])
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the Mime type for the format used in the request. If there is no format available, the first of the
|
||||
# accept types will be used. Examples:
|
||||
#
|
||||
# GET /posts/5.xml | request.format => Mime::XML
|
||||
# GET /posts/5.xhtml | request.format => Mime::HTML
|
||||
# GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers)
|
||||
def format
|
||||
@format ||= parameters[:format] ? Mime::Type.lookup_by_extension(parameters[:format]) : accepts.first
|
||||
end
|
||||
|
||||
|
||||
# Sets the format by string extension, which can be used to force custom formats that are not controlled by the extension.
|
||||
# Example:
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# before_filter :adjust_format_for_iphone
|
||||
#
|
||||
# private
|
||||
# def adjust_format_for_iphone
|
||||
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
|
||||
# end
|
||||
# end
|
||||
def format=(extension)
|
||||
parameters[:format] = extension.to_s
|
||||
format
|
||||
end
|
||||
|
||||
# Returns true if the request's "X-Requested-With" header contains
|
||||
# "XMLHttpRequest". (The Prototype Javascript library sends this header with
|
||||
# every Ajax request.)
|
||||
def xml_http_request?
|
||||
not /XMLHttpRequest/i.match(@env['HTTP_X_REQUESTED_WITH']).nil?
|
||||
!(@env['HTTP_X_REQUESTED_WITH'] !~ /XMLHttpRequest/i)
|
||||
end
|
||||
alias xhr? :xml_http_request?
|
||||
|
||||
|
@ -97,12 +128,17 @@ module ActionController
|
|||
# falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma-
|
||||
# delimited list in the case of multiple chained proxies; the first is
|
||||
# the originating IP.
|
||||
#
|
||||
# Security note: do not use if IP spoofing is a concern for your
|
||||
# application. Since remote_ip checks HTTP headers for addresses forwarded
|
||||
# by proxies, the client may send any IP. remote_addr can't be spoofed but
|
||||
# also doesn't work behind a proxy, since it's always the proxy's IP.
|
||||
def remote_ip
|
||||
return @env['HTTP_CLIENT_IP'] if @env.include? 'HTTP_CLIENT_IP'
|
||||
|
||||
if @env.include? 'HTTP_X_FORWARDED_FOR' then
|
||||
remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',').reject do |ip|
|
||||
ip =~ /^unknown$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
|
||||
ip.strip =~ /^unknown$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
|
||||
end
|
||||
|
||||
return remote_ips.first.strip unless remote_ips.empty?
|
||||
|
@ -111,46 +147,15 @@ module ActionController
|
|||
@env['REMOTE_ADDR']
|
||||
end
|
||||
|
||||
# Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify
|
||||
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
|
||||
def domain(tld_length = 1)
|
||||
return nil if !/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.match(host).nil? or host.nil?
|
||||
|
||||
host.split('.').last(1 + tld_length).join('.')
|
||||
# Returns the lowercase name of the HTTP server software.
|
||||
def server_software
|
||||
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
|
||||
end
|
||||
|
||||
# Returns all the subdomains as an array, so ["dev", "www"] would be returned for "dev.www.rubyonrails.org".
|
||||
# You can specify a different <tt>tld_length</tt>, such as 2 to catch ["www"] instead of ["www", "rubyonrails"]
|
||||
# in "www.rubyonrails.co.uk".
|
||||
def subdomains(tld_length = 1)
|
||||
return [] unless host
|
||||
parts = host.split('.')
|
||||
parts[0..-(tld_length+2)]
|
||||
end
|
||||
|
||||
# Receive the raw post data.
|
||||
# This is useful for services such as REST, XMLRPC and SOAP
|
||||
# which communicate over HTTP POST but don't use the traditional parameter format.
|
||||
def raw_post
|
||||
@env['RAW_POST_DATA']
|
||||
end
|
||||
|
||||
# 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']
|
||||
# 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
|
||||
@env['REQUEST_URI'] = uri
|
||||
end
|
||||
# Returns the complete URL used for this request
|
||||
def url
|
||||
protocol + host_with_port + request_uri
|
||||
end
|
||||
|
||||
# Return 'https://' if this is an SSL request and 'http://' otherwise.
|
||||
|
@ -163,28 +168,14 @@ module ActionController
|
|||
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
||||
end
|
||||
|
||||
# Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
|
||||
def path
|
||||
path = (uri = request_uri) ? uri.split('?').first : ''
|
||||
|
||||
# Cut off the path to the installation directory if given
|
||||
path.sub!(%r/^#{relative_url_root}/, '')
|
||||
path || ''
|
||||
# Returns the host for this request, such as example.com.
|
||||
def host
|
||||
end
|
||||
|
||||
# Returns the path minus the web server relative installation directory.
|
||||
# This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
|
||||
# It can be automatically extracted for Apache setups. If the server is not
|
||||
# Apache, this method returns an empty string.
|
||||
def relative_url_root
|
||||
@@relative_url_root ||= case
|
||||
when @env["RAILS_RELATIVE_URL_ROOT"]
|
||||
@env["RAILS_RELATIVE_URL_ROOT"]
|
||||
when server_software == 'apache'
|
||||
@env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '')
|
||||
else
|
||||
''
|
||||
end
|
||||
|
||||
# Returns a host:port string for this request, such as example.com or
|
||||
# example.com:8080.
|
||||
def host_with_port
|
||||
@host_with_port ||= host + port_string
|
||||
end
|
||||
|
||||
# Returns the port number of this request as an integer.
|
||||
|
@ -206,10 +197,94 @@ module ActionController
|
|||
(port == standard_port) ? '' : ":#{port}"
|
||||
end
|
||||
|
||||
# Returns a host:port string for this request, such as example.com or
|
||||
# example.com:8080.
|
||||
def host_with_port
|
||||
host + port_string
|
||||
# Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify
|
||||
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
|
||||
def domain(tld_length = 1)
|
||||
return nil unless named_host?(host)
|
||||
|
||||
host.split('.').last(1 + tld_length).join('.')
|
||||
end
|
||||
|
||||
# Returns all the subdomains as an array, so ["dev", "www"] would be returned for "dev.www.rubyonrails.org".
|
||||
# You can specify a different <tt>tld_length</tt>, such as 2 to catch ["www"] instead of ["www", "rubyonrails"]
|
||||
# in "www.rubyonrails.co.uk".
|
||||
def subdomains(tld_length = 1)
|
||||
return [] unless named_host?(host)
|
||||
parts = host.split('.')
|
||||
parts[0..-(tld_length+2)]
|
||||
end
|
||||
|
||||
# Return the query string, accounting for server idiosyncracies.
|
||||
def query_string
|
||||
if uri = @env['REQUEST_URI']
|
||||
uri.split('?', 2)[1] || ''
|
||||
else
|
||||
@env['QUERY_STRING'] || ''
|
||||
end
|
||||
end
|
||||
|
||||
# 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']
|
||||
# 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
|
||||
|
||||
if uri.nil?
|
||||
@env.delete('REQUEST_URI')
|
||||
uri
|
||||
else
|
||||
@env['REQUEST_URI'] = uri
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
|
||||
def path
|
||||
path = (uri = request_uri) ? uri.split('?').first.to_s : ''
|
||||
|
||||
# Cut off the path to the installation directory if given
|
||||
path.sub!(%r/^#{relative_url_root}/, '')
|
||||
path || ''
|
||||
end
|
||||
|
||||
# Returns the path minus the web server relative installation directory.
|
||||
# This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
|
||||
# It can be automatically extracted for Apache setups. If the server is not
|
||||
# Apache, this method returns an empty string.
|
||||
def relative_url_root
|
||||
@@relative_url_root ||= case
|
||||
when @env["RAILS_RELATIVE_URL_ROOT"]
|
||||
@env["RAILS_RELATIVE_URL_ROOT"]
|
||||
when server_software == 'apache'
|
||||
@env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '')
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Read the request body. This is useful for web services that need to
|
||||
# work with raw requests directly.
|
||||
def raw_post
|
||||
unless env.include? 'RAW_POST_DATA'
|
||||
env['RAW_POST_DATA'] = body.read(content_length)
|
||||
body.rewind if body.respond_to?(:rewind)
|
||||
end
|
||||
env['RAW_POST_DATA']
|
||||
end
|
||||
|
||||
# Returns both GET and POST parameters in a single hash.
|
||||
def parameters
|
||||
@parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
|
||||
end
|
||||
|
||||
def path_parameters=(parameters) #:nodoc:
|
||||
|
@ -222,33 +297,31 @@ module ActionController
|
|||
@symbolized_path_parameters ||= path_parameters.symbolize_keys
|
||||
end
|
||||
|
||||
# Returns a hash with the parameters used to form the path of the request
|
||||
# Returns a hash with the parameters used to form the path of the request.
|
||||
# Returned hash keys are strings. See <tt>symbolized_path_parameters</tt> for symbolized keys.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# {:action => 'my_action', :controller => 'my_controller'}
|
||||
# {'action' => 'my_action', 'controller' => 'my_controller'}
|
||||
def path_parameters
|
||||
@path_parameters ||= {}
|
||||
end
|
||||
|
||||
# Returns the lowercase name of the HTTP server software.
|
||||
def server_software
|
||||
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
|
||||
end
|
||||
|
||||
#--
|
||||
# Must be implemented in the concrete request
|
||||
#++
|
||||
|
||||
# The request body is an IO input stream.
|
||||
def body
|
||||
end
|
||||
|
||||
def query_parameters #:nodoc:
|
||||
end
|
||||
|
||||
def request_parameters #:nodoc:
|
||||
end
|
||||
|
||||
# Returns the host for this request, such as example.com.
|
||||
def host
|
||||
end
|
||||
|
||||
def cookies #:nodoc:
|
||||
end
|
||||
|
||||
|
@ -261,5 +334,397 @@ module ActionController
|
|||
|
||||
def reset_session #:nodoc:
|
||||
end
|
||||
|
||||
protected
|
||||
# The raw content type string. Use when you need parameters such as
|
||||
# charset or boundary which aren't included in the content_type MIME type.
|
||||
# Overridden by the X-POST_DATA_FORMAT header for backward compatibility.
|
||||
def content_type_with_parameters
|
||||
content_type_from_legacy_post_data_format_header ||
|
||||
env['CONTENT_TYPE'].to_s
|
||||
end
|
||||
|
||||
# The raw content type string with its parameters stripped off.
|
||||
def content_type_without_parameters
|
||||
@content_type_without_parameters ||= self.class.extract_content_type_without_parameters(content_type_with_parameters)
|
||||
end
|
||||
|
||||
private
|
||||
def content_type_from_legacy_post_data_format_header
|
||||
if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
|
||||
case x_post_format.to_s.downcase
|
||||
when 'yaml'; 'application/x-yaml'
|
||||
when 'xml'; 'application/xml'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def parse_formatted_request_parameters
|
||||
return {} if content_length.zero?
|
||||
|
||||
content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters)
|
||||
|
||||
# Don't parse params for unknown requests.
|
||||
return {} if content_type.blank?
|
||||
|
||||
mime_type = Mime::Type.lookup(content_type)
|
||||
strategy = ActionController::Base.param_parsers[mime_type]
|
||||
|
||||
# Only multipart form parsing expects a stream.
|
||||
body = (strategy && strategy != :multipart_form) ? raw_post : self.body
|
||||
|
||||
case strategy
|
||||
when Proc
|
||||
strategy.call(body)
|
||||
when :url_encoded_form
|
||||
self.class.clean_up_ajax_request_body! body
|
||||
self.class.parse_query_parameters(body)
|
||||
when :multipart_form
|
||||
self.class.parse_multipart_form_parameters(body, boundary, content_length, env)
|
||||
when :xml_simple, :xml_node
|
||||
body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
|
||||
when :yaml
|
||||
YAML.load(body)
|
||||
else
|
||||
{}
|
||||
end
|
||||
rescue Exception => e # YAML, XML or Ruby code block errors
|
||||
raise
|
||||
{ "body" => body,
|
||||
"content_type" => content_type_with_parameters,
|
||||
"content_length" => content_length,
|
||||
"exception" => "#{e.message} (#{e.class})",
|
||||
"backtrace" => e.backtrace }
|
||||
end
|
||||
|
||||
def named_host?(host)
|
||||
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
|
||||
end
|
||||
|
||||
class << self
|
||||
def parse_query_parameters(query_string)
|
||||
return {} if query_string.blank?
|
||||
|
||||
pairs = query_string.split('&').collect do |chunk|
|
||||
next if chunk.empty?
|
||||
key, value = chunk.split('=', 2)
|
||||
next if key.empty?
|
||||
value = value.nil? ? nil : CGI.unescape(value)
|
||||
[ CGI.unescape(key), value ]
|
||||
end.compact
|
||||
|
||||
UrlEncodedPairParser.new(pairs).result
|
||||
end
|
||||
|
||||
def parse_request_parameters(params)
|
||||
parser = UrlEncodedPairParser.new
|
||||
|
||||
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
|
||||
|
||||
parser.result
|
||||
end
|
||||
|
||||
def parse_multipart_form_parameters(body, boundary, content_length, env)
|
||||
parse_request_parameters(read_multipart(body, boundary, content_length, env))
|
||||
end
|
||||
|
||||
def extract_multipart_boundary(content_type_with_parameters)
|
||||
if content_type_with_parameters =~ MULTIPART_BOUNDARY
|
||||
['multipart/form-data', $1.dup]
|
||||
else
|
||||
extract_content_type_without_parameters(content_type_with_parameters)
|
||||
end
|
||||
end
|
||||
|
||||
def extract_content_type_without_parameters(content_type_with_parameters)
|
||||
$1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/
|
||||
end
|
||||
|
||||
def clean_up_ajax_request_body!(body)
|
||||
body.chop! if body[-1] == 0
|
||||
body.gsub!(/&_=$/, '')
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def get_typed_value(value)
|
||||
case value
|
||||
when String
|
||||
value
|
||||
when NilClass
|
||||
''
|
||||
when Array
|
||||
value.map { |v| get_typed_value(v) }
|
||||
else
|
||||
if value.is_a?(UploadedFile)
|
||||
# Uploaded file
|
||||
if value.original_filename
|
||||
value
|
||||
# Multipart param
|
||||
else
|
||||
result = value.read
|
||||
value.rewind
|
||||
result
|
||||
end
|
||||
# Unknown value, neither string nor multipart.
|
||||
else
|
||||
raise "Unknown form value: #{value.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
|
||||
|
||||
EOL = "\015\012"
|
||||
|
||||
def read_multipart(body, boundary, content_length, env)
|
||||
params = Hash.new([])
|
||||
boundary = "--" + boundary
|
||||
quoted_boundary = Regexp.quote(boundary, "n")
|
||||
buf = ""
|
||||
bufsize = 10 * 1024
|
||||
boundary_end=""
|
||||
|
||||
# start multipart/form-data
|
||||
body.binmode if defined? body.binmode
|
||||
boundary_size = boundary.size + EOL.size
|
||||
content_length -= boundary_size
|
||||
status = body.read(boundary_size)
|
||||
if nil == status
|
||||
raise EOFError, "no content body"
|
||||
elsif boundary + EOL != status
|
||||
raise EOFError, "bad content body"
|
||||
end
|
||||
|
||||
loop do
|
||||
head = nil
|
||||
content =
|
||||
if 10240 < content_length
|
||||
UploadedTempfile.new("CGI")
|
||||
else
|
||||
UploadedStringIO.new
|
||||
end
|
||||
content.binmode if defined? content.binmode
|
||||
|
||||
until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
|
||||
|
||||
if (not head) and /#{EOL}#{EOL}/n.match(buf)
|
||||
buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
|
||||
head = $1.dup
|
||||
""
|
||||
end
|
||||
next
|
||||
end
|
||||
|
||||
if head and ( (EOL + boundary + EOL).size < buf.size )
|
||||
content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
|
||||
buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
|
||||
end
|
||||
|
||||
c = if bufsize < content_length
|
||||
body.read(bufsize)
|
||||
else
|
||||
body.read(content_length)
|
||||
end
|
||||
if c.nil? || c.empty?
|
||||
raise EOFError, "bad content body"
|
||||
end
|
||||
buf.concat(c)
|
||||
content_length -= c.size
|
||||
end
|
||||
|
||||
buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
|
||||
content.print $1
|
||||
if "--" == $2
|
||||
content_length = -1
|
||||
end
|
||||
boundary_end = $2.dup
|
||||
""
|
||||
end
|
||||
|
||||
content.rewind
|
||||
|
||||
head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni
|
||||
if filename = $1 || $2
|
||||
if /Mac/ni.match(env['HTTP_USER_AGENT']) and
|
||||
/Mozilla/ni.match(env['HTTP_USER_AGENT']) and
|
||||
(not /MSIE/ni.match(env['HTTP_USER_AGENT']))
|
||||
filename = CGI.unescape(filename)
|
||||
end
|
||||
content.original_path = filename.dup
|
||||
end
|
||||
|
||||
head =~ /Content-Type: ([^\r]*)/ni
|
||||
content.content_type = $1.dup if $1
|
||||
|
||||
head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni
|
||||
name = $1.dup if $1
|
||||
|
||||
if params.has_key?(name)
|
||||
params[name].push(content)
|
||||
else
|
||||
params[name] = [content]
|
||||
end
|
||||
break if buf.size == 0
|
||||
break if content_length == -1
|
||||
end
|
||||
raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
|
||||
|
||||
begin
|
||||
body.rewind if body.respond_to?(:rewind)
|
||||
rescue Errno::ESPIPE
|
||||
# Handles exceptions raised by input streams that cannot be rewound
|
||||
# such as when using plain CGI under Apache
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class UrlEncodedPairParser < 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 value
|
||||
end
|
||||
|
||||
def type_conflict!(klass, value)
|
||||
raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value."
|
||||
end
|
||||
end
|
||||
|
||||
module UploadedFile
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
attr_accessor :original_path, :content_type
|
||||
alias_method :local_path, :path
|
||||
end
|
||||
end
|
||||
|
||||
# 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
|
||||
unless defined? @original_filename
|
||||
@original_filename =
|
||||
unless original_path.blank?
|
||||
if original_path =~ /^(?:.*[:\\\/])?(.*)/m
|
||||
$1
|
||||
else
|
||||
File.basename original_path
|
||||
end
|
||||
end
|
||||
end
|
||||
@original_filename
|
||||
end
|
||||
end
|
||||
|
||||
class UploadedStringIO < StringIO
|
||||
include UploadedFile
|
||||
end
|
||||
|
||||
class UploadedTempfile < Tempfile
|
||||
include UploadedFile
|
||||
end
|
||||
end
|
||||
|
|
132
vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb
vendored
Normal file
132
vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb
vendored
Normal file
|
@ -0,0 +1,132 @@
|
|||
module ActionController #:nodoc:
|
||||
class InvalidAuthenticityToken < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
module RequestForgeryProtection
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
class_inheritable_accessor :request_forgery_protection_options
|
||||
self.request_forgery_protection_options = {}
|
||||
helper_method :form_authenticity_token
|
||||
helper_method :protect_against_forgery?
|
||||
end
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Protect a controller's actions from CSRF attacks by ensuring that all forms are coming from the current web application, not
|
||||
# a forged link from another site. This is done by embedding a token based on the session (which an attacker wouldn't know) in
|
||||
# all forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only
|
||||
# HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
|
||||
# scheme there anyway). Also, GET requests are not protected as these should be indempotent anyway.
|
||||
#
|
||||
# You turn this on with the #protect_from_forgery method, which will perform the check and raise
|
||||
# an ActionController::InvalidAuthenticityToken if the token doesn't match what was expected. And it will add
|
||||
# a _authenticity_token parameter to all forms that are automatically generated by Rails. You can customize the error message
|
||||
# given through public/422.html.
|
||||
#
|
||||
# Learn more about CSRF (Cross-Site Request Forgery) attacks:
|
||||
#
|
||||
# * http://isc.sans.org/diary.html?storyid=1750
|
||||
# * http://en.wikipedia.org/wiki/Cross-site_request_forgery
|
||||
#
|
||||
# Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application.
|
||||
# There are a few guidelines you should follow:
|
||||
#
|
||||
# * Keep your GET requests safe and idempotent. More reading material:
|
||||
# * http://www.xml.com/pub/a/2002/04/24/deviant.html
|
||||
# * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
|
||||
# * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look for "Expires: at end of session"
|
||||
#
|
||||
# If you need to construct a request yourself, but still want to take advantage of forgery protection, you can grab the
|
||||
# authenticity_token using the form_authenticity_token helper method and make it part of the parameters yourself.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class FooController < ApplicationController
|
||||
# # uses the cookie session store (then you don't need a separate :secret)
|
||||
# protect_from_forgery :except => :index
|
||||
#
|
||||
# # uses one of the other session stores that uses a session_id value.
|
||||
# protect_from_forgery :secret => 'my-little-pony', :except => :index
|
||||
#
|
||||
# # you can disable csrf protection on controller-by-controller basis:
|
||||
# skip_before_filter :verify_authenticity_token
|
||||
# end
|
||||
#
|
||||
# If you are upgrading from Rails 1.x, disable forgery protection to
|
||||
# simplify your tests. Add this to config/environments/test.rb:
|
||||
#
|
||||
# # Disable request forgery protection in test environment
|
||||
# config.action_controller.allow_forgery_protection = false
|
||||
#
|
||||
# Valid Options:
|
||||
#
|
||||
# * <tt>:only/:except</tt> - passed to the before_filter call. Set which actions are verified.
|
||||
# * <tt>:secret</tt> - Custom salt used to generate the form_authenticity_token.
|
||||
# Leave this off if you are using the cookie session store.
|
||||
# * <tt>:digest</tt> - Message digest used for hashing. Defaults to 'SHA1'
|
||||
def protect_from_forgery(options = {})
|
||||
self.request_forgery_protection_token ||= :authenticity_token
|
||||
before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except)
|
||||
request_forgery_protection_options.update(options)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
# The actual before_filter that is used. Modify this to change how you handle unverified requests.
|
||||
def verify_authenticity_token
|
||||
verified_request? || raise(ActionController::InvalidAuthenticityToken)
|
||||
end
|
||||
|
||||
# Returns true or false if a request is verified. Checks:
|
||||
#
|
||||
# * is the format restricted? By default, only HTML and AJAX requests are checked.
|
||||
# * is it a GET request? Gets should be safe and idempotent
|
||||
# * Does the form_authenticity_token match the given _token value from the params?
|
||||
def verified_request?
|
||||
!protect_against_forgery? ||
|
||||
request.method == :get ||
|
||||
!verifiable_request_format? ||
|
||||
form_authenticity_token == params[request_forgery_protection_token]
|
||||
end
|
||||
|
||||
def verifiable_request_format?
|
||||
request.format.html? || request.format.js?
|
||||
end
|
||||
|
||||
# Sets the token value for the current session. Pass a :secret option in #protect_from_forgery to add a custom salt to the hash.
|
||||
def form_authenticity_token
|
||||
@form_authenticity_token ||= if request_forgery_protection_options[:secret]
|
||||
authenticity_token_from_session_id
|
||||
elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
|
||||
authenticity_token_from_cookie_session
|
||||
elsif session.nil?
|
||||
raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session."
|
||||
else
|
||||
raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store)."
|
||||
end
|
||||
end
|
||||
|
||||
# Generates a unique digest using the session_id and the CSRF secret.
|
||||
def authenticity_token_from_session_id
|
||||
key = if request_forgery_protection_options[:secret].respond_to?(:call)
|
||||
request_forgery_protection_options[:secret].call(@session)
|
||||
else
|
||||
request_forgery_protection_options[:secret]
|
||||
end
|
||||
digest = request_forgery_protection_options[:digest] ||= 'SHA1'
|
||||
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key.to_s, session.session_id.to_s)
|
||||
end
|
||||
|
||||
# No secret was given, so assume this is a cookie session store.
|
||||
def authenticity_token_from_cookie_session
|
||||
session[:csrf_id] ||= CGI::Session.generate_unique_id
|
||||
session.dbman.generate_digest(session[:csrf_id])
|
||||
end
|
||||
|
||||
def protect_against_forgery?
|
||||
allow_forgery_protection && request_forgery_protection_token
|
||||
end
|
||||
end
|
||||
end
|
138
vendor/rails/actionpack/lib/action_controller/request_profiler.rb
vendored
Executable file
138
vendor/rails/actionpack/lib/action_controller/request_profiler.rb
vendored
Executable file
|
@ -0,0 +1,138 @@
|
|||
require 'optparse'
|
||||
require 'action_controller/integration'
|
||||
|
||||
module ActionController
|
||||
class RequestProfiler
|
||||
# Wrap up the integration session runner.
|
||||
class Sandbox
|
||||
include Integration::Runner
|
||||
|
||||
def self.benchmark(n, script)
|
||||
new(script).benchmark(n)
|
||||
end
|
||||
|
||||
def initialize(script_path)
|
||||
@quiet = false
|
||||
define_run_method(File.read(script_path))
|
||||
reset!
|
||||
end
|
||||
|
||||
def benchmark(n)
|
||||
@quiet = true
|
||||
print ' '
|
||||
result = Benchmark.realtime do
|
||||
n.times do |i|
|
||||
run
|
||||
print i % 10 == 0 ? 'x' : '.'
|
||||
$stdout.flush
|
||||
end
|
||||
end
|
||||
puts
|
||||
result
|
||||
ensure
|
||||
@quiet = false
|
||||
end
|
||||
|
||||
def say(message)
|
||||
puts " #{message}" unless @quiet
|
||||
end
|
||||
|
||||
private
|
||||
def define_run_method(script)
|
||||
instance_eval "def run; #{script}; end", __FILE__, __LINE__
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options = {})
|
||||
@options = default_options.merge(options)
|
||||
end
|
||||
|
||||
|
||||
def self.run(args = nil, options = {})
|
||||
profiler = new(options)
|
||||
profiler.parse_options(args) if args
|
||||
profiler.run
|
||||
end
|
||||
|
||||
def run
|
||||
sandbox = Sandbox.new(options[:script])
|
||||
|
||||
puts 'Warming up once'
|
||||
|
||||
elapsed = warmup(sandbox)
|
||||
puts '%.2f sec, %d requests, %d req/sec' % [elapsed, sandbox.request_count, sandbox.request_count / elapsed]
|
||||
puts "\n#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x"
|
||||
|
||||
options[:benchmark] ? benchmark(sandbox) : profile(sandbox)
|
||||
end
|
||||
|
||||
def profile(sandbox)
|
||||
load_ruby_prof
|
||||
|
||||
results = RubyProf.profile { benchmark(sandbox) }
|
||||
|
||||
show_profile_results results
|
||||
results
|
||||
end
|
||||
|
||||
def benchmark(sandbox)
|
||||
sandbox.request_count = 0
|
||||
elapsed = sandbox.benchmark(options[:n]).to_f
|
||||
count = sandbox.request_count.to_i
|
||||
puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed]
|
||||
end
|
||||
|
||||
def warmup(sandbox)
|
||||
Benchmark.realtime { sandbox.run }
|
||||
end
|
||||
|
||||
def default_options
|
||||
{ :n => 100, :open => 'open %s &' }
|
||||
end
|
||||
|
||||
# Parse command-line options
|
||||
def parse_options(args)
|
||||
OptionParser.new do |opt|
|
||||
opt.banner = "USAGE: #{$0} [options] [session script path]"
|
||||
|
||||
opt.on('-n', '--times [0000]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i }
|
||||
opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v }
|
||||
opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v }
|
||||
opt.on('-h', '--help', 'Show this help') { puts opt; exit }
|
||||
|
||||
opt.parse args
|
||||
|
||||
if args.empty?
|
||||
puts opt
|
||||
exit
|
||||
end
|
||||
options[:script] = args.pop
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def load_ruby_prof
|
||||
begin
|
||||
require 'ruby-prof'
|
||||
#RubyProf.measure_mode = RubyProf::ALLOCATED_OBJECTS
|
||||
rescue LoadError
|
||||
abort '`gem install ruby-prof` to use the profiler'
|
||||
end
|
||||
end
|
||||
|
||||
def show_profile_results(results)
|
||||
File.open "#{RAILS_ROOT}/tmp/profile-graph.html", 'w' do |file|
|
||||
RubyProf::GraphHtmlPrinter.new(results).print(file)
|
||||
`#{options[:open] % file.path}` if options[:open]
|
||||
end
|
||||
|
||||
File.open "#{RAILS_ROOT}/tmp/profile-flat.txt", 'w' do |file|
|
||||
RubyProf::FlatPrinter.new(results).print(file)
|
||||
`#{options[:open] % file.path}` if options[:open]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,22 +1,110 @@
|
|||
module ActionController #:nodoc:
|
||||
# Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
|
||||
# Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
|
||||
# (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view
|
||||
# is already implemented by the Action Controller, but the public view should be tailored to your specific application. So too
|
||||
# could the decision on whether something is a public or a developer request.
|
||||
# is already implemented by the Action Controller, but the public view should be tailored to your specific application.
|
||||
#
|
||||
# The default behavior for public exceptions is to render a static html file with the name of the error code thrown. If no such
|
||||
# file exists, an empty response is sent with the correct status code.
|
||||
#
|
||||
# You can tailor the rescuing behavior and appearance by overwriting the following two stub methods.
|
||||
# You can override what constitutes a local request by overriding the <tt>local_request?</tt> method in your own controller.
|
||||
# Custom rescue behavior is achieved by overriding the <tt>rescue_action_in_public</tt> and <tt>rescue_action_locally</tt> methods.
|
||||
module Rescue
|
||||
LOCALHOST = '127.0.0.1'.freeze
|
||||
|
||||
DEFAULT_RESCUE_RESPONSE = :internal_server_error
|
||||
DEFAULT_RESCUE_RESPONSES = {
|
||||
'ActionController::RoutingError' => :not_found,
|
||||
'ActionController::UnknownAction' => :not_found,
|
||||
'ActiveRecord::RecordNotFound' => :not_found,
|
||||
'ActiveRecord::StaleObjectError' => :conflict,
|
||||
'ActiveRecord::RecordInvalid' => :unprocessable_entity,
|
||||
'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
|
||||
'ActionController::MethodNotAllowed' => :method_not_allowed,
|
||||
'ActionController::NotImplemented' => :not_implemented,
|
||||
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
|
||||
}
|
||||
|
||||
DEFAULT_RESCUE_TEMPLATE = 'diagnostics'
|
||||
DEFAULT_RESCUE_TEMPLATES = {
|
||||
'ActionController::MissingTemplate' => 'missing_template',
|
||||
'ActionController::RoutingError' => 'routing_error',
|
||||
'ActionController::UnknownAction' => 'unknown_action',
|
||||
'ActionView::TemplateError' => 'template_error'
|
||||
}
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
base.cattr_accessor :rescue_responses
|
||||
base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
|
||||
base.rescue_responses.update DEFAULT_RESCUE_RESPONSES
|
||||
|
||||
base.cattr_accessor :rescue_templates
|
||||
base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE)
|
||||
base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES
|
||||
|
||||
base.class_inheritable_array :rescue_handlers
|
||||
base.rescue_handlers = []
|
||||
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
alias_method_chain :perform_action, :rescue
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods #:nodoc:
|
||||
def process_with_exception(request, response, exception)
|
||||
module ClassMethods
|
||||
def process_with_exception(request, response, exception) #:nodoc:
|
||||
new.process(request, response, :rescue_action, exception)
|
||||
end
|
||||
|
||||
# Rescue exceptions raised in controller actions.
|
||||
#
|
||||
# <tt>rescue_from</tt> receives a series of exception classes or class
|
||||
# names, and a trailing :with option with the name of a method or a Proc
|
||||
# object to be called to handle them. Alternatively a block can be given.
|
||||
#
|
||||
# Handlers that take one argument will be called with the exception, so
|
||||
# that the exception can be inspected when dealing with it.
|
||||
#
|
||||
# Handlers are inherited. They are searched from right to left, from
|
||||
# bottom to top, and up the hierarchy. The handler of the first class for
|
||||
# which exception.is_a?(klass) holds true is the one invoked, if any.
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
|
||||
# rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
|
||||
#
|
||||
# rescue_from 'MyAppError::Base' do |exception|
|
||||
# render :xml => exception, :status => 500
|
||||
# end
|
||||
#
|
||||
# protected
|
||||
# def deny_access
|
||||
# ...
|
||||
# end
|
||||
#
|
||||
# def show_errors(exception)
|
||||
# exception.record.new_record? ? ...
|
||||
# end
|
||||
# end
|
||||
def rescue_from(*klasses, &block)
|
||||
options = klasses.extract_options!
|
||||
unless options.has_key?(:with)
|
||||
block_given? ? options[:with] = block : raise(ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument.")
|
||||
end
|
||||
|
||||
klasses.each do |klass|
|
||||
key = if klass.is_a?(Class) && klass <= Exception
|
||||
klass.name
|
||||
elsif klass.is_a?(String)
|
||||
klass
|
||||
else
|
||||
raise(ArgumentError, "#{klass} is neither an Exception nor a String")
|
||||
end
|
||||
|
||||
# Order is important, we put the pair at the end. When dealing with an
|
||||
# exception we will follow the documented order going from right to left.
|
||||
rescue_handlers << [key, options[:with]]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -25,6 +113,12 @@ module ActionController #:nodoc:
|
|||
log_error(exception) if logger
|
||||
erase_results if performed?
|
||||
|
||||
# Let the exception alter the response if it wants.
|
||||
# For example, MethodNotAllowed sets the Allow header.
|
||||
if exception.respond_to?(:handle_response!)
|
||||
exception.handle_response!(response)
|
||||
end
|
||||
|
||||
if consider_all_requests_local || local_request?
|
||||
rescue_action_locally(exception)
|
||||
else
|
||||
|
@ -47,96 +141,118 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>).
|
||||
# Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
|
||||
# default will call render_optional_error_file. Override this method to provide more user friendly error messages.s
|
||||
def rescue_action_in_public(exception) #:doc:
|
||||
case exception
|
||||
when RoutingError, UnknownAction
|
||||
render_text(IO.read(File.join(RAILS_ROOT, 'public', '404.html')), "404 Not Found")
|
||||
else
|
||||
render_text(IO.read(File.join(RAILS_ROOT, 'public', '500.html')), "500 Internal Error")
|
||||
render_optional_error_file response_code_for_rescue(exception)
|
||||
end
|
||||
|
||||
# Attempts to render a static error page based on the <tt>status_code</tt> thrown,
|
||||
# or just return headers if no such file exists. For example, if a 500 error is
|
||||
# being handled Rails will first attempt to render the file at <tt>public/500.html</tt>.
|
||||
# If the file doesn't exist, the body of the response will be left empty.
|
||||
def render_optional_error_file(status_code)
|
||||
status = interpret_status(status_code)
|
||||
path = "#{RAILS_ROOT}/public/#{status[0,3]}.html"
|
||||
if File.exist?(path)
|
||||
render :file => path, :status => status
|
||||
else
|
||||
head status
|
||||
end
|
||||
end
|
||||
|
||||
# Overwrite to expand the meaning of a local request in order to show local rescues on other occurrences than
|
||||
# the remote IP being 127.0.0.1. For example, this could include the IP of the developer machine when debugging
|
||||
# remotely.
|
||||
# True if the request came from localhost, 127.0.0.1. Override this
|
||||
# method if you wish to redefine the meaning of a local request to
|
||||
# include remote IP addresses or other criteria.
|
||||
def local_request? #:doc:
|
||||
[request.remote_addr, request.remote_ip] == ["127.0.0.1"] * 2
|
||||
request.remote_addr == LOCALHOST and request.remote_ip == LOCALHOST
|
||||
end
|
||||
|
||||
# Renders a detailed diagnostics screen on action exceptions.
|
||||
# Render detailed diagnostics for unhandled exceptions rescued from
|
||||
# a controller action.
|
||||
def rescue_action_locally(exception)
|
||||
add_variables_to_assigns
|
||||
@template.instance_variable_set("@exception", exception)
|
||||
@template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
|
||||
@template.send(:assign_variables_from_controller)
|
||||
@template.instance_variable_set("@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))
|
||||
|
||||
|
||||
response.content_type = Mime::HTML
|
||||
render_file(rescues_path("layout"), response_code_for_rescue(exception))
|
||||
render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
|
||||
end
|
||||
|
||||
|
||||
# Tries to rescue the exception by looking up and calling a registered handler.
|
||||
def rescue_action_with_handler(exception)
|
||||
if handler = handler_for_rescue(exception)
|
||||
if handler.arity != 0
|
||||
handler.call(exception)
|
||||
else
|
||||
handler.call
|
||||
end
|
||||
true # don't rely on the return value of the handler
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def perform_action_with_rescue #:nodoc:
|
||||
begin
|
||||
perform_action_without_rescue
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
callstack = caller
|
||||
callstack.slice!(0) if callstack.first["rescue.rb"]
|
||||
file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
|
||||
|
||||
message = "Exception at #{file}:#{line}#{" in `#{method}'" if method}." # `´ ( for ruby-mode)
|
||||
|
||||
Breakpoint.handle_breakpoint(context, message, file, line)
|
||||
end
|
||||
end)
|
||||
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
rescue_action(exception)
|
||||
end
|
||||
perform_action_without_rescue
|
||||
rescue Exception => exception # errors from action performed
|
||||
return if rescue_action_with_handler(exception)
|
||||
|
||||
rescue_action(exception)
|
||||
end
|
||||
|
||||
def rescues_path(template_name)
|
||||
File.dirname(__FILE__) + "/templates/rescues/#{template_name}.rhtml"
|
||||
"#{File.dirname(__FILE__)}/templates/rescues/#{template_name}.erb"
|
||||
end
|
||||
|
||||
def template_path_for_local_rescue(exception)
|
||||
rescues_path(
|
||||
case exception
|
||||
when MissingTemplate then "missing_template"
|
||||
when RoutingError then "routing_error"
|
||||
when UnknownAction then "unknown_action"
|
||||
when ActionView::TemplateError then "template_error"
|
||||
else "diagnostics"
|
||||
end
|
||||
)
|
||||
rescues_path(rescue_templates[exception.class.name])
|
||||
end
|
||||
|
||||
|
||||
def response_code_for_rescue(exception)
|
||||
case exception
|
||||
when UnknownAction, RoutingError
|
||||
"404 Page Not Found"
|
||||
else
|
||||
"500 Internal Error"
|
||||
rescue_responses[exception.class.name]
|
||||
end
|
||||
|
||||
def handler_for_rescue(exception)
|
||||
# We go from right to left because pairs are pushed onto rescue_handlers
|
||||
# as rescue_from declarations are found.
|
||||
_, handler = *rescue_handlers.reverse.detect do |klass_name, handler|
|
||||
# The purpose of allowing strings in rescue_from is to support the
|
||||
# declaration of handler associations for exception classes whose
|
||||
# definition is yet unknown.
|
||||
#
|
||||
# Since this loop needs the constants it would be inconsistent to
|
||||
# assume they should exist at this point. An early raised exception
|
||||
# could trigger some other handler and the array could include
|
||||
# precisely a string whose corresponding constant has not yet been
|
||||
# seen. This is why we are tolerant to unknown constants.
|
||||
#
|
||||
# Note that this tolerance only matters if the exception was given as
|
||||
# a string, otherwise a NameError will be raised by the interpreter
|
||||
# itself when rescue_from CONSTANT is executed.
|
||||
klass = self.class.const_get(klass_name) rescue nil
|
||||
klass ||= klass_name.constantize rescue nil
|
||||
exception.is_a?(klass) if klass
|
||||
end
|
||||
|
||||
case handler
|
||||
when Symbol
|
||||
method(handler)
|
||||
when Proc
|
||||
handler.bind(self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def clean_backtrace(exception)
|
||||
exception.backtrace.collect { |line| Object.const_defined?(:RAILS_ROOT) ? line.gsub(RAILS_ROOT, "") : line }
|
||||
if backtrace = exception.backtrace
|
||||
if defined?(RAILS_ROOT)
|
||||
backtrace.map { |line| line.sub RAILS_ROOT, '' }
|
||||
else
|
||||
backtrace
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,14 +1,56 @@
|
|||
module ActionController
|
||||
# == Overview
|
||||
#
|
||||
# ActionController::Resources are a way of defining RESTful resources. A RESTful resource, in basic terms,
|
||||
# is something that can be pointed at and it will respond with a representation of the data requested.
|
||||
# In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
|
||||
# requests XML data.
|
||||
#
|
||||
# RESTful design is based on the assumption that there are four generic verbs that a user of an
|
||||
# application can request from a resource (the noun).
|
||||
#
|
||||
# Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
|
||||
# denotes the type of action that should take place.
|
||||
#
|
||||
# === The Different Methods and their Usage
|
||||
#
|
||||
# +GET+ Requests for a resource, no saving or editing of a resource should occur in a GET request
|
||||
# +POST+ Creation of resources
|
||||
# +PUT+ Editing of attributes on a resource
|
||||
# +DELETE+ Deletion of a resource
|
||||
#
|
||||
# === Examples
|
||||
#
|
||||
# # A GET request on the Posts resource is asking for all Posts
|
||||
# GET /posts
|
||||
#
|
||||
# # A GET request on a single Post resource is asking for that particular Post
|
||||
# GET /posts/1
|
||||
#
|
||||
# # A POST request on the Posts resource is asking for a Post to be created with the supplied details
|
||||
# POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
|
||||
#
|
||||
# # A PUT request on a single Post resource is asking for a Post to be updated
|
||||
# PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
|
||||
#
|
||||
# # A DELETE request on a single Post resource is asking for it to be deleted
|
||||
# DELETE /posts # with => { :id => 1 }
|
||||
#
|
||||
# By using the REST convention, users of our application can assume certain things about how the data
|
||||
# is requested and how it is returned. Rails simplifies the routing part of RESTful design by
|
||||
# supplying you with methods to create them in your routes.rb file.
|
||||
#
|
||||
# Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
|
||||
module Resources
|
||||
class Resource #:nodoc:
|
||||
attr_reader :collection_methods, :member_methods, :new_methods
|
||||
attr_reader :path_prefix, :new_name_prefix
|
||||
attr_reader :path_prefix, :name_prefix
|
||||
attr_reader :plural, :singular
|
||||
attr_reader :options
|
||||
|
||||
def initialize(entities, options)
|
||||
@plural = entities
|
||||
@singular = options[:singular] || plural.to_s.singularize
|
||||
@plural ||= entities
|
||||
@singular ||= options[:singular] || plural.to_s.singularize
|
||||
|
||||
@options = options
|
||||
|
||||
|
@ -18,7 +60,18 @@ module ActionController
|
|||
end
|
||||
|
||||
def controller
|
||||
@controller ||= (options[:controller] || plural).to_s
|
||||
@controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
|
||||
end
|
||||
|
||||
def requirements(with_id = false)
|
||||
@requirements ||= @options[:requirements] || {}
|
||||
@id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
|
||||
|
||||
with_id ? @requirements.merge(@id_requirement) : @requirements
|
||||
end
|
||||
|
||||
def conditions
|
||||
@conditions = @options[:conditions] || {}
|
||||
end
|
||||
|
||||
def path
|
||||
|
@ -37,26 +90,18 @@ module ActionController
|
|||
@nesting_path_prefix ||= "#{path}/:#{singular}_id"
|
||||
end
|
||||
|
||||
def deprecate_name_prefix?
|
||||
@name_prefix.blank? && !@new_name_prefix.blank?
|
||||
end
|
||||
|
||||
def name_prefix
|
||||
deprecate_name_prefix? ? @new_name_prefix : @name_prefix
|
||||
end
|
||||
|
||||
def old_name_prefix
|
||||
@name_prefix
|
||||
end
|
||||
|
||||
def nesting_name_prefix
|
||||
"#{new_name_prefix}#{singular}_"
|
||||
"#{name_prefix}#{singular}_"
|
||||
end
|
||||
|
||||
def action_separator
|
||||
@action_separator ||= Base.resource_action_separator
|
||||
end
|
||||
|
||||
def uncountable?
|
||||
@singular.to_s == @plural.to_s
|
||||
end
|
||||
|
||||
protected
|
||||
def arrange_actions
|
||||
@collection_methods = arrange_actions_by_methods(options.delete(:collection))
|
||||
|
@ -72,7 +117,6 @@ module ActionController
|
|||
def set_prefixes
|
||||
@path_prefix = options.delete(:path_prefix)
|
||||
@name_prefix = options.delete(:name_prefix)
|
||||
@new_name_prefix = options.delete(:new_name_prefix)
|
||||
end
|
||||
|
||||
def arrange_actions_by_methods(actions)
|
||||
|
@ -89,103 +133,111 @@ module ActionController
|
|||
|
||||
class SingletonResource < Resource #:nodoc:
|
||||
def initialize(entity, options)
|
||||
@plural = @singular = entity
|
||||
@options = options
|
||||
arrange_actions
|
||||
add_default_actions
|
||||
set_prefixes
|
||||
@singular = @plural = entity
|
||||
options[:controller] ||= @singular.to_s.pluralize
|
||||
super
|
||||
end
|
||||
|
||||
alias_method :member_path, :path
|
||||
alias_method :nesting_path_prefix, :path
|
||||
end
|
||||
|
||||
# Creates named routes for implementing verb-oriented controllers. This is
|
||||
# useful for implementing REST API's, where a single resource has different
|
||||
# behavior based on the HTTP verb (method) used to access it.
|
||||
#
|
||||
# Example:
|
||||
# Creates named routes for implementing verb-oriented controllers
|
||||
# for a collection resource.
|
||||
#
|
||||
# map.resources :messages
|
||||
# For example:
|
||||
#
|
||||
# map.resources :messages
|
||||
#
|
||||
# will map the following actions in the corresponding controller:
|
||||
#
|
||||
# class MessagesController < ActionController::Base
|
||||
# # GET messages_url
|
||||
# def index
|
||||
# # return all messages
|
||||
# end
|
||||
#
|
||||
#
|
||||
# # GET new_message_url
|
||||
# def new
|
||||
# # return an HTML form for describing a new message
|
||||
# end
|
||||
#
|
||||
#
|
||||
# # POST messages_url
|
||||
# def create
|
||||
# # create a new message
|
||||
# end
|
||||
#
|
||||
#
|
||||
# # GET message_url(:id => 1)
|
||||
# def show
|
||||
# # find and return a specific message
|
||||
# end
|
||||
#
|
||||
#
|
||||
# # GET edit_message_url(:id => 1)
|
||||
# def edit
|
||||
# # return an HTML form for editing a specific message
|
||||
# end
|
||||
#
|
||||
#
|
||||
# # PUT message_url(:id => 1)
|
||||
# def update
|
||||
# # find and update a specific message
|
||||
# end
|
||||
#
|
||||
#
|
||||
# # DELETE message_url(:id => 1)
|
||||
# def destroy
|
||||
# # delete a specific message
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The #resources method sets HTTP method restrictions on the routes it generates. For example, making an
|
||||
# HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
|
||||
# <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for resource routes.
|
||||
#
|
||||
#
|
||||
# Along with the routes themselves, #resources generates named routes for use in
|
||||
# controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers:
|
||||
#
|
||||
#
|
||||
# Named Route Helpers
|
||||
# messages messages_url, hash_for_messages_url,
|
||||
# ============ =====================================================
|
||||
# messages messages_url, hash_for_messages_url,
|
||||
# messages_path, hash_for_messages_path
|
||||
# message message_url(id), hash_for_message_url(id),
|
||||
#
|
||||
# message message_url(id), hash_for_message_url(id),
|
||||
# message_path(id), hash_for_message_path(id)
|
||||
# new_message new_message_url, hash_for_new_message_url,
|
||||
#
|
||||
# new_message new_message_url, hash_for_new_message_url,
|
||||
# new_message_path, hash_for_new_message_path
|
||||
#
|
||||
# edit_message edit_message_url(id), hash_for_edit_message_url(id),
|
||||
# edit_message_path(id), hash_for_edit_message_path(id)
|
||||
#
|
||||
# You can use these helpers instead of #url_for or methods that take #url_for parameters:
|
||||
#
|
||||
# redirect_to :controller => 'messages', :action => 'index'
|
||||
# # becomes
|
||||
# redirect_to messages_url
|
||||
# You can use these helpers instead of #url_for or methods that take #url_for parameters. For example:
|
||||
#
|
||||
# redirect_to :controller => 'messages', :action => 'index'
|
||||
# # and
|
||||
# <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
|
||||
# # becomes
|
||||
#
|
||||
# now become:
|
||||
#
|
||||
# redirect_to messages_url
|
||||
# # and
|
||||
# <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
|
||||
#
|
||||
# Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
|
||||
# form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object:
|
||||
#
|
||||
# <%= form_tag message_path(@message), :method => :put %>
|
||||
#
|
||||
# or
|
||||
#
|
||||
#
|
||||
# or
|
||||
#
|
||||
# <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
|
||||
#
|
||||
# The #resources method accepts various options, too, to customize the resulting
|
||||
# routes:
|
||||
# * <tt>:controller</tt> -- specify the controller name for the routes.
|
||||
# * <tt>:singular</tt> -- specify the singular name used in the member routes.
|
||||
# * <tt>:path_prefix</tt> -- set a prefix to the routes with required route variables.
|
||||
#
|
||||
# The #resources method accepts the following options to customize the resulting routes:
|
||||
# * <tt>:collection</tt> - add named routes for other actions that operate on the collection.
|
||||
# Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>
|
||||
# or <tt>:any</tt> if the method does not matter. These routes map to a URL like /messages/rss, with a route of rss_messages_url.
|
||||
# * <tt>:member</tt> - same as :collection, but for actions that operate on a specific member.
|
||||
# * <tt>:new</tt> - same as :collection, but for actions that operate on the new resource action.
|
||||
# * <tt>:controller</tt> - specify the controller name for the routes.
|
||||
# * <tt>:singular</tt> - specify the singular name used in the member routes.
|
||||
# * <tt>:requirements</tt> - set custom routing parameter requirements.
|
||||
# * <tt>:conditions</tt> - specify custom routing recognition conditions. Resources sets the :method value for the method-specific routes.
|
||||
# * <tt>:path_prefix</tt> - set a prefix to the routes with required route variables.
|
||||
#
|
||||
# Weblog comments usually belong to a post, so you might use resources like:
|
||||
#
|
||||
# map.resources :articles
|
||||
|
@ -198,85 +250,102 @@ module ActionController
|
|||
# end
|
||||
#
|
||||
# The comment resources work the same, but must now include a value for :article_id.
|
||||
#
|
||||
#
|
||||
# article_comments_url(@article)
|
||||
# article_comment_url(@article, @comment)
|
||||
#
|
||||
# article_comments_url(:article_id => @article)
|
||||
# article_comment_url(:article_id => @article, :id => @comment)
|
||||
#
|
||||
# * <tt>:name_prefix</tt> -- define a prefix for all generated routes, usually ending in an underscore.
|
||||
# * <tt>:name_prefix</tt> - define a prefix for all generated routes, usually ending in an underscore.
|
||||
# Use this if you have named routes that may clash.
|
||||
#
|
||||
# map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
|
||||
# map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
|
||||
#
|
||||
# * <tt>:collection</tt> -- add named routes for other actions that operate on the collection.
|
||||
# Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>
|
||||
# or <tt>:any</tt> if the method does not matter. These routes map to a URL like /messages/rss, with a route of rss_messages_url.
|
||||
# * <tt>:member</tt> -- same as :collection, but for actions that operate on a specific member.
|
||||
# * <tt>:new</tt> -- same as :collection, but for actions that operate on the new resource action.
|
||||
# You may also use :name_prefix to override the generic named routes in a nested resource:
|
||||
#
|
||||
# map.resources :articles do |article|
|
||||
# article.resources :comments, :name_prefix => nil
|
||||
# end
|
||||
#
|
||||
# This will yield named resources like so:
|
||||
#
|
||||
# comments_url(@article)
|
||||
# comment_url(@article, @comment)
|
||||
#
|
||||
# If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
#
|
||||
# map.resources :messages, :path_prefix => "/thread/:thread_id"
|
||||
# # --> GET /thread/7/messages/1
|
||||
#
|
||||
#
|
||||
# map.resources :messages, :collection => { :rss => :get }
|
||||
# # --> GET /messages/rss (maps to the #rss action)
|
||||
# # also adds a named route called "rss_messages"
|
||||
#
|
||||
#
|
||||
# map.resources :messages, :member => { :mark => :post }
|
||||
# # --> POST /messages/1/mark (maps to the #mark action)
|
||||
# # also adds a named route called "mark_message"
|
||||
#
|
||||
#
|
||||
# map.resources :messages, :new => { :preview => :post }
|
||||
# # --> POST /messages/new/preview (maps to the #preview action)
|
||||
# # also adds a named route called "preview_new_message"
|
||||
#
|
||||
#
|
||||
# map.resources :messages, :new => { :new => :any, :preview => :post }
|
||||
# # --> POST /messages/new/preview (maps to the #preview action)
|
||||
# # also adds a named route called "preview_new_message"
|
||||
# # --> /messages/new can be invoked via any request method
|
||||
#
|
||||
#
|
||||
# map.resources :messages, :controller => "categories",
|
||||
# :path_prefix => "/category/:category_id",
|
||||
# :name_prefix => "category_"
|
||||
# # --> GET /categories/7/messages/1
|
||||
# # has named route "category_message"
|
||||
#
|
||||
# The #resources method sets HTTP method restrictions on the routes it generates. For example, making an
|
||||
# HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
|
||||
# <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for resource routes.
|
||||
def resources(*entities, &block)
|
||||
options = entities.last.is_a?(Hash) ? entities.pop : { }
|
||||
entities.each { |entity| map_resource entity, options.dup, &block }
|
||||
options = entities.extract_options!
|
||||
entities.each { |entity| map_resource(entity, options.dup, &block) }
|
||||
end
|
||||
|
||||
# Creates named routes for implementing verb-oriented controllers for a singleton resource.
|
||||
# A singleton resource is global to the current user visiting the application, such as a user's
|
||||
# /account profile.
|
||||
#
|
||||
# Creates named routes for implementing verb-oriented controllers for a singleton resource.
|
||||
# A singleton resource is global to its current context. For unnested singleton resources,
|
||||
# the resource is global to the current user visiting the application, such as a user's
|
||||
# /account profile. For nested singleton resources, the resource is global to its parent
|
||||
# resource, such as a <tt>projects</tt> resource that <tt>has_one :project_manager</tt>.
|
||||
# The <tt>project_manager</tt> should be mapped as a singleton resource under <tt>projects</tt>:
|
||||
#
|
||||
# map.resources :projects do |project|
|
||||
# project.resource :project_manager
|
||||
# end
|
||||
#
|
||||
# See map.resources for general conventions. These are the main differences:
|
||||
# - A singular name is given to map.resource. The default controller name is taken from the singular name.
|
||||
# - There is no <tt>:collection</tt> option as there is only the singleton resource.
|
||||
# - There is no <tt>:singular</tt> option as the singular name is passed to map.resource.
|
||||
# - No default index route is created for the singleton resource controller.
|
||||
# - When nesting singleton resources, only the singular name is used as the path prefix (example: 'account/messages/1')
|
||||
# * A singular name is given to map.resource. The default controller name is still taken from the plural name.
|
||||
# * To specify a custom plural name, use the :plural option. There is no :singular option.
|
||||
# * No default index route is created for the singleton resource controller.
|
||||
# * When nesting singleton resources, only the singular name is used as the path prefix (example: 'account/messages/1')
|
||||
#
|
||||
# Example:
|
||||
# For example:
|
||||
#
|
||||
# map.resource :account
|
||||
# map.resource :account
|
||||
#
|
||||
# class AccountController < ActionController::Base
|
||||
# # POST account_url
|
||||
# def create
|
||||
# # create an account
|
||||
# end
|
||||
# maps these actions in the Accounts controller:
|
||||
#
|
||||
# class AccountsController < ActionController::Base
|
||||
# # GET new_account_url
|
||||
# def new
|
||||
# # return an HTML form for describing the new account
|
||||
# end
|
||||
#
|
||||
# # POST account_url
|
||||
# def create
|
||||
# # create an account
|
||||
# end
|
||||
#
|
||||
# # GET account_url
|
||||
# def show
|
||||
# # find and return the account
|
||||
|
@ -298,17 +367,23 @@ module ActionController
|
|||
# end
|
||||
# end
|
||||
#
|
||||
# Along with the routes themselves, #resource generates named routes for use in
|
||||
# controllers and views. <tt>map.resource :account</tt> produces the following named routes and helpers:
|
||||
#
|
||||
# Along with the routes themselves, #resource generates named routes for
|
||||
# use in controllers and views. <tt>map.resource :account</tt> produces
|
||||
# these named routes and helpers:
|
||||
#
|
||||
# Named Route Helpers
|
||||
# account account_url, hash_for_account_url,
|
||||
# ============ =============================================
|
||||
# account account_url, hash_for_account_url,
|
||||
# account_path, hash_for_account_path
|
||||
#
|
||||
# new_account new_account_url, hash_for_new_account_url,
|
||||
# new_account_path, hash_for_new_account_path
|
||||
#
|
||||
# edit_account edit_account_url, hash_for_edit_account_url,
|
||||
# edit_account_path, hash_for_edit_account_path
|
||||
def resource(*entities, &block)
|
||||
options = entities.last.is_a?(Hash) ? entities.pop : { }
|
||||
entities.each { |entity| map_singleton_resource entity, options.dup, &block }
|
||||
options = entities.extract_options!
|
||||
entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -321,8 +396,10 @@ module ActionController
|
|||
map_new_actions(map, resource)
|
||||
map_member_actions(map, resource)
|
||||
|
||||
map_associations(resource, options)
|
||||
|
||||
if block_given?
|
||||
with_options(:path_prefix => resource.nesting_path_prefix, :new_name_prefix => resource.nesting_name_prefix, &block)
|
||||
with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -336,30 +413,32 @@ module ActionController
|
|||
map_new_actions(map, resource)
|
||||
map_member_actions(map, resource)
|
||||
|
||||
map_associations(resource, options)
|
||||
|
||||
if block_given?
|
||||
with_options(:path_prefix => resource.nesting_path_prefix, :new_name_prefix => resource.nesting_name_prefix, &block)
|
||||
with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def map_associations(resource, options)
|
||||
path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
|
||||
name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
|
||||
|
||||
Array(options[:has_many]).each do |association|
|
||||
resources(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace])
|
||||
end
|
||||
|
||||
Array(options[:has_one]).each do |association|
|
||||
resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace])
|
||||
end
|
||||
end
|
||||
|
||||
def map_collection_actions(map, resource)
|
||||
resource.collection_methods.each do |method, actions|
|
||||
actions.each do |action|
|
||||
action_options = action_options_for(action, resource, method)
|
||||
|
||||
unless resource.old_name_prefix.blank?
|
||||
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.old_name_prefix}#{action}_#{resource.plural}")
|
||||
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "formatted_#{resource.old_name_prefix}#{action}_#{resource.plural}")
|
||||
end
|
||||
|
||||
if resource.deprecate_name_prefix?
|
||||
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{action}_#{resource.plural}")
|
||||
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "formatted_#{action}_#{resource.plural}")
|
||||
end
|
||||
|
||||
map.named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
|
||||
map.connect("#{resource.path};#{action}", action_options)
|
||||
map.connect("#{resource.path}.:format;#{action}", action_options)
|
||||
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}.:format", action_options)
|
||||
end
|
||||
end
|
||||
|
@ -367,14 +446,15 @@ module ActionController
|
|||
|
||||
def map_default_collection_actions(map, resource)
|
||||
index_action_options = action_options_for("index", resource)
|
||||
map.named_route("#{resource.name_prefix}#{resource.plural}", resource.path, index_action_options)
|
||||
map.named_route("formatted_#{resource.name_prefix}#{resource.plural}", "#{resource.path}.:format", index_action_options)
|
||||
index_route_name = "#{resource.name_prefix}#{resource.plural}"
|
||||
|
||||
if resource.deprecate_name_prefix?
|
||||
map.deprecated_named_route("#{resource.name_prefix}#{resource.plural}", "#{resource.plural}")
|
||||
map.deprecated_named_route("formatted_#{resource.name_prefix}#{resource.plural}", "formatted_#{resource.plural}")
|
||||
if resource.uncountable?
|
||||
index_route_name << "_index"
|
||||
end
|
||||
|
||||
map.named_route(index_route_name, resource.path, index_action_options)
|
||||
map.named_route("formatted_#{index_route_name}", "#{resource.path}.:format", index_action_options)
|
||||
|
||||
create_action_options = action_options_for("create", resource)
|
||||
map.connect(resource.path, create_action_options)
|
||||
map.connect("#{resource.path}.:format", create_action_options)
|
||||
|
@ -391,37 +471,11 @@ module ActionController
|
|||
actions.each do |action|
|
||||
action_options = action_options_for(action, resource, method)
|
||||
if action == :new
|
||||
|
||||
unless resource.old_name_prefix.blank?
|
||||
map.deprecated_named_route("new_#{resource.name_prefix}#{resource.singular}", "#{resource.old_name_prefix}new_#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "formatted_#{resource.old_name_prefix}new_#{resource.singular}")
|
||||
end
|
||||
|
||||
if resource.deprecate_name_prefix?
|
||||
map.deprecated_named_route("new_#{resource.name_prefix}#{resource.singular}", "new_#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "formatted_new_#{resource.singular}")
|
||||
end
|
||||
|
||||
map.named_route("new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
|
||||
map.named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}.:format", action_options)
|
||||
|
||||
else
|
||||
|
||||
unless resource.old_name_prefix.blank?
|
||||
map.deprecated_named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.old_name_prefix}#{action}_new_#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "formatted_#{resource.old_name_prefix}#{action}_new_#{resource.singular}")
|
||||
end
|
||||
|
||||
if resource.deprecate_name_prefix?
|
||||
map.deprecated_named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{action}_new_#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "formatted_#{action}_new_#{resource.singular}")
|
||||
end
|
||||
|
||||
map.named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
|
||||
map.connect("#{resource.new_path};#{action}", action_options)
|
||||
map.connect("#{resource.new_path}.:format;#{action}", action_options)
|
||||
map.named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}.:format", action_options)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -431,22 +485,8 @@ module ActionController
|
|||
resource.member_methods.each do |method, actions|
|
||||
actions.each do |action|
|
||||
action_options = action_options_for(action, resource, method)
|
||||
|
||||
unless resource.old_name_prefix.blank?
|
||||
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.old_name_prefix}#{action}_#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "formatted_#{resource.old_name_prefix}#{action}_#{resource.singular}")
|
||||
end
|
||||
|
||||
if resource.deprecate_name_prefix?
|
||||
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{action}_#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "formatted_#{action}_#{resource.singular}")
|
||||
end
|
||||
|
||||
map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}", action_options)
|
||||
map.connect("#{resource.member_path};#{action}", action_options)
|
||||
map.connect("#{resource.member_path}.:format;#{action}", action_options)
|
||||
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}.:format", action_options)
|
||||
|
||||
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}.:format",action_options)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -454,11 +494,6 @@ module ActionController
|
|||
map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
|
||||
map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options)
|
||||
|
||||
if resource.deprecate_name_prefix?
|
||||
map.deprecated_named_route("#{resource.name_prefix}#{resource.singular}", "#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_#{resource.name_prefix}#{resource.singular}", "formatted_#{resource.singular}")
|
||||
end
|
||||
|
||||
update_action_options = action_options_for("update", resource)
|
||||
map.connect(resource.member_path, update_action_options)
|
||||
map.connect("#{resource.member_path}.:format", update_action_options)
|
||||
|
@ -468,23 +503,27 @@ module ActionController
|
|||
map.connect("#{resource.member_path}.:format", destroy_action_options)
|
||||
end
|
||||
|
||||
def conditions_for(method)
|
||||
{ :conditions => method == :any ? {} : { :method => method } }
|
||||
def add_conditions_for(conditions, method)
|
||||
returning({:conditions => conditions.dup}) do |options|
|
||||
options[:conditions][:method] = method unless method == :any
|
||||
end
|
||||
end
|
||||
|
||||
def action_options_for(action, resource, method = nil)
|
||||
default_options = { :action => action.to_s }
|
||||
require_id = resource.kind_of?(SingletonResource) ? {} : { :requirements => { :id => Regexp.new("[^#{Routing::SEPARATORS.join}]+") } }
|
||||
require_id = !resource.kind_of?(SingletonResource)
|
||||
case default_options[:action]
|
||||
when "index", "new" : default_options.merge(conditions_for(method || :get))
|
||||
when "create" : default_options.merge(conditions_for(method || :post))
|
||||
when "show", "edit" : default_options.merge(conditions_for(method || :get)).merge(require_id)
|
||||
when "update" : default_options.merge(conditions_for(method || :put)).merge(require_id)
|
||||
when "destroy" : default_options.merge(conditions_for(method || :delete)).merge(require_id)
|
||||
else default_options.merge(conditions_for(method))
|
||||
when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
|
||||
when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
|
||||
when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
|
||||
when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
|
||||
when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
|
||||
else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ActionController::Routing::RouteSet::Mapper.send :include, ActionController::Resources
|
||||
class ActionController::Routing::RouteSet::Mapper
|
||||
include ActionController::Resources
|
||||
end
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
require 'digest/md5'
|
||||
|
||||
module ActionController
|
||||
class AbstractResponse #:nodoc:
|
||||
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
|
||||
attr_accessor :request
|
||||
attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
|
||||
|
||||
def initialize
|
||||
|
@ -8,28 +11,66 @@ module ActionController
|
|||
end
|
||||
|
||||
def content_type=(mime_type)
|
||||
@headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type
|
||||
self.headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type
|
||||
end
|
||||
|
||||
def content_type
|
||||
content_type = String(@headers["Content-Type"]).split(";")[0]
|
||||
content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0]
|
||||
content_type.blank? ? nil : content_type
|
||||
end
|
||||
|
||||
def charset=(encoding)
|
||||
@headers["Content-Type"] = "#{content_type || "text/html"}; charset=#{encoding}"
|
||||
self.headers["Content-Type"] = "#{content_type || Mime::HTML}; charset=#{encoding}"
|
||||
end
|
||||
|
||||
def charset
|
||||
charset = String(@headers["Content-Type"]).split(";")[1]
|
||||
charset = String(headers["Content-Type"] || headers["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
|
||||
def redirect(to_url, response_status)
|
||||
self.headers["Status"] = response_status
|
||||
self.headers["Location"] = to_url
|
||||
|
||||
@body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
|
||||
self.body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
|
||||
end
|
||||
|
||||
def prepare!
|
||||
handle_conditional_get!
|
||||
convert_content_type!
|
||||
set_content_length!
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def handle_conditional_get!
|
||||
if body.is_a?(String) && (headers['Status'] ? headers['Status'][0..2] == '200' : true) && !body.empty?
|
||||
self.headers['ETag'] ||= %("#{Digest::MD5.hexdigest(body)}")
|
||||
self.headers['Cache-Control'] = 'private, max-age=0, must-revalidate' if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
|
||||
|
||||
if request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
|
||||
self.headers['Status'] = '304 Not Modified'
|
||||
self.body = ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def convert_content_type!
|
||||
if content_type = headers.delete("Content-Type")
|
||||
self.headers["type"] = content_type
|
||||
end
|
||||
if content_type = headers.delete("Content-type")
|
||||
self.headers["type"] = content_type
|
||||
end
|
||||
if content_type = headers.delete("content-type")
|
||||
self.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!
|
||||
self.headers["Content-Length"] = body.size unless body.respond_to?(:call)
|
||||
end
|
||||
end
|
||||
end
|
119
vendor/rails/actionpack/lib/action_controller/routing_optimisation.rb
vendored
Normal file
119
vendor/rails/actionpack/lib/action_controller/routing_optimisation.rb
vendored
Normal file
|
@ -0,0 +1,119 @@
|
|||
module ActionController
|
||||
module Routing
|
||||
# Much of the slow performance from routes comes from the
|
||||
# complexity of expiry, :requirements matching, defaults providing
|
||||
# and figuring out which url pattern to use. With named routes
|
||||
# we can avoid the expense of finding the right route. So if
|
||||
# they've provided the right number of arguments, and have no
|
||||
# :requirements, we can just build up a string and return it.
|
||||
#
|
||||
# To support building optimisations for other common cases, the
|
||||
# generation code is separated into several classes
|
||||
module Optimisation
|
||||
def generate_optimisation_block(route, kind)
|
||||
return "" unless route.optimise?
|
||||
OPTIMISERS.inject("") do |memo, klazz|
|
||||
memo << klazz.new(route, kind).source_code
|
||||
memo
|
||||
end
|
||||
end
|
||||
|
||||
class Optimiser
|
||||
attr_reader :route, :kind
|
||||
def initialize(route, kind)
|
||||
@route = route
|
||||
@kind = kind
|
||||
end
|
||||
|
||||
def guard_condition
|
||||
'false'
|
||||
end
|
||||
|
||||
def generation_code
|
||||
'nil'
|
||||
end
|
||||
|
||||
def source_code
|
||||
if applicable?
|
||||
"return #{generation_code} if #{guard_condition}\n"
|
||||
else
|
||||
"\n"
|
||||
end
|
||||
end
|
||||
|
||||
# Temporarily disabled :url optimisation pending proper solution to
|
||||
# Issues around request.host etc.
|
||||
def applicable?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Given a route:
|
||||
# map.person '/people/:id'
|
||||
#
|
||||
# If the user calls person_url(@person), we can simply
|
||||
# return a string like "/people/#{@person.to_param}"
|
||||
# rather than triggering the expensive logic in url_for
|
||||
class PositionalArguments < Optimiser
|
||||
def guard_condition
|
||||
number_of_arguments = route.segment_keys.size
|
||||
# if they're using foo_url(:id=>2) it's one
|
||||
# argument, but we don't want to generate /foos/id2
|
||||
if number_of_arguments == 1
|
||||
"defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)"
|
||||
else
|
||||
"defined?(request) && request && args.size == #{number_of_arguments}"
|
||||
end
|
||||
end
|
||||
|
||||
def generation_code
|
||||
elements = []
|
||||
idx = 0
|
||||
|
||||
if kind == :url
|
||||
elements << '#{request.protocol}'
|
||||
elements << '#{request.host_with_port}'
|
||||
end
|
||||
|
||||
elements << '#{request.relative_url_root if request.relative_url_root}'
|
||||
|
||||
# The last entry in route.segments appears to # *always* be a
|
||||
# 'divider segment' for '/' but we have assertions to ensure that
|
||||
# we don't include the trailing slashes, so skip them.
|
||||
(route.segments.size == 1 ? route.segments : route.segments[0..-2]).each do |segment|
|
||||
if segment.is_a?(DynamicSegment)
|
||||
elements << segment.interpolation_chunk("args[#{idx}].to_param")
|
||||
idx += 1
|
||||
else
|
||||
elements << segment.interpolation_chunk
|
||||
end
|
||||
end
|
||||
%("#{elements * ''}")
|
||||
end
|
||||
end
|
||||
|
||||
# This case is mostly the same as the positional arguments case
|
||||
# above, but it supports additional query parameters as the last
|
||||
# argument
|
||||
class PositionalArgumentsWithAdditionalParams < PositionalArguments
|
||||
def guard_condition
|
||||
"defined?(request) && request && args.size == #{route.segment_keys.size + 1} && !args.last.has_key?(:anchor) && !args.last.has_key?(:port) && !args.last.has_key?(:host)"
|
||||
end
|
||||
|
||||
# This case uses almost the same code as positional arguments,
|
||||
# but add an args.last.to_query on the end
|
||||
def generation_code
|
||||
super.insert(-2, '?#{args.last.to_query}')
|
||||
end
|
||||
|
||||
# To avoid generating http://localhost/?host=foo.example.com we
|
||||
# can't use this optimisation on routes without any segments
|
||||
def applicable?
|
||||
super && route.segment_keys.size > 0
|
||||
end
|
||||
end
|
||||
|
||||
OPTIMISERS = [PositionalArguments, PositionalArgumentsWithAdditionalParams]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,189 +0,0 @@
|
|||
module ActionController
|
||||
module Scaffolding # :nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# Scaffolding is a way to quickly put an Active Record class online by providing a series of standardized actions
|
||||
# for listing, showing, creating, updating, and destroying objects of the class. These standardized actions come
|
||||
# with both controller logic and default templates that through introspection already know which fields to display
|
||||
# and which input types to use. Example:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# scaffold :entry
|
||||
# end
|
||||
#
|
||||
# This tiny piece of code will add all of the following methods to the controller:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# verify :method => :post, :only => [ :destroy, :create, :update ],
|
||||
# :redirect_to => { :action => :list }
|
||||
#
|
||||
# def index
|
||||
# list
|
||||
# end
|
||||
#
|
||||
# def list
|
||||
# @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
|
||||
# flash[:notice] = "Entry was successfully created"
|
||||
# redirect_to :action => "list"
|
||||
# else
|
||||
# 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
|
||||
# else
|
||||
# render_scaffold('edit')
|
||||
# end
|
||||
# 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
|
||||
# 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
|
||||
# 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)
|
||||
|
||||
singular_name = model_id.to_s
|
||||
class_name = options[:class_name] || singular_name.camelize
|
||||
plural_name = singular_name.pluralize
|
||||
suffix = options[:suffix] ? "_#{singular_name}" : ""
|
||||
|
||||
unless options[:suffix]
|
||||
module_eval <<-"end_eval", __FILE__, __LINE__
|
||||
def index
|
||||
list
|
||||
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}"
|
||||
end
|
||||
|
||||
def show#{suffix}
|
||||
@#{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
|
||||
flash[:notice] = "#{class_name} was successfully created"
|
||||
redirect_to :action => "list#{suffix}"
|
||||
else
|
||||
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}]
|
||||
|
||||
if @#{singular_name}.save
|
||||
flash[:notice] = "#{class_name} was successfully updated"
|
||||
redirect_to :action => "show#{suffix}", :id => @#{singular_name}
|
||||
else
|
||||
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
|
||||
else
|
||||
@scaffold_class = #{class_name}
|
||||
@scaffold_singular_name, @scaffold_plural_name = "#{singular_name}", "#{plural_name}"
|
||||
@scaffold_suffix = "#{suffix}"
|
||||
add_instance_variables_to_assigns
|
||||
|
||||
@template.instance_variable_set("@content_for_layout", @template.render_file(scaffold_path(action.sub(/#{suffix}$/, "")), false))
|
||||
|
||||
if !active_layout.nil?
|
||||
render :file => active_layout, :use_full_path => true
|
||||
else
|
||||
render :file => scaffold_path('layout')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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
|
|
@ -122,13 +122,14 @@ class CGI
|
|||
@data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
|
||||
end
|
||||
|
||||
attr_writer :data
|
||||
|
||||
# Has the session been loaded yet?
|
||||
def loaded?
|
||||
!! @data
|
||||
end
|
||||
|
||||
private
|
||||
attr_writer :data
|
||||
|
||||
def marshal_data!
|
||||
return false if !loaded?
|
||||
|
@ -332,4 +333,4 @@ class CGI
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
164
vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb
vendored
Normal file
164
vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb
vendored
Normal file
|
@ -0,0 +1,164 @@
|
|||
require 'cgi'
|
||||
require 'cgi/session'
|
||||
require 'base64' # to convert Marshal.dump to ASCII
|
||||
require 'openssl' # to generate the HMAC message digest
|
||||
|
||||
# This cookie-based session store is the Rails default. Sessions typically
|
||||
# contain at most a user_id and flash message; both fit within the 4K cookie
|
||||
# size limit. Cookie-based sessions are dramatically faster than the
|
||||
# alternatives.
|
||||
#
|
||||
# If you have more than 4K of session data or don't want your data to be
|
||||
# visible to the user, pick another session store.
|
||||
#
|
||||
# CookieOverflow is raised if you attempt to store more than 4K of data.
|
||||
# TamperedWithCookie is raised if the data integrity check fails.
|
||||
#
|
||||
# A message digest is included with the cookie to ensure data integrity:
|
||||
# a user cannot alter his user_id without knowing the secret key included in
|
||||
# the hash. New apps are generated with a pregenerated secret in
|
||||
# config/environment.rb. Set your own for old apps you're upgrading.
|
||||
#
|
||||
# Session options:
|
||||
# :secret An application-wide key string or block returning a string
|
||||
# called per generated digest. The block is called with the
|
||||
# CGI::Session instance as an argument. It's important that the
|
||||
# secret is not vulnerable to a dictionary attack. Therefore,
|
||||
# you should choose a secret consisting of random numbers and
|
||||
# letters and more than 30 characters.
|
||||
#
|
||||
# Example: :secret => '449fe2e7daee471bffae2fd8dc02313d'
|
||||
# :secret => Proc.new { User.current_user.secret_key }
|
||||
#
|
||||
# :digest The message digest algorithm used to verify session integrity
|
||||
# defaults to 'SHA1' but may be any digest provided by OpenSSL,
|
||||
# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
|
||||
#
|
||||
# To generate a secret key for an existing application, run
|
||||
# `rake secret` and set the key in config/environment.rb
|
||||
#
|
||||
# Note that changing digest or secret invalidates all existing sessions!
|
||||
class CGI::Session::CookieStore
|
||||
# Cookies can typically store 4096 bytes.
|
||||
MAX = 4096
|
||||
SECRET_MIN_LENGTH = 30 # characters
|
||||
|
||||
# Raised when storing more than 4K of session data.
|
||||
class CookieOverflow < StandardError; end
|
||||
|
||||
# Raised when the cookie fails its integrity check.
|
||||
class TamperedWithCookie < StandardError; end
|
||||
|
||||
# Called from CGI::Session only.
|
||||
def initialize(session, options = {})
|
||||
# The session_key option is required.
|
||||
if options['session_key'].blank?
|
||||
raise ArgumentError, 'A session_key is required to write a cookie containing the session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase" } in config/environment.rb'
|
||||
end
|
||||
|
||||
# The secret option is required.
|
||||
ensure_secret_secure(options['secret'])
|
||||
|
||||
# Keep the session and its secret on hand so we can read and write cookies.
|
||||
@session, @secret = session, options['secret']
|
||||
|
||||
# Message digest defaults to SHA1.
|
||||
@digest = options['digest'] || 'SHA1'
|
||||
|
||||
# Default cookie options derived from session settings.
|
||||
@cookie_options = {
|
||||
'name' => options['session_key'],
|
||||
'path' => options['session_path'],
|
||||
'domain' => options['session_domain'],
|
||||
'expires' => options['session_expires'],
|
||||
'secure' => options['session_secure']
|
||||
}
|
||||
|
||||
# Set no_hidden and no_cookies since the session id is unused and we
|
||||
# set our own data cookie.
|
||||
options['no_hidden'] = true
|
||||
options['no_cookies'] = true
|
||||
end
|
||||
|
||||
# To prevent users from using something insecure like "Password" we make sure that the
|
||||
# secret they've provided is at least 30 characters in length.
|
||||
def ensure_secret_secure(secret)
|
||||
# There's no way we can do this check if they've provided a proc for the
|
||||
# secret.
|
||||
return true if secret.is_a?(Proc)
|
||||
|
||||
if secret.blank?
|
||||
raise ArgumentError, %Q{A secret is required to generate an integrity hash for cookie session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase of at least #{SECRET_MIN_LENGTH} characters" } in config/environment.rb}
|
||||
end
|
||||
|
||||
if secret.length < SECRET_MIN_LENGTH
|
||||
raise ArgumentError, %Q{Secret should be something secure, like "#{CGI::Session.generate_unique_id}". The value you provided, "#{secret}", is shorter than the minimum length of #{SECRET_MIN_LENGTH} characters}
|
||||
end
|
||||
end
|
||||
|
||||
# Restore session data from the cookie.
|
||||
def restore
|
||||
@original = read_cookie
|
||||
@data = unmarshal(@original) || {}
|
||||
end
|
||||
|
||||
# Wait until close to write the session data cookie.
|
||||
def update; end
|
||||
|
||||
# Write the session data cookie if it was loaded and has changed.
|
||||
def close
|
||||
if defined?(@data) && !@data.blank?
|
||||
updated = marshal(@data)
|
||||
raise CookieOverflow if updated.size > MAX
|
||||
write_cookie('value' => updated) unless updated == @original
|
||||
end
|
||||
end
|
||||
|
||||
# Delete the session data by setting an expired cookie with no data.
|
||||
def delete
|
||||
@data = nil
|
||||
clear_old_cookie_value
|
||||
write_cookie('value' => '', 'expires' => 1.year.ago)
|
||||
end
|
||||
|
||||
# Generate the HMAC keyed message digest. Uses SHA1 by default.
|
||||
def generate_digest(data)
|
||||
key = @secret.respond_to?(:call) ? @secret.call(@session) : @secret
|
||||
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), key, data)
|
||||
end
|
||||
|
||||
private
|
||||
# Marshal a session hash into safe cookie data. Include an integrity hash.
|
||||
def marshal(session)
|
||||
data = Base64.encode64(Marshal.dump(session)).chop
|
||||
CGI.escape "#{data}--#{generate_digest(data)}"
|
||||
end
|
||||
|
||||
# Unmarshal cookie data to a hash and verify its integrity.
|
||||
def unmarshal(cookie)
|
||||
if cookie
|
||||
data, digest = CGI.unescape(cookie).split('--')
|
||||
unless digest == generate_digest(data)
|
||||
delete
|
||||
raise TamperedWithCookie
|
||||
end
|
||||
Marshal.load(Base64.decode64(data))
|
||||
end
|
||||
end
|
||||
|
||||
# Read the session data cookie.
|
||||
def read_cookie
|
||||
@session.cgi.cookies[@cookie_options['name']].first
|
||||
end
|
||||
|
||||
# CGI likes to make you hack.
|
||||
def write_cookie(options)
|
||||
cookie = CGI::Cookie.new(@cookie_options.merge(options))
|
||||
@session.cgi.send :instance_variable_set, '@output_cookies', [cookie]
|
||||
end
|
||||
|
||||
# Clear cookie value so subsequent new_session doesn't reload old data.
|
||||
def clear_old_cookie_value
|
||||
@session.cgi.cookies[@cookie_options['name']].clear
|
||||
end
|
||||
end
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
begin
|
||||
require 'cgi/session'
|
||||
require 'memcache'
|
||||
require_library_or_gem 'memcache'
|
||||
|
||||
class CGI
|
||||
class Session
|
||||
|
@ -57,26 +57,22 @@ begin
|
|||
@expires = options['expires'] || 0
|
||||
@session_key = "session:#{id}"
|
||||
@session_data = {}
|
||||
# Add this key to the store if haven't done so yet
|
||||
unless @cache.get(@session_key)
|
||||
@cache.add(@session_key, @session_data, @expires)
|
||||
end
|
||||
end
|
||||
|
||||
# Restore session state from the session's memcache entry.
|
||||
#
|
||||
# Returns the session state as a hash.
|
||||
def restore
|
||||
begin
|
||||
@session_data = @cache[@session_key] || {}
|
||||
rescue
|
||||
@session_data = {}
|
||||
end
|
||||
@session_data = @cache[@session_key] || {}
|
||||
end
|
||||
|
||||
# Save session state to the session's memcache entry.
|
||||
def update
|
||||
begin
|
||||
@cache.set(@session_key, @session_data, @expires)
|
||||
rescue
|
||||
# Ignore session update failures.
|
||||
end
|
||||
@cache.set(@session_key, @session_data, @expires)
|
||||
end
|
||||
|
||||
# Update and close the session's memcache entry.
|
||||
|
@ -86,17 +82,14 @@ begin
|
|||
|
||||
# Delete the session's memcache entry.
|
||||
def delete
|
||||
begin
|
||||
@cache.delete(@session_key)
|
||||
rescue
|
||||
# Ignore session delete failures.
|
||||
end
|
||||
@cache.delete(@session_key)
|
||||
@session_data = {}
|
||||
end
|
||||
|
||||
def data
|
||||
@session_data
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
require 'action_controller/session/cookie_store'
|
||||
require 'action_controller/session/drb_store'
|
||||
require 'action_controller/session/mem_cache_store'
|
||||
if Object.const_defined?(:ActiveRecord)
|
||||
|
@ -7,16 +8,17 @@ end
|
|||
module ActionController #:nodoc:
|
||||
module SessionManagement #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
|
||||
base.send :alias_method_chain, :process, :session_management_support
|
||||
base.send :alias_method_chain, :process_cleanup, :session_management_support
|
||||
base.class_eval do
|
||||
extend ClassMethods
|
||||
alias_method_chain :process, :session_management_support
|
||||
alias_method_chain :process_cleanup, :session_management_support
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Set the session store to be used for keeping the session data between requests. The default is using the
|
||||
# file system, but you can also specify one of the other included stores (:active_record_store, :drb_store,
|
||||
# :mem_cache_store, or :memory_store) or use your own class.
|
||||
# Set the session store to be used for keeping the session data between requests. By default, sessions are stored
|
||||
# in browser cookies (:cookie_store), but you can also specify one of the other included stores
|
||||
# (:active_record_store, :p_store, drb_store, :mem_cache_store, or :memory_store) or your own custom class.
|
||||
def session_store=(store)
|
||||
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager] =
|
||||
store.is_a?(Symbol) ? CGI::Session.const_get(store == :drb_store ? "DRbStore" : store.to_s.camelize) : store
|
||||
|
@ -61,10 +63,14 @@ module ActionController #:nodoc:
|
|||
# session :off, :only => :foo,
|
||||
# :if => Proc.new { |req| req.parameters[:ws] }
|
||||
#
|
||||
# # the session will be disabled for non html/ajax requests
|
||||
# session :off,
|
||||
# :if => Proc.new { |req| !(req.format.html? || req.format.js?) }
|
||||
#
|
||||
# All session options described for ActionController::Base.process_cgi
|
||||
# are valid arguments.
|
||||
def session(*args)
|
||||
options = Hash === args.last ? args.pop : {}
|
||||
options = args.extract_options!
|
||||
|
||||
options[:disabled] = true if !args.empty?
|
||||
options[:only] = [*options[:only]].map { |o| o.to_s } if options[:only]
|
||||
|
@ -76,6 +82,9 @@ module ActionController #:nodoc:
|
|||
write_inheritable_array("session_options", [options])
|
||||
end
|
||||
|
||||
# So we can declare session options in the Rails initializer.
|
||||
alias_method :session=, :session
|
||||
|
||||
def cached_session_options #:nodoc:
|
||||
@session_options ||= read_inheritable_attribute("session_options") || []
|
||||
end
|
||||
|
|
|
@ -29,6 +29,9 @@ module ActionController #:nodoc:
|
|||
# * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
|
||||
# Defaults to 4096.
|
||||
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
||||
# * <tt>:url_based_filename</tt> - set to true if you want the browser guess the filename from
|
||||
# the URL, which is necessary for i18n filenames on certain browsers
|
||||
# (setting :filename overrides this option).
|
||||
#
|
||||
# The default Content-Type and Content-Disposition headers are
|
||||
# set to download arbitrary binary files in as many browsers as
|
||||
|
@ -42,7 +45,7 @@ module ActionController #:nodoc:
|
|||
# send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
|
||||
#
|
||||
# Show a 404 page in the browser:
|
||||
# send_file '/path/to/404.html, :type => 'text/html; charset=utf-8', :status => 404
|
||||
# send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404
|
||||
#
|
||||
# Read about the other Content-* HTTP headers if you'd like to
|
||||
# provide the user with more information (such as Content-Description).
|
||||
|
@ -59,7 +62,7 @@ module ActionController #:nodoc:
|
|||
raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
|
||||
|
||||
options[:length] ||= File.size(path)
|
||||
options[:filename] ||= File.basename(path)
|
||||
options[:filename] ||= File.basename(path) unless options[:url_based_filename]
|
||||
send_file_headers! options
|
||||
|
||||
@performed_render = false
|
||||
|
@ -121,7 +124,7 @@ module ActionController #:nodoc:
|
|||
|
||||
headers.update(
|
||||
'Content-Length' => options[:length],
|
||||
'Content-Type' => options[:type].strip, # fixes a problem with extra '\r' with some browsers
|
||||
'Content-Type' => options[:type].to_s.strip, # fixes a problem with extra '\r' with some browsers
|
||||
'Content-Disposition' => disposition,
|
||||
'Content-Transfer-Encoding' => 'binary'
|
||||
)
|
||||
|
|
24
vendor/rails/actionpack/lib/action_controller/templates/rescues/_request_and_response.erb
vendored
Normal file
24
vendor/rails/actionpack/lib/action_controller/templates/rescues/_request_and_response.erb
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
<% unless @exception.blamed_files.blank? %>
|
||||
<% if (hide = @exception.blamed_files.length > 8) %>
|
||||
<a href="#" onclick="document.getElementById('blame_trace').style.display='block'; return false;">Show blamed files</a>
|
||||
<% end %>
|
||||
<pre id="blame_trace" <%='style="display:none"' if hide %>><code><%=h @exception.describe_blame %></code></pre>
|
||||
<% end %>
|
||||
|
||||
<%
|
||||
clean_params = request.parameters.clone
|
||||
clean_params.delete("action")
|
||||
clean_params.delete("controller")
|
||||
|
||||
request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
|
||||
%>
|
||||
|
||||
<h2 style="margin-top: 30px">Request</h2>
|
||||
<p><b>Parameters</b>: <pre><%=h request_dump %></pre></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>
|
||||
|
||||
|
||||
<h2 style="margin-top: 30px">Response</h2>
|
||||
<p><b>Headers</b>: <pre><%=h response ? response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p>
|
|
@ -1,44 +0,0 @@
|
|||
<% unless @exception.blamed_files.blank? %>
|
||||
<% if (hide = @exception.blamed_files.length > 8) %>
|
||||
<a href="#" onclick="document.getElementById('blame_trace').style.display='block'; return false;">Show blamed files</a>
|
||||
<% end %>
|
||||
<pre id="blame_trace" <%='style="display:none"' if hide %>><code><%=h @exception.describe_blame %></code></pre>
|
||||
<% end %>
|
||||
|
||||
<% if false %>
|
||||
<br /><br />
|
||||
<% begin %>
|
||||
<%= form_tag(request.request_uri, "method" => request.method) %>
|
||||
<input type="hidden" name="BP-RETRY" value="1" />
|
||||
|
||||
<% for key, values in params %>
|
||||
<% next if key == "BP-RETRY" %>
|
||||
<% for value in Array(values) %>
|
||||
<input type="hidden" name="<%= key %>" value="<%= value %>" />
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<input type="submit" value="Retry with Breakpoint" />
|
||||
</form>
|
||||
<% rescue Exception => e %>
|
||||
<%=h "Couldn't render breakpoint link due to #{e.class} #{e.message}" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%
|
||||
request_parameters_without_action = request.parameters.clone
|
||||
request_parameters_without_action.delete("action")
|
||||
request_parameters_without_action.delete("controller")
|
||||
|
||||
request_dump = request_parameters_without_action.inspect.gsub(/,/, ",\n")
|
||||
%>
|
||||
|
||||
<h2 style="margin-top: 30px">Request</h2>
|
||||
<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>
|
||||
|
||||
|
||||
<h2 style="margin-top: 30px">Response</h2>
|
||||
<b>Headers</b>: <%=h response ? response.headers.inspect.gsub(/,/, ",\n") : "None" %><br/>
|
|
@ -6,6 +6,6 @@
|
|||
</h1>
|
||||
<pre><%=h @exception.clean_message %></pre>
|
||||
|
||||
<%= render_file(@rescues_path + "/_trace.rhtml", false) %>
|
||||
<%= render_file(@rescues_path + "/_trace.erb", false) %>
|
||||
|
||||
<%= render_file(@rescues_path + "/_request_and_response.rhtml", false) %>
|
||||
<%= render_file(@rescues_path + "/_request_and_response.erb", false) %>
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<% @real_exception = @exception
|
||||
@exception = @exception.original_exception || @exception %>
|
||||
<%= render_file(@rescues_path + "/_trace.rhtml", false) %>
|
||||
<%= render_file(@rescues_path + "/_trace.erb", false) %>
|
||||
<% @exception = @real_exception %>
|
||||
|
||||
<%= render_file(@rescues_path + "/_request_and_response.rhtml", false) %>
|
||||
<%= render_file(@rescues_path + "/_request_and_response.erb", false) %>
|
|
@ -1,7 +0,0 @@
|
|||
<h1>Editing <%= @scaffold_singular_name %></h1>
|
||||
|
||||
<%= error_messages_for(@scaffold_singular_name) %>
|
||||
<%= form(@scaffold_singular_name, :action => "update#{@scaffold_suffix}") %>
|
||||
|
||||
<%= link_to "Show", :action => "show#{@scaffold_suffix}", :id => instance_variable_get("@#{@scaffold_singular_name}") %> |
|
||||
<%= link_to "Back", :action => "list#{@scaffold_suffix}" %>
|
|
@ -1,69 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Scaffolding</title>
|
||||
<style>
|
||||
body { background-color: #fff; color: #333; }
|
||||
|
||||
body, p, ol, ul, td {
|
||||
font-family: verdana, arial, helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #eee;
|
||||
padding: 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
a { color: #000; }
|
||||
a:visited { color: #666; }
|
||||
a:hover { color: #fff; background-color:#000; }
|
||||
|
||||
.fieldWithErrors {
|
||||
padding: 2px;
|
||||
background-color: red;
|
||||
display: table;
|
||||
}
|
||||
|
||||
#errorExplanation {
|
||||
width: 400px;
|
||||
border: 2px solid red;
|
||||
padding: 7px;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
#errorExplanation h2 {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
padding: 5px 5px 5px 15px;
|
||||
font-size: 12px;
|
||||
margin: -7px;
|
||||
background-color: #c00;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#errorExplanation p {
|
||||
color: #333;
|
||||
margin-bottom: 0;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#errorExplanation ul li {
|
||||
font-size: 12px;
|
||||
list-style: square;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p style="color: green"><%= flash[:notice] %></p>
|
||||
|
||||
<%= yield %>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,27 +0,0 @@
|
|||
<h1>Listing <%= @scaffold_plural_name %></h1>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<% for column in @scaffold_class.content_columns %>
|
||||
<th><%= column.human_name %></th>
|
||||
<% end %>
|
||||
</tr>
|
||||
|
||||
<% for entry in instance_variable_get("@#{@scaffold_plural_name}") %>
|
||||
<tr>
|
||||
<% for column in @scaffold_class.content_columns %>
|
||||
<td><%= entry.send(column.name) %></td>
|
||||
<% 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?", :method => :post } %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
|
||||
<%= link_to "Previous page", { :page => instance_variable_get("@#{@scaffold_singular_name}_pages").current.previous } if instance_variable_get("@#{@scaffold_singular_name}_pages").current.previous %>
|
||||
<%= link_to "Next page", { :page => instance_variable_get("@#{@scaffold_singular_name}_pages").current.next } if instance_variable_get("@#{@scaffold_singular_name}_pages").current.next %>
|
||||
|
||||
<br />
|
||||
|
||||
<%= link_to "New #{@scaffold_singular_name}", :action => "new#{@scaffold_suffix}" %>
|
|
@ -1,6 +0,0 @@
|
|||
<h1>New <%= @scaffold_singular_name %></h1>
|
||||
|
||||
<%= error_messages_for(@scaffold_singular_name) %>
|
||||
<%= form(@scaffold_singular_name, :action => "create#{@scaffold_suffix}") %>
|
||||
|
||||
<%= link_to "Back", :action => "list#{@scaffold_suffix}" %>
|
|
@ -1,9 +0,0 @@
|
|||
<% for column in @scaffold_class.content_columns %>
|
||||
<p>
|
||||
<b><%= column.human_name %>:</b>
|
||||
<%= instance_variable_get("@#{@scaffold_singular_name}").send(column.name) %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= link_to "Edit", :action => "edit#{@scaffold_suffix}", :id => instance_variable_get("@#{@scaffold_singular_name}") %> |
|
||||
<%= link_to "Back", :action => "list#{@scaffold_suffix}" %>
|
53
vendor/rails/actionpack/lib/action_controller/test_case.rb
vendored
Normal file
53
vendor/rails/actionpack/lib/action_controller/test_case.rb
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
require 'active_support/test_case'
|
||||
|
||||
module ActionController
|
||||
class NonInferrableControllerError < ActionControllerError
|
||||
def initialize(name)
|
||||
super "Unable to determine the controller to test from #{name}. " +
|
||||
"You'll need to specify it using 'tests YourController' in your " +
|
||||
"test case definition"
|
||||
end
|
||||
end
|
||||
|
||||
class TestCase < ActiveSupport::TestCase
|
||||
@@controller_class = nil
|
||||
class << self
|
||||
def tests(controller_class)
|
||||
self.controller_class = controller_class
|
||||
end
|
||||
|
||||
def controller_class=(new_class)
|
||||
prepare_controller_class(new_class)
|
||||
write_inheritable_attribute(:controller_class, new_class)
|
||||
end
|
||||
|
||||
def controller_class
|
||||
if current_controller_class = read_inheritable_attribute(:controller_class)
|
||||
current_controller_class
|
||||
else
|
||||
self.controller_class= determine_default_controller_class(name)
|
||||
end
|
||||
end
|
||||
|
||||
def determine_default_controller_class(name)
|
||||
name.sub(/Test$/, '').constantize
|
||||
rescue NameError
|
||||
raise NonInferrableControllerError.new(name)
|
||||
end
|
||||
|
||||
def prepare_controller_class(new_class)
|
||||
new_class.class_eval do
|
||||
def rescue_action(e)
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
@controller = self.class.controller_class.new
|
||||
@request = TestRequest.new
|
||||
@response = TestResponse.new
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
require File.dirname(__FILE__) + '/assertions'
|
||||
require 'action_controller/assertions'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class Base
|
||||
|
@ -23,8 +23,7 @@ module ActionController #:nodoc:
|
|||
class TestRequest < AbstractRequest #:nodoc:
|
||||
attr_accessor :cookies, :session_options
|
||||
attr_accessor :query_parameters, :request_parameters, :path, :session, :env
|
||||
attr_accessor :host
|
||||
attr_reader :request_uri_overridden
|
||||
attr_accessor :host, :user_agent
|
||||
|
||||
def initialize(query_parameters = nil, request_parameters = nil, session = nil)
|
||||
@query_parameters = query_parameters || {}
|
||||
|
@ -41,18 +40,15 @@ module ActionController #:nodoc:
|
|||
@session = TestSession.new
|
||||
end
|
||||
|
||||
# Wraps raw_post in a StringIO.
|
||||
def body
|
||||
StringIO.new(raw_post)
|
||||
end
|
||||
|
||||
# Either the RAW_POST_DATA environment variable or the URL-encoded request
|
||||
# parameters.
|
||||
def raw_post
|
||||
if raw_post = env['RAW_POST_DATA']
|
||||
raw_post
|
||||
else
|
||||
params = self.request_parameters.dup
|
||||
%w(controller action only_path).each do |k|
|
||||
params.delete(k)
|
||||
params.delete(k.to_sym)
|
||||
end
|
||||
|
||||
params.map { |k,v| [ CGI.escape(k.to_s), CGI.escape(v.to_s) ].join('=') }.sort.join('&')
|
||||
end
|
||||
env['RAW_POST_DATA'] ||= url_encoded_request_parameters
|
||||
end
|
||||
|
||||
def port=(number)
|
||||
|
@ -68,14 +64,12 @@ module ActionController #:nodoc:
|
|||
# Used to check AbstractRequest's request_uri functionality.
|
||||
# Disables the use of @path and @request_uri so superclass can handle those.
|
||||
def set_REQUEST_URI(value)
|
||||
@request_uri_overridden = true
|
||||
@env["REQUEST_URI"] = value
|
||||
@request_uri = nil
|
||||
@path = nil
|
||||
end
|
||||
|
||||
def request_uri=(uri)
|
||||
@env["REQUEST_URI"] = uri
|
||||
@request_uri = uri
|
||||
@path = uri.split("?").first
|
||||
end
|
||||
|
@ -93,11 +87,11 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def request_uri
|
||||
@request_uri || super()
|
||||
@request_uri || super
|
||||
end
|
||||
|
||||
def path
|
||||
@path || super()
|
||||
@path || super
|
||||
end
|
||||
|
||||
def assign_parameters(controller_path, action, parameters)
|
||||
|
@ -127,6 +121,10 @@ module ActionController #:nodoc:
|
|||
@request_method, @accepts, @content_type = nil, nil, nil
|
||||
end
|
||||
|
||||
def referer
|
||||
@env["HTTP_REFERER"]
|
||||
end
|
||||
|
||||
private
|
||||
def initialize_containers
|
||||
@env, @cookies = {}, {}
|
||||
|
@ -135,10 +133,22 @@ module ActionController #:nodoc:
|
|||
def initialize_default_values
|
||||
@host = "test.host"
|
||||
@request_uri = "/"
|
||||
@user_agent = "Rails Testing"
|
||||
self.remote_addr = "0.0.0.0"
|
||||
@env["SERVER_PORT"] = 80
|
||||
@env['REQUEST_METHOD'] = "GET"
|
||||
end
|
||||
|
||||
def url_encoded_request_parameters
|
||||
params = self.request_parameters.dup
|
||||
|
||||
%w(controller action only_path).each do |k|
|
||||
params.delete(k)
|
||||
params.delete(k.to_sym)
|
||||
end
|
||||
|
||||
params.to_query
|
||||
end
|
||||
end
|
||||
|
||||
# A refactoring of TestResponse to allow the same behavior to be applied
|
||||
|
@ -260,13 +270,7 @@ module ActionController #:nodoc:
|
|||
require 'stringio'
|
||||
|
||||
sio = StringIO.new
|
||||
|
||||
begin
|
||||
$stdout = sio
|
||||
body.call
|
||||
ensure
|
||||
$stdout = STDOUT
|
||||
end
|
||||
body.call(self, sio)
|
||||
|
||||
sio.rewind
|
||||
sio.read
|
||||
|
@ -320,33 +324,37 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# Usage example, within a functional test:
|
||||
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
|
||||
#
|
||||
# Pass a true third parameter to ensure the uploaded file is opened in binary mode (only required for Windows):
|
||||
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary)
|
||||
require 'tempfile'
|
||||
class TestUploadedFile
|
||||
# The filename, *not* including the path, of the "uploaded" file
|
||||
attr_reader :original_filename
|
||||
|
||||
|
||||
# The content type of the "uploaded" file
|
||||
attr_reader :content_type
|
||||
|
||||
def initialize(path, content_type = 'text/plain')
|
||||
|
||||
def initialize(path, content_type = Mime::TEXT, binary = false)
|
||||
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)
|
||||
@tempfile.binmode if binary
|
||||
FileUtils.copy_file(path, @tempfile.path)
|
||||
end
|
||||
|
||||
|
||||
def path #:nodoc:
|
||||
@tempfile.path
|
||||
end
|
||||
|
||||
|
||||
alias local_path path
|
||||
|
||||
|
||||
def method_missing(method_name, *args, &block) #:nodoc:
|
||||
@tempfile.send(method_name, *args, &block)
|
||||
@tempfile.send!(method_name, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module TestProcess
|
||||
def self.included(base)
|
||||
# execute the request simulating a specific http method and set/volley the response
|
||||
|
@ -365,7 +373,7 @@ module ActionController #:nodoc:
|
|||
# Sanity check for required instance variables so we can give an
|
||||
# understandable error message.
|
||||
%w(@controller @request @response).each do |iv_name|
|
||||
if !instance_variables.include?(iv_name) || instance_variable_get(iv_name).nil?
|
||||
if !(instance_variables.include?(iv_name) || instance_variables.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
|
||||
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
|
||||
end
|
||||
end
|
||||
|
@ -388,7 +396,7 @@ module ActionController #:nodoc:
|
|||
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
|
||||
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
|
||||
@request.env['HTTP_ACCEPT'] = 'text/javascript, text/html, application/xml, text/xml, */*'
|
||||
returning self.send(request_method, action, parameters, session, flash) do
|
||||
returning send!(request_method, action, parameters, session, flash) do
|
||||
@request.env.delete 'HTTP_X_REQUESTED_WITH'
|
||||
@request.env.delete 'HTTP_ACCEPT'
|
||||
end
|
||||
|
@ -429,17 +437,18 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def build_request_uri(action, parameters)
|
||||
unless @request.request_uri_overridden
|
||||
options = @controller.send(:rewrite_options, parameters)
|
||||
unless @request.env['REQUEST_URI']
|
||||
options = @controller.send!(:rewrite_options, parameters)
|
||||
options.update(:only_path => true, :action => action)
|
||||
|
||||
url = ActionController::UrlRewriter.new(@request, parameters)
|
||||
@request.request_uri = url.rewrite(options)
|
||||
@request.set_REQUEST_URI(url.rewrite(options))
|
||||
end
|
||||
end
|
||||
|
||||
def html_document
|
||||
@html_document ||= HTML::Document.new(@response.body)
|
||||
xml = @response.content_type =~ /xml$/
|
||||
@html_document ||= HTML::Document.new(@response.body, false, xml)
|
||||
end
|
||||
|
||||
def find_tag(conditions)
|
||||
|
@ -451,16 +460,20 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def method_missing(selector, *args)
|
||||
return @controller.send(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
|
||||
return @controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
|
||||
return super
|
||||
end
|
||||
|
||||
# Shortcut for ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type). Example:
|
||||
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
|
||||
def fixture_file_upload(path, mime_type = nil)
|
||||
#
|
||||
# To upload binary files on Windows, pass :binary as the last parameter. This will not affect other platforms.
|
||||
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
|
||||
def fixture_file_upload(path, mime_type = nil, binary = false)
|
||||
ActionController::TestUploadedFile.new(
|
||||
Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path,
|
||||
mime_type
|
||||
mime_type,
|
||||
binary
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -483,15 +496,15 @@ module ActionController #:nodoc:
|
|||
#
|
||||
def with_routing
|
||||
real_routes = ActionController::Routing::Routes
|
||||
ActionController::Routing.send :remove_const, :Routes
|
||||
ActionController::Routing.module_eval { remove_const :Routes }
|
||||
|
||||
temporary_routes = ActionController::Routing::RouteSet.new
|
||||
ActionController::Routing.send :const_set, :Routes, temporary_routes
|
||||
|
||||
ActionController::Routing.module_eval { const_set :Routes, temporary_routes }
|
||||
|
||||
yield temporary_routes
|
||||
ensure
|
||||
if ActionController::Routing.const_defined? :Routes
|
||||
ActionController::Routing.send(:remove_const, :Routes)
|
||||
ActionController::Routing.module_eval { remove_const :Routes }
|
||||
end
|
||||
ActionController::Routing.const_set(:Routes, real_routes) if real_routes
|
||||
end
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
module ActionController
|
||||
|
||||
module ActionController
|
||||
# Write URLs from arbitrary places in your codebase, such as your mailers.
|
||||
#
|
||||
# Example:
|
||||
|
@ -15,59 +14,71 @@ module ActionController
|
|||
#
|
||||
# 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
|
||||
ActionController::Routing::Routes.install_helpers 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:
|
||||
# Generate a url based on the options provided, default_url_options and the
|
||||
# routes defined in routes.rb. The following options are supported:
|
||||
#
|
||||
# * :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.
|
||||
# * <tt>:only_path</tt> If true, the relative url is returned. Defaults to false.
|
||||
# * <tt>:protocol</tt> The protocol to connect to. Defaults to 'http'.
|
||||
# * <tt>:host</tt> Specifies the host the link should be targetted at. If <tt>:only_path</tt> is false, this option must be
|
||||
# provided either explicitly, or via default_url_options.
|
||||
# * <tt>:port</tt> Optionally specify the port to connect to.
|
||||
# * <tt>:anchor</tt> An anchor name to be appended to the path.
|
||||
#
|
||||
# Any other key(:controller, :action, etc...) given to <tt>url_for</tt> is forwarded to the Routes module.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
|
||||
#
|
||||
def url_for(options)
|
||||
options = self.class.default_url_options.merge(options)
|
||||
|
||||
url = ''
|
||||
|
||||
unless options.delete :only_path
|
||||
url << (options.delete(:protocol) || 'http')
|
||||
url << '://'
|
||||
url << '://' unless url.match("://") #dont add separator if its already been specified in :protocol
|
||||
|
||||
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
|
||||
anchor = "##{options.delete(:anchor)}" if options.key?(:anchor)
|
||||
|
||||
anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options.key?(:anchor)
|
||||
url << Routing::Routes.generate(options, {})
|
||||
return "#{url}#{anchor}"
|
||||
end
|
||||
|
||||
url << anchor if anchor
|
||||
|
||||
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]
|
||||
RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root]
|
||||
def initialize(request, parameters)
|
||||
@request, @parameters = request, parameters
|
||||
end
|
||||
|
||||
def rewrite(options = {})
|
||||
rewrite_url(rewrite_path(options), options)
|
||||
rewrite_url(options)
|
||||
end
|
||||
|
||||
def to_str
|
||||
|
@ -78,16 +89,20 @@ module ActionController
|
|||
|
||||
private
|
||||
# Given a path and options, returns a rewritten URL string
|
||||
def rewrite_url(path, options)
|
||||
def rewrite_url(options)
|
||||
rewritten_url = ""
|
||||
|
||||
unless options[:only_path]
|
||||
rewritten_url << (options[:protocol] || @request.protocol)
|
||||
rewritten_url << "://" unless rewritten_url.match("://")
|
||||
rewritten_url << rewrite_authentication(options)
|
||||
rewritten_url << (options[:host] || @request.host_with_port)
|
||||
rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
|
||||
end
|
||||
|
||||
path = rewrite_path(options)
|
||||
rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
rewritten_url << path
|
||||
rewritten_url << '/' if options[:trailing_slash]
|
||||
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
|
||||
rewritten_url << "##{options[:anchor]}" if options[:anchor]
|
||||
|
||||
rewritten_url
|
||||
|
@ -97,15 +112,24 @@ module ActionController
|
|||
def rewrite_path(options)
|
||||
options = options.symbolize_keys
|
||||
options.update(options[:params].symbolize_keys) if options[:params]
|
||||
|
||||
if (overwrite = options.delete(:overwrite_params))
|
||||
options.update(@parameters.symbolize_keys)
|
||||
options.update(overwrite)
|
||||
options.update(overwrite.symbolize_keys)
|
||||
end
|
||||
RESERVED_OPTIONS.each {|k| options.delete k}
|
||||
|
||||
RESERVED_OPTIONS.each { |k| options.delete(k) }
|
||||
|
||||
# Generates the query string, too
|
||||
Routing::Routes.generate(options, @request.symbolized_path_parameters)
|
||||
end
|
||||
|
||||
def rewrite_authentication(options)
|
||||
if options[:user] && options[:password]
|
||||
"#{CGI.escape(options.delete(:user))}:#{CGI.escape(options.delete(:password))}@"
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
require File.dirname(__FILE__) + '/tokenizer'
|
||||
require File.dirname(__FILE__) + '/node'
|
||||
require File.dirname(__FILE__) + '/selector'
|
||||
require 'html/tokenizer'
|
||||
require 'html/node'
|
||||
require 'html/selector'
|
||||
require 'html/sanitizer'
|
||||
|
||||
module HTML #:nodoc:
|
||||
|
||||
# A top-level HTMl document. You give it a body of text, and it will parse that
|
||||
# text into a tree of nodes.
|
||||
class Document #:nodoc:
|
||||
|
@ -23,6 +23,9 @@ module HTML #:nodoc:
|
|||
if node.tag?
|
||||
if node_stack.length > 1 && node.closing == :close
|
||||
if node_stack.last.name == node.name
|
||||
if node_stack.last.children.empty?
|
||||
node_stack.last.children << Text.new(node_stack.last, node.line, node.position, "")
|
||||
end
|
||||
node_stack.pop
|
||||
else
|
||||
open_start = node_stack.last.position - 20
|
||||
|
|
173
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
vendored
Normal file
173
vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
vendored
Normal file
|
@ -0,0 +1,173 @@
|
|||
module HTML
|
||||
class Sanitizer
|
||||
def sanitize(text, options = {})
|
||||
return text unless sanitizeable?(text)
|
||||
tokenize(text, options).join
|
||||
end
|
||||
|
||||
def sanitizeable?(text)
|
||||
!(text.nil? || text.empty? || !text.index("<"))
|
||||
end
|
||||
|
||||
protected
|
||||
def tokenize(text, options)
|
||||
tokenizer = HTML::Tokenizer.new(text)
|
||||
result = []
|
||||
while token = tokenizer.next
|
||||
node = Node.parse(nil, 0, 0, token, false)
|
||||
process_node node, result, options
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def process_node(node, result, options)
|
||||
result << node.to_s
|
||||
end
|
||||
end
|
||||
|
||||
class FullSanitizer < Sanitizer
|
||||
def sanitize(text, options = {})
|
||||
result = super
|
||||
# strip any comments, and if they have a newline at the end (ie. line with
|
||||
# only a comment) strip that too
|
||||
result.gsub!(/<!--(.*?)-->[\n]?/m, "") if result
|
||||
# Recurse - handle all dirty nested tags
|
||||
result == text ? result : sanitize(result, options)
|
||||
end
|
||||
|
||||
def process_node(node, result, options)
|
||||
result << node.to_s if node.class == HTML::Text
|
||||
end
|
||||
end
|
||||
|
||||
class LinkSanitizer < FullSanitizer
|
||||
cattr_accessor :included_tags, :instance_writer => false
|
||||
self.included_tags = Set.new(%w(a href))
|
||||
|
||||
def sanitizeable?(text)
|
||||
!(text.nil? || text.empty? || !((text.index("<a") || text.index("<href")) && text.index(">")))
|
||||
end
|
||||
|
||||
protected
|
||||
def process_node(node, result, options)
|
||||
result << node.to_s unless node.is_a?(HTML::Tag) && included_tags.include?(node.name)
|
||||
end
|
||||
end
|
||||
|
||||
class WhiteListSanitizer < Sanitizer
|
||||
[:protocol_separator, :uri_attributes, :allowed_attributes, :allowed_tags, :allowed_protocols, :bad_tags,
|
||||
:allowed_css_properties, :allowed_css_keywords, :shorthand_css_properties].each do |attr|
|
||||
class_inheritable_accessor attr, :instance_writer => false
|
||||
end
|
||||
|
||||
# A regular expression of the valid characters used to separate protocols like
|
||||
# the ':' in 'http://foo.com'
|
||||
self.protocol_separator = /:|(�*58)|(p)|(%|%)3A/
|
||||
|
||||
# Specifies a Set of HTML attributes that can have URIs.
|
||||
self.uri_attributes = Set.new(%w(href src cite action longdesc xlink:href lowsrc))
|
||||
|
||||
# Specifies a Set of 'bad' tags that the #sanitize helper will remove completely, as opposed
|
||||
# to just escaping harmless tags like <font>
|
||||
self.bad_tags = Set.new(%w(script))
|
||||
|
||||
# Specifies the default Set of tags that the #sanitize helper will allow unscathed.
|
||||
self.allowed_tags = Set.new(%w(strong em b i p code pre tt samp kbd var sub
|
||||
sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dt dd abbr
|
||||
acronym a img blockquote del ins))
|
||||
|
||||
# Specifies the default Set of html attributes that the #sanitize helper will leave
|
||||
# in the allowed tag.
|
||||
self.allowed_attributes = Set.new(%w(href src width height alt cite datetime title class name xml:lang abbr))
|
||||
|
||||
# Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept.
|
||||
self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto
|
||||
feed svn urn aim rsync tag ssh sftp rtsp afs))
|
||||
|
||||
# Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept.
|
||||
self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse
|
||||
border-color border-left-color border-right-color border-top-color clear color cursor direction display
|
||||
elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height
|
||||
overflow pause pause-after pause-before pitch pitch-range richness speak speak-header speak-numeral speak-punctuation
|
||||
speech-rate stress text-align text-decoration text-indent unicode-bidi vertical-align voice-family volume white-space
|
||||
width))
|
||||
|
||||
# Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept.
|
||||
self.allowed_css_keywords = Set.new(%w(auto aqua black block blue bold both bottom brown center
|
||||
collapse dashed dotted fuchsia gray green !important italic left lime maroon medium none navy normal
|
||||
nowrap olive pointer purple red right solid silver teal top transparent underline white yellow))
|
||||
|
||||
# Specifies the default Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers.
|
||||
self.shorthand_css_properties = Set.new(%w(background border margin padding))
|
||||
|
||||
# Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute
|
||||
def sanitize_css(style)
|
||||
# disallow urls
|
||||
style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
|
||||
|
||||
# gauntlet
|
||||
if style !~ /^([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*$/ ||
|
||||
style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$))*$/
|
||||
return ''
|
||||
end
|
||||
|
||||
clean = []
|
||||
style.scan(/([-\w]+)\s*:\s*([^:;]*)/) do |prop,val|
|
||||
if allowed_css_properties.include?(prop.downcase)
|
||||
clean << prop + ': ' + val + ';'
|
||||
elsif shorthand_css_properties.include?(prop.split('-')[0].downcase)
|
||||
unless val.split().any? do |keyword|
|
||||
!allowed_css_keywords.include?(keyword) &&
|
||||
keyword !~ /^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$/
|
||||
end
|
||||
clean << prop + ': ' + val + ';'
|
||||
end
|
||||
end
|
||||
end
|
||||
clean.join(' ')
|
||||
end
|
||||
|
||||
protected
|
||||
def tokenize(text, options)
|
||||
options[:parent] = []
|
||||
options[:attributes] ||= allowed_attributes
|
||||
options[:tags] ||= allowed_tags
|
||||
super
|
||||
end
|
||||
|
||||
def process_node(node, result, options)
|
||||
result << case node
|
||||
when HTML::Tag
|
||||
if node.closing == :close
|
||||
options[:parent].shift
|
||||
else
|
||||
options[:parent].unshift node.name
|
||||
end
|
||||
|
||||
process_attributes_for node, options
|
||||
|
||||
options[:tags].include?(node.name) ? node : nil
|
||||
else
|
||||
bad_tags.include?(options[:parent].first) ? nil : node.to_s.gsub(/</, "<")
|
||||
end
|
||||
end
|
||||
|
||||
def process_attributes_for(node, options)
|
||||
return unless node.attributes
|
||||
node.attributes.keys.each do |attr_name|
|
||||
value = node.attributes[attr_name].to_s
|
||||
|
||||
if !options[:attributes].include?(attr_name) || contains_bad_protocols?(attr_name, value)
|
||||
node.attributes.delete(attr_name)
|
||||
else
|
||||
node.attributes[attr_name] = attr_name == 'style' ? sanitize_css(value) : CGI::escapeHTML(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def contains_bad_protocols?(attr_name, value)
|
||||
uri_attributes.include?(attr_name) &&
|
||||
(value =~ /(^[^\/:]*):|(�*58)|(p)|(%|%)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -240,19 +240,24 @@ module HTML
|
|||
raise ArgumentError, "CSS expression cannot be empty" if selector.empty?
|
||||
@source = ""
|
||||
values = values[0] if values.size == 1 && values[0].is_a?(Array)
|
||||
|
||||
# We need a copy to determine if we failed to parse, and also
|
||||
# preserve the original pass by-ref statement.
|
||||
statement = selector.strip.dup
|
||||
|
||||
# Create a simple selector, along with negation.
|
||||
simple_selector(statement, values).each { |name, value| instance_variable_set("@#{name}", value) }
|
||||
|
||||
@alternates = []
|
||||
@depends = nil
|
||||
|
||||
# Alternative selector.
|
||||
if statement.sub!(/^\s*,\s*/, "")
|
||||
second = Selector.new(statement, values)
|
||||
(@alternates ||= []) << second
|
||||
@alternates << second
|
||||
# If there are alternate selectors, we group them in the top selector.
|
||||
if alternates = second.instance_variable_get(:@alternates)
|
||||
second.instance_variable_set(:@alternates, nil)
|
||||
second.instance_variable_set(:@alternates, [])
|
||||
@alternates.concat alternates
|
||||
end
|
||||
@source << " , " << second.to_s
|
||||
|
@ -412,7 +417,7 @@ module HTML
|
|||
|
||||
# If this selector is part of the group, try all the alternative
|
||||
# selectors (unless first_only).
|
||||
if @alternates && (!first_only || !matches)
|
||||
if !first_only || !matches
|
||||
@alternates.each do |alternate|
|
||||
break if matches && first_only
|
||||
if subset = alternate.match(element, first_only)
|
||||
|
@ -789,15 +794,15 @@ module HTML
|
|||
# eventually, and array of substitution values.
|
||||
#
|
||||
# This method is called from four places, so it helps to put it here
|
||||
# for resue. The only logic deals with the need to detect comma
|
||||
# for reuse. The only logic deals with the need to detect comma
|
||||
# separators (alternate) and apply them to the selector group of the
|
||||
# top selector.
|
||||
def next_selector(statement, values)
|
||||
second = Selector.new(statement, values)
|
||||
# If there are alternate selectors, we group them in the top selector.
|
||||
if alternates = second.instance_variable_get(:@alternates)
|
||||
second.instance_variable_set(:@alternates, nil)
|
||||
(@alternates ||= []).concat alternates
|
||||
second.instance_variable_set(:@alternates, [])
|
||||
@alternates.concat alternates
|
||||
end
|
||||
second
|
||||
end
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
require 'rexml/document'
|
||||
|
||||
# SimpleXML like xml parser. Written by leon breet from the ruby on rails Mailing list
|
||||
class XmlNode #:nodoc:
|
||||
attr :node
|
||||
|
||||
def initialize(node, options = {})
|
||||
@node = node
|
||||
@children = {}
|
||||
@raise_errors = options[:raise_errors]
|
||||
end
|
||||
|
||||
def self.from_xml(xml_or_io)
|
||||
document = REXML::Document.new(xml_or_io)
|
||||
if document.root
|
||||
XmlNode.new(document.root)
|
||||
else
|
||||
XmlNode.new(document)
|
||||
end
|
||||
end
|
||||
|
||||
def node_encoding
|
||||
@node.encoding
|
||||
end
|
||||
|
||||
def node_name
|
||||
@node.name
|
||||
end
|
||||
|
||||
def node_value
|
||||
@node.text
|
||||
end
|
||||
|
||||
def node_value=(value)
|
||||
@node.text = value
|
||||
end
|
||||
|
||||
def xpath(expr)
|
||||
matches = nil
|
||||
REXML::XPath.each(@node, expr) do |element|
|
||||
matches ||= XmlNodeList.new
|
||||
matches << (@children[element] ||= XmlNode.new(element))
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
def method_missing(name, *args)
|
||||
name = name.to_s
|
||||
nodes = nil
|
||||
@node.each_element(name) do |element|
|
||||
nodes ||= XmlNodeList.new
|
||||
nodes << (@children[element] ||= XmlNode.new(element))
|
||||
end
|
||||
nodes
|
||||
end
|
||||
|
||||
def <<(node)
|
||||
if node.is_a? REXML::Node
|
||||
child = node
|
||||
elsif node.respond_to? :node
|
||||
child = node.node
|
||||
end
|
||||
@node.add_element child
|
||||
@children[child] ||= XmlNode.new(child)
|
||||
end
|
||||
|
||||
def [](name)
|
||||
@node.attributes[name.to_s]
|
||||
end
|
||||
|
||||
def []=(name, value)
|
||||
@node.attributes[name.to_s] = value
|
||||
end
|
||||
|
||||
def to_s
|
||||
@node.to_s
|
||||
end
|
||||
|
||||
def to_i
|
||||
to_s.to_i
|
||||
end
|
||||
end
|
||||
|
||||
class XmlNodeList < Array #:nodoc:
|
||||
def [](i)
|
||||
i.is_a?(String) ? super(0)[i] : super(i)
|
||||
end
|
||||
|
||||
def []=(i, value)
|
||||
i.is_a?(String) ? self[0][i] = value : super(i, value)
|
||||
end
|
||||
|
||||
def method_missing(name, *args)
|
||||
name = name.to_s
|
||||
self[0].__send__(name, *args)
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load diff
|
@ -12,7 +12,8 @@ module ActionController #:nodoc:
|
|||
# parameters being set, or without certain session values existing.
|
||||
#
|
||||
# When a verification is violated, values may be inserted into the flash, and
|
||||
# a specified redirection is triggered.
|
||||
# a specified redirection is triggered. If no specific action is configured,
|
||||
# verification failures will by default result in a 400 Bad Request response.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
|
@ -42,37 +43,37 @@ module ActionController #:nodoc:
|
|||
# the user is redirected to a different action. The +options+ parameter
|
||||
# is a hash consisting of the following key/value pairs:
|
||||
#
|
||||
# * <tt>:params</tt>: a single key or an array of keys that must
|
||||
# * <tt>:params</tt> - a single key or an array of keys that must
|
||||
# be in the <tt>params</tt> hash in order for the action(s) to be safely
|
||||
# called.
|
||||
# * <tt>:session</tt>: a single key or an array of keys that must
|
||||
# * <tt>:session</tt> - a single key or an array of keys that must
|
||||
# be in the <tt>session</tt> in order for the action(s) to be safely called.
|
||||
# * <tt>:flash</tt>: a single key or an array of keys that must
|
||||
# * <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
|
||||
# * <tt>:method</tt> - a single key or an array of keys--any one of which
|
||||
# must match the current request method in order for the action(s) to
|
||||
# be safely called. (The key should be a symbol: <tt>:get</tt> or
|
||||
# <tt>:post</tt>, for example.)
|
||||
# * <tt>:xhr</tt>: true/false option to ensure that the request is coming
|
||||
# * <tt>:xhr</tt> - true/false option to ensure that the request is coming
|
||||
# from an Ajax call or not.
|
||||
# * <tt>:add_flash</tt>: a hash of name/value pairs that should be merged
|
||||
# * <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
|
||||
# * <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
|
||||
# * <tt>:redirect_to</tt> - the redirection parameters to be used when
|
||||
# redirecting if the prerequisites cannot be satisfied. You can
|
||||
# redirect either to named route or to the action in some controller.
|
||||
# * <tt>:render</tt>: the render parameters to be used when
|
||||
# * <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
|
||||
# * <tt>:only</tt> - only apply this verification to the actions specified
|
||||
# in the associated array (may also be a single value).
|
||||
# * <tt>:except</tt>: do not apply this verification to the actions
|
||||
# * <tt>:except</tt> - do not apply this verification to the actions
|
||||
# specified in the associated array (may also be a single value).
|
||||
def verify(options={})
|
||||
filter_opts = { :only => options[:only], :except => options[:except] }
|
||||
before_filter(filter_opts) do |c|
|
||||
c.send :verify_action, options
|
||||
c.send! :verify_action, options
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -81,7 +82,7 @@ module ActionController #:nodoc:
|
|||
prereqs_invalid =
|
||||
[*options[:params] ].find { |v| params[v].nil? } ||
|
||||
[*options[:session]].find { |v| session[v].nil? } ||
|
||||
[*options[:flash] ].find { |v| flash[v].nil? }
|
||||
[*options[:flash] ].find { |v| flash[v].nil? }
|
||||
|
||||
if !prereqs_invalid && options[:method]
|
||||
prereqs_invalid ||=
|
||||
|
@ -93,16 +94,21 @@ module ActionController #:nodoc:
|
|||
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]
|
||||
options[:redirect_to] = self.send(options[:redirect_to]) if options[:redirect_to].is_a? Symbol
|
||||
redirect_to(options[:redirect_to]) if options[:redirect_to]
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
unless performed?
|
||||
case
|
||||
when options[:render]
|
||||
render(options[:render])
|
||||
when options[:redirect_to]
|
||||
options[:redirect_to] = self.send!(options[:redirect_to]) if options[:redirect_to].is_a?(Symbol)
|
||||
redirect_to(options[:redirect_to])
|
||||
else
|
||||
head(:bad_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private :verify_action
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue