mirror of https://github.com/rails/rails
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:
commit
69742ca8fa
8
Rakefile
8
Rakefile
|
@ -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)
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -518,6 +518,7 @@ module TMail
|
|||
|
||||
def parse_body( f = nil )
|
||||
return if @body_parsed
|
||||
|
||||
if f
|
||||
parse_body_0 f
|
||||
else
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -1 +1 @@
|
|||
Have a lovely picture, from me. Enjoy!
|
||||
Have some dots. Enjoy!
|
|
@ -1,2 +0,0 @@
|
|||
xml.instruct!
|
||||
xml.test
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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')
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module AbstractController
|
||||
class ActionNotFound < StandardError ; end
|
||||
end
|
||||
class ActionNotFound < StandardError; end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)")
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
|
@ -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
|
|
@ -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")) %>
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require 'active_support/core_ext/hash/except'
|
||||
|
||||
module ActionController
|
||||
module Routing
|
||||
class RouteBuilder #:nodoc:
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue