Merge branch 'master' into active_model

Conflicts:
	activemodel/lib/active_model/core.rb
	activemodel/test/cases/state_machine/event_test.rb
	activemodel/test/cases/state_machine/state_transition_test.rb
	activerecord/lib/active_record/validations.rb
	activerecord/test/cases/validations/i18n_validation_test.rb
	activeresource/lib/active_resource.rb
	activeresource/test/abstract_unit.rb
This commit is contained in:
Joshua Peek 2009-05-29 16:06:21 -05:00
commit 69742ca8fa
570 changed files with 11461 additions and 5820 deletions

View File

@ -1,6 +1,5 @@
require 'rake'
require 'rake/rdoctask'
require 'rake/contrib/sshpublisher'
env = %(PKG_BUILD="#{ENV['PKG_BUILD']}") if ENV['PKG_BUILD']
@ -13,12 +12,14 @@ end
desc 'Run all tests by default'
task :default => :test
%w(test rdoc pgem package release).each do |task_name|
%w(test isolated_test rdoc pgem package release).each do |task_name|
desc "Run #{task_name} task for all projects"
task task_name do
errors = []
PROJECTS.each do |project|
system %(cd #{project} && #{env} #{$0} #{task_name})
system(%(cd #{project} && #{env} #{$0} #{task_name})) || errors << project
end
fail("Errors in #{errors.join(', ')}") unless errors.empty?
end
end
@ -74,6 +75,7 @@ end
desc "Publish API docs for Rails as a whole and for each component"
task :pdoc => :rdoc do
require 'rake/contrib/sshpublisher'
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/api", "doc/rdoc").upload
PROJECTS.each do |project|
system %(cd #{project} && #{env} #{$0} pdoc)

View File

@ -28,6 +28,12 @@ Rake::TestTask.new { |t|
t.warning = false
}
task :isolated_test do
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
Dir.glob("test/*_test.rb").all? do |file|
system(ruby, '-Ilib:test', file)
end or raise "Failures"
end
# Generate the RDoc documentation
Rake::RDocTask.new { |rdoc|

View File

@ -465,48 +465,48 @@ module ActionMailer #:nodoc:
def create!(method_name, *parameters) #:nodoc:
initialize_defaults(method_name)
__send__(method_name, *parameters)
# If an explicit, textual body has not been set, we check assumptions.
unless String === @body
# First, we look to see if there are any likely templates that match,
# which include the content-type in their file name (i.e.,
# "the_template_file.text.html.erb", etc.). Only do this if parts
# have not already been specified manually.
if @parts.empty?
Dir.glob("#{template_path}/#{@template}.*").each do |path|
template = template_root.find_by_parts("#{mailer_name}/#{File.basename(path)}")
# Skip unless template has a multipart format
next unless template && template.multipart?
# if @parts.empty?
template_root.find_all_by_parts(@template, {}, template_path).each do |template|
@parts << Part.new(
:content_type => template.content_type,
:content_type => template.mime_type ? template.mime_type.to_s : "text/plain",
:disposition => "inline",
:charset => charset,
:body => render_template(template, @body)
)
end
unless @parts.empty?
if @parts.size > 1
@content_type = "multipart/alternative" if @content_type !~ /^multipart/
@parts = sort_parts(@parts, @implicit_parts_order)
end
end
# end
# Then, if there were such templates, we check to see if we ought to
# also render a "normal" template (without the content type). If a
# normal template exists (or if there were no implicit parts) we render
# it.
template_exists = @parts.empty?
template_exists ||= template_root.find_by_parts("#{mailer_name}/#{@template}")
@body = render_message(@template, @body) if template_exists
# ====
# TODO: Revisit this
# template_exists = @parts.empty?
# template_exists ||= template_root.find_by_parts("#{mailer_name}/#{@template}")
# @body = render_message(@template, @body) if template_exists
# Finally, if there are other message parts and a textual body exists,
# we shift it onto the front of the parts and set the body to nil (so
# that create_mail doesn't try to render it in addition to the parts).
if !@parts.empty? && String === @body
@parts.unshift Part.new(:charset => charset, :body => @body)
@body = nil
end
# ====
# TODO: Revisit this
# if !@parts.empty? && String === @body
# @parts.unshift Part.new(:charset => charset, :body => @body)
# @body = nil
# end
end
# If this is a multipart e-mail add the mime_version if it is not
@ -555,12 +555,13 @@ module ActionMailer #:nodoc:
end
def render_template(template, body)
if template.respond_to?(:content_type)
@current_template_content_type = template.content_type
if template.respond_to?(:mime_type)
@current_template_content_type = template.mime_type && template.mime_type.to_sym.to_s
end
@template = initialize_template_class(body)
layout = _pick_layout(layout, true) unless template.exempt_from_layout?
layout = _pick_layout(layout, true) unless
ActionController::Base.exempt_from_layout.include?(template.handler)
@template._render_template_with_layout(template, layout, {})
ensure
@current_template_content_type = nil
@ -580,11 +581,11 @@ module ActionMailer #:nodoc:
if file
prefix = mailer_name unless file =~ /\//
template = view_paths.find_by_parts(file, formats, prefix)
template = view_paths.find_by_parts(file, {:formats => formats}, prefix)
end
layout = _pick_layout(layout,
!template || !template.exempt_from_layout?)
!template || ActionController::Base.exempt_from_layout.include?(template.handler))
if template
@template._render_template_with_layout(template, layout, opts)
@ -611,7 +612,7 @@ module ActionMailer #:nodoc:
end
def template_path
"#{template_root}/#{mailer_name}"
"#{mailer_name}"
end
def initialize_template_class(assigns)
@ -622,7 +623,7 @@ module ActionMailer #:nodoc:
def sort_parts(parts, order = [])
order = order.collect { |s| s.downcase }
parts = parts.sort do |a, b|
a_ct = a.content_type.downcase
b_ct = b.content_type.downcase
@ -663,10 +664,13 @@ module ActionMailer #:nodoc:
headers.each { |k, v| m[k] = v }
real_content_type, ctype_attrs = parse_content_type
if @parts.empty?
m.set_content_type(real_content_type, nil, ctype_attrs)
m.body = normalize_new_lines(body)
elsif @parts.size == 1 && @parts.first.parts.empty?
m.set_content_type(real_content_type, nil, ctype_attrs)
m.body = normalize_new_lines(@parts.first.body)
else
if String === body
part = TMail::Mail.new

View File

@ -518,6 +518,7 @@ module TMail
def parse_body( f = nil )
return if @body_parsed
if f
parse_body_0 f
else

View File

@ -12,6 +12,7 @@ end
require 'tmail'
require 'active_support/core_ext/kernel/reporting'
silence_warnings do
TMail::Encoder.const_set("MAX_LINE_LEN", 200)
end

View File

@ -1,4 +1,4 @@
require File.dirname(__FILE__) + '/abstract_unit'
require 'abstract_unit'
require 'action_mailer/adv_attr_accessor'
class AdvAttrTest < Test::Unit::TestCase
@ -15,6 +15,4 @@ class AdvAttrTest < Test::Unit::TestCase
assert_raise(ArgumentError) {bob.name 'x', 'y'}
end
end
end

View File

@ -1 +1 @@
Have a lovely picture, from me. Enjoy!
Have some dots. Enjoy!

View File

@ -1,2 +0,0 @@
xml.instruct!
xml.test

View File

@ -6,10 +6,10 @@ class FunkyPathMailer < ActionMailer::Base
def multipart_with_template_path_with_dots(recipient)
recipients recipient
subject "Have a lovely picture"
subject "This path has dots"
from "Chad Fowler <chad@chadfowler.com>"
attachment :content_type => "image/jpeg",
:body => "not really a jpeg, we're only testing, after all"
attachment :content_type => "text/plain",
:body => "dots dots dots..."
end
end
@ -338,6 +338,7 @@ class ActionMailerTest < Test::Unit::TestCase
def test_nested_parts_with_body
created = nil
TestMailer.create_nested_multipart_with_body(@recipient)
assert_nothing_raised { created = TestMailer.create_nested_multipart_with_body(@recipient)}
assert_equal 1,created.parts.size
assert_equal 2,created.parts.first.parts.size
@ -351,8 +352,8 @@ class ActionMailerTest < Test::Unit::TestCase
def test_attachment_with_custom_header
created = nil
assert_nothing_raised { created = TestMailer.create_attachment_with_custom_header(@recipient)}
assert_equal "<test@test.com>", created.parts[1].header['content-id'].to_s
assert_nothing_raised { created = TestMailer.create_attachment_with_custom_header(@recipient) }
assert created.parts.any? { |p| p.header['content-id'].to_s == "<test@test.com>" }
end
def test_signed_up
@ -824,7 +825,7 @@ EOF
assert_equal 3, mail.parts.length
assert_equal "1.0", mail.mime_version
assert_equal "multipart/alternative", mail.content_type
assert_equal "text/yaml", mail.parts[0].content_type
assert_equal "application/x-yaml", mail.parts[0].content_type
assert_equal "utf-8", mail.parts[0].sub_header("content-type", "charset")
assert_equal "text/plain", mail.parts[1].content_type
assert_equal "utf-8", mail.parts[1].sub_header("content-type", "charset")
@ -835,11 +836,11 @@ EOF
def test_implicitly_multipart_messages_with_custom_order
assert ActionView::Template.template_handler_extensions.include?("bak"), "bak extension was not registered"
mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["text/yaml", "text/plain"])
mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["application/x-yaml", "text/plain"])
assert_equal 3, mail.parts.length
assert_equal "text/html", mail.parts[0].content_type
assert_equal "text/plain", mail.parts[1].content_type
assert_equal "text/yaml", mail.parts[2].content_type
assert_equal "application/x-yaml", mail.parts[2].content_type
end
def test_implicitly_multipart_messages_with_charset
@ -938,8 +939,8 @@ EOF
def test_multipart_with_template_path_with_dots
mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient)
assert_equal 2, mail.parts.length
assert_equal 'text/plain', mail.parts[0].content_type
assert_equal 'utf-8', mail.parts[0].charset
assert "text/plain", mail.parts[1].content_type
assert "utf-8", mail.parts[1].charset
end
def test_custom_content_type_attributes
@ -994,13 +995,13 @@ end
class InheritableTemplateRootTest < Test::Unit::TestCase
def test_attr
expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
expected = File.expand_path("#{File.dirname(__FILE__)}/fixtures/path.with.dots")
assert_equal expected, FunkyPathMailer.template_root.to_s
sub = Class.new(FunkyPathMailer)
sub.template_root = 'test/path'
assert_equal 'test/path', sub.template_root.to_s
assert_equal File.expand_path('test/path'), sub.template_root.to_s
assert_equal expected, FunkyPathMailer.template_root.to_s
end
end

View File

@ -1,5 +1,15 @@
*Edge*
* Ruby 1.9: ERB template encoding using a magic comment at the top of the file. [Jeremy Kemper]
<%# encoding: utf-8 %>
* Change integration test helpers to accept Rack environment instead of just HTTP Headers [Pratik Naik]
Before : get '/path', {}, 'Accept' => 'text/javascript'
After : get '/path', {}, 'HTTP_ACCEPT' => 'text/javascript'
* Instead of checking Rails.env.test? in Failsafe middleware, check env["rails.raise_exceptions"] [Bryan Helmkamp]
* Fixed that TestResponse.cookies was returning cookies unescaped #1867 [Doug McInnes]

View File

@ -4,7 +4,6 @@ require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rake/contrib/sshpublisher'
require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version')
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
@ -23,26 +22,59 @@ task :default => [ :test ]
# Run the unit tests
desc "Run all unit tests"
task :test => [:test_action_pack, :test_active_record_integration]
task :test => [:test_action_pack, :test_active_record_integration, :test_new_base, :test_new_base_on_old_tests]
test_lib_dirs = [ENV["NEW"] ? "test/new_base" : "test", "test/lib"]
Rake::TestTask.new(:test_action_pack) do |t|
t.libs << "test"
t.libs.concat test_lib_dirs
# make sure we include the tests in alphabetical order as on some systems
# this will not happen automatically and the tests (as a whole) will error
t.test_files = Dir.glob( "test/[cdft]*/**/*_test.rb" ).sort
t.test_files = Dir.glob( "test/{controller,dispatch,template,html-scanner}/**/*_test.rb" ).sort
t.verbose = true
#t.warning = true
end
task :isolated_test do
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
Dir.glob("test/{controller,dispatch,template}/**/*_test.rb").all? do |file|
system(ruby, "-Ilib:#{test_lib_dirs * ':'}", file)
end or raise "Failures"
end
desc 'ActiveRecord Integration Tests'
Rake::TestTask.new(:test_active_record_integration) do |t|
t.libs << "test"
t.libs.concat test_lib_dirs
t.test_files = Dir.glob("test/activerecord/*_test.rb")
t.verbose = true
end
desc 'New Controller Tests'
Rake::TestTask.new(:test_new_base) do |t|
t.libs << "test/new_base" << "test/lib"
t.test_files = Dir.glob("test/{abstract_controller,new_base}/*_test.rb")
t.verbose = true
end
desc 'Old Controller Tests on New Base'
Rake::TestTask.new(:test_new_base_on_old_tests) do |t|
t.libs << "test/new_base" << "test/lib"
t.verbose = true
# ==== Not ported
# * filters
t.test_files = Dir.glob( "test/{dispatch,template}/**/*_test.rb" ).sort + %w(
action_pack_assertions addresses_render assert_select
base benchmark caching capture content_type cookie dispatcher
filter_params flash helper http_basic_authentication
http_digest_authentication integration layout logging mime_responds
record_identifier redirect render render_js render_json
render_other render_xml request_forgery_protection rescue
resources routing selector send_file test url_rewriter
verification view_paths webservice
).map { |name| "test/controller/#{name}_test.rb" }
end
# Genereate the RDoc documentation
@ -136,12 +168,14 @@ task :update_js => [ :update_scriptaculous ]
desc "Publish the API documentation"
task :pgem => [:package] do
require 'rake/contrib/sshpublisher'
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
require 'rake/contrib/sshpublisher'
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload
end

View File

@ -0,0 +1,58 @@
# Pass NEW=1 to run with the new Base
ENV['RAILS_ENV'] ||= 'production'
ENV['NO_RELOAD'] ||= '1'
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
require 'action_controller'
require 'action_controller/new_base' if ENV['NEW']
require 'benchmark'
class Runner
def initialize(app)
@app = app
end
def call(env)
env['n'].to_i.times { @app.call(env) }
@app.call(env).tap { |response| report(env, response) }
end
def report(env, response)
out = env['rack.errors']
out.puts response[0], response[1].to_yaml, '---'
response[2].each { |part| out.puts part }
out.puts '---'
end
def self.run(app, n, label = nil)
puts '=' * label.size, label, '=' * label.size if label
env = { 'n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout }
t = Benchmark.realtime { new(app).call(env) }
puts "%d ms / %d req = %.1f usec/req" % [10**3 * t, n, 10**6 * t / n]
puts
end
end
N = (ENV['N'] || 1000).to_i
class BasePostController < ActionController::Base
def index
render :text => ''
end
end
OK = [200, {}, []]
MetalPostController = lambda { OK }
if ActionController.const_defined?(:Http)
class HttpPostController < ActionController::Http
def index
self.response_body = ''
end
end
end
Runner.run(MetalPostController, N, 'metal')
Runner.run(HttpPostController.action(:index), N, 'http') if defined? HttpPostController
Runner.run(BasePostController.action(:index), N, 'base')

View File

@ -21,15 +21,9 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
begin
require 'active_support'
rescue LoadError
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
if File.directory?(activesupport_path)
$:.unshift activesupport_path
require 'active_support'
end
end
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
$:.unshift activesupport_path if File.directory?(activesupport_path)
require 'active_support'
require File.join(File.dirname(__FILE__), "action_pack")
@ -59,7 +53,7 @@ module ActionController
autoload :Redirector, 'action_controller/base/redirect'
autoload :Renderer, 'action_controller/base/render'
autoload :RequestForgeryProtection, 'action_controller/base/request_forgery_protection'
autoload :Rescue, 'action_controller/dispatch/rescue'
autoload :Rescue, 'action_controller/base/rescue'
autoload :Resources, 'action_controller/routing/resources'
autoload :Responder, 'action_controller/base/responder'
autoload :Routing, 'action_controller/routing'
@ -72,6 +66,7 @@ module ActionController
autoload :UrlRewriter, 'action_controller/routing/generation/url_rewriter'
autoload :UrlWriter, 'action_controller/routing/generation/url_rewriter'
autoload :Verification, 'action_controller/base/verification'
autoload :FilterParameterLogging, 'action_controller/base/filter_parameter_logging'
module Assertions
autoload :DomAssertions, 'action_controller/testing/assertions/dom'

View File

@ -1,5 +1,9 @@
require "active_support/core_ext/module/attr_internal"
require "active_support/core_ext/module/delegation"
module AbstractController
autoload :Base, "action_controller/abstract/base"
autoload :Benchmarker, "action_controller/abstract/benchmarker"
autoload :Callbacks, "action_controller/abstract/callbacks"
autoload :Helpers, "action_controller/abstract/helpers"
autoload :Layouts, "action_controller/abstract/layouts"
@ -7,4 +11,4 @@ module AbstractController
autoload :Renderer, "action_controller/abstract/renderer"
# === Exceptions
autoload :ActionNotFound, "action_controller/abstract/exceptions"
end
end

View File

@ -1,41 +1,115 @@
require 'active_support/core_ext/module/attr_internal'
module AbstractController
class Error < StandardError; end
class DoubleRenderError < Error
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 Base
attr_internal :response_body
attr_internal :response_obj
attr_internal :action_name
def self.process(action)
new.process(action)
class << self
attr_reader :abstract
def abstract!
@abstract = true
end
alias_method :abstract?, :abstract
def inherited(klass)
::AbstractController::Base.subclasses << klass.to_s
super
end
def subclasses
@subclasses ||= []
end
def internal_methods
controller = self
controller = controller.superclass until controller.abstract?
controller.public_instance_methods(true)
end
def process(action)
new.process(action.to_s)
end
def hidden_actions
[]
end
def action_methods
@action_methods ||=
# All public instance methods of this class, including ancestors
public_instance_methods(true).map { |m| m.to_s }.to_set -
# Except for public instance methods of Base and its ancestors
internal_methods.map { |m| m.to_s } +
# Be sure to include shadowed public instance methods of this class
public_instance_methods(false).map { |m| m.to_s } -
# And always exclude explicitly hidden actions
hidden_actions
end
end
def self.inherited(klass)
end
abstract!
def initialize
self.response_obj = {}
end
def process(action_name)
unless respond_to_action?(action_name)
raise ActionNotFound, "The action '#{action_name}' could not be found"
def process(action)
@_action_name = action_name = action.to_s
unless action_name = method_for_action(action_name)
raise ActionNotFound, "The action '#{action}' could not be found"
end
@_action_name = action_name
process_action
self.response_obj[:body] = self.response_body
process_action(action_name)
self
end
private
def process_action
respond_to?(action_name) ? send(action_name) : send(:action_missing, action_name)
def action_methods
self.class.action_methods
end
def respond_to_action?(action_name)
respond_to?(action_name) || respond_to?(:action_missing, true)
def action_method?(action)
action_methods.include?(action)
end
# It is possible for respond_to?(action_name) to be false and
# respond_to?(:action_missing) to be false if respond_to_action?
# is overridden in a subclass. For instance, ActionController::Base
# overrides it to include the case where a template matching the
# action_name is found.
def process_action(method_name)
send_action(method_name)
end
alias send_action send
def _handle_action_missing
action_missing(@_action_name)
end
# Override this to change the conditions that will raise an
# ActionNotFound error. If you accept a difference case,
# you must handle it by also overriding process_action and
# handling the case.
def method_for_action(action_name)
if action_method?(action_name) then action_name
elsif respond_to?(:action_missing, true) then "_handle_action_missing"
end
end
end
end
end

View File

@ -0,0 +1,28 @@
module AbstractController
module Benchmarker
extend ActiveSupport::Concern
depends_on Logger
module ClassMethods
def benchmark(title, log_level = ::Logger::DEBUG, use_silence = true)
if logger && logger.level >= log_level
result = nil
ms = Benchmark.ms { result = use_silence ? silence { yield } : yield }
logger.add(log_level, "#{title} (#{('%.1f' % ms)}ms)")
result
else
yield
end
end
# Silences the logger for the duration of the block.
def silence
old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger
yield
ensure
logger.level = old_logger_level if logger
end
end
end
end

View File

@ -1,28 +1,31 @@
module AbstractController
module Callbacks
setup do
include ActiveSupport::NewCallbacks
define_callbacks :process_action
extend ActiveSupport::Concern
depends_on ActiveSupport::NewCallbacks
included do
define_callbacks :process_action, "response_body"
end
def process_action
_run_process_action_callbacks(action_name) do
def process_action(method_name)
_run_process_action_callbacks(method_name) do
super
end
end
module ClassMethods
def _normalize_callback_options(options)
if only = options[:only]
only = Array(only).map {|o| "action_name == :#{o}"}.join(" || ")
only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ")
options[:per_key] = {:if => only}
end
if except = options[:except]
except = Array(except).map {|e| "action_name == :#{e}"}.join(" || ")
except = Array(except).map {|e| "action_name == '#{e}'"}.join(" || ")
options[:per_key] = {:unless => except}
end
end
[:before, :after, :around].each do |filter|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{filter}_filter(*names, &blk)
@ -33,8 +36,28 @@ module AbstractController
process_action_callback(:#{filter}, name, options)
end
end
def prepend_#{filter}_filter(*names, &blk)
options = names.last.is_a?(Hash) ? names.pop : {}
_normalize_callback_options(options)
names.push(blk) if block_given?
names.each do |name|
process_action_callback(:#{filter}, name, options.merge(:prepend => true))
end
end
def skip_#{filter}_filter(*names, &blk)
options = names.last.is_a?(Hash) ? names.pop : {}
_normalize_callback_options(options)
names.push(blk) if block_given?
names.each do |name|
skip_process_action_callback(:#{filter}, name, options)
end
end
alias_method :append_#{filter}_filter, :#{filter}_filter
RUBY_EVAL
end
end
end
end
end

View File

@ -1,3 +1,3 @@
module AbstractController
class ActionNotFound < StandardError ; end
end
class ActionNotFound < StandardError; end
end

View File

@ -1,19 +1,14 @@
module AbstractController
module Helpers
extend ActiveSupport::Concern
depends_on Renderer
setup do
included do
extlib_inheritable_accessor :master_helper_module
self.master_helper_module = Module.new
end
# def self.included(klass)
# klass.class_eval do
# extlib_inheritable_accessor :master_helper_module
# self.master_helper_module = Module.new
# end
# end
def _action_view
@_action_view ||= begin
av = super
@ -21,19 +16,38 @@ module AbstractController
av
end
end
module ClassMethods
def inherited(klass)
klass.master_helper_module = Module.new
klass.master_helper_module.__send__ :include, master_helper_module
super
end
# 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
# available to the templates.
def add_template_helper(mod)
master_helper_module.module_eval { include mod }
end
# 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, :logged_in?
#
# def current_user
# @current_user ||= User.find_by_id(session[:user])
# end
#
# def logged_in?
# current_user != nil
# end
# end
#
# In a view:
# <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%>
def helper_method(*meths)
meths.flatten.each do |meth|
master_helper_module.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
@ -43,17 +57,16 @@ module AbstractController
ruby_eval
end
end
def helper(*args, &blk)
def helper(*args, &block)
args.flatten.each do |arg|
case arg
when Module
add_template_helper(arg)
end
end
master_helper_module.module_eval(&blk) if block_given?
master_helper_module.module_eval(&block) if block_given?
end
end
end
end
end

View File

@ -1,25 +1,34 @@
module AbstractController
module Layouts
extend ActiveSupport::Concern
depends_on Renderer
included do
extlib_inheritable_accessor :_layout_conditions
self._layout_conditions = {}
end
module ClassMethods
def layout(layout)
def layout(layout, conditions = {})
unless [String, Symbol, FalseClass, NilClass].include?(layout.class)
raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
end
conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
self._layout_conditions = conditions
@_layout = layout || false # Converts nil to false
_write_layout_method
end
def _implied_layout_name
name.underscore
end
# Takes the specified layout and creates a _layout method to be called
# by _default_layout
#
#
# If the specified layout is a:
# String:: return the string
# Symbol:: call the method specified by the symbol
@ -30,15 +39,15 @@ module AbstractController
def _write_layout_method
case @_layout
when String
self.class_eval %{def _layout() #{@_layout.inspect} end}
self.class_eval %{def _layout(details) #{@_layout.inspect} end}
when Symbol
self.class_eval %{def _layout() #{@_layout} end}
self.class_eval %{def _layout(details) #{@_layout} end}
when false
self.class_eval %{def _layout() end}
self.class_eval %{def _layout(details) end}
else
self.class_eval %{
def _layout
if view_paths.find_by_parts?("#{_implied_layout_name}", formats, "layouts")
def _layout(details)
if view_paths.find_by_parts?("#{_implied_layout_name}", details, "layouts")
"#{_implied_layout_name}"
else
super
@ -48,35 +57,51 @@ module AbstractController
end
end
end
def _render_template(template, options)
_action_view._render_template_with_layout(template, options[:_layout])
end
private
def _layout() end # This will be overwritten
def _layout_for_name(name)
# This will be overwritten
def _layout(details)
end
# :api: plugin
# ====
# Override this to mutate the inbound layout name
def _layout_for_name(name, details = {:formats => formats})
unless [String, FalseClass, NilClass].include?(name.class)
raise ArgumentError, "String, false, or nil expected; you passed #{name.inspect}"
end
name && view_paths.find_by_parts(name, formats, "layouts")
name && view_paths.find_by_parts(name, details, _layout_prefix(name))
end
def _default_layout(require_layout = false)
if require_layout && !_layout
raise ArgumentError,
# TODO: Decide if this is the best hook point for the feature
def _layout_prefix(name)
"layouts"
end
def _default_layout(require_layout = false, details = {:formats => formats})
if require_layout && _action_has_layout? && !_layout(details)
raise ArgumentError,
"There was no default layout for #{self.class} in #{view_paths.inspect}"
end
begin
layout = _layout_for_name(_layout)
_layout_for_name(_layout(details), details) if _action_has_layout?
rescue NameError => e
raise NoMethodError,
raise NoMethodError,
"You specified #{@_layout.inspect} as the layout, but no such method was found"
end
end
def _action_has_layout?
conditions = _layout_conditions
if only = conditions[:only]
only.include?(action_name)
elsif except = conditions[:except]
!except.include?(action_name)
else
true
end
end
end
end
end

View File

@ -1,7 +1,45 @@
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/logger'
module AbstractController
module Logger
setup do
extend ActiveSupport::Concern
class DelayedLog
def initialize(&blk)
@blk = blk
end
def to_s
@blk.call
end
alias to_str to_s
end
included do
cattr_accessor :logger
end
def process(action)
ret = super
if logger
log = DelayedLog.new do
"\n\nProcessing #{self.class.name}\##{action_name} " \
"to #{request.formats} " \
"(for #{request_origin}) [#{request.method.to_s.upcase}]"
end
logger.info(log)
end
ret
end
def request_origin
# this *needs* to be cached!
# otherwise you'd get different results if calling it more than once
@request_origin ||= "#{request.remote_ip} at #{Time.now.to_s(:db)}"
end
end
end
end

View File

@ -2,52 +2,63 @@ require "action_controller/abstract/logger"
module AbstractController
module Renderer
extend ActiveSupport::Concern
depends_on AbstractController::Logger
setup do
included do
attr_internal :formats
extlib_inheritable_accessor :_view_paths
self._view_paths ||= ActionView::PathSet.new
end
def _action_view
@_action_view ||= ActionView::Base.new(self.class.view_paths, {}, self)
@_action_view ||= ActionView::Base.new(self.class.view_paths, {}, self)
end
def render(options = {})
self.response_body = render_to_body(options)
def render(*args)
if response_body
raise AbstractController::DoubleRenderError, "OMG"
end
self.response_body = render_to_body(*args)
end
# Raw rendering of a template to a Rack-compatible body.
# ====
# @option _prefix<String> The template's path prefix
# @option _layout<String> The relative path to the layout template to use
#
#
# :api: plugin
def render_to_body(options = {})
name = options[:_template_name] || action_name
template = options[:_template] || view_paths.find_by_parts(name.to_s, formats, options[:_prefix])
_render_template(template, options)
# TODO: Refactor so we can just use the normal template logic for this
if options[:_partial_object]
_action_view._render_partial_from_controller(options)
else
_determine_template(options)
_render_template(options)
end
end
# Raw rendering of a template to a string.
# ====
# @option _prefix<String> The template's path prefix
# @option _layout<String> The relative path to the layout template to use
#
#
# :api: plugin
def render_to_string(options = {})
AbstractController::Renderer.body_to_s(render_to_body(options))
end
def _render_template(template, options)
_action_view._render_template_with_layout(template)
def _render_template(options)
_action_view._render_template_from_controller(options[:_template], options[:_layout], options, options[:_partial])
end
def view_paths()
_view_paths
end
def view_paths() _view_paths end
# Return a string representation of a Rack-compatible response body.
def self.body_to_s(body)
@ -61,16 +72,28 @@ module AbstractController
end
end
private
def _determine_template(options)
name = (options[:_template_name] || action_name).to_s
options[:_template] ||= view_paths.find_by_parts(
name, { :formats => formats }, options[:_prefix], options[:_partial]
)
end
module ClassMethods
def append_view_path(path)
self.view_paths << path
end
def prepend_view_path(path)
self.view_paths.unshift(path)
end
def view_paths
self._view_paths
end
def view_paths=(paths)
self._view_paths = paths.is_a?(ActionView::PathSet) ?
paths : ActionView::Base.process_view_paths(paths)

View File

@ -1,5 +1,7 @@
require 'action_controller/deprecated'
require 'set'
require 'active_support/core_ext/class/inheritable_attributes'
require 'active_support/core_ext/module/attr_internal'
module ActionController #:nodoc:
class ActionControllerError < StandardError #:nodoc:
@ -30,10 +32,6 @@ module ActionController #:nodoc:
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:
@ -238,13 +236,12 @@ module ActionController #:nodoc:
cattr_reader :protected_instance_variables
# Controller specific instance variables which will not be accessible inside views.
@@protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller
@action_name @before_filter_chain_aborted @action_cache_path @_session @_headers @_params
@action_name @before_filter_chain_aborted @action_cache_path @_headers @_params
@_flash @_response)
# Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
# and images to a dedicated asset server away from the main web server. Example:
# ActionController::Base.asset_host = "http://assets.example.com"
@@asset_host = ""
cattr_accessor :asset_host
# All requests are considered local by default, so everyone will be exposed to detailed debugging screens on errors.
@ -356,7 +353,9 @@ module ActionController #:nodoc:
# Holds a hash of objects in the session. Accessed like <tt>session[:person]</tt> to get the object tied to the "person"
# key. The session will hold any type of object as values, but the key should be a string or symbol.
attr_internal :session
def session
request.session
end
# Holds a hash of header names and values. Accessed like <tt>headers["Cache-Control"]</tt> to get the value of the Cache-Control
# directive. Values should always be specified as strings.
@ -365,19 +364,24 @@ module ActionController #:nodoc:
# Returns the name of the action this controller is processing.
attr_accessor :action_name
attr_reader :template
def action(name, env)
request = ActionDispatch::Request.new(env)
response = ActionDispatch::Response.new
self.action_name = name && name.to_s
process(request, response).to_a
end
class << self
def call(env)
# HACK: For global rescue to have access to the original request and response
request = env["action_controller.rescue.request"] ||= ActionDispatch::Request.new(env)
response = env["action_controller.rescue.response"] ||= ActionDispatch::Response.new
process(request, response)
def action(name = nil)
@actions ||= {}
@actions[name] ||= proc do |env|
new.action(name, env)
end
end
# Factory for the standard create, process loop where the controller is discarded after processing.
def process(request, response) #:nodoc:
new.process(request, response)
end
# Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
def controller_class_name
@controller_class_name ||= name.demodulize
@ -443,60 +447,27 @@ module ActionController #:nodoc:
@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 parameter hash and all
# subhashes is passed to it, the value or key
# can be replaced using String#replace or similar method.
#
# Examples:
# filter_parameter_logging
# => Does nothing, just slows the logging process down
#
# filter_parameter_logging :password
# => replaces the value to all keys matching /password/i with "[FILTERED]"
#
# filter_parameter_logging :foo, "bar"
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
#
# filter_parameter_logging { |k,v| v.reverse! if k =~ /secret/i }
# => reverses the value to all keys matching /secret/i
#
# filter_parameter_logging(:foo, "bar") { |k,v| v.reverse! if k =~ /secret/i }
# => reverses the value to all keys matching /secret/i, and
# replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
def filter_parameter_logging(*filter_words, &block)
parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0
define_method(:filter_parameters) do |unfiltered_parameters|
filtered_parameters = {}
unfiltered_parameters.each do |key, value|
if key =~ parameter_filter
filtered_parameters[key] = '[FILTERED]'
elsif value.is_a?(Hash)
filtered_parameters[key] = filter_parameters(value)
elsif block_given?
key = key.dup
value = value.dup if value
yield key, value
filtered_parameters[key] = value
else
filtered_parameters[key] = value
end
end
filtered_parameters
@@exempt_from_layout = [ActionView::TemplateHandlers::RJS]
def exempt_from_layout(*types)
types.each do |type|
@@exempt_from_layout <<
ActionView::Template.handler_class_for_extension(type)
end
protected :filter_parameters
@@exempt_from_layout
end
delegate :exempt_from_layout, :to => 'ActionView::Template'
end
public
def call(env)
request = ActionDispatch::Request.new(env)
response = ActionDispatch::Response.new
process(request, response).to_a
end
# Extracts the action_name from the request parameters and performs that action.
def process(request, response, method = :perform_action, *arguments) #:nodoc:
response.request = request
@ -504,7 +475,6 @@ module ActionController #:nodoc:
assign_shortcuts(request, response)
initialize_template_class(response)
initialize_current_url
assign_names
log_processing
send(method, *arguments)
@ -787,7 +757,6 @@ module ActionController #:nodoc:
# Resets the session by clearing out all the objects stored within and initializing a new session object.
def reset_session #:doc:
request.reset_session
@_session = request.session
end
private
@ -804,20 +773,14 @@ module ActionController #:nodoc:
end
def initialize_template_class(response)
@template = response.template = ActionView::Base.new(self.class.view_paths, {}, self, formats)
response.template.helpers.send :include, self.class.master_helper_module
response.redirected_to = nil
@template = ActionView::Base.new(self.class.view_paths, {}, self, formats)
response.template = @template if response.respond_to?(:template=)
@template.helpers.send :include, self.class.master_helper_module
@performed_render = @performed_redirect = false
end
def assign_shortcuts(request, response)
@_request, @_params = request, request.parameters
@_response = response
@_response.session = request.session
@_session = @_response.session
@_request, @_response, @_params = request, response, request.parameters
@_headers = @_response.headers
end
@ -840,13 +803,6 @@ module ActionController #:nodoc:
logger.info(request_id)
end
def log_processing_for_parameters
parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
parameters = parameters.except!(:controller, :action, :format, :_method)
logger.info " Parameters: #{parameters.inspect}" unless parameters.empty?
end
def default_render #:nodoc:
render
end
@ -861,13 +817,13 @@ module ActionController #:nodoc:
return (performed? ? ret : default_render) if called
begin
default_render
rescue ActionView::MissingTemplate => e
raise e unless e.action_name == action_name
# If the path is the same as the action_name, the action is completely missing
view_paths.find_by_parts(action_name, {:formats => formats, :locales => [I18n.locale]}, controller_path)
rescue => e
raise UnknownAction, "No action responded to #{action_name}. Actions: " +
"#{action_methods.sort.to_sentence}", caller
end
default_render
end
# Returns true if a render or redirect has already been performed.
@ -875,10 +831,6 @@ module ActionController #:nodoc:
@performed_render || @performed_redirect
end
def assign_names
@action_name = (params['action'] || 'index')
end
def reset_variables_added_to_assigns
@template.instance_variable_set("@assigns_added", nil)
end
@ -894,10 +846,6 @@ module ActionController #:nodoc:
"#{request.protocol}#{request.host}#{request.request_uri}"
end
def close_session
# @_session.close if @_session && @_session.respond_to?(:close)
end
def default_template(action_name = self.action_name)
self.view_paths.find_template(default_template_name(action_name), default_template_format)
end
@ -921,7 +869,6 @@ module ActionController #:nodoc:
end
def process_cleanup
close_session
end
end
@ -929,7 +876,7 @@ module ActionController #:nodoc:
[ Filters, Layout, Renderer, Redirector, Responder, Benchmarking, Rescue, Flash, MimeResponds, Helpers,
Cookies, Caching, Verification, Streaming, SessionManagement,
HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods, RecordIdentifier,
RequestForgeryProtection, Translation
RequestForgeryProtection, Translation, FilterParameterLogging
].each do |mod|
include mod
end

View File

@ -1,4 +1,4 @@
require 'benchmark'
require 'active_support/core_ext/benchmark'
module ActionController #:nodoc:
# The benchmarking module times the performance of actions and reports to the logger. If the Active Record
@ -21,7 +21,7 @@ module ActionController #:nodoc:
# easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
# will only be conducted if the log level is low enough.
def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
if logger && logger.level == log_level
if logger && logger.level >= log_level
result = nil
ms = Benchmark.ms { result = use_silence ? silence { yield } : yield }
logger.add(log_level, "#{title} (#{('%.1f' % ms)}ms)")

View File

@ -160,7 +160,7 @@ module ActionController #:nodoc:
def convert_only_and_except_options_to_sets_of_strings(opts)
[:only, :except].each do |key|
if values = opts[key]
opts[key] = Array(values).map(&:to_s).to_set
opts[key] = Array(values).map {|val| val.to_s }.to_set
end
end
end
@ -571,12 +571,7 @@ module ActionController #:nodoc:
# Returns an array of Filter objects for this controller.
def filter_chain
if chain = read_inheritable_attribute('filter_chain')
return chain
else
write_inheritable_attribute('filter_chain', FilterChain.new)
return filter_chain
end
read_inheritable_attribute('filter_chain') || write_inheritable_attribute('filter_chain', FilterChain.new)
end
# Returns all the before filters for this class and all its ancestors.

View File

@ -26,9 +26,18 @@ module ActionController #:nodoc:
#
# See docs on the FlashHash class for more details about the flash.
module Flash
def self.included(base)
base.class_eval do
include InstanceMethods
extend ActiveSupport::Concern
# TODO : Remove the defined? check when new base is the main base
depends_on Session if defined?(ActionController::Http)
included do
# TODO : Remove the defined? check when new base is the main base
if defined?(ActionController::Http)
include InstanceMethodsForNewBase
else
include InstanceMethodsForBase
alias_method_chain :perform_action, :flash
alias_method_chain :reset_session, :flash
end
@ -120,44 +129,68 @@ module ActionController #:nodoc:
(@used.keys - keys).each{ |k| @used.delete(k) }
end
def store(session, key = "flash")
return if self.empty?
session[key] = self
end
private
# Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
# use() # marks the entire flash as used
# use('msg') # marks the "msg" entry as used
# use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
# use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
def use(k=nil, v=true)
unless k.nil?
@used[k] = v
else
keys.each{ |key| use(key, v) }
end
# Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
# if no key is passed.
def use(key = nil, used = true)
Array(key || keys).each { |k| @used[k] = used }
return key ? self[key] : self
end
end
module InstanceMethods #:nodoc:
module InstanceMethodsForBase #:nodoc:
protected
def perform_action_with_flash
perform_action_without_flash
remove_instance_variable(:@_flash) if defined? @_flash
if defined? @_flash
@_flash.store(session)
remove_instance_variable(:@_flash)
end
end
def reset_session_with_flash
reset_session_without_flash
remove_instance_variable(:@_flash) if defined? @_flash
end
# 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.
def flash #:doc:
unless defined? @_flash
@_flash = session["flash"] ||= FlashHash.new
@_flash.sweep
end
@_flash
remove_instance_variable(:@_flash) if defined?(@_flash)
end
end
module InstanceMethodsForNewBase #:nodoc:
protected
def process_action(method_name)
super
if defined? @_flash
@_flash.store(session)
remove_instance_variable(:@_flash)
end
end
def reset_session
super
remove_instance_variable(:@_flash) if defined?(@_flash)
end
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.
def flash #:doc:
if !defined?(@_flash)
@_flash = session["flash"] || FlashHash.new
@_flash.sweep
end
@_flash
end
end
end

View File

@ -51,7 +51,7 @@ module ActionController #:nodoc:
protected
# Returns the cookie container, which operates as described above.
def cookies
CookieJar.new(self)
@cookies ||= CookieJar.new(self)
end
end

View File

@ -0,0 +1,97 @@
module ActionController
module FilterParameterLogging
extend ActiveSupport::Concern
# TODO : Remove the defined? check when new base is the main base
if defined?(ActionController::Http)
depends_on AbstractController::Logger
end
included do
if defined?(ActionController::Http)
include InstanceMethodsForNewBase
end
end
module ClassMethods
# 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 parameter hash and all
# subhashes is passed to it, the value or key
# can be replaced using String#replace or similar method.
#
# Examples:
# filter_parameter_logging
# => Does nothing, just slows the logging process down
#
# filter_parameter_logging :password
# => replaces the value to all keys matching /password/i with "[FILTERED]"
#
# filter_parameter_logging :foo, "bar"
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
#
# filter_parameter_logging { |k,v| v.reverse! if k =~ /secret/i }
# => reverses the value to all keys matching /secret/i
#
# filter_parameter_logging(:foo, "bar") { |k,v| v.reverse! if k =~ /secret/i }
# => reverses the value to all keys matching /secret/i, and
# replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
def filter_parameter_logging(*filter_words, &block)
parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0
define_method(:filter_parameters) do |unfiltered_parameters|
filtered_parameters = {}
unfiltered_parameters.each do |key, value|
if key =~ parameter_filter
filtered_parameters[key] = '[FILTERED]'
elsif value.is_a?(Hash)
filtered_parameters[key] = filter_parameters(value)
elsif block_given?
key = key.dup
value = value.dup if value
yield key, value
filtered_parameters[key] = value
else
filtered_parameters[key] = value
end
end
filtered_parameters
end
protected :filter_parameters
end
end
module InstanceMethodsForNewBase
# TODO : Fix the order of information inside such that it's exactly same as the old base
def process(*)
ret = super
if logger
parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
parameters = parameters.except!(:controller, :action, :format, :_method, :only_path)
unless parameters.empty?
# TODO : Move DelayedLog to AS
log = AbstractController::Logger::DelayedLog.new { " Parameters: #{parameters.inspect}" }
logger.info(log)
end
end
ret
end
end
private
# TODO : This method is not needed for the new base
def log_processing_for_parameters
parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
parameters = parameters.except!(:controller, :action, :format, :_method)
logger.info " Parameters: #{parameters.inspect}" unless parameters.empty?
end
end
end

View File

@ -3,23 +3,19 @@ require 'active_support/dependencies'
# FIXME: helper { ... } is broken on Ruby 1.9
module ActionController #:nodoc:
module Helpers #:nodoc:
def self.included(base)
extend ActiveSupport::Concern
included do
# Initialize the base module to aggregate its helpers.
base.class_inheritable_accessor :master_helper_module
base.master_helper_module = Module.new
class_inheritable_accessor :master_helper_module
self.master_helper_module = Module.new
# Set the default directory for helpers
base.class_inheritable_accessor :helpers_dir
base.helpers_dir = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers")
class_inheritable_accessor :helpers_dir
self.helpers_dir = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers")
# Extend base with class methods to declare helpers.
base.extend(ClassMethods)
base.class_eval do
# Wrap inherited to create a new master helper module for subclasses.
class << self
alias_method_chain :inherited, :helper
end
class << self
alias_method_chain :inherited, :helper
end
end

View File

@ -1,3 +1,5 @@
require 'active_support/base64'
module ActionController
module HttpAuthentication
# Makes it dead easy to do HTTP Basic authentication.
@ -192,9 +194,10 @@ module ActionController
if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque]
password = password_procedure.call(credentials[:username])
method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
[true, false].any? do |password_is_ha1|
expected = expected_response(request.env['REQUEST_METHOD'], request.env['REQUEST_URI'], credentials, password, password_is_ha1)
expected = expected_response(method, request.env['REQUEST_URI'], credentials, password, password_is_ha1)
expected == credentials[:response]
end
end
@ -276,7 +279,7 @@ module ActionController
t = time.to_i
hashed = [t, secret_key]
digest = ::Digest::MD5.hexdigest(hashed.join(":"))
Base64.encode64("#{t}:#{digest}").gsub("\n", '')
ActiveSupport::Base64.encode64("#{t}:#{digest}").gsub("\n", '')
end
# Might want a shorter timeout depending on whether the request
@ -285,7 +288,7 @@ module ActionController
# allow a user to use new nonce without prompting user again for their
# username and password.
def validate_nonce(request, value, seconds_to_timeout=5*60)
t = Base64.decode64(value).split(":").first.to_i
t = ActiveSupport::Base64.decode64(value).split(":").first.to_i
nonce(t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
end

View File

@ -1,3 +1,7 @@
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/class/inheritable_attributes'
module ActionController #:nodoc:
module Layout #:nodoc:
def self.included(base)
@ -182,7 +186,7 @@ module ActionController #:nodoc:
def memoized_find_layout(layout, formats) #:nodoc:
return layout if layout.nil? || layout.respond_to?(:render)
prefix = layout.to_s =~ /layouts\// ? nil : "layouts"
view_paths.find_by_parts(layout.to_s, formats, prefix)
view_paths.find_by_parts(layout.to_s, {:formats => formats}, prefix)
end
def find_layout(*args)

View File

@ -1,111 +1,103 @@
module ActionController #:nodoc:
module MimeResponds #:nodoc:
def self.included(base)
base.module_eval do
include ActionController::MimeResponds::InstanceMethods
end
end
module InstanceMethods
# Without web-service support, an action which collects the data for displaying a list of people
# might look something like this:
#
# 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."
# (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
# (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
# (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>
# ...
# <company>
# <id>...</id>
# <name>...</name>
# ...
# </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
# 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'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 node), 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(self)
block.call(responder)
responder.respond
end
# Without web-service support, an action which collects the data for displaying a list of people
# might look something like this:
#
# 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."
# (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
# (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
# (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>
# ...
# <company>
# <id>...</id>
# <name>...</name>
# ...
# </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
# 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'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 node), 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(self)
block.call(responder)
responder.respond
end
class Responder #:nodoc:
@ -127,8 +119,14 @@ module ActionController #:nodoc:
@order << mime_type
@responses[mime_type] ||= Proc.new do
@response.template.formats = [mime_type.to_sym]
# TODO: Remove this when new base is merged in
if defined?(Http)
@controller.formats = [mime_type.to_sym]
end
@controller.template.formats = [mime_type.to_sym]
@response.content_type = mime_type.to_s
block_given? ? block.call : @controller.send(:render, :action => @controller.action_name)
end
end

View File

@ -48,8 +48,6 @@ module ActionController
status = 302
end
response.redirected_to = options
case options
# The scheme name consist of a letter followed by any combination of
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
@ -72,7 +70,9 @@ module ActionController
def redirect_to_full_url(url, status)
raise DoubleRenderError if performed?
logger.info("Redirected to #{url}") if logger && logger.info?
response.redirect(url, interpret_status(status))
response.status = interpret_status(status)
response.location = url.gsub(/[\r\n]/, '')
response.body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>"
@performed_redirect = true
end
@ -82,8 +82,6 @@ module ActionController
# The response body is not reset here, see +erase_render_results+
def erase_redirect_results #:nodoc:
@performed_redirect = false
response.redirected_to = nil
response.redirected_to_method_params = nil
response.status = DEFAULT_RENDER_STATUS_CODE
response.headers.delete('Location')
end

View File

@ -253,8 +253,9 @@ module ActionController
response.content_type ||= Mime::JS
render_for_text(js)
elsif json = options[:json]
json = json.to_json unless json.is_a?(String)
elsif options.include?(:json)
json = options[:json]
json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str)
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
response.content_type ||= Mime::JSON
render_for_text(json)
@ -374,12 +375,18 @@ module ActionController
render_for_file(name.sub(/^\//, ''), [layout, true], options)
end
end
def render_for_parts(parts, layout, options = {})
# ==== Arguments
# parts<Array[String, Array[Symbol*], String, Boolean]>::
# Example: ["show", [:html, :xml], "users", false]
def render_for_parts(parts, layout_details, options = {})
parts[1] = {:formats => parts[1], :locales => [I18n.locale]}
tmp = view_paths.find_by_parts(*parts)
layout = _pick_layout(*layout) unless tmp.exempt_from_layout?
layout = _pick_layout(*layout_details) unless
self.class.exempt_from_layout.include?(tmp.handler)
render_for_text(
@template._render_template_with_layout(tmp, layout, options, parts[3]))
end

View File

@ -3,12 +3,26 @@ module ActionController #:nodoc:
end
module RequestForgeryProtection
def self.included(base)
base.class_eval do
helper_method :form_authenticity_token
helper_method :protect_against_forgery?
extend ActiveSupport::Concern
# TODO : Remove the defined? check when new base is the main base
if defined?(ActionController::Http)
depends_on AbstractController::Helpers, Session
end
included do
if defined?(ActionController::Http)
# Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+
# sets it to <tt>:authenticity_token</tt> by default.
cattr_accessor :request_forgery_protection_token
# 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
end
base.extend(ClassMethods)
helper_method :form_authenticity_token
helper_method :protect_against_forgery?
end
# Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a

View File

@ -0,0 +1,50 @@
module ActionController #:nodoc:
# 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.
#
# 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 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
def self.included(base) #:nodoc:
base.send :include, ActiveSupport::Rescuable
base.extend(ClassMethods)
base.class_eval do
alias_method_chain :perform_action, :rescue
end
end
module ClassMethods
def rescue_action(env)
exception = env.delete('action_dispatch.rescue.exception')
request = ActionDispatch::Request.new(env)
response = ActionDispatch::Response.new
new.process(request, response, :rescue_action, exception).to_a
end
end
protected
# Exception handler called when the performance of an action raises
# an exception.
def rescue_action(exception)
rescue_with_handler(exception) || raise(exception)
end
private
def perform_action_with_rescue
perform_action_without_rescue
rescue Exception => exception
rescue_action(exception)
end
end
end

View File

@ -2,6 +2,13 @@ module ActionController #:nodoc:
# Methods for sending arbitrary data and for streaming files to the browser,
# instead of rendering.
module Streaming
extend ActiveSupport::Concern
# TODO : Remove the defined? check when new base is the main base
if defined?(ActionController::Http)
depends_on ActionController::Renderer
end
DEFAULT_SEND_FILE_OPTIONS = {
:type => 'application/octet-stream'.freeze,
:disposition => 'attachment'.freeze,
@ -88,6 +95,7 @@ module ActionController #:nodoc:
head options[:status], X_SENDFILE_HEADER => path
else
if options[:stream]
# TODO : Make render :text => proc {} work with the new base
render :status => options[:status], :text => Proc.new { |response, output|
logger.info "Streaming file #{path}" unless logger.nil?
len = options[:buffer_size] || 4096

View File

@ -1,7 +1,10 @@
module ActionController #:nodoc:
module Verification #:nodoc:
def self.included(base) #:nodoc:
base.extend(ClassMethods)
extend ActiveSupport::Concern
# TODO : Remove the defined? check when new base is the main base
if defined?(ActionController::Http)
depends_on AbstractController::Callbacks, Session, Flash, Renderer
end
# This module provides a class-level method for specifying that certain
@ -102,7 +105,7 @@ module ActionController #:nodoc:
end
def verify_presence_of_keys_in_hash_flash_or_params(options) # :nodoc:
[*options[:params] ].find { |v| params[v].nil? } ||
[*options[:params] ].find { |v| v && params[v.to_sym].nil? } ||
[*options[:session]].find { |v| session[v].nil? } ||
[*options[:flash] ].find { |v| flash[v].nil? }
end

View File

@ -24,31 +24,31 @@ module ActionController #:nodoc:
# ActionController::Base.cache_store = :mem_cache_store, "localhost"
# ActionController::Base.cache_store = MyOwnStore.new("parameter")
module Caching
extend ActiveSupport::Concern
autoload :Actions, 'action_controller/caching/actions'
autoload :Fragments, 'action_controller/caching/fragments'
autoload :Pages, 'action_controller/caching/pages'
autoload :Sweeper, 'action_controller/caching/sweeping'
autoload :Sweeping, 'action_controller/caching/sweeping'
def self.included(base) #:nodoc:
base.class_eval do
@@cache_store = nil
cattr_reader :cache_store
included do
@@cache_store = nil
cattr_reader :cache_store
# Defines the storage option for cached fragments
def self.cache_store=(store_option)
@@cache_store = ActiveSupport::Cache.lookup_store(store_option)
end
# Defines the storage option for cached fragments
def self.cache_store=(store_option)
@@cache_store = ActiveSupport::Cache.lookup_store(store_option)
end
include Pages, Actions, Fragments
include Sweeping if defined?(ActiveRecord)
include Pages, Actions, Fragments
include Sweeping if defined?(ActiveRecord)
@@perform_caching = true
cattr_accessor :perform_caching
@@perform_caching = true
cattr_accessor :perform_caching
def self.cache_configured?
perform_caching && cache_store
end
def self.cache_configured?
perform_caching && cache_store
end
end

View File

@ -61,7 +61,15 @@ module ActionController #:nodoc:
filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) }
cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options)
around_filter(cache_filter, filter_options)
# TODO: Remove this once new base is swapped in.
if defined?(ActionController::Http)
around_filter cache_filter, filter_options
else
around_filter(filter_options) do |controller, action|
cache_filter.filter(controller, action)
end
end
end
end
@ -83,8 +91,24 @@ module ActionController #:nodoc:
@options = options
end
# TODO: Remove once New Base is merged
if defined?(ActionController::Http)
def filter(controller)
should_continue = before(controller)
yield if should_continue
after(controller)
end
else
def filter(controller, action)
should_continue = before(controller)
action.call if should_continue
after(controller)
end
end
def before(controller)
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path)))
if cache = controller.read_fragment(cache_path.path, @options[:store_options])
controller.rendered_action_cache = true
set_content_type!(controller, cache_path.extension)
@ -121,7 +145,9 @@ module ActionController #:nodoc:
end
def content_for_layout(controller)
controller.response.layout && controller.response.template.instance_variable_get('@cached_content_for_layout')
# TODO: Remove this when new base is merged in
template = controller.respond_to?(:template) ? controller.template : controller._action_view
template.layout && template.instance_variable_get('@cached_content_for_layout')
end
end

View File

@ -1,96 +1,65 @@
require 'active_support/core_ext/module/delegation'
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
cattr_accessor :prepare_each_request
self.prepare_each_request = false
cattr_accessor :router
self.router = Routing::Routes
cattr_accessor :middleware
self.middleware = ActionDispatch::MiddlewareStack.new do |middleware|
middlewares = File.join(File.dirname(__FILE__), "middlewares.rb")
middleware.instance_eval(File.read(middlewares), middlewares, 1)
end
class << self
def define_dispatcher_callbacks(cache_classes)
unless cache_classes
unless self.middleware.include?(ActionDispatch::Reloader)
self.middleware.insert_after(ActionDispatch::Failsafe, ActionDispatch::Reloader)
# Run prepare callbacks before every request in development mode
self.prepare_each_request = true
# Development mode callbacks
ActionDispatch::Callbacks.before_dispatch do |app|
ActionController::Dispatcher.router.reload
end
ActionDispatch::Callbacks.after_dispatch do
# Cleanup the application before processing the current request.
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
ActiveSupport::Dependencies.clear
ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
end
ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
end
if defined?(ActiveRecord)
to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers }
to_prepare(:activerecord_instantiate_observers) do
ActiveRecord::Base.instantiate_observers
end
end
after_dispatch :flush_logger if Base.logger && Base.logger.respond_to?(:flush)
if Base.logger && Base.logger.respond_to?(:flush)
after_dispatch do
Base.logger.flush
end
end
to_prepare do
I18n.reload!
end
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)
@prepare_dispatch_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new
callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier)
@prepare_dispatch_callbacks.replace_or_append!(callback)
delegate :to_prepare, :prepare_dispatch, :before_dispatch, :after_dispatch,
:to => ActionDispatch::Callbacks
def new
@@middleware.build(@@router)
end
def run_prepare_callbacks
new.send :run_callbacks, :prepare_dispatch
end
def reload_application
# Run prepare callbacks before every request in development mode
run_prepare_callbacks
Routing::Routes.reload
end
def cleanup_application
# Cleanup the application before processing the current request.
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
ActiveSupport::Dependencies.clear
ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
end
end
cattr_accessor :middleware
self.middleware = ActionDispatch::MiddlewareStack.new do |middleware|
middlewares = File.join(File.dirname(__FILE__), "middlewares.rb")
middleware.instance_eval(File.read(middlewares))
end
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
def initialize
@app = @@middleware.build(lambda { |env| self._call(env) })
freeze
end
def call(env)
@app.call(env)
end
def _call(env)
begin
run_callbacks :before_dispatch
Routing::Routes.call(env)
rescue Exception => exception
if controller ||= (::ApplicationController rescue Base)
controller.call_with_exception(env, exception).to_a
else
raise exception
end
ensure
run_callbacks :after_dispatch, :enumerator => :reverse_each
end
end
def flush_logger
Base.logger.flush
end
end
end

View File

@ -2,12 +2,17 @@ use "Rack::Lock", :if => lambda {
!ActionController::Base.allow_concurrency
}
use "ActionDispatch::Failsafe"
use "ActionDispatch::ShowExceptions", lambda { ActionController::Base.consider_all_requests_local }
use "ActionDispatch::Callbacks", lambda { ActionController::Dispatcher.prepare_each_request }
use "ActionDispatch::Rescue", lambda {
controller = (::ApplicationController rescue ActionController::Base)
# TODO: Replace with controller.action(:_rescue_action)
controller.method(:rescue_action)
}
use lambda { ActionController::Base.session_store },
lambda { ActionController::Base.session_options }
use "ActionDispatch::RewindableInput"
use "ActionDispatch::ParamsParser"
use "Rack::MethodOverride"
use "Rack::Head"
use "Rack::Head"

View File

@ -1,185 +0,0 @@
module ActionController #:nodoc:
# 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.
#
# 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 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 = {
'ActionView::MissingTemplate' => 'missing_template',
'ActionController::RoutingError' => 'routing_error',
'ActionController::UnknownAction' => 'unknown_action',
'ActionView::TemplateError' => 'template_error'
}
RESCUES_TEMPLATE_PATH = ActionView::Template::FileSystemPath.new(
File.join(File.dirname(__FILE__), "templates"))
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.extend(ClassMethods)
base.send :include, ActiveSupport::Rescuable
base.class_eval do
alias_method_chain :perform_action, :rescue
end
end
module ClassMethods
def call_with_exception(env, exception) #:nodoc:
request = env["action_controller.rescue.request"] ||= ActionDispatch::Request.new(env)
response = env["action_controller.rescue.response"] ||= ActionDispatch::Response.new
new.process(request, response, :rescue_action, exception)
end
end
protected
# Exception handler called when the performance of an action raises
# an exception.
def rescue_action(exception)
rescue_with_handler(exception) ||
rescue_action_without_handler(exception)
end
# Overwrite to implement custom logging of errors. By default
# logs as fatal.
def log_error(exception) #:doc:
ActiveSupport::Deprecation.silence do
if ActionView::TemplateError === exception
logger.fatal(exception.to_s)
else
logger.fatal(
"\n#{exception.class} (#{exception.message}):\n " +
clean_backtrace(exception).join("\n ") + "\n\n"
)
end
end
end
# Overwrite to implement public exception handling (for requests
# answering false to <tt>local_request?</tt>). By default will call
# render_optional_error_file. Override this method to provide more
# user friendly error messages.
def rescue_action_in_public(exception) #:doc:
render_optional_error_file response_code_for_rescue(exception)
end
# Attempts to render a static error page based on the
# <tt>status_code</tt> thrown, or just return headers if no such file
# exists. At first, it will try to render a localized static page.
# For example, if a 500 error is being handled Rails and locale is :da,
# it will first attempt to render the file at <tt>public/500.da.html</tt>
# then attempt to render <tt>public/500.html</tt>. If none of them exist,
# the body of the response will be left empty.
def render_optional_error_file(status_code)
status = interpret_status(status_code)
locale_path = "#{Rails.public_path}/#{status[0,3]}.#{I18n.locale}.html" if I18n.locale
path = "#{Rails.public_path}/#{status[0,3]}.html"
if locale_path && File.exist?(locale_path)
render :file => locale_path, :status => status, :content_type => Mime::HTML
elsif File.exist?(path)
render :file => path, :status => status, :content_type => Mime::HTML
else
head status
end
end
# 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 == LOCALHOST && request.remote_ip == LOCALHOST
end
# Render detailed diagnostics for unhandled exceptions rescued from
# a controller action.
def rescue_action_locally(exception)
@template.instance_variable_set("@exception", exception)
@template.instance_variable_set("@rescues_path", RESCUES_TEMPLATE_PATH)
@template.instance_variable_set("@contents",
@template._render_template(template_path_for_local_rescue(exception)))
response.content_type = Mime::HTML
response.status = interpret_status(response_code_for_rescue(exception))
content = @template._render_template(rescues_path("layout"))
render_for_text(content)
end
def rescue_action_without_handler(exception)
log_error(exception) if logger
erase_results if performed?
# Let the exception alter the response if it wants.
# For example, MethodNotAllowed sets the Allow header.
if exception.respond_to?(:handle_response!)
exception.handle_response!(response)
end
if consider_all_requests_local || local_request?
rescue_action_locally(exception)
else
rescue_action_in_public(exception)
end
end
private
def perform_action_with_rescue #:nodoc:
perform_action_without_rescue
rescue Exception => exception
rescue_action(exception)
end
def rescues_path(template_name)
RESCUES_TEMPLATE_PATH.find_by_parts("rescues/#{template_name}.erb")
end
def template_path_for_local_rescue(exception)
rescues_path(rescue_templates[exception.class.name])
end
def response_code_for_rescue(exception)
rescue_responses[exception.class.name]
end
def clean_backtrace(exception)
defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
Rails.backtrace_cleaner.clean(exception.backtrace) :
exception.backtrace
end
end
end

View File

@ -1,10 +0,0 @@
<h1>
<%=h @exception.class.to_s %>
<% if request.parameters['controller'] %>
in <%=h request.parameters['controller'].humanize %>Controller<% if request.parameters['action'] %>#<%=h request.parameters['action'] %><% end %>
<% end %>
</h1>
<pre><%=h @exception.clean_message %></pre>
<%= @template._render_template(@rescues_path.find_by_parts("rescues/_trace.erb")) %>
<%= @template._render_template(@rescues_path.find_by_parts("rescues/_request_and_response.erb")) %>

View File

@ -1,7 +1,47 @@
module ActionController
autoload :AbstractBase, "action_controller/new_base/base"
autoload :HideActions, "action_controller/new_base/hide_actions"
autoload :Layouts, "action_controller/new_base/layouts"
autoload :Renderer, "action_controller/new_base/renderer"
autoload :UrlFor, "action_controller/new_base/url_for"
end
autoload :Base, "action_controller/new_base/base"
autoload :ConditionalGet, "action_controller/new_base/conditional_get"
autoload :HideActions, "action_controller/new_base/hide_actions"
autoload :Http, "action_controller/new_base/http"
autoload :Layouts, "action_controller/new_base/layouts"
autoload :RackConvenience, "action_controller/new_base/rack_convenience"
autoload :Rails2Compatibility, "action_controller/new_base/compatibility"
autoload :Redirector, "action_controller/new_base/redirector"
autoload :Renderer, "action_controller/new_base/renderer"
autoload :RenderOptions, "action_controller/new_base/render_options"
autoload :Renderers, "action_controller/new_base/render_options"
autoload :Rescue, "action_controller/new_base/rescuable"
autoload :Testing, "action_controller/new_base/testing"
autoload :UrlFor, "action_controller/new_base/url_for"
autoload :Session, "action_controller/new_base/session"
autoload :Helpers, "action_controller/new_base/helpers"
# Ported modules
# require 'action_controller/routing'
autoload :Caching, 'action_controller/caching'
autoload :Dispatcher, 'action_controller/dispatch/dispatcher'
autoload :MimeResponds, 'action_controller/base/mime_responds'
autoload :PolymorphicRoutes, 'action_controller/routing/generation/polymorphic_routes'
autoload :RecordIdentifier, 'action_controller/record_identifier'
autoload :Resources, 'action_controller/routing/resources'
autoload :SessionManagement, 'action_controller/base/session_management'
autoload :TestCase, 'action_controller/testing/test_case'
autoload :UrlRewriter, 'action_controller/routing/generation/url_rewriter'
autoload :UrlWriter, 'action_controller/routing/generation/url_rewriter'
autoload :Verification, 'action_controller/base/verification'
autoload :Flash, 'action_controller/base/chained/flash'
autoload :RequestForgeryProtection, 'action_controller/base/request_forgery_protection'
autoload :Streaming, 'action_controller/base/streaming'
autoload :HttpAuthentication, 'action_controller/base/http_authentication'
autoload :FilterParameterLogging, 'action_controller/base/filter_parameter_logging'
autoload :Translation, 'action_controller/translation'
autoload :Cookies, 'action_controller/base/cookies'
require 'action_controller/routing'
end
autoload :HTML, 'action_controller/vendor/html-scanner'
require 'action_dispatch'
require 'action_view'

View File

@ -1,60 +1,173 @@
module ActionController
class AbstractBase < AbstractController::Base
# :api: public
attr_internal :request, :response, :params
class Base < Http
abstract!
# :api: public
def self.controller_name
@controller_name ||= controller_path.split("/").last
include AbstractController::Benchmarker
include AbstractController::Callbacks
include AbstractController::Logger
include ActionController::Helpers
include ActionController::HideActions
include ActionController::UrlFor
include ActionController::Redirector
include ActionController::Renderer
include ActionController::Renderers::All
include ActionController::Layouts
include ActionController::ConditionalGet
include ActionController::RackConvenience
# Legacy modules
include SessionManagement
include ActionDispatch::StatusCodes
include ActionController::Caching
include ActionController::MimeResponds
# Rails 2.x compatibility
include ActionController::Rails2Compatibility
include ActionController::Cookies
include ActionController::Session
include ActionController::Flash
include ActionController::Verification
include ActionController::RequestForgeryProtection
include ActionController::Streaming
include ActionController::HttpAuthentication::Basic::ControllerMethods
include ActionController::HttpAuthentication::Digest::ControllerMethods
include ActionController::FilterParameterLogging
include ActionController::Translation
# TODO: Extract into its own module
# This should be moved together with other normalizing behavior
module ImplicitRender
def send_action(method_name)
ret = super
default_render unless performed?
ret
end
def default_render
render
end
def method_for_action(action_name)
super || begin
if view_paths.find_by_parts?(action_name.to_s, {:formats => formats, :locales => [I18n.locale]}, controller_path)
"default_render"
end
end
end
end
# :api: public
def controller_name() self.class.controller_name end
include ImplicitRender
# :api: public
def self.controller_path
@controller_path ||= self.name.sub(/Controller$/, '').underscore
end
# :api: public
def controller_path() self.class.controller_path end
# :api: private
def self.action_methods
@action_names ||= Set.new(self.public_instance_methods - self::CORE_METHODS)
end
# :api: private
def self.action_names() action_methods end
# :api: private
def action_methods() self.class.action_names end
include ActionController::Rescue
# :api: private
def action_names() action_methods end
# :api: plugin
def self.call(env)
controller = new
controller.call(env).to_rack
def self.inherited(klass)
::ActionController::Base.subclasses << klass.to_s
super
end
# :api: plugin
def response_body=(body)
@_response.body = body
def self.subclasses
@subclasses ||= []
end
# :api: private
def call(env)
@_request = ActionDispatch::Request.new(env)
@_response = ActionDispatch::Response.new
process(@_request.parameters[:action])
def self.app_loaded!
@subclasses.each do |subclass|
subclass.constantize._write_layout_method
end
end
# :api: private
def to_rack
response.to_a
def _normalize_options(action = nil, options = {}, &blk)
if action.is_a?(Hash)
options, action = action, nil
elsif action.is_a?(String) || action.is_a?(Symbol)
key = case action = action.to_s
when %r{^/} then :file
when %r{/} then :template
else :action
end
options.merge! key => action
elsif action
options.merge! :partial => action
end
if options.key?(:action) && options[:action].to_s.index("/")
options[:template] = options.delete(:action)
end
if options[:status]
options[:status] = interpret_status(options[:status]).to_i
end
options[:update] = blk if block_given?
options
end
def render(action = nil, options = {}, &blk)
options = _normalize_options(action, options, &blk)
super(options)
end
def render_to_string(action = nil, options = {}, &blk)
options = _normalize_options(action, options, &blk)
super(options)
end
# 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>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
# * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) - Is passed straight through as the target for redirection.
# * <tt>String</tt> not containing a protocol - 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 <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
#
# 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 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 rescuing RedirectBackError.
def redirect_to(options = {}, response_status = {}) #:doc:
raise ActionControllerError.new("Cannot redirect to nil!") if options.nil?
status = if options.is_a?(Hash) && options.key?(:status)
interpret_status(options.delete(:status))
elsif response_status.key?(:status)
interpret_status(response_status[:status])
else
302
end
url = case options
# The scheme name consist of a letter followed by any combination of
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
# characters; and is terminated by a colon (":").
when %r{^\w[\w\d+.-]*:.*}
options
when String
request.protocol + request.host_with_port + options
when :back
raise RedirectBackError unless refer = request.headers["Referer"]
refer
else
url_for(options)
end
super(url, status)
end
end
end

View File

@ -0,0 +1,138 @@
module ActionController
module Rails2Compatibility
extend ActiveSupport::Concern
class ::ActionController::ActionControllerError < StandardError #:nodoc:
end
# Temporary hax
included do
::ActionController::UnknownAction = ::AbstractController::ActionNotFound
::ActionController::DoubleRenderError = ::AbstractController::DoubleRenderError
cattr_accessor :session_options
self.session_options = {}
cattr_accessor :allow_concurrency
self.allow_concurrency = false
cattr_accessor :param_parsers
self.param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
Mime::URL_ENCODED_FORM => :url_encoded_form,
Mime::XML => :xml_simple,
Mime::JSON => :json }
cattr_accessor :relative_url_root
self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT']
cattr_accessor :default_charset
self.default_charset = "utf-8"
# cattr_reader :protected_instance_variables
cattr_accessor :protected_instance_variables
self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller
@action_name @before_filter_chain_aborted @action_cache_path @_headers @_params
@_flash @_response)
# Indicates whether or not optimise the generated named
# route helper methods
cattr_accessor :optimise_named_routes
self.optimise_named_routes = true
cattr_accessor :resources_path_names
self.resources_path_names = { :new => 'new', :edit => 'edit' }
# Controls the resource action separator
cattr_accessor :resource_action_separator
self.resource_action_separator = "/"
cattr_accessor :use_accept_header
self.use_accept_header = true
cattr_accessor :page_cache_directory
self.page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
cattr_reader :cache_store
cattr_accessor :consider_all_requests_local
self.consider_all_requests_local = true
# Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
# and images to a dedicated asset server away from the main web server. Example:
# ActionController::Base.asset_host = "http://assets.example.com"
cattr_accessor :asset_host
cattr_accessor :ip_spoofing_check
self.ip_spoofing_check = true
end
# For old tests
def initialize_template_class(*) end
def assign_shortcuts(*) end
# TODO: Remove this after we flip
def template
@template ||= _action_view
end
def process_action(*)
template
super
end
module ClassMethods
def consider_all_requests_local
end
def rescue_action(env)
raise env["action_dispatch.rescue.exception"]
end
# Defines the storage option for cached fragments
def cache_store=(store_option)
@@cache_store = ActiveSupport::Cache.lookup_store(store_option)
end
end
def render_to_body(options)
if options.is_a?(Hash) && options.key?(:template)
options[:template].sub!(/^\//, '')
end
options[:text] = nil if options[:nothing] == true
body = super
body = [' '] if body.blank?
body
end
def _handle_method_missing
method_missing(@_action_name.to_sym)
end
def method_for_action(action_name)
super || (respond_to?(:method_missing) && "_handle_method_missing")
end
def _layout_prefix(name)
super unless name =~ /\blayouts/
end
def performed?
response_body
end
# ==== Request only view path switching ====
def append_view_path(path)
view_paths.push(*path)
end
def prepend_view_path(path)
view_paths.unshift(*path)
end
def view_paths
_action_view.view_paths
end
end
end

View File

@ -0,0 +1,133 @@
module ActionController
module ConditionalGet
extend ActiveSupport::Concern
depends_on RackConvenience
# Sets the etag, last_modified, or both on the response and renders a
# "304 Not Modified" response if the request is already fresh.
#
# Parameters:
# * <tt>:etag</tt>
# * <tt>:last_modified</tt>
# * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches).
#
# Example:
#
# def show
# @article = Article.find(params[:id])
# fresh_when(:etag => @article, :last_modified => @article.created_at.utc, :public => true)
# end
#
# This will render the show template if the request isn't sending a matching etag or
# If-Modified-Since header and just a "304 Not Modified" response if there's a match.
#
def fresh_when(options)
options.assert_valid_keys(:etag, :last_modified, :public)
response.etag = options[:etag] if options[:etag]
response.last_modified = options[:last_modified] if options[:last_modified]
if options[:public]
cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip }
cache_control.delete("private")
cache_control.delete("no-cache")
cache_control << "public"
response.headers["Cache-Control"] = cache_control.join(', ')
end
if request.fresh?(response)
head :not_modified
end
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
# significant headers:
#
# head :created, :location => person_path(@person)
#
# It can also be used to return exceptional conditions:
#
# return head(:method_not_allowed) unless request.post?
# return head(:bad_request) unless valid_request?
# render
def head(*args)
if args.length > 2
raise ArgumentError, "too many arguments to head"
elsif args.empty?
raise ArgumentError, "too few arguments to head"
end
options = args.extract_options!
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
end
render :nothing => true, :status => status
end
# Sets the etag and/or last_modified on the response and checks it against
# the client request. If the request doesn't match the options provided, the
# request is considered stale and should be generated from scratch. Otherwise,
# it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent.
#
# Parameters:
# * <tt>:etag</tt>
# * <tt>:last_modified</tt>
# * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches).
#
# Example:
#
# def show
# @article = Article.find(params[:id])
#
# if stale?(:etag => @article, :last_modified => @article.created_at.utc)
# @statistics = @article.really_expensive_call
# respond_to do |format|
# # all the supported formats
# end
# end
# end
def stale?(options)
fresh_when(options)
!request.fresh?(response)
end
# Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that
# intermediate caches shouldn't cache the response.
#
# Examples:
# expires_in 20.minutes
# expires_in 3.hours, :public => true
# expires in 3.hours, 'max-stale' => 5.hours, :public => true
#
# This method will overwrite an existing Cache-Control header.
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
def expires_in(seconds, options = {}) #:doc:
cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip }
cache_control << "max-age=#{seconds}"
cache_control.delete("no-cache")
if options[:public]
cache_control.delete("private")
cache_control << "public"
else
cache_control << "private"
end
# This allows for additional headers to be passed through like 'max-stale' => 5.hours
cache_control += options.symbolize_keys.reject{|k,v| k == :public || k == :private }.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
response.headers["Cache-Control"] = cache_control.join(', ')
end
# Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or
# intermediate caches (like caching proxy servers).
def expires_now #:doc:
response.headers["Cache-Control"] = "no-cache"
end
end
end

View File

@ -0,0 +1,129 @@
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/name_error'
require 'active_support/dependencies'
module ActionController
module Helpers
extend ActiveSupport::Concern
depends_on AbstractController::Helpers
included do
# Set the default directory for helpers
class_inheritable_accessor :helpers_dir
self.helpers_dir = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers")
end
module ClassMethods
def inherited(klass)
klass.__send__ :default_helper_module!
super
end
# 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 beneath
# <tt>ActionController::Base.helpers_dir</tt> (defaults to <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 }
#
def helper(*args, &block)
args.flatten.each do |arg|
case 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.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
super class_name.constantize
else
super args
end
end
# Evaluate block in template class if given.
master_helper_module.module_eval(&block) if block_given?
end
# 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
def helper_attr(*attrs)
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
end
# Provides a proxy to access helpers methods from outside the view.
def helpers
unless @helper_proxy
@helper_proxy = ActionView::Base.new
@helper_proxy.extend master_helper_module
else
@helper_proxy
end
end
private
def default_helper_module!
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
# 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

View File

@ -1,31 +1,39 @@
module ActionController
module HideActions
setup do
extend ActiveSupport::Concern
included do
extlib_inheritable_accessor :hidden_actions
self.hidden_actions ||= Set.new
self.hidden_actions ||= Set.new
end
def action_methods() self.class.action_names end
def action_names() action_methods end
private
def respond_to_action?(action_name)
!hidden_actions.include?(action_name) && (super || respond_to?(:method_missing))
def action_methods
self.class.action_names
end
module ClassMethods
def hide_action(*args)
args.each do |arg|
self.hidden_actions << arg.to_s
end
end
def action_methods
@action_names ||= Set.new(super.reject {|name| self.hidden_actions.include?(name.to_s)})
def action_names
action_methods
end
private
def action_method?(action_name)
!hidden_actions.include?(action_name) && super
end
def self.action_names() action_methods end
end
module ClassMethods
def hide_action(*args)
args.each do |arg|
self.hidden_actions << arg.to_s
end
end
def action_methods
@action_names ||= Set.new(super.reject {|name| self.hidden_actions.include?(name.to_s)})
end
def self.action_names
action_methods
end
end
end
end
end

View File

@ -0,0 +1,91 @@
require 'action_controller/abstract'
require 'active_support/core_ext/module/delegation'
module ActionController
class Http < AbstractController::Base
abstract!
# :api: public
attr_internal :params, :env
# :api: public
def self.controller_name
@controller_name ||= controller_path.split("/").last
end
# :api: public
def controller_name
self.class.controller_name
end
# :api: public
def self.controller_path
@controller_path ||= self.name.sub(/Controller$/, '').underscore
end
# :api: public
def controller_path
self.class.controller_path
end
# :api: private
def self.action_names
action_methods
end
# :api: private
def action_names
action_methods
end
# :api: plugin
def self.call(env)
controller = new
controller.call(env).to_rack
end
# The details below can be overridden to support a specific
# Request and Response object. The default ActionController::Base
# implementation includes RackConvenience, which makes a request
# and response object available. You might wish to control the
# environment and response manually for performance reasons.
attr_internal :status, :headers, :content_type
def initialize(*)
@_headers = {}
super
end
# Basic implements for content_type=, location=, and headers are
# provided to reduce the dependency on the RackConvenience module
# in Renderer and Redirector.
def content_type=(type)
headers["Content-Type"] = type.to_s
end
def location=(url)
headers["Location"] = url
end
# :api: private
def call(name, env)
@_env = env
process(name)
to_rack
end
# :api: private
def to_rack
[status, headers, response_body]
end
def self.action(name)
@actions ||= {}
@actions[name.to_s] ||= proc do |env|
new.call(name, env)
end
end
end
end

View File

@ -1,37 +1,34 @@
module ActionController
module Layouts
extend ActiveSupport::Concern
depends_on ActionController::Renderer
depends_on AbstractController::Layouts
module ClassMethods
def _implied_layout_name
controller_path
end
end
def render_to_body(options)
# render :text => ..., :layout => ...
# or
# render :anything_else
if !options.key?(:text) || options.key?(:layout)
options[:_layout] = options.key?(:layout) ? _layout_for_option(options[:layout]) : _default_layout
private
def _determine_template(options)
super
if (!options.key?(:text) && !options.key?(:inline) && !options.key?(:partial)) || options.key?(:layout)
options[:_layout] = _layout_for_option(options.key?(:layout) ? options[:layout] : :none, options[:_template].details)
end
end
super
end
private
def _layout_for_option(name)
case name
when String then _layout_for_name(name)
when true then _default_layout(true)
when false, nil then nil
else
raise ArgumentError,
"String, true, or false, expected for `layout'; you passed #{name.inspect}"
def _layout_for_option(name, details)
case name
when String then _layout_for_name(name, details)
when true then _default_layout(true, details)
when :none then _default_layout(false, details)
when false, nil then nil
else
raise ArgumentError,
"String, true, or false, expected for `layout'; you passed #{name.inspect}"
end
end
end
end
end

View File

@ -0,0 +1,33 @@
module ActionController
module RackConvenience
extend ActiveSupport::Concern
included do
delegate :headers, :status=, :location=,
:status, :location, :content_type, :to => "@_response"
attr_internal :request, :response
end
def call(name, env)
@_request = ActionDispatch::Request.new(env)
@_response = ActionDispatch::Response.new
@_response.request = request
super
end
def params
@_params ||= @_request.parameters
end
# :api: private
def to_rack
@_response.prepare!
@_response.to_a
end
def response_body=(body)
response.body = body if response
super
end
end
end

View File

@ -0,0 +1,19 @@
module ActionController
class RedirectBackError < AbstractController::Error #: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"].'
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
end
end
module Redirector
def redirect_to(url, status) #:doc:
raise AbstractController::DoubleRenderError if response_body
logger.info("Redirected to #{url}") if logger && logger.info?
self.status = status
self.location = url.gsub(/[\r\n]/, '')
self.response_body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>"
end
end
end

View File

@ -0,0 +1,103 @@
module ActionController
module RenderOptions
extend ActiveSupport::Concern
included do
extlib_inheritable_accessor :_renderers
self._renderers = []
end
module ClassMethods
def _write_render_options
renderers = _renderers.map do |r|
<<-RUBY_EVAL
if options.key?(:#{r})
_process_options(options)
return _render_#{r}(options[:#{r}], options)
end
RUBY_EVAL
end
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def _handle_render_options(options)
#{renderers.join}
end
RUBY_EVAL
end
def _add_render_option(name)
_renderers << name
_write_render_options
end
end
def render_to_body(options)
_handle_render_options(options) || super
end
end
module RenderOption #:nodoc:
def self.extended(base)
base.extend ActiveSupport::Concern
base.depends_on ::ActionController::RenderOptions
def base.register_renderer(name)
included { _add_render_option(name) }
end
end
end
module Renderers
module Json
extend RenderOption
register_renderer :json
def _render_json(json, options)
json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str)
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
self.content_type ||= Mime::JSON
self.response_body = json
end
end
module Js
extend RenderOption
register_renderer :js
def _render_js(js, options)
self.content_type ||= Mime::JS
self.response_body = js
end
end
module Xml
extend RenderOption
register_renderer :xml
def _render_xml(xml, options)
self.content_type ||= Mime::XML
self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml
end
end
module RJS
extend RenderOption
register_renderer :update
def _render_update(proc, options)
generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(_action_view, &proc)
self.content_type = Mime::JS
self.response_body = generator.to_s
end
end
module All
extend ActiveSupport::Concern
depends_on ActionController::Renderers::Json
depends_on ActionController::Renderers::Js
depends_on ActionController::Renderers::Xml
depends_on ActionController::Renderers::RJS
end
end
end

View File

@ -1,62 +1,78 @@
module ActionController
module Renderer
extend ActiveSupport::Concern
depends_on AbstractController::Renderer
def initialize(*)
self.formats = [:html]
def process_action(*)
self.formats = request.formats.map {|x| x.to_sym}
super
end
def render(action, options = {})
# TODO: Move this into #render_to_body
if action.is_a?(Hash)
options, action = action, nil
else
options.merge! :action => action
def render(options)
super
options[:_template] ||= _action_view._partial
self.content_type ||= begin
mime = options[:_template].mime_type
formats.include?(mime && mime.to_sym) || formats.include?(:all) ? mime : Mime::Type.lookup_by_extension(formats.first)
end
_process_options(options)
self.response_body = render_to_body(options)
response_body
end
def render_to_body(options)
unless options.is_a?(Hash)
options = {:action => options}
_process_options(options)
if options.key?(:partial)
_render_partial(options[:partial], options)
end
if options.key?(:text)
options[:_template] = ActionView::TextTemplate.new(_text(options))
template = nil
elsif options.key?(:template)
options[:_template_name] = options[:template]
elsif options.key?(:action)
options[:_template_name] = options[:action].to_s
options[:_prefix] = _prefix
end
super(options)
super
end
private
def _prefix
controller_path
end
def _text(options)
text = options[:text]
case text
when nil then " "
else text.to_s
private
def _prefix
controller_path
end
end
def _process_options(options)
if status = options[:status]
response.status = status.to_i
def _determine_template(options)
if options.key?(:text)
options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first)
elsif options.key?(:inline)
handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb")
template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {})
options[:_template] = template
elsif options.key?(:template)
options[:_template_name] = options[:template]
elsif options.key?(:file)
options[:_template_name] = options[:file]
elsif !options.key?(:partial)
options[:_template_name] = (options[:action] || action_name).to_s
options[:_prefix] = _prefix
end
super
end
def _render_partial(partial, options)
case partial
when true
options[:_prefix] = _prefix
when String
options[:_prefix] = _prefix unless partial.index('/')
options[:_template_name] = partial
else
options[:_partial_object] = true
return
end
options[:_partial] = options[:object] || true
end
def _process_options(options)
status, content_type, location = options.values_at(:status, :content_type, :location)
self.status = status if status
self.content_type = content_type if content_type
self.headers["Location"] = url_for(location) if location
end
end
end
end

View File

@ -0,0 +1,52 @@
module ActionController #:nodoc:
# 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.
#
# 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 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
extend ActiveSupport::Concern
included do
include ActiveSupport::Rescuable
end
module ClassMethods
# This can be removed once we can move action(:_rescue_action) into middlewares.rb
# Currently, it does controller.method(:rescue_action), which is hiding the implementation
# difference between the old and new base.
def rescue_action(env)
action(:_rescue_action).call(env)
end
end
attr_internal :rescued_exception
private
def method_for_action(action_name)
return action_name if self.rescued_exception = request.env.delete("action_dispatch.rescue.exception")
super
end
def _rescue_action
rescue_with_handler(rescued_exception) || raise(rescued_exception)
end
def process_action(*)
super
rescue Exception => exception
self.rescued_exception = exception
_rescue_action
end
end
end

View File

@ -0,0 +1,15 @@
module ActionController
module Session
extend ActiveSupport::Concern
depends_on RackConvenience
def session
@_request.session
end
def reset_session
@_request.reset_session
end
end
end

View File

@ -0,0 +1,39 @@
module ActionController
module Testing
extend ActiveSupport::Concern
depends_on RackConvenience
# OMG MEGA HAX
def process_with_new_base_test(request, response)
@_request = request
@_response = response
@_response.request = request
ret = process(request.parameters[:action])
@_response.body ||= self.response_body
@_response.prepare!
set_test_assigns
ret
end
def set_test_assigns
@assigns = {}
(instance_variable_names - self.class.protected_instance_variables).each do |var|
name, value = var[1..-1], instance_variable_get(var)
@assigns[name] = value
end
end
# TODO : Rewrite tests using controller.headers= to use Rack env
def headers=(new_headers)
@_response ||= ActionDispatch::Response.new
@_response.headers.replace(new_headers)
end
module ClassMethods
def before_filters
_process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name}
end
end
end
end

View File

@ -1,5 +1,14 @@
module ActionController
module UrlFor
extend ActiveSupport::Concern
depends_on RackConvenience
def process_action(*)
initialize_current_url
super
end
def initialize_current_url
@url = UrlRewriter.new(request, params.clone)
end
@ -16,7 +25,7 @@ module ActionController
# by this method.
def default_url_options(options = nil)
end
def rewrite_options(options) #:nodoc:
if defaults = default_url_options(options)
defaults.merge(options)
@ -24,7 +33,7 @@ module ActionController
options
end
end
def url_for(options = {})
options ||= {}
case options
@ -37,4 +46,4 @@ module ActionController
end
end
end
end
end

View File

@ -1,3 +1,5 @@
require 'active_support/core_ext/module'
module ActionController
# The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
# Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate

View File

@ -1,5 +1,9 @@
require 'cgi'
require 'uri'
require 'set'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/attribute_accessors'
require 'action_controller/routing/optimisations'
require 'action_controller/routing/routing_ext'
require 'action_controller/routing/route'

View File

@ -1,3 +1,5 @@
require 'active_support/core_ext/hash/except'
module ActionController
module Routing
class RouteBuilder #:nodoc:

View File

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/misc'
module ActionController
module Routing
class Route #:nodoc:
@ -65,7 +67,7 @@ module ActionController
# map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
#
def parameter_shell
@parameter_shell ||= returning({}) do |shell|
@parameter_shell ||= {}.tap do |shell|
requirements.each do |key, requirement|
shell[key] = requirement unless requirement.is_a? Regexp
end
@ -76,7 +78,7 @@ module ActionController
# includes keys that appear inside the path, and keys that have requirements
# placed upon them.
def significant_keys
@significant_keys ||= returning([]) do |sk|
@significant_keys ||= [].tap do |sk|
segments.each { |segment| sk << segment.key if segment.respond_to? :key }
sk.concat requirements.keys
sk.uniq!
@ -86,7 +88,7 @@ module ActionController
# Return a hash of key/value pairs representing the keys in the route that
# have defaults, or which are specified by non-regexp requirements.
def defaults
@defaults ||= returning({}) do |hash|
@defaults ||= {}.tap do |hash|
segments.each do |segment|
next unless segment.respond_to? :default
hash[segment.key] = segment.default unless segment.default.nil?

View File

@ -430,7 +430,7 @@ module ActionController
def call(env)
request = ActionDispatch::Request.new(env)
app = Routing::Routes.recognize(request)
app.call(env).to_a
app.action(request.parameters[:action] || 'index').call(env)
end
def recognize(request)

View File

@ -1,7 +1,7 @@
module ActionController
module Routing
class Segment #:nodoc:
RESERVED_PCHAR = ':@&=+$,;'
RESERVED_PCHAR = ':@&=+$,;%'
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
if RUBY_VERSION >= '1.9'
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze

View File

@ -1,150 +0,0 @@
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
#
# You can also pass an explicit status number like assert_response(501)
# or its symbolic equivalent assert_response(:not_implemented).
# See ActionDispatch::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}?")
assert_block("") { true } # to count the assertion
elsif type.is_a?(Fixnum) && @response.response_code == type
assert_block("") { true } # to count the assertion
elsif type.is_a?(Symbol) && @response.response_code == ActionDispatch::StatusCodes::SYMBOL_TO_STATUS_CODE[type]
assert_block("") { true } # to count the assertion
else
if @response.error?
exception = @response.template.instance_variable_get(:@exception)
exception_message = exception && exception.message
assert_block(build_message(message, "Expected response to be a <?>, but was <?>\n<?>", type, @response.response_code, exception_message.to_s)) { false }
else
assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
end
end
end
end
# 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
#
# # assert that the redirection was to the url for @customer
# assert_redirected_to @customer
#
def assert_redirected_to(options = {}, message=nil)
clean_backtrace do
assert_response(:redirect, message)
return true if options == @response.redirected_to
# Support partial arguments for hash redirections
if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
return true if options.all? {|(key, value)| @response.redirected_to[key] == value}
end
redirected_to_after_normalisation = normalize_argument_to_redirection(@response.redirected_to)
options_after_normalisation = normalize_argument_to_redirection(options)
if redirected_to_after_normalisation != options_after_normalisation
flunk "Expected response to be a redirect to <#{options_after_normalisation}> but was a redirect to <#{redirected_to_after_normalisation}>"
end
end
end
# Asserts that the request was rendered with the appropriate template file or partials
#
# ==== Examples
#
# # assert that the "new" view template was rendered
# assert_template "new"
#
# # assert that the "_customer" partial was rendered twice
# assert_template :partial => '_customer', :count => 2
#
# # assert that no partials were rendered
# assert_template :partial => false
#
def assert_template(options = {}, message = nil)
clean_backtrace do
case options
when NilClass, String
rendered = @response.rendered[:template].to_s
msg = build_message(message,
"expecting <?> but rendering with <?>",
options, rendered)
assert_block(msg) do
if options.nil?
@response.rendered[:template].blank?
else
rendered.to_s.match(options)
end
end
when Hash
if expected_partial = options[:partial]
partials = @response.rendered[:partials]
if expected_count = options[:count]
found = partials.detect { |p, _| p.to_s.match(expected_partial) }
actual_count = found.nil? ? 0 : found.second
msg = build_message(message,
"expecting ? to be rendered ? time(s) but rendered ? time(s)",
expected_partial, expected_count, actual_count)
assert(actual_count == expected_count.to_i, msg)
else
msg = build_message(message,
"expecting partial <?> but action rendered <?>",
options[:partial], partials.keys)
assert(partials.keys.any? { |p| p.to_s.match(expected_partial) }, msg)
end
else
assert @response.rendered[:partials].empty?,
"Expected no partials to be rendered"
end
end
end
end
private
# Proxy to to_param if the object will respond to it.
def parameterize(value)
value.respond_to?(:to_param) ? value.to_param : value
end
def normalize_argument_to_redirection(fragment)
after_routing = @controller.url_for(fragment)
if after_routing =~ %r{^\w+://.*}
after_routing
else
# FIXME - this should probably get removed.
if after_routing.first != '/'
after_routing = '/' + after_routing
end
@request.protocol + @request.host_with_port + after_routing
end
end
end
end
end

View File

@ -2,176 +2,12 @@ require 'stringio'
require 'uri'
require 'active_support/test_case'
require 'rack/mock_session'
require 'rack/test/cookie_jar'
module ActionController
module Integration #:nodoc:
# An integration Session instance represents a set of requests and responses
# performed sequentially by some virtual user. Because you can instantiate
# multiple sessions and run them side-by-side, you can also mimic (to some
# limited extent) multiple simultaneous users interacting with your system.
#
# Typically, you will instantiate a new session using
# IntegrationTest#open_session, rather than instantiating
# Integration::Session directly.
class Session
include Test::Unit::Assertions
include ActionController::TestCase::Assertions
include ActionController::TestProcess
# Rack application to use
attr_accessor :application
# The integer HTTP status code of the last request.
attr_reader :status
# The status message that accompanied the status code of the last request.
attr_reader :status_message
# The body of the last request.
attr_reader :body
# The URI of the last request.
attr_reader :path
# The hostname used in the last request.
attr_accessor :host
# The remote_addr used in the last request.
attr_accessor :remote_addr
# The Accept header to send.
attr_accessor :accept
# A map of the cookies returned by the last response, and which will be
# sent with the next request.
attr_reader :cookies
# A map of the headers returned by the last response.
attr_reader :headers
# A reference to the controller instance used by the last request.
attr_reader :controller
# A reference to the request instance used by the last request.
attr_reader :request
# A reference to the response instance used by the last request.
attr_reader :response
# A running counter of the number of requests processed.
attr_accessor :request_count
class MultiPartNeededException < Exception
end
# Create and initialize a new Session instance.
def initialize(app = nil)
@application = app || ActionController::Dispatcher.new
reset!
end
# Resets the instance. This can be used to reset the state information
# in an existing session instance, so it can be used from a clean-slate
# condition.
#
# session.reset!
def reset!
@status = @path = @headers = nil
@result = @status_message = nil
@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 defined? @named_routes_configured
# install the named routes in this session instance.
klass = class << self; self; end
Routing::Routes.install_helpers(klass)
# the helpers are made protected by default--we make them public for
# easier access during testing and troubleshooting.
klass.module_eval { public *Routing::Routes.named_routes.helpers }
@named_routes_configured = true
end
end
# Specify whether or not the session should mimic a secure HTTPS request.
#
# session.https!
# session.https!(false)
def https!(flag = true)
@https = flag
end
# Return +true+ if the session is mimicking a secure HTTPS request.
#
# if session.https?
# ...
# end
def https?
@https
end
# Set the host name to use in the next request.
#
# session.host! "www.example.com"
def host!(name)
@host = name
end
# Follow a single redirect response. If the last response was not a
# redirect, an exception will be raised. Otherwise, the redirect is
# performed on the location header.
def follow_redirect!
raise "not a redirect! #{@status} #{@status_message}" unless redirect?
get(interpret_uri(headers['location']))
status
end
# 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 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.
def redirect?
status/100 == 3
end
module RequestHelpers
# Performs a GET request with the given parameters.
#
# - +path+: The URI (as a String) on which you want to perform a GET
@ -229,12 +65,166 @@ module ActionController
# with 'HTTP_' if not already.
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
headers['X-Requested-With'] = 'XMLHttpRequest'
headers['Accept'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
process(request_method, path, parameters, headers)
end
alias xhr :xml_http_request
# Follow a single redirect response. If the last response was not a
# redirect, an exception will be raised. Otherwise, the redirect is
# performed on the location header.
def follow_redirect!
raise "not a redirect! #{status} #{status_message}" unless redirect?
get(response.location)
status
end
# 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)
process(http_method, path, parameters, headers)
follow_redirect! while redirect?
status
end
# 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
end
# An integration Session instance represents a set of requests and responses
# performed sequentially by some virtual user. Because you can instantiate
# multiple sessions and run them side-by-side, you can also mimic (to some
# limited extent) multiple simultaneous users interacting with your system.
#
# Typically, you will instantiate a new session using
# IntegrationTest#open_session, rather than instantiating
# Integration::Session directly.
class Session
DEFAULT_HOST = "www.example.com"
include Test::Unit::Assertions
include ActionDispatch::Assertions
include ActionController::TestProcess
include RequestHelpers
%w( status status_message headers body redirect? ).each do |method|
delegate method, :to => :response, :allow_nil => true
end
%w( path ).each do |method|
delegate method, :to => :request, :allow_nil => true
end
# The hostname used in the last request.
attr_accessor :host
# The remote_addr used in the last request.
attr_accessor :remote_addr
# The Accept header to send.
attr_accessor :accept
# A map of the cookies returned by the last response, and which will be
# sent with the next request.
def cookies
@mock_session.cookie_jar
end
# A reference to the controller instance used by the last request.
attr_reader :controller
# A reference to the request instance used by the last request.
attr_reader :request
# A reference to the response instance used by the last request.
attr_reader :response
# A running counter of the number of requests processed.
attr_accessor :request_count
# Create and initialize a new Session instance.
def initialize(app = nil)
@app = app || ActionController::Dispatcher.new
reset!
end
# Resets the instance. This can be used to reset the state information
# in an existing session instance, so it can be used from a clean-slate
# condition.
#
# session.reset!
def reset!
@https = false
@mock_session = Rack::MockSession.new(@app, DEFAULT_HOST)
@controller = @request = @response = nil
@request_count = 0
self.host = DEFAULT_HOST
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 defined? @named_routes_configured
# install the named routes in this session instance.
klass = class << self; self; end
Routing::Routes.install_helpers(klass)
# the helpers are made protected by default--we make them public for
# easier access during testing and troubleshooting.
klass.module_eval { public *Routing::Routes.named_routes.helpers }
@named_routes_configured = true
end
end
# Specify whether or not the session should mimic a secure HTTPS request.
#
# session.https!
# session.https!(false)
def https!(flag = true)
@https = flag
end
# Return +true+ if the session is mimicking a secure HTTPS request.
#
# if session.https?
# ...
# end
def https?
@https
end
# Set the host name to use in the next request.
#
# session.host! "www.example.com"
def host!(name)
@host = name
end
# Returns the URL for the given options, according to the rules specified
# in the application's routes.
def url_for(options)
@ -244,63 +234,14 @@ module ActionController
end
private
# Tailors the session based on the given URI, setting the HTTPS value
# and the hostname.
def interpret_uri(path)
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
host! location.host if location.host
location.query ? "#{location.path}?#{location.query}" : location.path
end
# Performs the actual request.
def process(method, path, parameters = nil, headers = nil)
data = requestify(parameters)
path = interpret_uri(path) if path =~ %r{://}
path = "/#{path}" unless path[0] == ?/
@path = path
env = {}
if method == :get
env["QUERY_STRING"] = data
data = nil
end
env["QUERY_STRING"] ||= ""
data = data.is_a?(IO) ? data : StringIO.new(data || '')
env.update(
"REQUEST_METHOD" => method.to_s.upcase,
"SERVER_NAME" => host,
"SERVER_PORT" => (https? ? "443" : "80"),
"HTTPS" => https? ? "on" : "off",
"rack.url_scheme" => https? ? "https" : "http",
"SCRIPT_NAME" => "",
"REQUEST_URI" => path,
"PATH_INFO" => path,
"HTTP_HOST" => host,
"REMOTE_ADDR" => remote_addr,
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
"CONTENT_LENGTH" => data ? data.length.to_s : nil,
"HTTP_COOKIE" => encode_cookies,
"HTTP_ACCEPT" => accept,
"rack.version" => [0,1],
"rack.input" => data,
"rack.errors" => StringIO.new,
"rack.multithread" => true,
"rack.multiprocess" => true,
"rack.run_once" => false,
"rack.test" => true
)
(headers || {}).each do |key, value|
key = key.to_s.upcase.gsub(/-/, "_")
key = "HTTP_#{key}" unless env.has_key?(key) || key =~ /^HTTP_/
env[key] = value
def process(method, path, parameters = nil, rack_environment = nil)
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
host! location.host if location.host
path = location.query ? "#{location.path}?#{location.query}" : location.path
end
[ControllerCapture, ActionController::ProcessWithTest].each do |mod|
@ -309,72 +250,38 @@ module ActionController
end
end
ActionController::Base.clear_last_instantiation!
opts = {
:method => method,
:params => parameters,
app = @application
# Rack::Lint doesn't accept String headers or bodies in Ruby 1.9
unless RUBY_VERSION >= '1.9.0' && Rack.release <= '0.9.0'
app = Rack::Lint.new(app)
"SERVER_NAME" => host,
"SERVER_PORT" => (https? ? "443" : "80"),
"HTTPS" => https? ? "on" : "off",
"rack.url_scheme" => https? ? "https" : "http",
"REQUEST_URI" => path,
"PATH_INFO" => path,
"HTTP_HOST" => host,
"REMOTE_ADDR" => remote_addr,
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
"HTTP_ACCEPT" => accept
}
env = Rack::MockRequest.env_for(path, opts)
(rack_environment || {}).each do |key, value|
env[key] = value
end
status, headers, body = app.call(env)
@request_count += 1
@controller = ActionController::Base.capture_instantiation do
@mock_session.request(URI.parse(path), env)
end
@request_count += 1
@request = ActionDispatch::Request.new(env)
@response = ActionDispatch::TestResponse.from_response(@mock_session.last_response)
@html_document = nil
@status = status.to_i
@status_message = ActionDispatch::StatusCodes::STATUS_CODES[@status]
@headers = Rack::Utils::HeaderHash.new(headers)
(@headers['Set-Cookie'] || "").split("\n").each do |cookie|
name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
@cookies[name] = value
end
if body.is_a?(String)
@body_parts = [body]
@body = body
else
@body_parts = []
body.each { |part| @body_parts << part.to_s }
@body = @body_parts.join
end
if @controller = ActionController::Base.last_instantiation
@request = @controller.request
@response = @controller.response
@controller.send(:set_test_assigns)
else
# Decorate responses from Rack Middleware and Rails Metal
# as an Response for the purposes of integration testing
@response = ActionDispatch::Response.new
@response.status = status.to_s
@response.headers.replace(@headers)
@response.body = @body_parts
end
# Decorate the response with the standard behavior of the
# TestResponse so that things like assert_response can be
# used in integration tests.
@response.extend(TestResponseBehavior)
return @status
rescue MultiPartNeededException
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
status = process(method, path,
multipart_body(parameters, boundary),
(headers || {}).merge(
{"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
return status
end
# Encode the cookies hash in a format suitable for passing to a
# request.
def encode_cookies
cookies.inject("") do |string, (name, value)|
string << "#{name}=#{value}; "
end
return response.status
end
# Get a temporary URL writer object
@ -389,97 +296,29 @@ module ActionController
}
UrlRewriter.new(ActionDispatch::Request.new(env), {})
end
def name_with_prefix(prefix, name)
prefix ? "#{prefix}[#{name}]" : name.to_s
end
# Convert the given parameters to a request string. The parameters may
# be a string, +nil+, or a Hash.
def requestify(parameters, prefix=nil)
if TestUploadedFile === parameters
raise MultiPartNeededException
elsif Hash === parameters
return nil if parameters.empty?
parameters.map { |k,v|
requestify(v, name_with_prefix(prefix, k))
}.join("&")
elsif Array === parameters
parameters.map { |v|
requestify(v, name_with_prefix(prefix, ""))
}.join("&")
elsif prefix.nil?
parameters
else
"#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
end
end
def multipart_requestify(params, first=true)
returning Hash.new do |p|
params.each do |key, value|
k = first ? CGI.escape(key.to_s) : "[#{CGI.escape(key.to_s)}]"
if Hash === value
multipart_requestify(value, false).each do |subkey, subvalue|
p[k + subkey] = subvalue
end
else
p[k] = value
end
end
end
end
def multipart_body(params, boundary)
multipart_requestify(params).map do |key, value|
if value.respond_to?(:original_filename)
File.open(value.path, "rb") do |f|
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
<<-EOF
--#{boundary}\r
Content-Disposition: form-data; name="#{key}"; filename="#{CGI.escape(value.original_filename)}"\r
Content-Type: #{value.content_type}\r
Content-Length: #{File.stat(value.path).size}\r
\r
#{f.read}\r
EOF
end
else
<<-EOF
--#{boundary}\r
Content-Disposition: form-data; name="#{key}"\r
\r
#{value}\r
EOF
end
end.join("")+"--#{boundary}--\r"
end
end
# A module used to extend ActionController::Base, so that integration tests
# can capture the controller used to satisfy a request.
module ControllerCapture #:nodoc:
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
class << self
alias_method_chain :new, :capture
end
end
extend ActiveSupport::Concern
included do
alias_method_chain :initialize, :capture
end
def initialize_with_capture(*args)
initialize_without_capture
self.class.last_instantiation ||= self
end
module ClassMethods #:nodoc:
mattr_accessor :last_instantiation
def clear_last_instantiation!
def capture_instantiation
self.last_instantiation = nil
end
def new_with_capture(*args)
controller = new_without_capture(*args)
self.last_instantiation ||= controller
controller
yield
return last_instantiation
end
end
end
@ -513,8 +352,8 @@ EOF
# 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(application = nil)
session = Integration::Session.new(application)
def open_session(app = nil)
session = Integration::Session.new(app)
# delegate the fixture accessors back to the test instance
extras = Module.new { attr_accessor :delegate, :test_result }
@ -636,54 +475,5 @@ EOF
# end
class IntegrationTest < ActiveSupport::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
# symbols.
def initialize(name) #:nodoc:
super(name.to_s)
end
# Work around test/unit's requirement that every subclass of TestCase have
# at least one test method. Note that this implementation extends to all
# subclasses, as well, so subclasses of IntegrationTest may also exist
# without any test methods.
def run(*args) #:nodoc:
return if @method_name == "default_test"
super
end
# Because of how use_instantiated_fixtures and use_transactional_fixtures
# are defined, we need to treat them as special cases. Otherwise, users
# would potentially have to set their values for both Test::Unit::TestCase
# ActionController::IntegrationTest, since by the time the value is set on
# TestCase, IntegrationTest has already been defined and cannot inherit
# changes to those variables. So, we make those two attributes
# copy-on-write.
class << self
def use_transactional_fixtures=(flag) #:nodoc:
@_use_transactional_fixtures = true
@use_transactional_fixtures = flag
end
def use_instantiated_fixtures=(flag) #:nodoc:
@_use_instantiated_fixtures = true
@use_instantiated_fixtures = flag
end
def use_transactional_fixtures #:nodoc:
@_use_transactional_fixtures ?
@use_transactional_fixtures :
superclass.use_transactional_fixtures
end
def use_instantiated_fixtures #:nodoc:
@_use_instantiated_fixtures ?
@use_instantiated_fixtures :
superclass.use_instantiated_fixtures
end
end
end
end

View File

@ -1,94 +1,13 @@
require 'rack/session/abstract/id'
require 'active_support/core_ext/object/conversions'
module ActionController #:nodoc:
class TestRequest < ActionDispatch::Request #:nodoc:
attr_accessor :cookies, :session_options
attr_accessor :query_parameters, :path, :session
attr_accessor :host
def self.new(env = {})
super
end
class TestRequest < ActionDispatch::TestRequest #:nodoc:
def initialize(env = {})
super(Rack::MockRequest.env_for("/").merge(env))
super
@query_parameters = {}
@session = TestSession.new
default_rack_options = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
@session_options ||= {:id => generate_sid(default_rack_options[:sidbits])}.merge(default_rack_options)
initialize_default_values
initialize_containers
end
def reset_session
@session.reset
end
# Wraps raw_post in a StringIO.
def body_stream #:nodoc:
StringIO.new(raw_post)
end
# Either the RAW_POST_DATA environment variable or the URL-encoded request
# parameters.
def raw_post
@env['RAW_POST_DATA'] ||= begin
data = url_encoded_request_parameters
data.force_encoding(Encoding::BINARY) if data.respond_to?(:force_encoding)
data
end
end
def port=(number)
@env["SERVER_PORT"] = number.to_i
end
def action=(action_name)
@query_parameters.update({ "action" => action_name })
@parameters = nil
end
# 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)
@env["REQUEST_URI"] = value
@request_uri = nil
@path = nil
end
def request_uri=(uri)
@request_uri = uri
@path = uri.split("?").first
end
def request_method=(method)
@request_method = method
end
def accept=(mime_types)
@env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
@accepts = nil
end
def if_modified_since=(last_modified)
@env["HTTP_IF_MODIFIED_SINCE"] = last_modified
end
def if_none_match=(etag)
@env["HTTP_IF_NONE_MATCH"] = etag
end
def remote_addr=(addr)
@env['REMOTE_ADDR'] = addr
end
def request_uri(*args)
@request_uri || super()
end
def path(*args)
@path || super()
self.session = TestSession.new
self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16))
end
def assign_parameters(controller_path, action, parameters)
@ -108,247 +27,46 @@ module ActionController #:nodoc:
path_parameters[key.to_s] = value
end
end
raw_post # populate env['RAW_POST_DATA']
@parameters = nil # reset TestRequest#parameters to use the new path_parameters
params = self.request_parameters.dup
%w(controller action only_path).each do |k|
params.delete(k)
params.delete(k.to_sym)
end
data = params.to_query
@env['CONTENT_LENGTH'] = data.length.to_s
@env['rack.input'] = StringIO.new(data)
end
def recycle!
@env["action_controller.request.request_parameters"] = {}
self.query_parameters = {}
self.path_parameters = {}
@headers, @request_method, @accepts, @content_type = nil, nil, nil, nil
end
def user_agent=(user_agent)
@env['HTTP_USER_AGENT'] = user_agent
end
private
def generate_sid(sidbits)
"%0#{sidbits / 4}x" % rand(2**sidbits - 1)
end
def initialize_containers
@cookies = {}
end
def initialize_default_values
@host = "test.host"
@request_uri = "/"
@env['HTTP_USER_AGENT'] = "Rails Testing"
@env['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
# to the "real" CgiResponse class in integration tests.
module TestResponseBehavior #:nodoc:
# The response code of the request
def response_code
status.to_s[0,3].to_i rescue 0
end
# Returns a String to ensure compatibility with Net::HTTPResponse
def code
status.to_s.split(' ')[0]
end
def message
status.to_s.split(' ',2)[1]
end
# Was the response successful?
def success?
(200..299).include?(response_code)
end
# Was the URL not found?
def missing?
response_code == 404
end
# Were we redirected?
def redirect?
(300..399).include?(response_code)
end
# Was there a server-side error?
def error?
(500..599).include?(response_code)
end
alias_method :server_error?, :error?
# Was there a client client?
def client_error?
(400..499).include?(response_code)
end
# Returns the redirection location or nil
def redirect_url
headers['Location']
end
# Does the redirect location match this regexp pattern?
def redirect_url_match?( pattern )
return false if redirect_url.nil?
p = Regexp.new(pattern) if pattern.class == String
p = pattern if pattern.class == Regexp
return false if p.nil?
p.match(redirect_url) != nil
end
# Returns the template of the file which was used to
# render this response (or nil)
def rendered
template.instance_variable_get(:@_rendered)
end
# A shortcut to the flash. Returns an empty hash if no session flash exists.
def flash
session['flash'] || {}
end
# Do we have a flash?
def has_flash?
!flash.empty?
end
# Do we have a flash that has contents?
def has_flash_with_contents?
!flash.empty?
end
# Does the specified flash object exist?
def has_flash_object?(name=nil)
!flash[name].nil?
end
# Does the specified object exist in the session?
def has_session_object?(name=nil)
!session[name].nil?
end
# A shortcut to the template.assigns
def template_objects
template.assigns || {}
end
# Does the specified template object exist?
def has_template_object?(name=nil)
!template_objects[name].nil?
end
# Returns the response cookies, converted to a Hash of (name => value) pairs
#
# assert_equal 'AuthorOfNewPage', r.cookies['author']
def cookies
cookies = {}
Array(headers['Set-Cookie']).each do |cookie|
key, value = cookie.split(";").first.split("=").map {|val| Rack::Utils.unescape(val)}
cookies[key] = value
end
cookies
end
# Returns binary content (downloadable file), converted to a String
def binary_content
raise "Response body is not a Proc: #{body_parts.inspect}" unless body_parts.kind_of?(Proc)
require 'stringio'
sio = StringIO.new
body_parts.call(self, sio)
sio.rewind
sio.read
@formats = nil
@env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
@env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
@env['action_dispatch.request.query_parameters'] = {}
end
end
# Integration test methods such as ActionController::Integration::Session#get
# and ActionController::Integration::Session#post return objects of class
# TestResponse, which represent the HTTP response results of the requested
# controller actions.
#
# See Response for more information on controller response objects.
class TestResponse < ActionDispatch::Response
include TestResponseBehavior
class TestResponse < ActionDispatch::TestResponse
def recycle!
body_parts.clear
headers.delete('ETag')
headers.delete('Last-Modified')
@status = 200
@header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS)
@writer = lambda { |x| @body << x }
@block = nil
@length = 0
@body = []
@request = @template = nil
end
end
class TestSession < Hash #:nodoc:
attr_accessor :session_id
class TestSession < ActionDispatch::Session::AbstractStore::SessionHash #:nodoc:
DEFAULT_OPTIONS = ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS
def initialize(attributes = nil)
reset_session_id
replace_attributes(attributes)
end
def reset
reset_session_id
replace_attributes({ })
end
def data
to_hash
end
def [](key)
super(key.to_s)
end
def []=(key, value)
super(key.to_s, value)
end
def update(hash = nil)
if hash.nil?
ActiveSupport::Deprecation.warn('use replace instead', caller)
replace({})
else
super(hash)
end
end
def delete(key = nil)
if key.nil?
ActiveSupport::Deprecation.warn('use clear instead', caller)
clear
else
super(key.to_s)
end
end
def close
ActiveSupport::Deprecation.warn('sessions should no longer be closed', caller)
end
private
def reset_session_id
@session_id = ''
end
def replace_attributes(attributes = nil)
attributes ||= {}
replace(attributes.stringify_keys)
def initialize(session = {})
replace(session.stringify_keys)
@loaded = true
end
end
@ -363,34 +81,7 @@ module ActionController #:nodoc:
#
# 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(ActionController::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_accessor :content_type
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.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
@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)
end
end
TestUploadedFile = Rack::Utils::Multipart::UploadedFile
module TestProcess
def self.included(base)
@ -433,9 +124,7 @@ module ActionController #:nodoc:
@response.recycle!
@html_document = nil
@request.env['REQUEST_METHOD'] = http_method
@request.action = action.to_s
@request.request_method = http_method
parameters ||= {}
@request.assign_parameters(@controller.class.controller_path, action.to_s, parameters)
@ -445,7 +134,19 @@ module ActionController #:nodoc:
build_request_uri(action, parameters)
Base.class_eval { include ProcessWithTest } unless Base < ProcessWithTest
@controller.process_with_test(@request, @response)
env = @request.env
app = @controller
# TODO: Enable Lint
# app = Rack::Lint.new(app)
status, headers, body = app.action(action, env)
response = Rack::MockResponse.new(status, headers, body)
@response.request, @response.template = @request, @controller.template
@response.status, @response.headers, @response.body = response.status, response.headers, response.body
@response
end
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@ -459,11 +160,13 @@ module ActionController #:nodoc:
alias xhr :xml_http_request
def assigns(key = nil)
if key.nil?
@response.template.assigns
else
@response.template.assigns[key.to_s]
assigns = {}
@controller.instance_variable_names.each do |ivar|
next if ActionController::Base.protected_instance_variables.include?(ivar)
assigns[ivar[1..-1]] = @controller.instance_variable_get(ivar)
end
key.nil? ? assigns : assigns[key.to_s]
end
def session
@ -471,7 +174,7 @@ module ActionController #:nodoc:
end
def flash
@response.flash
@request.flash
end
def cookies
@ -488,7 +191,7 @@ module ActionController #:nodoc:
options.update(:only_path => true, :action => action)
url = ActionController::UrlRewriter.new(@request, parameters)
@request.set_REQUEST_URI(url.rewrite(options))
@request.request_uri = url.rewrite(options)
end
end
@ -561,11 +264,14 @@ module ActionController #:nodoc:
module ProcessWithTest #:nodoc:
def self.included(base)
base.class_eval { attr_reader :assigns }
base.class_eval {
attr_reader :assigns
alias_method_chain :process, :test
}
end
def process_with_test(*args)
process(*args).tap { set_test_assigns }
process_without_test(*args).tap { set_test_assigns }
end
private
@ -574,7 +280,7 @@ module ActionController #:nodoc:
(instance_variable_names - self.class.protected_instance_variables).each do |var|
name, value = var[1..-1], instance_variable_get(var)
@assigns[name] = value
response.template.assigns[name] = value if response
@template.assigns[name] = value if response
end
end
end

View File

@ -0,0 +1,74 @@
require "action_controller/testing/process"
module ActionController
module TestProcess
# Executes a request simulating GET HTTP method and set/volley the response
def get(action, parameters = nil, session = nil, flash = nil)
process(action, parameters, session, flash, "GET")
end
# Executes a request simulating POST HTTP method and set/volley the response
def post(action, parameters = nil, session = nil, flash = nil)
process(action, parameters, session, flash, "POST")
end
# Executes a request simulating PUT HTTP method and set/volley the response
def put(action, parameters = nil, session = nil, flash = nil)
process(action, parameters, session, flash, "PUT")
end
# Executes a request simulating DELETE HTTP method and set/volley the response
def delete(action, parameters = nil, session = nil, flash = nil)
process(action, parameters, session, flash, "DELETE")
end
# Executes a request simulating HEAD HTTP method and set/volley the response
def head(action, parameters = nil, session = nil, flash = nil)
process(action, parameters, session, flash, "HEAD")
end
def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
# Sanity check for required instance variables so we can give an
# understandable error message.
%w(@controller @request @response).each do |iv_name|
if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
end
end
@request.recycle!
@response.recycle!
@controller.response_body = nil
@controller.formats = nil
@controller.params = nil
@html_document = nil
@request.env['REQUEST_METHOD'] = http_method
parameters ||= {}
@request.assign_parameters(@controller.class.controller_path, action.to_s, parameters)
@request.session = ActionController::TestSession.new(session) unless session.nil?
@request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
@controller.request = @request
@controller.params.merge!(parameters)
build_request_uri(action, parameters)
# Base.class_eval { include ProcessWithTest } unless Base < ProcessWithTest
@controller.process_with_new_base_test(@request, @response)
@response
end
def build_request_uri(action, 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)
end
end
end
end

View File

@ -105,20 +105,7 @@ module ActionController
class TestCase < ActiveSupport::TestCase
include TestProcess
module Assertions
%w(response selector tag dom routing model).each do |kind|
include ActionController::Assertions.const_get("#{kind.camelize}Assertions")
end
def clean_backtrace(&block)
yield
rescue ActiveSupport::TestCase::Assertion => 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
include Assertions
include ActionDispatch::Assertions
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
# (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular

View File

@ -73,7 +73,7 @@ module HTML
# 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
sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dl dt dd abbr
acronym a img blockquote del ins))
# Specifies the default Set of html attributes that the #sanitize helper will leave

View File

@ -21,35 +21,36 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
$:.unshift activesupport_path if File.directory?(activesupport_path)
require 'active_support'
begin
require 'active_support'
rescue LoadError
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
if File.directory?(activesupport_path)
$:.unshift activesupport_path
require 'active_support'
end
gem 'rack', '~> 1.1.pre'
rescue Gem::LoadError, ArgumentError
$:.unshift "#{File.dirname(__FILE__)}/action_dispatch/vendor/rack-1.1.pre"
end
$:.unshift "#{File.dirname(__FILE__)}/action_dispatch/vendor/rack-1.0"
begin
gem 'rack', '~> 1.0.0'
require 'rack'
rescue Gem::LoadError
require 'action_dispatch/vendor/rack-1.0/rack'
end
require 'rack'
$:.unshift "#{File.dirname(__FILE__)}/action_dispatch/vendor/rack-test"
module ActionDispatch
autoload :Request, 'action_dispatch/http/request'
autoload :Response, 'action_dispatch/http/response'
autoload :StatusCodes, 'action_dispatch/http/status_codes'
autoload :Failsafe, 'action_dispatch/middleware/failsafe'
autoload :Callbacks, 'action_dispatch/middleware/callbacks'
autoload :ParamsParser, 'action_dispatch/middleware/params_parser'
autoload :Reloader, 'action_dispatch/middleware/reloader'
autoload :RewindableInput, 'action_dispatch/middleware/rewindable_input'
autoload :Rescue, 'action_dispatch/middleware/rescue'
autoload :ShowExceptions, 'action_dispatch/middleware/show_exceptions'
autoload :MiddlewareStack, 'action_dispatch/middleware/stack'
autoload :HTML, 'action_controller/vendor/html-scanner'
autoload :Assertions, 'action_dispatch/testing/assertions'
autoload :TestRequest, 'action_dispatch/testing/test_request'
autoload :TestResponse, 'action_dispatch/testing/test_response'
module Http
autoload :Headers, 'action_dispatch/http/headers'
end

View File

@ -1,8 +1,9 @@
require 'set'
require 'active_support/core_ext/class/attribute_accessors'
module Mime
SET = []
EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
EXTENSION_LOOKUP = {}
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
def self.[](type)

View File

@ -1,9 +1,9 @@
# Build list of Mime types for HTTP responses
# http://www.iana.org/assignments/media-types/
Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
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

View File

@ -3,6 +3,9 @@ require 'stringio'
require 'strscan'
require 'active_support/memoizable'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/object/tap'
module ActionDispatch
class Request < Rack::Request
@ -31,7 +34,7 @@ module ActionDispatch
# <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS
# constant above, an UnknownHttpMethod exception is raised.
def request_method
@request_method ||= HTTP_METHOD_LOOKUP[super] || raise(ActionController::UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
HTTP_METHOD_LOOKUP[super] || raise(ActionController::UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
end
# Returns the HTTP request \method used for action processing as a
@ -85,7 +88,7 @@ module ActionDispatch
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_type
@content_type ||= begin
@env["action_dispatch.request.content_type"] ||= begin
if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
Mime::Type.lookup($1.strip.downcase)
else
@ -93,10 +96,14 @@ module ActionDispatch
end
end
end
def media_type
content_type.to_s
end
# Returns the accepted MIME type for the request.
def accepts
@accepts ||= begin
@env["action_dispatch.request.accepts"] ||= begin
header = @env['HTTP_ACCEPT'].to_s.strip
fallback = xhr? ? Mime::JS : Mime::HTML
@ -156,7 +163,7 @@ module ActionDispatch
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
def format(view_path = [])
@format ||=
@env["action_dispatch.request.format"] ||=
if parameters[:format]
Mime[parameters[:format]]
elsif ActionController::Base.use_accept_header && !(accepts == ONLY_ALL)
@ -167,12 +174,23 @@ module ActionDispatch
end
def formats
@formats =
if ActionController::Base.use_accept_header
Array(Mime[parameters[:format]] || accepts)
if ActionController::Base.use_accept_header
if param = parameters[:format]
Array.wrap(Mime[param])
else
[format]
accepts.dup
end.tap do |ret|
if defined?(ActionController::Http)
if ret == ONLY_ALL
ret.replace Mime::SET
elsif all = ret.index(Mime::ALL)
ret.delete_at(all) && ret.insert(all, *Mime::SET)
end
end
end
else
[format] + Mime::SET
end
end
# Sets the \format by string extension, which can be used to force custom formats
@ -188,7 +206,7 @@ module ActionDispatch
# end
def format=(extension)
parameters[:format] = extension.to_s
@format = Mime::Type.lookup_by_extension(parameters[:format])
@env["action_dispatch.request.format"] = Mime::Type.lookup_by_extension(parameters[:format])
end
# Returns a symbolized version of the <tt>:format</tt> parameter of the request.
@ -324,6 +342,10 @@ EOM
port == standard_port ? '' : ":#{port}"
end
def server_port
@env['SERVER_PORT'].to_i
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)
@ -344,7 +366,7 @@ EOM
# Returns the query string, accounting for server idiosyncrasies.
def query_string
@env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '')
@env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '')
end
# Returns the request URI, accounting for server idiosyncrasies.
@ -392,18 +414,19 @@ EOM
# Returns both GET and POST \parameters in a single hash.
def parameters
@parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
@env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
end
alias_method :params, :parameters
def path_parameters=(parameters) #:nodoc:
@env["rack.routing_args"] = parameters
@symbolized_path_parameters = @parameters = nil
@env.delete("action_dispatch.request.symbolized_path_parameters")
@env.delete("action_dispatch.request.parameters")
@env["action_dispatch.request.path_parameters"] = parameters
end
# The same as <tt>path_parameters</tt> with explicitly symbolized keys.
def symbolized_path_parameters
@symbolized_path_parameters ||= path_parameters.symbolize_keys
@env["action_dispatch.request.symbolized_path_parameters"] ||= path_parameters.symbolize_keys
end
# Returns a hash with the \parameters used to form the \path of the request.
@ -413,7 +436,7 @@ EOM
#
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
def path_parameters
@env["rack.routing_args"] ||= {}
@env["action_dispatch.request.path_parameters"] ||= {}
end
# The request body is an IO input stream. If the RAW_POST_DATA environment
@ -433,13 +456,13 @@ EOM
# Override Rack's GET method to support indifferent access
def GET
@env["action_controller.request.query_parameters"] ||= normalize_parameters(super)
@env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super)
end
alias_method :query_parameters, :GET
# Override Rack's POST method to support indifferent access
def POST
@env["action_controller.request.request_parameters"] ||= normalize_parameters(super)
@env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super)
end
alias_method :request_parameters, :POST
@ -447,29 +470,21 @@ EOM
@env['rack.input']
end
def session
@env['rack.session'] ||= {}
def reset_session
self.session_options.delete(:id)
self.session = {}
end
def session=(session) #:nodoc:
@env['rack.session'] = session
end
def reset_session
@env['rack.session.options'].delete(:id)
@env['rack.session'] = {}
end
def session_options
@env['rack.session.options'] ||= {}
end
def session_options=(options)
@env['rack.session.options'] = options
end
def server_port
@env['SERVER_PORT'].to_i
def flash
session['flash'] || {}
end
private

View File

@ -1,4 +1,5 @@
require 'digest/md5'
require 'active_support/core_ext/module/delegation'
module ActionDispatch # :nodoc:
# Represents an HTTP response generated by a controller action. One can use
@ -34,17 +35,31 @@ module ActionDispatch # :nodoc:
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
attr_accessor :request
attr_accessor :session, :assigns, :template, :layout
attr_accessor :redirected_to, :redirected_to_method_params
attr_writer :header
alias_method :headers=, :header=
delegate :default_charset, :to => 'ActionController::Base'
def initialize
super
@header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS)
@session, @assigns = [], []
end
# The response code of the request
def response_code
status.to_s[0,3].to_i rescue 0
end
# Returns a String to ensure compatibility with Net::HTTPResponse
def code
status.to_s.split(' ')[0]
end
def message
status.to_s.split(' ',2)[1] || StatusCodes::STATUS_CODES[response_code]
end
alias_method :status_message, :message
def body
str = ''
each { |part| str << part.to_s }
@ -53,7 +68,7 @@ module ActionDispatch # :nodoc:
def body=(body)
@body =
if body.is_a?(String)
if body.respond_to?(:to_str)
[body]
else
body
@ -64,9 +79,14 @@ module ActionDispatch # :nodoc:
@body
end
def location; headers['Location'] end
def location=(url) headers['Location'] = url end
def location
headers['Location']
end
alias_method :redirect_url, :location
def location=(url)
headers['Location'] = url
end
# Sets the HTTP response's content MIME type. For example, in the controller
# you could write this:
@ -137,19 +157,20 @@ module ActionDispatch # :nodoc:
end
end
def redirect(url, status)
self.status = status
self.location = url.gsub(/[\r\n]/, '')
self.body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>"
end
def sending_file?
headers["Content-Transfer-Encoding"] == "binary"
end
def assign_default_content_type_and_charset!
self.content_type ||= Mime::HTML
self.charset ||= default_charset unless sending_file?
if type = headers['Content-Type'] || headers['type']
unless type =~ /charset=/ || sending_file?
headers['Content-Type'] = "#{type}; charset=#{default_charset}"
end
else
type = Mime::HTML.to_s
type += "; charset=#{default_charset}" unless sending_file?
headers['Content-Type'] = type
end
end
def prepare!
@ -165,10 +186,8 @@ module ActionDispatch # :nodoc:
if @body.respond_to?(:call)
@writer = lambda { |x| callback.call(x) }
@body.call(self, self)
elsif @body.is_a?(String)
callback.call(@body)
else
@body.each(&callback)
@body.each { |part| callback.call(part.to_s) }
end
@writer = callback
@ -192,6 +211,23 @@ module ActionDispatch # :nodoc:
super(key, value)
end
# Returns the response cookies, converted to a Hash of (name => value) pairs
#
# assert_equal 'AuthorOfNewPage', r.cookies['author']
def cookies
cookies = {}
if header = headers['Set-Cookie']
header = header.split("\n") if header.respond_to?(:to_str)
header.each do |cookie|
if pair = cookie.split(';').first
key, value = pair.split("=").map { |v| Rack::Utils.unescape(v) }
cookies[key] = value
end
end
end
cookies
end
private
def handle_conditional_get!
if etag? || last_modified?
@ -245,7 +281,13 @@ module ActionDispatch # :nodoc:
end
def convert_cookies!
headers['Set-Cookie'] = Array(headers['Set-Cookie']).compact
headers['Set-Cookie'] =
if header = headers['Set-Cookie']
header = header.split("\n") if header.respond_to?(:to_str)
header.compact
else
[]
end
end
end
end

View File

@ -1,3 +1,5 @@
require 'active_support/inflector'
module ActionDispatch
module StatusCodes #:nodoc:
STATUS_CODES = Rack::Utils::HTTP_STATUS_CODES.merge({
@ -16,7 +18,7 @@ module ActionDispatch
# :created or :not_implemented) into its corresponding HTTP status
# code (like 200 or 501).
SYMBOL_TO_STATUS_CODE = STATUS_CODES.inject({}) { |hash, (code, message)|
hash[message.gsub(/ /, "").underscore.to_sym] = code
hash[ActiveSupport::Inflector.underscore(message.gsub(/ /, "")).to_sym] = code
hash
}.freeze
@ -37,4 +39,4 @@ module ActionDispatch
end
end
end
end
end

View File

@ -0,0 +1,40 @@
module ActionDispatch
class Callbacks
include ActiveSupport::Callbacks
define_callbacks :prepare, :before, :after
class << self
# DEPRECATED
alias_method :prepare_dispatch, :prepare
alias_method :before_dispatch, :before
alias_method :after_dispatch, :after
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 self.to_prepare(identifier = nil, &block)
@prepare_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new
callback = ActiveSupport::Callbacks::Callback.new(:prepare, block, :identifier => identifier)
@prepare_callbacks.replace_or_append!(callback)
end
def initialize(app, prepare_each_request = false)
@app, @prepare_each_request = app, prepare_each_request
run_callbacks :prepare
end
def call(env)
run_callbacks :before
run_callbacks :prepare if @prepare_each_request
@app.call(env)
ensure
run_callbacks :after, :enumerator => :reverse_each
end
end
end

View File

@ -1,52 +0,0 @@
module ActionDispatch
class Failsafe
cattr_accessor :error_file_path
self.error_file_path = Rails.public_path if defined?(Rails.public_path)
def initialize(app)
@app = app
end
def call(env)
@app.call(env)
rescue Exception => exception
# Reraise exception in test environment
if env["rack.test"]
raise exception
else
failsafe_response(exception)
end
end
private
def failsafe_response(exception)
log_failsafe_exception(exception)
[500, {'Content-Type' => 'text/html'}, failsafe_response_body]
rescue Exception => failsafe_error # Logger or IO errors
$stderr.puts "Error during failsafe response: #{failsafe_error}"
end
def failsafe_response_body
error_path = "#{self.class.error_file_path}/500.html"
if File.exist?(error_path)
File.read(error_path)
else
"<html><body><h1>500 Internal Server Error</h1></body></html>"
end
end
def log_failsafe_exception(exception)
message = "/!\\ FAILSAFE /!\\ #{Time.now}\n Status: 500 Internal Server Error\n"
message << " #{exception}\n #{exception.backtrace.join("\n ")}" if exception
failsafe_logger.fatal(message)
end
def failsafe_logger
if defined?(Rails) && Rails.logger
Rails.logger
else
Logger.new($stderr)
end
end
end
end

View File

@ -1,3 +1,5 @@
require 'active_support/json'
module ActionDispatch
class ParamsParser
ActionController::Base.param_parsers[Mime::XML] = :xml_simple
@ -9,7 +11,7 @@ module ActionDispatch
def call(env)
if params = parse_formatted_parameters(env)
env["action_controller.request.request_parameters"] = params
env["action_dispatch.request.request_parameters"] = params
end
@app.call(env)
@ -30,16 +32,14 @@ module ActionDispatch
when Proc
strategy.call(request.raw_post)
when :xml_simple, :xml_node
body = request.raw_post
body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
request.body.size == 0 ? {} : Hash.from_xml(request.body).with_indifferent_access
when :yaml
YAML.load(request.raw_post)
YAML.load(request.body)
when :json
body = request.raw_post
if body.blank?
if request.body.size == 0
{}
else
data = ActiveSupport::JSON.decode(body)
data = ActiveSupport::JSON.decode(request.body)
data = {:_json => data} unless data.is_a?(Hash)
data.with_indifferent_access
end

View File

@ -1,14 +0,0 @@
module ActionDispatch
class Reloader
def initialize(app)
@app = app
end
def call(env)
ActionController::Dispatcher.reload_application
@app.call(env)
ensure
ActionController::Dispatcher.cleanup_application
end
end
end

View File

@ -0,0 +1,14 @@
module ActionDispatch
class Rescue
def initialize(app, rescuer)
@app, @rescuer = app, rescuer
end
def call(env)
@app.call(env)
rescue Exception => exception
env['action_dispatch.rescue.exception'] = exception
@rescuer.call(env)
end
end
end

View File

@ -1,19 +0,0 @@
module ActionDispatch
class RewindableInput
def initialize(app)
@app = app
end
def call(env)
begin
env['rack.input'].rewind
rescue NoMethodError, Errno::ESPIPE
# Handles exceptions raised by input streams that cannot be rewound
# such as when using plain CGI under Apache
env['rack.input'] = StringIO.new(env['rack.input'].read)
end
@app.call(env)
end
end
end

View File

@ -15,6 +15,7 @@ module ActionDispatch
@by = by
@env = env
@loaded = false
@updated = false
end
def session_id
@ -26,12 +27,13 @@ module ActionDispatch
def [](key)
load! unless @loaded
super
super(key.to_s)
end
def []=(key, value)
load! unless @loaded
super
super(key.to_s, value)
@updated = true
end
def to_hash
@ -40,6 +42,24 @@ module ActionDispatch
h
end
def update(hash = nil)
if hash.nil?
ActiveSupport::Deprecation.warn('use replace instead', caller)
replace({})
else
super(hash.stringify_keys)
end
end
def delete(key = nil)
if key.nil?
ActiveSupport::Deprecation.warn('use clear instead', caller)
clear
else
super(key.to_s)
end
end
def data
ActiveSupport::Deprecation.warn(
"ActionController::Session::AbstractStore::SessionHash#data " +
@ -47,6 +67,10 @@ module ActionDispatch
to_hash
end
def close
ActiveSupport::Deprecation.warn('sessions should no longer be closed', caller)
end
def inspect
load! unless @loaded
super
@ -57,11 +81,15 @@ module ActionDispatch
@loaded
end
def updated?
@updated
end
def load!
stale_session_check! do
id, session = @by.send(:load_session, @env)
(@env[ENV_SESSION_OPTIONS_KEY] ||= {})[:id] = id
replace(session)
replace(session.stringify_keys)
@loaded = true
end
end
@ -74,7 +102,7 @@ module ActionDispatch
# Note that the regexp does not allow $1 to end with a ':'
$1.constantize
rescue LoadError, NameError => const_error
raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn\\'t available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: \#{const_error.message} [\#{const_error.class}])\n"
raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n"
end
retry
@ -125,7 +153,10 @@ module ActionDispatch
options = env[ENV_SESSION_OPTIONS_KEY]
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
if session_data.is_a?(AbstractStore::SessionHash)
session_data.send(:load!) if !session_data.send(:loaded?)
return response if !session_data.send(:updated?)
end
sid = options[:id] || generate_sid

View File

@ -143,7 +143,8 @@ module ActionDispatch
request = Rack::Request.new(env)
session_data = request.cookies[@key]
data = unmarshal(session_data) || persistent_session_id!({})
[data[:session_id], data]
data.stringify_keys!
[data["session_id"], data]
end
# Marshal a session hash into safe cookie data. Include an integrity hash.
@ -206,12 +207,12 @@ module ActionDispatch
end
def inject_persistent_session_id(data)
requires_session_id?(data) ? { :session_id => generate_sid } : {}
requires_session_id?(data) ? { "session_id" => generate_sid } : {}
end
def requires_session_id?(data)
if data
data.respond_to?(:key?) && !data.key?(:session_id)
data.respond_to?(:key?) && !data.key?("session_id")
else
true
end

View File

@ -0,0 +1,141 @@
module ActionDispatch
class ShowExceptions
include StatusCodes
LOCALHOST = '127.0.0.1'.freeze
RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
cattr_accessor :rescue_responses
@@rescue_responses = Hash.new(:internal_server_error)
@@rescue_responses.update({
'ActionController::RoutingError' => :not_found,
# TODO: Clean this up after the switch
ActionController::UnknownAction.name => :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
})
cattr_accessor :rescue_templates
@@rescue_templates = Hash.new('diagnostics')
@@rescue_templates.update({
'ActionView::MissingTemplate' => 'missing_template',
'ActionController::RoutingError' => 'routing_error',
ActionController::UnknownAction.name => 'unknown_action',
'ActionView::TemplateError' => 'template_error'
})
FAILSAFE_RESPONSE = [500, {'Content-Type' => 'text/html'},
["<html><body><h1>500 Internal Server Error</h1>" <<
"If you are the administrator of this website, then please read this web " <<
"application's log file and/or the web server's log file to find out what " <<
"went wrong.</body></html>"]]
def initialize(app, consider_all_requests_local = false)
@app = app
@consider_all_requests_local = consider_all_requests_local
end
def call(env)
@app.call(env)
rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false
render_exception(env, exception)
end
private
def render_exception(env, exception)
log_error(exception)
request = Request.new(env)
if @consider_all_requests_local || local_request?(request)
rescue_action_locally(request, exception)
else
rescue_action_in_public(exception)
end
rescue Exception => failsafe_error
$stderr.puts "Error during failsafe response: #{failsafe_error}"
FAILSAFE_RESPONSE
end
# Render detailed diagnostics for unhandled exceptions rescued from
# a controller action.
def rescue_action_locally(request, exception)
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
:request => request,
:exception => exception
)
file = "rescues/#{@@rescue_templates[exception.class.name]}.erb"
body = template.render(:file => file, :layout => 'rescues/layout.erb')
render(status_code(exception), body)
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. At first, it will try to render a localized static page.
# For example, if a 500 error is being handled Rails and locale is :da,
# it will first attempt to render the file at <tt>public/500.da.html</tt>
# then attempt to render <tt>public/500.html</tt>. If none of them exist,
# the body of the response will be left empty.
def rescue_action_in_public(exception)
status = status_code(exception)
locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
path = "#{public_path}/#{status}.html"
if locale_path && File.exist?(locale_path)
render(status, File.read(locale_path))
elsif File.exist?(path)
render(status, File.read(path))
else
render(status, '')
end
end
# True if the request came from localhost, 127.0.0.1.
def local_request?(request)
request.remote_addr == LOCALHOST && request.remote_ip == LOCALHOST
end
def status_code(exception)
interpret_status(@@rescue_responses[exception.class.name]).to_i
end
def render(status, body)
[status, {'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s}, [body]]
end
def public_path
defined?(Rails.public_path) ? Rails.public_path : 'public_path'
end
def log_error(exception)
return unless logger
ActiveSupport::Deprecation.silence do
if ActionView::TemplateError === exception
logger.fatal(exception.to_s)
else
logger.fatal(
"\n#{exception.class} (#{exception.message}):\n " +
clean_backtrace(exception).join("\n ") + "\n\n"
)
end
end
end
def clean_backtrace(exception)
defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
Rails.backtrace_cleaner.clean(exception.backtrace) :
exception.backtrace
end
def logger
defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
end
end
end

View File

@ -34,8 +34,6 @@ module ActionDispatch
else
@klass.to_s.constantize
end
rescue NameError
@klass
end
def active?
@ -61,7 +59,7 @@ module ActionDispatch
def inspect
str = klass.to_s
args.each { |arg| str += ", #{arg.inspect}" }
args.each { |arg| str += ", #{build_args.inspect}" }
str
end
@ -74,7 +72,6 @@ module ActionDispatch
end
private
def build_args
Array(args).map { |arg| arg.respond_to?(:call) ? arg.call : arg }
end

View File

@ -6,7 +6,7 @@
<% end %>
<%
clean_params = request.parameters.clone
clean_params = @request.parameters.clone
clean_params.delete("action")
clean_params.delete("controller")
@ -17,8 +17,8 @@
<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>
<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>
<p><b>Headers</b>: <pre><%=h @response ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p>

Some files were not shown because too many files have changed in this diff Show More