提交 e68bfaf1 编写于 作者: P Pratik Naik

Merge remote branch 'mainstream/master'

Conflicts:
	activerecord/lib/active_record/base.rb
	railties/lib/rails/configuration.rb
	railties/lib/rails/log_subscriber.rb
*.gem
pkg
.bundle
debug.log
doc/rdoc
activemodel/doc
......@@ -16,6 +19,3 @@ railties/doc/guides/html/images
railties/doc/guides/html/stylesheets
railties/guides/output
railties/tmp
bin
.bundle
pkg
path File.dirname(__FILE__)
source 'http://gemcutter.org'
source 'http://rubygems.org'
gem "arel", :git => "git://github.com/rails/arel.git"
gem "rails", "3.0.0.beta1"
gem "rake", ">= 0.8.7"
......@@ -14,7 +15,7 @@ end
gem "sqlite3-ruby", ">= 1.2.5", :require => 'sqlite3'
group :test do
gem "pg", ">= 0.8.0"
gem "pg", ">= 0.9.0"
gem "mysql", ">= 2.8.1"
end
......@@ -22,6 +23,10 @@ end
gem "rack-test", "0.5.3", :require => 'rack/test'
gem "RedCloth", ">= 4.2.2"
group :documentation do
gem 'rdoc', '2.1'
end
if ENV['CI']
gem "nokogiri", ">= 1.4.0"
......
......@@ -2,23 +2,17 @@ require 'rake'
require 'rake/rdoctask'
require 'rake/gempackagetask'
env = %(PKG_BUILD="#{ENV['PKG_BUILD']}") if ENV['PKG_BUILD']
PROJECTS = %w(activesupport activemodel actionpack actionmailer activeresource activerecord railties)
Dir["#{File.dirname(__FILE__)}/*/lib/*/version.rb"].each do |version_path|
require version_path
end
desc 'Run all tests by default'
task :default => %w(test test:isolated)
%w(test test:isolated rdoc pgem package gem gemspec).each do |task_name|
%w(test test:isolated rdoc package gem).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})) || errors << project
system(%(cd #{project} && #{$0} #{task_name})) || errors << project
end
fail("Errors in #{errors.join(', ')}") unless errors.empty?
end
......@@ -27,9 +21,9 @@ end
desc "Smoke-test all projects"
task :smoke do
(PROJECTS - %w(activerecord)).each do |project|
system %(cd #{project} && #{env} #{$0} test:isolated)
system %(cd #{project} && #{$0} test:isolated)
end
system %(cd activerecord && #{env} #{$0} sqlite3:isolated_test)
system %(cd activerecord && #{$0} sqlite3:isolated_test)
end
spec = eval(File.read('rails.gemspec'))
......@@ -48,12 +42,14 @@ desc "Release all components to gemcutter."
task :release_projects => :package do
errors = []
PROJECTS.each do |project|
system(%(cd #{project} && #{env} #{$0} release)) || errors << project
system(%(cd #{project} && #{$0} release)) || errors << project
end
fail("Errors in #{errors.join(', ')}") unless errors.empty?
end
desc "Install gems for all projects."
task :install => :gem do
require File.expand_path("../actionpack/lib/action_pack/version", __FILE__)
(PROJECTS - ["railties"]).each do |project|
puts "INSTALLING #{project}"
system("gem install #{project}/pkg/#{project}-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc")
......@@ -121,6 +117,34 @@ 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)
system %(cd #{project} && #{$0} pdoc)
end
end
task :update_versions do
require File.dirname(__FILE__) + "/version"
File.open("RAILS_VERSION", "w") do |f|
f.write Rails::VERSION::STRING + "\n"
end
constants = {
"activesupport" => "ActiveSupport",
"activemodel" => "ActiveModel",
"actionpack" => "ActionPack",
"actionmailer" => "ActionMailer",
"activeresource" => "ActiveResource",
"activerecord" => "ActiveRecord",
"railties" => "Rails"
}
version_file = File.read("version.rb")
PROJECTS.each do |project|
Dir["#{project}/lib/*/version.rb"].each do |file|
File.open(file, "w") do |f|
f.write version_file.gsub(/Rails/, constants[project])
end
end
end
end
......@@ -4,17 +4,6 @@ require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version')
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
PKG_NAME = 'actionmailer'
PKG_VERSION = ActionMailer::VERSION::STRING + PKG_BUILD
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
RELEASE_NAME = "REL #{PKG_VERSION}"
RUBY_FORGE_PROJECT = "actionmailer"
RUBY_FORGE_USER = "webster132"
desc "Default Task"
task :default => [ :test ]
......
version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = 'actionmailer'
s.version = '3.0.0.beta1'
s.summary = 'Email composition, delivery, and recieval framework (part of Rails).'
s.description = 'Email composition, delivery, and recieval framework (part of Rails).'
s.version = version
s.summary = 'Email composition, delivery, and receiving framework (part of Rails).'
s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.'
s.required_ruby_version = '>= 1.8.7'
s.author = 'David Heinemeier Hansson'
......@@ -17,7 +19,7 @@
s.has_rdoc = true
s.add_dependency('actionpack', '= 3.0.0.beta1')
s.add_dependency('mail', '~> 2.1.2')
s.add_dependency('actionpack', version)
s.add_dependency('mail', '~> 2.1.3')
s.add_dependency('text-format', '~> 1.0.0')
end
......@@ -34,6 +34,7 @@
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/string/inflections'
require 'active_support/lazy_load_hooks'
module ActionMailer
extend ::ActiveSupport::Autoload
......
......@@ -181,6 +181,18 @@ module ActionMailer #:nodoc:
# and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book
# with the filename +free_book.pdf+.
#
# = Observing and Intercepting Mails
#
# ActionMailer provides hooks into the Mail observer and interceptor methods. These allow you to
# register objects that are called during the mail delivery life cycle.
#
# An observer object must implement the <tt>:delivered_email(message)</tt> method which will be
# called once for every email sent after the email has been sent.
#
# An interceptor object must implement the <tt>:delivering_email(message)</tt> method which will be
# called before the email is sent, allowing you to make modifications to the email before it hits
# the delivery agents. Your object should make and needed modifications directly to the passed
# in Mail::Message instance.
#
# = Configuration options
#
......@@ -255,16 +267,17 @@ class Base < AbstractController::Base
include AbstractController::Logger
include AbstractController::Rendering
include AbstractController::LocalizedCache
include AbstractController::Layouts
include AbstractController::Helpers
include AbstractController::Translation
include AbstractController::Compatibility
helper ActionMailer::MailHelper
include ActionMailer::OldApi
include ActionMailer::DeprecatedApi
delegate :register_observer, :to => Mail
delegate :register_interceptor, :to => Mail
private_class_method :new #:nodoc:
......@@ -276,6 +289,8 @@ class Base < AbstractController::Base
:parts_order => [ "text/plain", "text/enriched", "text/html" ]
}.freeze
ActionMailer.run_base_hooks(self)
class << self
def mailer_name
......@@ -452,10 +467,27 @@ def attachments
# field for the 'envelope from' value.
#
# If you do not pass a block to the +mail+ method, it will find all templates in the
# template path that match the method name that it is being called from, it will then
# create parts for each of these templates intelligently, making educated guesses
# on correct content type and sequence, and return a fully prepared Mail::Message
# ready to call <tt>:deliver</tt> on to send.
# view paths using by default the mailer name and the method name that it is being
# called from, it will then create parts for each of these templates intelligently,
# making educated guesses on correct content type and sequence, and return a fully
# prepared Mail::Message ready to call <tt>:deliver</tt> on to send.
#
# For example:
#
# class Notifier < ActionMailer::Base
# default :from => 'no-reply@test.lindsaar.net',
#
# def welcome
# mail(:to => 'mikel@test.lindsaar.net')
# end
# end
#
# Will look for all templates at "app/views/notifier" with name "welcome". However, those
# can be customized:
#
# mail(:template_path => 'notifications', :template_name => 'another')
#
# And now it will look for all templates at "app/views/notifications" with name "another".
#
# If you do pass a block, you can render specific templates of your choice:
#
......@@ -493,7 +525,7 @@ def mail(headers={}, &block)
# Merge defaults from class
headers = headers.reverse_merge(self.class.default)
charset = headers[:charset]
charset = headers.delete(:charset)
# Quote fields
headers[:subject] ||= default_i18n_subject
......@@ -514,13 +546,11 @@ def mail(headers={}, &block)
end
# Set configure delivery behavior
wrap_delivery_behavior!(headers[:delivery_method])
wrap_delivery_behavior!(headers.delete(:delivery_method))
# Remove headers already treated and assign all others
headers.except!(:subject, :to, :from, :cc, :bcc, :reply_to)
headers.except!(:body, :parts_order, :content_type, :charset, :delivery_method)
# Remove any missing configuration header and assign all others
headers.except!(:parts_order, :content_type)
headers.each { |k, v| m[k] = v }
m
end
......@@ -548,12 +578,12 @@ def default_i18n_subject #:nodoc:
# TODO: Move this into Mail
def quote_fields!(headers, charset) #:nodoc:
m = @_message
m.subject ||= quote_if_necessary(headers[:subject], charset) if headers[:subject]
m.to ||= quote_address_if_necessary(headers[:to], charset) if headers[:to]
m.from ||= quote_address_if_necessary(headers[:from], charset) if headers[:from]
m.cc ||= quote_address_if_necessary(headers[:cc], charset) if headers[:cc]
m.bcc ||= quote_address_if_necessary(headers[:bcc], charset) if headers[:bcc]
m.reply_to ||= quote_address_if_necessary(headers[:reply_to], charset) if headers[:reply_to]
m.subject ||= quote_if_necessary(headers.delete(:subject), charset) if headers[:subject]
m.to ||= quote_address_if_necessary(headers.delete(:to), charset) if headers[:to]
m.from ||= quote_address_if_necessary(headers.delete(:from), charset) if headers[:from]
m.cc ||= quote_address_if_necessary(headers.delete(:cc), charset) if headers[:cc]
m.bcc ||= quote_address_if_necessary(headers.delete(:bcc), charset) if headers[:bcc]
m.reply_to ||= quote_address_if_necessary(headers.delete(:reply_to), charset) if headers[:reply_to]
end
def collect_responses_and_parts_order(headers) #:nodoc:
......@@ -566,13 +596,16 @@ def collect_responses_and_parts_order(headers) #:nodoc:
responses = collector.responses
elsif headers[:body]
responses << {
:body => headers[:body],
:body => headers.delete(:body),
:content_type => self.class.default[:content_type] || "text/plain"
}
else
each_template do |template|
templates_path = headers.delete(:template_path) || self.class.mailer_name
templates_name = headers.delete(:template_name) || action_name
each_template(templates_path, templates_name) do |template|
responses << {
:body => render_to_body(:_template => template),
:body => render(:_template => template),
:content_type => template.mime_type.to_s
}
end
......@@ -581,10 +614,10 @@ def collect_responses_and_parts_order(headers) #:nodoc:
[responses, parts_order]
end
def each_template(&block) #:nodoc:
self.class.view_paths.each do |load_paths|
templates = load_paths.find_all(action_name, {}, self.class.mailer_name)
templates = templates.uniq_by { |t| t.details[:formats] }
def each_template(paths, name, &block) #:nodoc:
Array(paths).each do |path|
templates = lookup_context.find_all(name, path)
templates = templates.uniq_by { |t| t.formats }
unless templates.empty?
templates.each(&block)
......
......@@ -206,8 +206,8 @@ def create_parts
if String === @body
@parts.unshift create_inline_part(@body)
elsif @parts.empty? || @parts.all? { |p| p.content_disposition =~ /^attachment/ }
self.class.view_paths.first.find_all(@template, {}, @mailer_name).each do |template|
@parts << create_inline_part(render_to_body(:_template => template), template.mime_type)
lookup_context.find_all(@template, @mailer_name).each do |template|
@parts << create_inline_part(render(:_template => template), template.mime_type)
end
if @parts.size > 1
......
......@@ -22,7 +22,7 @@ def quoted_printable_encode(character)
# A quick-and-dirty regexp for determining whether a string contains any
# characters that need escaping.
if !defined?(CHARS_NEEDING_QUOTING)
CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/
CHARS_NEEDING_QUOTING = Regexp.new('[\000-\011\013\014\016-\037\177-\377]', nil, 'n')
end
# Quote the given text if it contains any "illegal" characters
......
......@@ -6,19 +6,21 @@ class Railtie < Rails::Railtie
railtie_name :action_mailer
initializer "action_mailer.url_for", :before => :load_environment_config do |app|
ActionMailer::Base.send(:include, ActionController::UrlFor) if defined?(ActionController)
ActionMailer.base_hook { include app.routes.url_helpers }
end
require "action_mailer/railties/subscriber"
subscriber ActionMailer::Railties::Subscriber.new
require "action_mailer/railties/log_subscriber"
log_subscriber ActionMailer::Railties::LogSubscriber.new
initializer "action_mailer.logger" do
ActionMailer::Base.logger ||= Rails.logger
ActionMailer.base_hook { self.logger ||= Rails.logger }
end
initializer "action_mailer.set_configs" do |app|
app.config.action_mailer.each do |k,v|
ActionMailer::Base.send "#{k}=", v
ActionMailer.base_hook do
app.config.action_mailer.each do |k,v|
send "#{k}=", v
end
end
end
end
......
module ActionMailer
module Railties
class Subscriber < Rails::Subscriber
class LogSubscriber < Rails::LogSubscriber
def deliver(event)
recipients = Array(event.payload[:to]).join(', ')
info("\nSent mail to #{recipients} (%1.fms)" % event.duration)
......
......@@ -2,8 +2,9 @@ module ActionMailer
module VERSION #:nodoc:
MAJOR = 3
MINOR = 0
TINY = "0.beta1"
TINY = 0
BUILD = "beta1"
STRING = [MAJOR, MINOR, TINY].join('.')
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
end
require File.expand_path('../../../load_paths', __FILE__)
lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
require 'test/unit'
require 'action_mailer'
require 'action_mailer/test_case'
......@@ -14,7 +17,7 @@
FIXTURE_LOAD_PATH = File.expand_path('fixtures', File.dirname(__FILE__))
ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH
class MockSMTP
class MockSMTP
def self.deliveries
@@deliveries
end
......
......@@ -14,8 +14,13 @@ def welcome(hash = {})
mail({:subject => "The first email on new API!"}.merge!(hash))
end
def simple(hash = {})
mail(hash)
def welcome_with_headers(hash = {})
headers hash
mail
end
def welcome_from_another_path(path)
mail(:template_name => "welcome", :template_path => path)
end
def html_only(hash = {})
......@@ -25,11 +30,6 @@ def html_only(hash = {})
def plain_text_only(hash = {})
mail(hash)
end
def simple_with_headers(hash = {})
headers hash
mail
end
def attachment_with_content(hash = {})
attachments['invoice.pdf'] = 'This is test File content'
......@@ -78,8 +78,12 @@ def custom_block(include_html=false)
format.html{ render "welcome" } if include_html
end
end
def different_template(template_name='')
def implicit_different_template(template_name='')
mail(:template_name => template_name)
end
def explicit_different_template(template_name='')
mail do |format|
format.text { render :template => "#{mailer_name}/#{template_name}" }
format.html { render :template => "#{mailer_name}/#{template_name}" }
......@@ -88,13 +92,10 @@ def different_template(template_name='')
def different_layout(layout_name='')
mail do |format|
format.text {
render :layout => layout_name
}
format.text { render :layout => layout_name }
format.html { render :layout => layout_name }
end
end
end
test "method call to mail does not raise error" do
......@@ -154,7 +155,7 @@ def different_layout(layout_name='')
test "can pass random headers in as a hash to mail" do
hash = {'X-Special-Domain-Specific-Header' => "SecretValue",
'In-Reply-To' => '1234@mikel.me.com' }
mail = BaseMailer.simple(hash)
mail = BaseMailer.welcome(hash)
assert_equal('SecretValue', mail['X-Special-Domain-Specific-Header'].decoded)
assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded)
end
......@@ -162,7 +163,7 @@ def different_layout(layout_name='')
test "can pass random headers in as a hash" do
hash = {'X-Special-Domain-Specific-Header' => "SecretValue",
'In-Reply-To' => '1234@mikel.me.com' }
mail = BaseMailer.simple_with_headers(hash)
mail = BaseMailer.welcome_with_headers(hash)
assert_equal('SecretValue', mail['X-Special-Domain-Specific-Header'].decoded)
assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded)
end
......@@ -247,9 +248,9 @@ def different_layout(layout_name='')
end
test "uses random default headers from class" do
with_default BaseMailer, "X-SPAM" => "Not spam" do
email = BaseMailer.simple
assert_equal("Not spam", email["X-SPAM"].decoded)
with_default BaseMailer, "X-Custom" => "Custom" do
email = BaseMailer.welcome
assert_equal("Custom", email["X-Custom"].decoded)
end
end
......@@ -476,18 +477,58 @@ def different_layout(layout_name='')
end
# Rendering
test "that you can specify a different template" do
mail = BaseMailer.different_template('explicit_multipart_templates')
test "you can specify a different template for implicit render" do
mail = BaseMailer.implicit_different_template('implicit_multipart')
assert_equal("HTML Implicit Multipart", mail.html_part.body.decoded)
assert_equal("TEXT Implicit Multipart", mail.text_part.body.decoded)
end
test "you can specify a different template for explicit render" do
mail = BaseMailer.explicit_different_template('explicit_multipart_templates')
assert_equal("HTML Explicit Multipart Templates", mail.html_part.body.decoded)
assert_equal("TEXT Explicit Multipart Templates", mail.text_part.body.decoded)
end
test "that you can specify a different layout" do
test "you can specify a different layout" do
mail = BaseMailer.different_layout('different_layout')
assert_equal("HTML -- HTML", mail.html_part.body.decoded)
assert_equal("PLAIN -- PLAIN", mail.text_part.body.decoded)
end
test "you can specify the template path for implicit lookup" do
mail = BaseMailer.welcome_from_another_path('another.path/base_mailer')
assert_equal("Welcome from another path", mail.body.encoded)
mail = BaseMailer.welcome_from_another_path(['unknown/invalid', 'another.path/base_mailer'])
assert_equal("Welcome from another path", mail.body.encoded)
end
# Before and After hooks
class MyObserver
def self.delivered_email(mail)
end
end
test "you can register an observer to the mail object that gets informed on email delivery" do
ActionMailer::Base.register_observer(MyObserver)
mail = BaseMailer.welcome
MyObserver.expects(:delivered_email).with(mail)
mail.deliver
end
class MyInterceptor
def self.delivering_email(mail)
end
end
test "you can register an interceptor to the mail object that gets passed the mail object before delivery" do
ActionMailer::Base.register_interceptor(MyInterceptor)
mail = BaseMailer.welcome
MyInterceptor.expects(:delivering_email).with(mail)
mail.deliver
end
protected
# Execute the block setting the given values and restoring old values after
......
require "abstract_unit"
require "rails/subscriber/test_helper"
require "action_mailer/railties/subscriber"
require "rails/log_subscriber/test_helper"
require "action_mailer/railties/log_subscriber"
class AMSubscriberTest < ActionMailer::TestCase
include Rails::Subscriber::TestHelper
Rails::Subscriber.add(:action_mailer, ActionMailer::Railties::Subscriber.new)
class AMLogSubscriberTest < ActionMailer::TestCase
include Rails::LogSubscriber::TestHelper
def setup
super
Rails::LogSubscriber.add(:action_mailer, ActionMailer::Railties::LogSubscriber.new)
end
class TestMailer < ActionMailer::Base
def basic
......
......@@ -14,6 +14,10 @@ def setup
set_delivery_method :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries.clear
AssetHostMailer.configure do |c|
c.asset_host = "http://www.example.com"
c.assets_dir = ''
end
end
def teardown
......@@ -21,13 +25,12 @@ def teardown
end
def test_asset_host_as_string
ActionController::Base.asset_host = "http://www.example.com"
mail = AssetHostMailer.email_with_asset
assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.to_s.strip
end
def test_asset_host_as_one_arguement_proc
ActionController::Base.asset_host = Proc.new { |source|
AssetHostMailer.config.asset_host = Proc.new { |source|
if source.starts_with?('/images')
"http://images.example.com"
else
......@@ -39,7 +42,7 @@ def test_asset_host_as_one_arguement_proc
end
def test_asset_host_as_two_arguement_proc
ActionController::Base.asset_host = Proc.new {|source,request|
ActionController::Base.config.asset_host = Proc.new {|source,request|
if request && request.ssl?
"https://www.example.com"
else
......
......@@ -4,13 +4,19 @@
class WelcomeController < ActionController::Base
end
AppRoutes = ActionDispatch::Routing::RouteSet.new
class ActionMailer::Base
include ActionController::UrlFor
include AppRoutes.url_helpers
end
class TestMailer < ActionMailer::Base
default_url_options[:host] = 'www.basecamphq.com'
configure do |c|
c.assets_dir = '' # To get the tests to pass
end
def signed_up_with_url(recipient)
@recipients = recipient
@subject = "[Signed up] Welcome #{recipient}"
......@@ -61,7 +67,7 @@ def teardown
def test_signed_up_with_url
TestMailer.delivery_method = :test
ActionController::Routing::Routes.draw do |map|
AppRoutes.draw do |map|
map.connect ':controller/:action/:id'
map.welcome 'welcome', :controller=>"foo", :action=>"bar"
end
......
......@@ -4,17 +4,6 @@ require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version')
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
PKG_NAME = 'actionpack'
PKG_VERSION = ActionPack::VERSION::STRING + PKG_BUILD
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
RELEASE_NAME = "REL #{PKG_VERSION}"
RUBY_FORGE_PROJECT = "actionpack"
RUBY_FORGE_USER = "webster132"
desc "Default Task"
task :default => :test
......@@ -114,27 +103,8 @@ task :update_js => [ :update_scriptaculous ]
# Publishing ------------------------------------------------------
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
desc "Publish the release files to RubyForge."
task :release => [ :package ] do
require 'rubyforge'
require 'rake/contrib/rubyforgepublisher'
packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
rubyforge = RubyForge.new
rubyforge.login
rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages)
end
end
\ No newline at end of file
version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = 'actionpack'
s.version = '3.0.0.beta1'
s.version = version
s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).'
s.description = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).'
s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.'
s.required_ruby_version = '>= 1.8.7'
s.author = 'David Heinemeier Hansson'
......@@ -17,10 +19,10 @@
s.has_rdoc = true
s.add_dependency('activesupport', '= 3.0.0.beta1')
s.add_dependency('activemodel', '= 3.0.0.beta1')
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
s.add_dependency('rack', '~> 1.1.0')
s.add_dependency('rack-test', '~> 0.5.0')
s.add_dependency('rack-mount', '~> 0.4.7')
s.add_dependency('rack-mount', '~> 0.6.0')
s.add_dependency('erubis', '~> 2.6.5')
end
......@@ -3,8 +3,11 @@
require 'active_support/ruby/shim'
require 'active_support/dependencies/autoload'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/module/anonymous'
require 'active_support/i18n'
module AbstractController
extend ActiveSupport::Autoload
......@@ -12,11 +15,10 @@ module AbstractController
autoload :Base
autoload :Callbacks
autoload :Collector
autoload :Compatibility
autoload :Helpers
autoload :Layouts
autoload :LocalizedCache
autoload :Logger
autoload :Rendering
autoload :Translation
autoload :ViewPaths
end
require 'active_support/ordered_options'
module AbstractController
class Error < StandardError; end
class ActionNotFound < StandardError; end
......@@ -5,7 +7,6 @@ class ActionNotFound < StandardError; end
class Base
attr_internal :response_body
attr_internal :action_name
attr_internal :formats
class << self
attr_reader :abstract
......@@ -28,6 +29,14 @@ def descendants
@descendants ||= []
end
def config
@config ||= ActiveSupport::InheritableOptions.new(superclass < Base ? superclass.config : {})
end
def configure
yield config
end
# A list of all internal methods for a controller. This finds the first
# abstract superclass of a controller, and gets a list of all public
# instance methods on that abstract class. Public instance methods of
......@@ -84,15 +93,14 @@ def action_methods
# ==== Returns
# String
def controller_path
@controller_path ||= name && name.sub(/Controller$/, '').underscore
@controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
end
end
abstract!
# Initialize controller with nil formats.
def initialize #:nodoc:
@_formats = nil
def config
@config ||= ActiveSupport::InheritableOptions.new(self.class.config)
end
# Calls the action going through the entire action dispatch stack.
......
require "action_dispatch/http/mime_type"
module AbstractController
module Collector
def self.generate_method_for_mime(mime)
......
module AbstractController
module Compatibility
extend ActiveSupport::Concern
def _find_layout(name, details)
details[:prefix] = nil if name =~ /\blayouts/
super
end
# Move this into a "don't run in production" module
def _default_layout(details, require_layout = false)
super
rescue ActionView::MissingTemplate
_find_layout(_layout({}), {})
nil
end
end
end
require 'active_support/dependencies'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/module/delegation'
module AbstractController
module Helpers
......@@ -27,7 +25,7 @@ module ClassMethods
def inherited(klass)
helpers = _helpers
klass._helpers = Module.new { include helpers }
klass.class_eval { default_helper_module! unless name.blank? }
klass.class_eval { default_helper_module! unless anonymous? }
super
end
......@@ -99,7 +97,7 @@ def #{meth}(*args, &blk)
def helper(*args, &block)
self._helper_serial = AbstractController::Helpers.next_serial + 1
_modules_for_helpers(args).each do |mod|
modules_for_helpers(args).each do |mod|
add_template_helper(mod)
end
......@@ -134,7 +132,7 @@ def add_template_helper(mod)
# ==== Returns
# Array[Module]:: A normalized list of modules for the list of
# helpers provided.
def _modules_for_helpers(args)
def modules_for_helpers(args)
args.flatten.map! do |arg|
case arg
when String, Symbol
......
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/module/delegation'
module AbstractController
# Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
# repeated setups. The inclusion pattern has pages that look like this:
......@@ -173,27 +170,7 @@ module Layouts
module ClassMethods
def inherited(klass)
super
klass.class_eval do
_write_layout_method
@found_layouts = {}
end
end
def clear_template_caches!
@found_layouts.clear if defined? @found_layouts
super
end
def cache_layout(details)
layout = @found_layouts
key = Thread.current[:format_locale_key]
# Cache nil
if layout.key?(key)
return layout[key]
else
layout[key] = yield
end
klass._write_layout_method
end
# This module is mixed in if layout conditions are provided. This means
......@@ -262,10 +239,10 @@ def _implied_layout_name
def _write_layout_method
case defined?(@_layout) ? @_layout : nil
when String
self.class_eval %{def _layout(details) #{@_layout.inspect} end}
self.class_eval %{def _layout; #{@_layout.inspect} end}
when Symbol
self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
def _layout(details)
def _layout
#{@_layout}.tap do |layout|
unless layout.is_a?(String) || !layout
raise ArgumentError, "Your layout method :#{@_layout} returned \#{layout}. It " \
......@@ -276,21 +253,21 @@ def _layout(details)
ruby_eval
when Proc
define_method :_layout_from_proc, &@_layout
self.class_eval %{def _layout(details) _layout_from_proc(self) end}
self.class_eval %{def _layout; _layout_from_proc(self) end}
when false
self.class_eval %{def _layout(details) end}
self.class_eval %{def _layout; end}
when true
raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
when nil
if name
_prefix = "layouts" unless _implied_layout_name =~ /\blayouts/
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def _layout(details)
self.class.cache_layout(details) do
if template_exists?("#{_implied_layout_name}", details, :_prefix => "layouts")
"#{_implied_layout_name}"
else
super
end
def _layout
if template_exists?("#{_implied_layout_name}", #{_prefix.inspect})
"#{_implied_layout_name}"
else
super
end
end
RUBY
......@@ -300,42 +277,20 @@ def _layout(details)
end
end
def render_to_body(options = {})
# In the case of a partial with a layout, handle the layout
# here, and make sure the view does not try to handle it
layout = options.delete(:layout) if options.key?(:partial)
response = super
def _normalize_options(options)
super
# This is a little bit messy. We need to explicitly handle partial
# layouts here since the core lookup logic is in the view, but
# we need to determine the layout based on the controller
#
# TODO: An easier way to handle this would probably be to override
# render_template
if layout
layout = _layout_for_option(layout, options[:_template].details)
response = layout.render(view_context, options[:locals] || {}) { response }
if _include_layout?(options)
layout = options.key?(:layout) ? options.delete(:layout) : :default
value = _layout_for_option(layout)
options[:layout] = (value =~ /\blayouts/ ? value : "layouts/#{value}") if value
end
response
end
private
# This will be overwritten by _write_layout_method
def _layout(details) end
# Determine the layout for a given name and details.
#
# ==== Parameters
# name<String>:: The name of the template
# details<Hash{Symbol => Object}>:: A list of details to restrict
# the lookup to. By default, layout lookup is limited to the
# formats specified for the current request.
def _layout_for_name(name, details)
name && _find_layout(name, details)
end
def _layout; end
# Determine the layout for a given name and details, taking into account
# the name type.
......@@ -345,11 +300,11 @@ def _layout_for_name(name, details)
# details<Hash{Symbol => Object}>:: A list of details to restrict
# the lookup to. By default, layout lookup is limited to the
# formats specified for the current request.
def _layout_for_option(name, details)
def _layout_for_option(name)
case name
when String then _layout_for_name(name, details)
when true then _default_layout(details, true)
when :default then _default_layout(details, false)
when String then name
when true then _default_layout(true)
when :default then _default_layout(false)
when false, nil then nil
else
raise ArgumentError,
......@@ -357,29 +312,6 @@ def _layout_for_option(name, details)
end
end
def _determine_template(options)
super
return unless (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
layout = options.key?(:layout) ? options[:layout] : :default
options[:_layout] = _layout_for_option(layout, options[:_template].details)
end
# Take in the name and details and find a Template.
#
# ==== Parameters
# name<String>:: The name of the template to retrieve
# details<Hash>:: A list of details to restrict the search by. This
# might include details like the format or locale of the template.
#
# ==== Returns
# Template:: A template object matching the name and details
def _find_layout(name, details)
# TODO: Make prefix actually part of details in ViewPath#find_by_parts
prefix = details.key?(:prefix) ? details.delete(:prefix) : "layouts"
find_template(name, details, :_prefix => prefix)
end
# Returns the default layout for this controller and a given set of details.
# Optionally raises an exception if the layout could not be found.
#
......@@ -392,18 +324,24 @@ def _find_layout(name, details)
#
# ==== Returns
# Template:: The template object for the default layout (or nil)
def _default_layout(details, require_layout = false)
if require_layout && _action_has_layout? && !_layout(details)
raise ArgumentError,
"There was no default layout for #{self.class} in #{view_paths.inspect}"
end
def _default_layout(require_layout = false)
begin
_layout_for_name(_layout(details), details) if _action_has_layout?
layout_name = _layout if _action_has_layout?
rescue NameError => e
raise NoMethodError,
"You specified #{@_layout.inspect} as the layout, but no such method was found"
end
if require_layout && _action_has_layout? && !layout_name
raise ArgumentError,
"There was no default layout for #{self.class} in #{view_paths.inspect}"
end
layout_name
end
def _include_layout?(options)
(options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
end
def _action_has_layout?
......
module AbstractController
class HashKey
@hash_keys = Hash.new {|h,k| h[k] = Hash.new {|sh,sk| sh[sk] = {} } }
def self.get(klass, formats, locale)
@hash_keys[klass][formats][locale] ||= new(klass, formats, locale)
end
attr_accessor :hash
def initialize(klass, formats, locale)
@formats, @locale = formats, locale
@hash = [formats, locale].hash
end
alias_method :eql?, :equal?
def inspect
"#<HashKey -- formats: #{@formats.inspect} locale: #{@locale.inspect}>"
end
end
module LocalizedCache
extend ActiveSupport::Concern
module ClassMethods
def clear_template_caches!
ActionView::Partials::PartialRenderer::TEMPLATES.clear
template_cache.clear
super
end
def template_cache
@template_cache ||= Hash.new {|h,k| h[k] = {} }
end
end
def render(*args)
Thread.current[:format_locale_key] = HashKey.get(self.class, formats, I18n.locale)
super
end
private
def with_template_cache(name)
self.class.template_cache[Thread.current[:format_locale_key]][name] ||= super
end
end
end
require 'active_support/core_ext/logger'
require 'active_support/benchmarkable'
require "active_support/core_ext/logger"
require "active_support/benchmarkable"
module AbstractController
module Logger
......
require "abstract_controller/base"
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/array/wrap'
module AbstractController
class DoubleRenderError < Error
......@@ -12,32 +9,47 @@ def initialize(message = nil)
end
end
# This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
# it will trigger the lookup_context and consequently expire the cache.
# TODO Add some deprecation warnings to remove I18n.locale from controllers
class I18nProxy < ::I18n::Config #:nodoc:
attr_reader :i18n_config, :lookup_context
def initialize(i18n_config, lookup_context)
@i18n_config, @lookup_context = i18n_config, lookup_context
end
def locale
@i18n_config.locale
end
def locale=(value)
@i18n_config.locale = value
@lookup_context.update_details(:locale => @i18n_config.locale)
end
end
module Rendering
extend ActiveSupport::Concern
include AbstractController::ViewPaths
included do
class_attribute :_view_paths
delegate :_view_paths, :to => :'self.class'
self._view_paths = ActionView::PathSet.new
# Overwrite process to setup I18n proxy.
def process(*) #:nodoc:
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
super
ensure
I18n.config = old_config
end
# An instance of a view class. The default view class is ActionView::Base
#
# The view class must have the following methods:
# View.for_controller[controller] Create a new ActionView instance for a
# controller
# View#render_partial[options]
# - responsible for setting options[:_template]
# - Returns String with the rendered partial
# options<Hash>:: see _render_partial in ActionView::Base
# View#render_template[template, layout, options, partial]
# - Returns String with the rendered template
# template<ActionView::Template>:: The template to render
# layout<ActionView::Template>:: The layout to render around the template
# options<Hash>:: See _render_template_with_layout in ActionView::Base
# partial<Boolean>:: Whether or not the template to render is a partial
# View.for_controller[controller]
# Create a new ActionView instance for a controller
# View#render_template[options]
# Returns String with the rendered template
#
# Override this method in a to change the default behavior.
# Override this method in a module to change the default behavior.
def view_context
@_view_context ||= ActionView::Base.for_controller(self)
end
......@@ -45,57 +57,29 @@ def view_context
# Mostly abstracts the fact that calling render twice is a DoubleRenderError.
# Delegates render_to_body and sticks the result in self.response_body.
def render(*args, &block)
options = _normalize_options(*args, &block)
options = _normalize_args(*args, &block)
_normalize_options(options)
self.response_body = render_to_body(options)
end
# Raw rendering of a template to a Rack-compatible body.
#
# ==== Options
# _partial_object<Object>:: The object that is being rendered. If this
# exists, we are in the special case of rendering an object as a partial.
#
# :api: plugin
def render_to_body(options = {})
# TODO: Refactor so we can just use the normal template logic for this
if options.key?(:partial)
_render_partial(options)
else
_determine_template(options)
_render_template(options)
end
_process_options(options)
_render_template(options)
end
# Raw rendering of a template to a string. Just convert the results of
# render_to_body into a String.
#
# :api: plugin
def render_to_string(*args)
options = _normalize_options(*args)
def render_to_string(options={})
_normalize_options(options)
AbstractController::Rendering.body_to_s(render_to_body(options))
end
# Renders the template from an object.
#
# ==== Options
# _template<ActionView::Template>:: The template to render
# _layout<ActionView::Template>:: The layout to wrap the template in (optional)
# Find and renders a template based on the options given.
def _render_template(options)
view_context.render_template(options)
end
# Renders the given partial.
#
# ==== Options
# partial<String|Object>:: The partial name or the object to be rendered
def _render_partial(options)
view_context.render_partial(options)
end
# The list of view paths for this controller. See ActionView::ViewPathSet for
# more details about writing custom view paths.
def view_paths
_view_paths
view_context.render_template(options) { |template| _with_template_hook(template) }
end
# The prefix used in render "foo" shortcuts.
......@@ -117,122 +101,42 @@ def self.body_to_s(body)
private
# Normalize options, by converting render "foo" to render :template => "prefix/foo"
# and render "/foo" to render :file => "/foo".
def _normalize_options(action=nil, options={})
# Normalize options by converting render "foo" to render :action => "foo" and
# render "foo/bar" to render :file => "foo/bar".
def _normalize_args(action=nil, options={})
case action
when NilClass
when Hash
options, action = action, nil
when String, Symbol
action = action.to_s
case action.index("/")
when NilClass
options[:_prefix] = _prefix
options[:_template_name] = action
when 0
options[:file] = action
else
options[:template] = action
end
key = action.include?(?/) ? :file : :action
options[key] = action
else
options.merge!(:partial => action)
end
options
end
# Take in a set of options and determine the template to render
#
# ==== Options
# _template<ActionView::Template>:: If this is provided, the search is over
# _template_name<#to_s>:: The name of the template to look up. Otherwise,
# use the current action name.
# _prefix<String>:: The prefix to look inside of. In a file system, this corresponds
# to a directory.
# _partial<TrueClass, FalseClass>:: Whether or not the file to look up is a partial
def _determine_template(options)
if options.key?(:text)
options[:_template] = ActionView::Template::Text.new(options[:text], format_for_text)
elsif options.key?(:inline)
handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb")
template = ActionView::Template.new(options[:inline], "inline template", handler, {})
options[:_template] = template
elsif options.key?(:template)
options[:_template_name] = options[:template]
elsif options.key?(:file)
options[:_template_name] = options[:file]
def _normalize_options(options)
if options[:partial] == true
options[:partial] = action_name
end
name = (options[:_template_name] || options[:action] || action_name).to_s
options[:_prefix] ||= _prefix if (options.keys & [:partial, :file, :template]).empty?
details = _normalize_details(options)
options[:_template] ||= with_template_cache(name) do
find_template(name, details, options)
if (options.keys & [:partial, :file, :template]).empty?
options[:prefix] ||= _prefix
end
end
def _normalize_details(options)
details = { :formats => formats }
details[:formats] = Array(options[:format]) if options[:format]
details[:locale] = Array(options[:locale]) if options[:locale]
details
end
def find_template(name, details, options)
view_paths.find(name, details, options[:_prefix], options[:_partial])
end
def template_exists?(name, details, options)
view_paths.exists?(name, details, options[:_prefix], options[:_partial])
end
def with_template_cache(name)
yield
options[:template] ||= (options[:action] || action_name).to_s
options
end
def format_for_text
Mime[:text]
def _process_options(options)
end
module ClassMethods
def clear_template_caches!
end
# Append a path to the list of view paths for this controller.
#
# ==== Parameters
# path<String, ViewPath>:: If a String is provided, it gets converted into
# the default view path. You may also provide a custom view path
# (see ActionView::ViewPathSet for more information)
def append_view_path(path)
self.view_paths = view_paths.dup + Array.wrap(path)
end
# Prepend a path to the list of view paths for this controller.
#
# ==== Parameters
# path<String, ViewPath>:: If a String is provided, it gets converted into
# the default view path. You may also provide a custom view path
# (see ActionView::ViewPathSet for more information)
def prepend_view_path(path)
clear_template_caches!
self.view_paths = Array.wrap(path) + view_paths.dup
end
# A list of all of the default view paths for this controller.
def view_paths
_view_paths
end
# Set the view paths.
#
# ==== Parameters
# paths<ViewPathSet, Object>:: If a ViewPathSet is provided, use that;
# otherwise, process the parameter into a ViewPathSet.
def view_paths=(paths)
clear_template_caches!
self._view_paths = paths.is_a?(ActionView::PathSet) ? paths : ActionView::Base.process_view_paths(paths)
_view_paths.freeze
end
def _with_template_hook(template)
self.formats = template.formats
end
end
end
module AbstractController
module ViewPaths
extend ActiveSupport::Concern
included do
class_attribute :_view_paths
self._view_paths = ActionView::PathSet.new
end
delegate :template_exists?, :view_paths, :formats, :formats=,
:locale, :locale=, :to => :lookup_context
# LookupContext is the object responsible to hold all information required to lookup
# templates, i.e. view paths and details. Check ActionView::LookupContext for more
# information.
def lookup_context
@lookup_context ||= ActionView::LookupContext.new(self.class._view_paths, details_for_lookup)
end
def details_for_lookup
{ }
end
def append_view_path(path)
lookup_context.view_paths.push(*path)
end
def prepend_view_path(path)
lookup_context.view_paths.unshift(*path)
end
def template_exists?(*args)
lookup_context.exists?(*args)
end
module ClassMethods
# Append a path to the list of view paths for this controller.
#
# ==== Parameters
# path<String, ViewPath>:: If a String is provided, it gets converted into
# the default view path. You may also provide a custom view path
# (see ActionView::ViewPathSet for more information)
def append_view_path(path)
self.view_paths = view_paths.dup + Array(path)
end
# Prepend a path to the list of view paths for this controller.
#
# ==== Parameters
# path<String, ViewPath>:: If a String is provided, it gets converted into
# the default view path. You may also provide a custom view path
# (see ActionView::ViewPathSet for more information)
def prepend_view_path(path)
self.view_paths = Array(path) + view_paths.dup
end
# A list of all of the default view paths for this controller.
def view_paths
_view_paths
end
# Set the view paths.
#
# ==== Parameters
# paths<ViewPathSet, Object>:: If a ViewPathSet is provided, use that;
# otherwise, process the parameter into a ViewPathSet.
def view_paths=(paths)
self._view_paths = paths.is_a?(ActionView::PathSet) ? paths : ActionView::Base.process_view_paths(paths)
self._view_paths.freeze
end
end
end
end
\ No newline at end of file
......@@ -13,13 +13,13 @@ module ActionController
autoload_under "metal" do
autoload :Compatibility
autoload :ConditionalGet
autoload :Configuration
autoload :Cookies
autoload :Flash
autoload :Head
autoload :Helpers
autoload :HideActions
autoload :HttpAuthentication
autoload :ImplicitRender
autoload :Instrumentation
autoload :MimeResponds
autoload :RackDelegation
......@@ -46,7 +46,6 @@ module ActionController
eager_autoload do
autoload :RecordIdentifier
autoload :UrlRewriter
# TODO: Don't autoload exceptions, setup explicit
# requires for files that need them
......
......@@ -15,7 +15,6 @@ class Base < Metal
include ActionController::Renderers::All
include ActionController::ConditionalGet
include ActionController::RackDelegation
include ActionController::Configuration
# Legacy modules
include SessionManagement
......@@ -30,35 +29,13 @@ class Base < Metal
include ActionController::Verification
include ActionController::RequestForgeryProtection
include ActionController::Streaming
include ActionController::RecordIdentifier
include ActionController::HttpAuthentication::Basic::ControllerMethods
include ActionController::HttpAuthentication::Digest::ControllerMethods
# Add instrumentations hooks at the bottom, to ensure they instrument
# all the methods properly.
include ActionController::Instrumentation
# TODO: Extract into its own module
# This should be moved together with other normalizing behavior
module ImplicitRender
def send_action(*)
ret = super
default_render unless response_body
ret
end
def default_render
render
end
def method_for_action(action_name)
super || begin
if template_exists?(action_name.to_s, {:formats => formats}, :_prefix => controller_path)
"default_render"
end
end
end
end
include ImplicitRender
include ActionController::Rescue
......@@ -82,12 +59,9 @@ def self.filter_parameter_logging(*args, &block)
filter
end
protected
ActionController.run_base_hooks(self)
# Overwrite url rewriter to use request.
def _url_rewriter
return ActionController::UrlRewriter unless request
@_url_rewriter ||= ActionController::UrlRewriter.new(request, params)
end
end
end
require "action_controller/deprecated/base"
......@@ -40,28 +40,34 @@ module Caching
autoload :Sweeping, 'action_controller/caching/sweeping'
end
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)
module ConfigMethods
def cache_store
config.cache_store
end
include Pages, Actions, Fragments
include Sweeping if defined?(ActiveRecord)
def cache_store=(store)
config.cache_store = ActiveSupport::Cache.lookup_store(store)
end
@@perform_caching = true
cattr_accessor :perform_caching
end
private
module ClassMethods
def cache_configured?
perform_caching && cache_store
end
end
include ConfigMethods
include Pages, Actions, Fragments
include Sweeping if defined?(ActiveRecord)
included do
extend ConfigMethods
@@perform_caching = true
cattr_accessor :perform_caching
end
def caching_allowed?
request.get? && response.status == 200
end
......@@ -75,10 +81,5 @@ def cache(key, options = {}, &block)
yield
end
end
private
def cache_configured?
self.class.cache_configured?
end
end
end
......@@ -41,7 +41,9 @@ def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
else
pos = buffer.length
block.call
write_fragment(name, buffer[pos..-1], options)
content = buffer[pos..-1]
content = content.as_str if content.respond_to?(:as_str)
write_fragment(name, content, options)
end
else
block.call
......
require 'fileutils'
require 'uri'
require 'active_support/core_ext/class/attribute_accessors'
module ActionController #:nodoc:
module Caching
......@@ -34,27 +35,26 @@ module Caching
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
# expired.
module Pages
def self.included(base) #:nodoc:
base.extend(ClassMethods)
base.class_eval do
@@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
##
# :singleton-method:
# The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
# For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>RAILS_ROOT + "/public"</tt>). Changing
# this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
# web server to look in the new location for cached files.
cattr_accessor :page_cache_directory
@@page_cache_extension = '.html'
##
# :singleton-method:
# Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
# order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
# If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
# extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
cattr_accessor :page_cache_extension
end
extend ActiveSupport::Concern
included do
@@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
##
# :singleton-method:
# The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
# For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>RAILS_ROOT + "/public"</tt>). Changing
# this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
# web server to look in the new location for cached files.
cattr_accessor :page_cache_directory
@@page_cache_extension = '.html'
##
# :singleton-method:
# Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
# order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
# If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
# extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
cattr_accessor :page_cache_extension
end
module ClassMethods
......@@ -121,10 +121,10 @@ def expire_page(options = {})
if options.is_a?(Hash)
if options[:action].is_a?(Array)
options[:action].dup.each do |action|
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action)))
self.class.expire_page(url_for(options.merge(:only_path => true, :action => action)))
end
else
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true)))
self.class.expire_page(url_for(options.merge(:only_path => true)))
end
else
self.class.expire_page(options)
......@@ -139,7 +139,7 @@ def cache_page(content = nil, options = nil)
path = case options
when Hash
url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format]))
url_for(options.merge(:only_path => true, :format => params[:format]))
when String
options
else
......
......@@ -30,9 +30,7 @@ module Caching
# cache_sweeper OpenBar::Sweeper, :only => [ :edit, :destroy, :share ]
# end
module Sweeping
def self.included(base) #:nodoc:
base.extend(ClassMethods)
end
extend ActiveSupport::Concern
module ClassMethods #:nodoc:
def cache_sweeper(*sweepers)
......
ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
ActionController::Routing = ActionDispatch::Routing
ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new
ActionController::UrlWriter = ActionController::UrlFor
module ActionController
class Base
class << self
def deprecated_config_accessor(option, message = nil)
deprecated_config_reader(option, message)
deprecated_config_writer(option, message)
end
def deprecated_config_reader(option, message = nil)
message ||= "Reading #{option} directly from ActionController::Base is deprecated. " \
"Please read it from config.#{option}"
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{option}
ActiveSupport::Deprecation.warn #{message.inspect}, caller
config.#{option}
end
RUBY
end
def deprecated_config_writer(option, message = nil)
message ||= "Setting #{option} directly on ActionController::Base is deprecated. " \
"Please set it on config.action_controller.#{option}"
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{option}=(val)
ActiveSupport::Deprecation.warn #{message.inspect}, caller
config.#{option} = val
end
RUBY
end
def consider_all_requests_local
ActiveSupport::Deprecation.warn "ActionController::Base.consider_all_requests_local is deprecated, " <<
"use Rails.application.config.consider_all_requests_local instead", caller
Rails.application.config.consider_all_requests_local
end
def consider_all_requests_local=(value)
ActiveSupport::Deprecation.warn "ActionController::Base.consider_all_requests_local= is deprecated. " <<
"Please configure it on your application with config.consider_all_requests_local=", caller
Rails.application.config.consider_all_requests_local = value
end
def allow_concurrency
ActiveSupport::Deprecation.warn "ActionController::Base.allow_concurrency is deprecated, " <<
"use Rails.application.config.allow_concurrency instead", caller
Rails.application.config.allow_concurrency
end
def allow_concurrency=(value)
ActiveSupport::Deprecation.warn "ActionController::Base.allow_concurrency= is deprecated. " <<
"Please configure it on your application with config.allow_concurrency=", caller
Rails.application.config.allow_concurrency = value
end
def ip_spoofing_check=(value)
ActiveSupport::Deprecation.warn "ActionController::Base.ip_spoofing_check= is deprecated. " <<
"Please configure it on your application with config.action_dispatch.ip_spoofing_check=", caller
Rails.application.config.action_dispatch.ip_spoofing_check = value
end
def ip_spoofing_check
ActiveSupport::Deprecation.warn "ActionController::Base.ip_spoofing_check is deprecated. " <<
"Configuring ip_spoofing_check on the application configures a middleware.", caller
Rails.application.config.action_disaptch.ip_spoofing_check
end
def trusted_proxies=(value)
ActiveSupport::Deprecation.warn "ActionController::Base.trusted_proxies= is deprecated. " <<
"Please configure it on your application with config.action_dispatch.trusted_proxies=", caller
Rails.application.config.action_dispatch.ip_spoofing_check = value
end
def trusted_proxies
ActiveSupport::Deprecation.warn "ActionController::Base.trusted_proxies is deprecated. " <<
"Configuring trusted_proxies on the application configures a middleware.", caller
Rails.application.config.action_dispatch.ip_spoofing_check = value
end
def session(*args)
ActiveSupport::Deprecation.warn(
"Disabling sessions for a single controller has been deprecated. " +
"Sessions are now lazy loaded. So if you don't access them, " +
"consider them off. You can still modify the session cookie " +
"options with request.session_options.", caller)
end
def session=(value)
ActiveSupport::Deprecation.warn "ActionController::Base.session= is deprecated. " <<
"Please configure it on your application with config.session_store :cookie_store, :key => '....'", caller
if value.delete(:disabled)
Rails.application.config.session_store :disabled
else
store = Rails.application.config.session_store
Rails.application.config.session_store store, value
end
end
# Controls the resource action separator
def resource_action_separator
@resource_action_separator ||= "/"
end
def resource_action_separator=(val)
ActiveSupport::Deprecation.warn "ActionController::Base.resource_action_separator is deprecated and only " \
"works with the deprecated router DSL."
@resource_action_separator = val
end
def use_accept_header
ActiveSupport::Deprecation.warn "ActionController::Base.use_accept_header doesn't do anything anymore. " \
"The accept header is always taken into account."
end
def use_accept_header=(val)
use_accept_header
end
end
deprecated_config_writer :session_store
deprecated_config_writer :session_options
deprecated_config_accessor :relative_url_root, "relative_url_root is ineffective. Please stop using it"
deprecated_config_accessor :assets_dir
deprecated_config_accessor :javascripts_dir
deprecated_config_accessor :stylesheets_dir
delegate :consider_all_requests_local, :consider_all_requests_local=,
:allow_concurrency, :allow_concurrency=, :to => :"self.class"
end
end
\ No newline at end of file
......@@ -34,7 +34,8 @@ def controller_name
# and response object available. You might wish to control the
# environment and response manually for performance reasons.
attr_internal :status, :headers, :content_type, :response
attr_internal :status, :headers, :content_type, :response, :request
delegate :session, :to => "@_request"
def initialize(*)
@_headers = {}
......@@ -49,6 +50,14 @@ def content_type=(type)
headers["Content-Type"] = type.to_s
end
def content_type
headers["Content-Type"]
end
def location
headers["Location"]
end
def location=(url)
headers["Location"] = url
end
......@@ -58,8 +67,9 @@ def status=(status)
end
# :api: private
def dispatch(name, env)
@_env = env
def dispatch(name, request)
@_request = request
@_env = request.env
@_env['action_controller.instance'] = self
process(name)
to_a
......@@ -70,31 +80,12 @@ def to_a
response ? response.to_a : [status, headers, response_body]
end
class ActionEndpoint
@@endpoints = Hash.new {|h,k| h[k] = Hash.new {|sh,sk| sh[sk] = {} } }
def self.for(controller, action, stack)
@@endpoints[controller][action][stack] ||= begin
endpoint = new(controller, action)
stack.build(endpoint)
end
end
def initialize(controller, action)
@controller, @action = controller, action
@_formats = [Mime::HTML]
end
def call(env)
@controller.new.dispatch(@action, env)
end
end
class_attribute :middleware_stack
self.middleware_stack = ActionDispatch::MiddlewareStack.new
def self.inherited(base)
self.middleware_stack = base.middleware_stack.dup
super
end
def self.use(*args)
......@@ -118,8 +109,10 @@ def self.call(env)
#
# ==== Returns
# Proc:: A rack application
def self.action(name)
ActionEndpoint.for(self, name, middleware_stack)
def self.action(name, klass = ActionDispatch::Request)
middleware_stack.build do |env|
new.dispatch(name, klass.new(env))
end
end
end
end
......@@ -2,26 +2,23 @@ module ActionController
module Compatibility
extend ActiveSupport::Concern
include AbstractController::Compatibility
class ::ActionController::ActionControllerError < StandardError #:nodoc:
end
module ClassMethods
end
# Temporary hax
included do
::ActionController::UnknownAction = ::AbstractController::ActionNotFound
::ActionController::DoubleRenderError = ::AbstractController::DoubleRenderError
cattr_accessor :session_options
self.session_options = {}
cattr_accessor :relative_url_root
self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT']
# ROUTES TODO: This should be handled by a middleware and route generation
# should be able to handle SCRIPT_NAME
self.config.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT']
class << self
delegate :default_charset=, :to => "ActionDispatch::Response"
delegate :resources_path_names, :to => "ActionController::Routing::Routes"
delegate :resources_path_names=, :to => "ActionController::Routing::Routes"
end
# cattr_reader :protected_instance_variables
......@@ -32,31 +29,17 @@ class << self
@before_filter_chain_aborted @_headers @_params
@_response)
# Controls the resource action separator
cattr_accessor :resource_action_separator
self.resource_action_separator = "/"
cattr_accessor :use_accept_header
self.use_accept_header = true
def rescue_action(env)
raise env["action_dispatch.rescue.exception"]
end
self.page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
# 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
cattr_accessor :trusted_proxies
end
# For old tests
def initialize_template_class(*) end
def assign_shortcuts(*) end
# TODO: Remove this after we flip
def template
@template ||= view_context
end
......@@ -66,52 +49,20 @@ def process_action(*)
super
end
module ClassMethods
def consider_all_requests_local
ActiveSupport::Deprecation.warn "ActionController::Base.consider_all_requests_local is deprecated, " <<
"use Rails.application.config.consider_all_requests_local instead"
Rails.application.config.consider_all_requests_local
end
def consider_all_requests_local=(value)
ActiveSupport::Deprecation.warn "ActionController::Base.consider_all_requests_local= is no longer effective. " <<
"Please configure it on your application with config.consider_all_requests_local="
Rails.application.config.consider_all_requests_local = value
end
def allow_concurrency
ActiveSupport::Deprecation.warn "ActionController::Base.allow_concurrency is deprecated, " <<
"use Rails.application.config.allow_concurrency instead"
Rails.application.config.allow_concurrency
end
def allow_concurrency=(value)
ActiveSupport::Deprecation.warn "ActionController::Base.allow_concurrency= is no longer effective. " <<
"Please configure it on your application with config.allow_concurrency="
Rails.application.config.allow_concurrency = value
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
delegate :consider_all_requests_local, :consider_all_requests_local=,
:allow_concurrency, :allow_concurrency=, :to => :"self.class"
def render_to_body(options)
if options.is_a?(Hash) && options.key?(:template)
options[:template].sub!(/^\//, '')
def _normalize_options(options)
if options[:action] && options[:action].to_s.include?(?/)
ActiveSupport::Deprecation.warn "Giving a path to render :action is deprecated. " <<
"Please use render :template instead", caller
options[:template] = options.delete(:action)
end
options[:text] = nil if options.delete(:nothing) == true
options[:text] = " " if options.key?(:text) && options[:text].nil?
super
end
def render_to_body(options)
options[:template].sub!(/^\//, '') if options.key?(:template)
super || " "
end
......@@ -126,18 +77,5 @@ def method_for_action(action_name)
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
view_context.view_paths
end
end
end
module ActionController
module Configuration
extend ActiveSupport::Concern
def config
@config ||= self.class.config
end
def config=(config)
@config = config
end
module ClassMethods
def default_config
@default_config ||= {}
end
def config
self.config ||= default_config
end
def config=(config)
@config = ActiveSupport::OrderedHash.new
@config.merge!(config)
end
end
end
end
\ No newline at end of file
module ActionController
module Head
extend ActiveSupport::Concern
include ActionController::UrlFor
# Return a response that has no content (merely headers). The options
......
......@@ -86,7 +86,7 @@ def helpers
end
private
# Overwrite _modules_for_helpers to accept :all as argument, which loads
# Overwrite modules_for_helpers to accept :all as argument, which loads
# all helpers in helpers_dir.
#
# ==== Parameters
......@@ -95,7 +95,7 @@ def helpers
# ==== Returns
# Array[Module]:: A normalized list of modules for the list of
# helpers provided.
def _modules_for_helpers(args)
def modules_for_helpers(args)
args += all_application_helpers if args.delete(:all)
super(args)
end
......
......@@ -15,10 +15,8 @@ module HideActions
# Overrides AbstractController::Base#action_method? to return false if the
# action name is in the list of hidden actions.
def action_method?(action_name)
self.class.visible_action?(action_name) do
!self.class.hidden_actions.include?(action_name) && super
end
def method_for_action(action_name)
self.class.visible_action?(action_name) && super
end
module ClassMethods
......@@ -31,13 +29,13 @@ def hide_action(*args)
end
def inherited(klass)
klass.instance_variable_set("@visible_actions", {})
klass.class_eval { @visible_actions = {} }
super
end
def visible_action?(action_name)
return @visible_actions[action_name] if @visible_actions.key?(action_name)
@visible_actions[action_name] = yield
@visible_actions[action_name] = !hidden_actions.include?(action_name)
end
# Overrides AbstractController::Base#action_methods to remove any methods
......
......@@ -124,7 +124,7 @@ def request_http_basic_authentication(realm = "Application")
end
def authenticate(request, &login_procedure)
unless authorization(request).blank?
unless request.authorization.blank?
login_procedure.call(*user_name_and_password(request))
end
end
......@@ -133,15 +133,8 @@ def user_name_and_password(request)
decode_credentials(request).split(/:/, 2)
end
def authorization(request)
request.env['HTTP_AUTHORIZATION'] ||
request.env['X-HTTP_AUTHORIZATION'] ||
request.env['X_HTTP_AUTHORIZATION'] ||
request.env['REDIRECT_X_HTTP_AUTHORIZATION']
end
def decode_credentials(request)
ActiveSupport::Base64.decode64(authorization(request).split(' ', 2).last || '')
ActiveSupport::Base64.decode64(request.authorization.split(' ', 2).last || '')
end
def encode_credentials(user_name, password)
......@@ -165,7 +158,7 @@ def authenticate_or_request_with_http_digest(realm = "Application", &password_pr
# Authenticate with HTTP Digest, returns true or false
def authenticate_with_http_digest(realm = "Application", &password_procedure)
HttpAuthentication::Digest.authenticate(request, realm, &password_procedure)
HttpAuthentication::Digest.authenticate(config.secret, request, realm, &password_procedure)
end
# Render output including the HTTP Digest authentication header
......@@ -175,30 +168,23 @@ def request_http_digest_authentication(realm = "Application", message = nil)
end
# Returns false on a valid response, true otherwise
def authenticate(request, realm, &password_procedure)
authorization(request) && validate_digest_response(request, realm, &password_procedure)
end
def authorization(request)
request.env['HTTP_AUTHORIZATION'] ||
request.env['X-HTTP_AUTHORIZATION'] ||
request.env['X_HTTP_AUTHORIZATION'] ||
request.env['REDIRECT_X_HTTP_AUTHORIZATION']
def authenticate(secret_key, request, realm, &password_procedure)
request.authorization && validate_digest_response(secret_key, request, realm, &password_procedure)
end
# Returns false unless the request credentials response value matches the expected value.
# First try the password as a ha1 digest password. If this fails, then try it as a plain
# text password.
def validate_digest_response(request, realm, &password_procedure)
def validate_digest_response(secret_key, request, realm, &password_procedure)
credentials = decode_credentials_header(request)
valid_nonce = validate_nonce(request, credentials[:nonce])
valid_nonce = validate_nonce(secret_key, request, credentials[:nonce])
if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque]
if valid_nonce && realm == credentials[:realm] && opaque(secret_key) == credentials[:opaque]
password = password_procedure.call(credentials[:username])
return false unless password
method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
uri = credentials[:uri][0,1] == '/' ? request.request_uri : request.url
uri = credentials[:uri][0,1] == '/' ? request.fullpath : request.url
[true, false].any? do |password_is_ha1|
expected = expected_response(method, uri, credentials, password, password_is_ha1)
......@@ -226,7 +212,7 @@ def encode_credentials(http_method, credentials, password, password_is_ha1)
end
def decode_credentials_header(request)
decode_credentials(authorization(request))
decode_credentials(request.authorization)
end
def decode_credentials(header)
......@@ -238,6 +224,9 @@ def decode_credentials(header)
end
def authentication_header(controller, realm)
secret_key = controller.config.secret
nonce = self.nonce(secret_key)
opaque = opaque(secret_key)
controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}")
end
......@@ -280,7 +269,7 @@ def authentication_request(controller, realm, message = nil)
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
# key from the Rails session secret generated upon creation of project. Ensures
# the time cannot be modified by client.
def nonce(time = Time.now)
def nonce(secret_key, time = Time.now)
t = time.to_i
hashed = [t, secret_key]
digest = ::Digest::MD5.hexdigest(hashed.join(":"))
......@@ -292,21 +281,16 @@ def nonce(time = Time.now)
# Can be much shorter if the Stale directive is implemented. This would
# 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)
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
t = ActiveSupport::Base64.decode64(value).split(":").first.to_i
nonce(t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
end
# Opaque based on random generation - but changing each request?
def opaque()
def opaque(secret_key)
::Digest::MD5.hexdigest(secret_key)
end
# Set in /initializers/session_store.rb, and loaded even if sessions are not in use.
def secret_key
ActionController::Base.session_options[:secret]
end
end
end
end
module ActionController
module ImplicitRender
def send_action(*)
ret = super
default_render unless response_body
ret
end
def default_render
render
end
def method_for_action(action_name)
super || begin
if template_exists?(action_name.to_s, _prefix)
"default_render"
end
end
end
end
end
\ No newline at end of file
......@@ -20,7 +20,7 @@ def process_action(action, *args)
:params => request.filtered_parameters,
:formats => request.formats.map(&:to_sym),
:method => request.method,
:path => (request.request_uri rescue "unknown")
:path => (request.fullpath rescue "unknown")
}
ActiveSupport::Notifications.instrument("action_controller.start_processing", raw_payload.dup)
......
......@@ -6,14 +6,11 @@ module RackDelegation
extend ActiveSupport::Concern
included do
delegate :session, :to => "@_request"
delegate :headers, :status=, :location=, :content_type=,
:status, :location, :content_type, :to => "@_response"
attr_internal :request
end
def dispatch(action, env)
@_request = ActionDispatch::Request.new(env)
def dispatch(action, request)
@_response = ActionDispatch::Response.new
@_response.request = request
super
......
......@@ -11,6 +11,7 @@ module Redirecting
extend ActiveSupport::Concern
include AbstractController::Logger
include ActionController::RackDelegation
include ActionController::UrlFor
# Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
......
......@@ -19,7 +19,7 @@ def _write_render_options
<<-RUBY_EVAL
if options.key?(:#{name})
_process_options(options)
return _render_option_#{name}(options[:#{name}], options)
return _render_option_#{name}(options.delete(:#{name}), options)
end
RUBY_EVAL
end
......
......@@ -2,68 +2,54 @@ module ActionController
module Rendering
extend ActiveSupport::Concern
included do
include AbstractController::Rendering
include AbstractController::LocalizedCache
end
include ActionController::RackDelegation
include AbstractController::Rendering
def process_action(*)
self.formats = request.formats.map {|x| x.to_sym}
def process(*)
self.formats = request.formats.map { |x| x.to_sym }
super
end
def render(*args)
if response_body
raise ::AbstractController::DoubleRenderError
end
args << {} unless args.last.is_a?(Hash)
super(*args)
self.content_type ||= args.last[:_template].mime_type.to_s
response_body
end
def render_to_body(options)
_process_options(options)
raise ::AbstractController::DoubleRenderError if response_body
super
response_body
end
private
def _render_partial(options)
options[:partial] = action_name if options[:partial] == true
options[:_details] = {:formats => formats}
super
def _normalize_args(action=nil, options={}, &blk)
options = super
options[:update] = blk if block_given?
options
end
def format_for_text
formats.first
def _normalize_options(options)
if options.key?(:text) && options[:text].respond_to?(:to_text)
options[:text] = options[:text].to_text
end
if options[:status]
options[:status] = Rack::Utils.status_code(options[:status])
end
super
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
def _normalize_options(action=nil, options={}, &blk)
case action
when NilClass
when Hash
options = super(action.delete(:action), action)
when String, Symbol
options = super
else
options.merge! :partial => action
end
if options[:status]
options[:status] = Rack::Utils.status_code(options[:status])
end
super
end
options[:update] = blk if block_given?
options
def _with_template_hook(template)
super
self.content_type ||= template.mime_type.to_s
end
end
end
......@@ -12,11 +12,10 @@ module RequestForgeryProtection
included do
# 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
config.request_forgery_protection_token ||= :authenticity_token
# Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode.
class_attribute :allow_forgery_protection
self.allow_forgery_protection = true
config.allow_forgery_protection ||= true
helper_method :form_authenticity_token
helper_method :protect_against_forgery?
......@@ -80,9 +79,47 @@ def protect_from_forgery(options = {})
self.request_forgery_protection_token ||= :authenticity_token
before_filter :verify_authenticity_token, options
end
def request_forgery_protection_token
config.request_forgery_protection_token
end
def request_forgery_protection_token=(val)
config.request_forgery_protection_token = val
end
def allow_forgery_protection
config.allow_forgery_protection
end
def allow_forgery_protection=(val)
config.allow_forgery_protection = val
end
end
protected
def protect_from_forgery(options = {})
self.request_forgery_protection_token ||= :authenticity_token
before_filter :verify_authenticity_token, options
end
def request_forgery_protection_token
config.request_forgery_protection_token
end
def request_forgery_protection_token=(val)
config.request_forgery_protection_token = val
end
def allow_forgery_protection
config.allow_forgery_protection
end
def allow_forgery_protection=(val)
config.allow_forgery_protection = val
end
# The actual before_filter that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
verified_request? || raise(ActionController::InvalidAuthenticityToken)
......@@ -109,7 +146,7 @@ def form_authenticity_param
end
def protect_against_forgery?
self.class.allow_forgery_protection
config.allow_forgery_protection
end
end
end
......@@ -2,44 +2,8 @@ module ActionController #:nodoc:
module SessionManagement #:nodoc:
extend ActiveSupport::Concern
include ActionController::Configuration
module ClassMethods
# Set the session store to be used for keeping the session data between requests.
# By default, sessions are stored in browser cookies (<tt>:cookie_store</tt>),
# but you can also specify one of the other included stores (<tt>:active_record_store</tt>,
# <tt>:mem_cache_store</tt>, or your own custom class.
def session_store=(store)
if store == :active_record_store
self.session_store = ActiveRecord::SessionStore
else
@@session_store = store.is_a?(Symbol) ?
ActionDispatch::Session.const_get(store.to_s.camelize) :
store
end
end
# Returns the session store class currently used.
def session_store
if defined? @@session_store
@@session_store
else
ActionDispatch::Session::CookieStore
end
end
def session=(options = {})
self.session_store = nil if options.delete(:disabled)
session_options.merge!(options)
end
def session(*args)
ActiveSupport::Deprecation.warn(
"Disabling sessions for a single controller has been deprecated. " +
"Sessions are now lazy loaded. So if you don't access them, " +
"consider them off. You can still modify the session cookie " +
"options with request.session_options.", caller)
end
end
end
end
......@@ -9,18 +9,13 @@ module Streaming
DEFAULT_SEND_FILE_OPTIONS = {
:type => 'application/octet-stream'.freeze,
:disposition => 'attachment'.freeze,
:stream => true,
:buffer_size => 4096,
:x_sendfile => false
}.freeze
X_SENDFILE_HEADER = 'X-Sendfile'.freeze
protected
# Sends the file, by default streaming it 4096 bytes at a time. This way the
# whole file doesn't need to be read into memory at once. This makes it
# feasible to send even large files. You can optionally turn off streaming
# and send the whole file at once.
# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
# via the Rack::Sendfile middleware. The header to use is set via
# config.action_dispatch.x_sendfile_header, and defaults to "X-Sendfile".
# Your server can also configure this for you by setting the X-Sendfile-Type header.
#
# Be careful to sanitize the path parameter if it is coming from a web
# page. <tt>send_file(params[:path])</tt> allows a malicious user to
......@@ -31,24 +26,12 @@ module Streaming
# Defaults to <tt>File.basename(path)</tt>.
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
# either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
# * <tt>:length</tt> - used to manually override the length (in bytes) of the content that
# is going to be sent to the client. Defaults to <tt>File.size(path)</tt>.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
# * <tt>:stream</tt> - whether to send the file to the user agent as it is read (+true+)
# or to read the entire file before sending (+false+). Defaults to +true+.
# * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
# Defaults to 4096.
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
# the URL, which is necessary for i18n filenames on certain browsers
# (setting <tt>:filename</tt> overrides this option).
# * <tt>:x_sendfile</tt> - uses X-Sendfile to send the file when set to +true+. This is currently
# only available with Lighttpd/Apache2 and specific modules installed and activated. Since this
# uses the web server to send the file, this may lower memory consumption on your server and
# it will not block your application for further requests.
# See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and
# http://tn123.ath.cx/mod_xsendfile/ for details. Defaults to +false+.
#
# The default Content-Type and Content-Disposition headers are
# set to download arbitrary binary files in as many browsers as
......@@ -81,29 +64,16 @@ module Streaming
def send_file(path, options = {}) #:doc:
raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
options[:length] ||= File.size(path)
options[:filename] ||= File.basename(path) unless options[:url_based_filename]
send_file_headers! options
@performed_render = false
if options[:x_sendfile]
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|
len = options[:buffer_size] || 4096
File.open(path, 'rb') do |file|
while buf = file.read(len)
output.write(buf)
end
end
}
else
File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read }
end
ActiveSupport::Deprecation.warn(":x_sendfile is no longer needed in send_file", caller)
end
self.status = options[:status] || 200
self.content_type = options[:content_type] if options.key?(:content_type)
self.response_body = File.open(path, "rb")
end
# Sends the given binary data to the browser. This method is similar to
......@@ -138,32 +108,35 @@ def send_file(path, options = {}) #:doc:
# data to the browser, then use <tt>render :text => proc { ... }</tt>
# instead. See ActionController::Base#render for more information.
def send_data(data, options = {}) #:doc:
send_file_headers! options.merge(:length => data.bytesize)
render :status => options[:status], :text => data
send_file_headers! options.dup
render options.slice(:status, :content_type).merge(:text => data)
end
private
def send_file_headers!(options)
options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
[:length, :type, :disposition].each do |arg|
[:type, :disposition].each do |arg|
raise ArgumentError, ":#{arg} option required" if options[arg].nil?
end
disposition = options[:disposition].dup || 'attachment'
if options.key?(:length)
ActiveSupport::Deprecation.warn("You do not need to provide the file's length", caller)
end
disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
disposition = options[:disposition]
disposition += %(; filename="#{options[:filename]}") if options[:filename]
content_type = options[:type]
if content_type.is_a?(Symbol)
raise ArgumentError, "Unknown MIME type #{options[:type]}" unless Mime::EXTENSION_LOOKUP.key?(content_type.to_s)
self.content_type = Mime::Type.lookup_by_extension(content_type.to_s)
extension = Mime[content_type]
raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension
self.content_type = extension
else
self.content_type = content_type
end
headers.merge!(
'Content-Length' => options[:length].to_s,
'Content-Disposition' => disposition,
'Content-Transfer-Encoding' => 'binary'
)
......
......@@ -13,7 +13,6 @@ def process_with_new_base_test(request, response)
if cookies = @_request.env['action_dispatch.cookies']
cookies.write(@_response)
end
@_response.body ||= self.response_body
@_response.prepare!
set_test_assigns
ret
......
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/module/attribute_accessors'
module ActionController
# In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
# is also possible: an URL can be generated from one of your routing definitions.
# URL generation functionality is centralized in this module.
#
# See ActionController::Routing and ActionController::Resources for general
# information about routing and routes.rb.
#
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
# then ActionController::UrlFor is what you're looking for. Read on for
# an introduction.
#
# == URL generation from parameters
#
# As you may know, some functions - such as ActionController::Base#url_for
# and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
# of parameters. For example, you've probably had the chance to write code
# like this in one of your views:
#
# <%= link_to('Click here', :controller => 'users',
# :action => 'new', :message => 'Welcome!') %>
#
# #=> Generates a link to: /users/new?message=Welcome%21
#
# link_to, and all other functions that require URL generation functionality,
# actually use ActionController::UrlFor under the hood. And in particular,
# they use the ActionController::UrlFor#url_for method. One can generate
# the same path as the above example by using the following code:
#
# include UrlFor
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
# :only_path => true)
# # => "/users/new?message=Welcome%21"
#
# Notice the <tt>:only_path => true</tt> part. This is because UrlFor has no
# information about the website hostname that your Rails app is serving. So if you
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
# argument:
#
# include UrlFor
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
# :host => 'www.example.com') # Changed this.
# # => "http://www.example.com/users/new?message=Welcome%21"
#
# By default, all controllers and views have access to a special version of url_for,
# that already knows what the current hostname is. So if you use url_for in your
# controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
# argument.
#
# For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for.
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for'
# in full. However, mailers don't have hostname information, and what's why you'll still
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
#
#
# == URL generation for named routes
#
# UrlFor also allows one to access methods that have been auto-generated from
# named routes. For example, suppose that you have a 'users' resource in your
# <b>routes.rb</b>:
#
# map.resources :users
#
# This generates, among other things, the method <tt>users_path</tt>. By default,
# this method is accessible from your controllers, views and mailers. If you need
# to access this auto-generated method from other places (such as a model), then
# you can do that by including ActionController::UrlFor in your class:
#
# class User < ActiveRecord::Base
# include ActionController::UrlFor
#
# def base_uri
# user_path(self)
# end
# end
#
# User.find(1).base_uri # => "/users/1"
#
module UrlFor
extend ActiveSupport::Concern
included do
ActionController::Routing::Routes.install_helpers(self)
# Including in a class uses an inheritable hash. Modules get a plain hash.
if respond_to?(:class_attribute)
class_attribute :default_url_options
else
mattr_accessor :default_url_options
end
include ActionDispatch::Routing::UrlFor
self.default_url_options = {}
def url_options
super.reverse_merge(
:host => request.host_with_port,
:protocol => request.protocol,
:_path_segments => request.symbolized_path_parameters
).merge(:script_name => request.script_name)
end
# Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
# the form of a hash, just like the one you would use for url_for directly. Example:
#
# def default_url_options(options)
# { :project => @project.active? ? @project.url_name : "unknown" }
# end
#
# As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
# urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
# by this method.
def default_url_options(options = nil)
self.class.default_url_options
end
def rewrite_options(options) #:nodoc:
if options.delete(:use_defaults) != false && (defaults = default_url_options(options))
defaults.merge(options)
else
options
end
end
# Generate a url based on the options provided, default_url_options and the
# routes defined in routes.rb. The following options are supported:
#
# * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
# * <tt>:host</tt> - Specifies the host the link should be targeted at.
# If <tt>:only_path</tt> is false, this option must be
# provided either explicitly, or via +default_url_options+.
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
# +relative_url_root+ set in ActionController::Base.relative_url_root.
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
#
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
# +url_for+ is forwarded to the Routes module.
#
# Examples:
#
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
# url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
def url_for(options = {})
options ||= {}
case options
when String
options
when Hash
_url_rewriter.rewrite(rewrite_options(options))
else
polymorphic_url(options)
end
end
protected
def _url_rewriter
ActionController::UrlRewriter
def _router
raise "In order to use #url_for, you must include the helpers of a particular " \
"router. For instance, `include Rails.application.routes.url_helpers"
end
end
end
end
\ No newline at end of file
......@@ -6,7 +6,8 @@ def initialize(controller, app)
end
def call(env)
@controller.build(@app).dispatch(:index, env)
request = ActionDispatch::Request.new(env)
@controller.build(@app).dispatch(:index, request)
end
end
......
......@@ -92,8 +92,7 @@ def polymorphic_url(record_or_hash_or_array, options = {})
inflection = if options[:action].to_s == "new"
args.pop
:singular
elsif (record.respond_to?(:new_record?) && record.new_record?) ||
(record.respond_to?(:destroyed?) && record.destroyed?)
elsif (record.respond_to?(:persisted?) && !record.persisted?)
args.pop
:plural
elsif record.is_a?(Class)
......
require "action_controller"
require "rails"
require "action_controller"
require "action_view/railtie"
require "active_support/core_ext/class/subclasses"
require "active_support/deprecation/proxy_wrappers"
require "active_support/deprecation"
module ActionController
class Railtie < Rails::Railtie
railtie_name :action_controller
require "action_controller/railties/subscriber"
subscriber ActionController::Railties::Subscriber.new
require "action_controller/railties/log_subscriber"
require "action_controller/railties/url_helpers"
ad = config.action_dispatch
config.action_controller.singleton_class.send(:define_method, :session) do
ActiveSupport::Deprecation.warn "config.action_controller.session has been " \
"renamed to config.action_dispatch.session.", caller
ad.session
end
config.action_controller.singleton_class.send(:define_method, :session=) do |val|
ActiveSupport::Deprecation.warn "config.action_controller.session has been " \
"renamed to config.action_dispatch.session.", caller
ad.session = val
end
config.action_controller.singleton_class.send(:define_method, :session_store) do
ActiveSupport::Deprecation.warn "config.action_controller.session_store has been " \
"renamed to config.action_dispatch.session_store.", caller
ad.session_store
end
config.action_controller.singleton_class.send(:define_method, :session_store=) do |val|
ActiveSupport::Deprecation.warn "config.action_controller.session_store has been " \
"renamed to config.action_dispatch.session_store.", caller
ad.session_store = val
end
log_subscriber ActionController::Railties::LogSubscriber.new
initializer "action_controller.logger" do
ActionController::Base.logger ||= Rails.logger
ActionController.base_hook { self.logger ||= Rails.logger }
end
initializer "action_controller.set_configs" do |app|
app.config.action_controller.each do |k,v|
ActionController::Base.send "#{k}=", v
end
paths = app.config.paths
ac = app.config.action_controller
ac.assets_dir = paths.public.to_a.first
ac.javascripts_dir = paths.public.javascripts.to_a.first
ac.stylesheets_dir = paths.public.stylesheets.to_a.first
ac.secret = app.config.cookie_secret
ActionController.base_hook { self.config.replace(ac) }
end
initializer "action_controller.initialize_framework_caches" do
ActionController::Base.cache_store ||= RAILS_CACHE
ActionController.base_hook { self.cache_store ||= RAILS_CACHE }
end
initializer "action_controller.set_helpers_path" do |app|
ActionController::Base.helpers_path = app.config.paths.app.helpers.to_a
ActionController.base_hook do
self.helpers_path = app.config.paths.app.helpers.to_a
end
end
initializer "action_controller.url_helpers" do |app|
ActionController.base_hook do
extend ::ActionController::Railtie::UrlHelpers.with(app.routes)
end
message = "ActionController::Routing::Routes is deprecated. " \
"Instead, use Rails.application.routes"
proxy = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(app.routes, message)
ActionController::Routing::Routes = proxy
end
end
end
end
\ No newline at end of file
module ActionController
module Railties
class Subscriber < Rails::Subscriber
class LogSubscriber < Rails::LogSubscriber
INTERNAL_PARAMS = %w(controller action format _method only_path)
def start_processing(event)
......@@ -22,15 +22,7 @@ def process_action(event)
end
def send_file(event)
message = if event.payload[:x_sendfile]
header = ActionController::Streaming::X_SENDFILE_HEADER
"Sent #{header} header %s"
elsif event.payload[:stream]
"Streamed file %s"
else
"Sent file %s"
end
message = "Sent file %s"
message << " (%.1fms)"
info(message % [event.payload[:path], event.duration])
end
......
module ActionController
class Railtie
module UrlHelpers
def self.with(router)
Module.new do
define_method(:inherited) do |klass|
super(klass)
klass.send(:include, router.url_helpers)
end
end
end
end
end
end
\ No newline at end of file
......@@ -60,13 +60,32 @@ def dom_class(record_or_class, prefix = nil)
#
# dom_id(Post.find(45), :edit) # => "edit_post_45"
def dom_id(record, prefix = nil)
if record_id = record.id
if record_id = record_key_for_dom_id(record)
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
else
dom_class(record, prefix || NEW)
end
end
# Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id.
# This can be overwritten to customize the default generated string representation if desired.
# If you need to read back a key from a dom_id in order to query for the underlying database record,
# you should write a helper like 'person_record_from_dom_id' that will extract the key either based
# on the default implementation (which just joins all key attributes with '-') or on your own
# overwritten version of the method. By default, this implementation passes the key string through a
# method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to
# make sure yourself that your dom ids are valid, in case you overwrite this method.
def record_key_for_dom_id(record)
return record.id unless record.respond_to?(:to_model)
key = record.to_model.to_key
key ? sanitize_dom_id(key.join('_')) : key
end
# Replaces characters that are invalid in HTML DOM ids with valid ones.
def sanitize_dom_id(candidate_id)
candidate_id # TODO implement conversion to valid DOM id values
end
# Returns the plural class name of a record or class. Examples:
#
# plural_class_name(post) # => "posts"
......
......@@ -17,9 +17,9 @@ def self.new_escaped(strings)
end
end
def assign_parameters(controller_path, action, parameters = {})
def assign_parameters(router, controller_path, action, parameters = {})
parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
extra_keys = ActionController::Routing::Routes.extra_keys(parameters)
extra_keys = router.extra_keys(parameters)
non_path_parameters = get? ? query_parameters : request_parameters
parameters.each do |key, value|
if value.is_a? Fixnum
......@@ -220,7 +220,7 @@ def xml_http_request(request_method, action, parameters = nil, session = nil, fl
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|
%w(@router @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
......@@ -236,7 +236,7 @@ def process(action, parameters = nil, session = nil, flash = nil, http_method =
@request.env['REQUEST_METHOD'] = http_method
parameters ||= {}
@request.assign_parameters(@controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters)
@request.assign_parameters(@router, @controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters)
@request.session = ActionController::TestSession.new(session) unless session.nil?
@request.session["flash"] = @request.flash.update(flash || {})
......@@ -322,6 +322,8 @@ def setup_controller_request_and_response
@controller ||= klass.new rescue nil
end
@request.env.delete('PATH_INFO')
if @controller
@controller.request = @request
@controller.params = {}
......@@ -335,13 +337,20 @@ def rescue_action_in_public!
private
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)
unless @request.env["PATH_INFO"]
options = @controller.__send__(:url_options).merge(parameters)
options.update(
:only_path => true,
:action => action,
:relative_url_root => nil,
:_path_segments => @request.symbolized_path_parameters)
url, query_string = @router.url_for(options).split("?", 2)
@request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
@request.env["PATH_INFO"] = url
@request.env["QUERY_STRING"] = query_string || ""
end
end
end
end
end
require 'active_support/core_ext/hash/except'
module ActionController
# Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
class UrlRewriter #:nodoc:
RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root]
def initialize(request, parameters)
@request, @parameters = request, parameters
end
def rewrite(options = {})
options[:host] ||= @request.host_with_port
options[:protocol] ||= @request.protocol
self.class.rewrite(options, @request.symbolized_path_parameters) do |options|
process_path_options(options)
end
end
def to_str
"#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}"
end
alias_method :to_s, :to_str
def self.rewrite(options, path_segments=nil)
rewritten_url = ""
unless options[:only_path]
rewritten_url << (options[:protocol] || "http")
rewritten_url << "://" unless rewritten_url.match("://")
rewritten_url << rewrite_authentication(options)
raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
rewritten_url << options[:host]
rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
end
path_options = options.except(*RESERVED_OPTIONS)
path_options = yield(path_options) if block_given?
path = Routing::Routes.generate(path_options, path_segments || {})
rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
rewritten_url << "##{Rack::Utils.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
rewritten_url
end
protected
def self.rewrite_authentication(options)
if options[:user] && options[:password]
"#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@"
else
""
end
end
# Given a Hash of options, generates a route
def process_path_options(options)
options = options.symbolize_keys
options.update(options[:params].symbolize_keys) if options[:params]
if (overwrite = options.delete(:overwrite_params))
options.update(@parameters.symbolize_keys)
options.update(overwrite.symbolize_keys)
end
options
end
end
end
......@@ -48,6 +48,7 @@ module ActionDispatch
autoload :Flash
autoload :Head
autoload :ParamsParser
autoload :RemoteIp
autoload :Rescue
autoload :ShowExceptions
autoload :Static
......
......@@ -37,8 +37,21 @@ def fresh?(response)
end
module Response
def cache_control
@cache_control ||= {}
attr_reader :cache_control
def initialize(*)
status, header, body = super
@cache_control = {}
@etag = self["ETag"]
if cache_control = self["Cache-Control"]
cache_control.split(/,\s*/).each do |segment|
first, last = segment.split("=")
last ||= true
@cache_control[first.to_sym] = last
end
end
end
def last_modified
......@@ -65,7 +78,7 @@ def etag?
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
@etag = %("#{Digest::MD5.hexdigest(key)}")
@etag = self["ETag"] = %("#{Digest::MD5.hexdigest(key)}")
end
private
......@@ -100,6 +113,8 @@ def string_body?
def set_conditional_cache_control!
control = @cache_control
return if self["Cache-Control"].present?
if control.empty?
headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
elsif @cache_control[:no_cache]
......
......@@ -25,9 +25,16 @@ module Http
module FilterParameters
extend ActiveSupport::Concern
mattr_reader :compiled_parameter_filter_for
@@compiled_parameter_filter_for = {}
# Return a hash of parameters with all sensitive data replaced.
def filtered_parameters
@filtered_parameters ||= process_parameter_filter(parameters)
@filtered_parameters ||= if filtering_parameters?
process_parameter_filter(parameters)
else
parameters.dup
end
end
alias :fitered_params :filtered_parameters
......@@ -46,10 +53,18 @@ def filtered_env
protected
def compile_parameter_filter #:nodoc:
def filtering_parameters? #:nodoc:
@env["action_dispatch.parameter_filter"].present?
end
def process_parameter_filter(params) #:nodoc:
compiled_parameter_filter_for(@env["action_dispatch.parameter_filter"]).call(params)
end
def compile_parameter_filter(filters) #:nodoc:
strings, regexps, blocks = [], [], []
Array(@env["action_dispatch.parameter_filter"]).each do |item|
filters.each do |item|
case item
when NilClass
when Proc
......@@ -65,34 +80,34 @@ def compile_parameter_filter #:nodoc:
[regexps, blocks]
end
def filtering_parameters? #:nodoc:
@env["action_dispatch.parameter_filter"].present?
end
def compiled_parameter_filter_for(filters) #:nodoc:
@@compiled_parameter_filter_for[filters] ||= begin
regexps, blocks = compile_parameter_filter(filters)
def process_parameter_filter(original_params) #:nodoc:
return original_params.dup unless filtering_parameters?
lambda do |original_params|
filtered_params = {}
filtered_params = {}
regexps, blocks = compile_parameter_filter
original_params.each do |key, value|
if regexps.find { |r| key =~ r }
value = '[FILTERED]'
elsif value.is_a?(Hash)
value = process_parameter_filter(value)
elsif value.is_a?(Array)
value = value.map { |v| v.is_a?(Hash) ? process_parameter_filter(v) : v }
elsif blocks.present?
key = key.dup
value = value.dup if value.duplicable?
blocks.each { |b| b.call(key, value) }
end
original_params.each do |key, value|
if regexps.find { |r| key =~ r }
value = '[FILTERED]'
elsif value.is_a?(Hash)
value = process_parameter_filter(value)
elsif value.is_a?(Array)
value = value.map { |i| process_parameter_filter(i) }
elsif blocks.present?
key = key.dup
value = value.dup if value.duplicable?
blocks.each { |b| b.call(key, value) }
end
filtered_params[key] = value
end
filtered_params[key] = value
filtered_params
end
end
filtered_params
end
end
end
end
\ No newline at end of file
......@@ -67,21 +67,6 @@ def format=(extension)
@env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
end
# Returns a symbolized version of the <tt>:format</tt> parameter of the request.
# If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
# otherwise.
def template_format
parameter_format = parameters[:format]
if parameter_format
parameter_format
elsif xhr?
:js
else
:html
end
end
# Receives an array of mimes and return the first user sent mime that
# matches the order array.
#
......
......@@ -30,6 +30,14 @@ def #{env.sub(/^HTTP_/n, '').downcase}
METHOD
end
def self.new(env)
if request = env["action_dispatch.request"] && request.instance_of?(self)
return request
end
super
end
def key?(key)
@env.key?(key)
end
......@@ -119,36 +127,7 @@ def xml_http_request?
# delimited list in the case of multiple chained proxies; the last
# address which is not trusted is the originating IP.
def remote_ip
remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].scan(/[^,\s]+/)
unless remote_addr_list.blank?
not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES || addr =~ ActionController::Base.trusted_proxies}
return not_trusted_addrs.first unless not_trusted_addrs.empty?
end
remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
if @env.include? 'HTTP_CLIENT_IP'
if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
# We don't know which came from the proxy, and which from the user
raise ActionController::ActionControllerError.new <<EOM
IP spoofing attack?!
HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}
HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}
EOM
end
return @env['HTTP_CLIENT_IP']
end
if remote_ips
while remote_ips.size > 1 && (TRUSTED_PROXIES =~ remote_ips.last.strip || ActionController::Base.trusted_proxies =~ remote_ips.last.strip)
remote_ips.pop
end
return remote_ips.last.strip
end
@env['REMOTE_ADDR']
(@env["action_dispatch.remote_ip"] || ip).to_s
end
# Returns the lowercase name of the HTTP server software.
......
......@@ -32,31 +32,38 @@ module ActionDispatch # :nodoc:
# end
# end
class Response < Rack::Response
include ActionDispatch::Http::Cache::Response
attr_accessor :request, :blank
attr_writer :header, :sending_file
alias_method :headers=, :header=
def initialize
@status = 200
@header = {}
@cache_control = {}
module Setup
def initialize(status = 200, header = {}, body = [])
@writer = lambda { |x| @body << x }
@block = nil
@length = 0
@writer = lambda { |x| @body << x }
@block = nil
@length = 0
@status, @header = status, header
self.body = body
@body, @cookie = [], []
@sending_file = false
@cookie = []
@sending_file = false
@blank = false
@etag = nil
@blank = false
if content_type = self["Content-Type"]
type, charset = content_type.split(/;\s*charset=/)
@content_type = Mime::Type.lookup(type)
@charset = charset || "UTF-8"
end
yield self if block_given?
yield self if block_given?
end
end
include Setup
include ActionDispatch::Http::Cache::Response
def status=(status)
@status = Rack::Utils.status_code(status)
end
......@@ -76,6 +83,18 @@ def message
end
alias_method :status_message, :message
def respond_to?(method)
if method.to_sym == :to_path
@body.respond_to?(:to_path)
else
super
end
end
def to_path
@body.to_path
end
def body
str = ''
each { |part| str << part.to_s }
......@@ -120,7 +139,7 @@ def to_a
assign_default_content_type_and_charset!
handle_conditional_get!
self["Set-Cookie"] = @cookie.join("\n") unless @cookie.blank?
self["ETag"] = @etag if @etag
self["ETag"] = @_etag if @_etag
super
end
......
......@@ -3,7 +3,7 @@ module Http
module URL
# Returns the complete URL used for this request.
def url
protocol + host_with_port + request_uri
protocol + host_with_port + fullpath
end
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
......@@ -81,42 +81,15 @@ def subdomains(tld_length = 1)
parts[0..-(tld_length+2)]
end
# Returns the query string, accounting for server idiosyncrasies.
def query_string
@env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '')
def subdomain(tld_length = 1)
subdomains(tld_length).join('.')
end
# Returns the request URI, accounting for server idiosyncrasies.
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
def request_uri
if uri = @env['REQUEST_URI']
# Remove domain, which webrick puts into the request_uri.
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
else
# Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
uri = @env['PATH_INFO'].to_s
if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
uri = uri.sub(/#{script_filename}\//, '')
end
env_qs = @env['QUERY_STRING'].to_s
uri += "?#{env_qs}" unless env_qs.empty?
if uri.blank?
@env.delete('REQUEST_URI')
else
@env['REQUEST_URI'] = uri
end
end
end
# Returns the interpreted \path to requested resource after all the installation
# directory of this application was taken into account.
def path
path = request_uri.to_s[/\A[^\?]*/]
path.sub!(/\A#{ActionController::Base.relative_url_root}/, '')
path
ActiveSupport::Deprecation.warn "Using #request_uri is deprecated. Use fullpath instead.", caller
fullpath
end
private
......
......@@ -37,7 +37,7 @@ def self.after(*args, &block)
def initialize(app, prepare_each_request = false)
@app, @prepare_each_request = app, prepare_each_request
run_callbacks(:prepare) unless @prepare_each_request
run_callbacks(:prepare)
end
def call(env)
......
require 'active_support/json'
require 'action_dispatch/http/request'
module ActionDispatch
......
module ActionDispatch
class RemoteIp
class IpSpoofAttackError < StandardError ; end
class RemoteIpGetter
def initialize(env, check_ip_spoofing, trusted_proxies)
@env = env
@check_ip_spoofing = check_ip_spoofing
@trusted_proxies = trusted_proxies
end
def remote_addrs
@remote_addrs ||= begin
list = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : []
list.reject { |addr| addr =~ @trusted_proxies }
end
end
def to_s
return remote_addrs.first if remote_addrs.any?
forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : []
if client_ip = @env['HTTP_CLIENT_IP']
if @check_ip_spoofing && !forwarded_ips.include?(client_ip)
# We don't know which came from the proxy, and which from the user
raise IpSpoofAttackError, "IP spoofing attack?!" \
"HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
"HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
end
return client_ip
end
return forwarded_ips.reject { |ip| ip =~ @trusted_proxies }.last || @env["REMOTE_ADDR"]
end
end
def initialize(app, check_ip_spoofing = true, trusted_proxies = nil)
@app = app
@check_ip_spoofing = check_ip_spoofing
regex = '(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)'
regex << "|(#{trusted_proxies})" if trusted_proxies
@trusted_proxies = Regexp.new(regex, "i")
end
def call(env)
env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies)
@app.call(env)
end
end
end
\ No newline at end of file
require 'active_support/core_ext/hash/keys'
require 'rack/request'
module ActionDispatch
module Session
......@@ -177,9 +176,8 @@ def ensure_session_key(key)
if key.blank?
raise ArgumentError, 'A key is required to write a ' +
'cookie containing the session data. Use ' +
'config.action_controller.session = { :key => ' +
'"_myapp_session", :secret => "some secret phrase" } in ' +
'config/application.rb'
'config.action_controller.session_store :cookie_store, { :key => ' +
'"_myapp_session" } in config/application.rb'
end
end
......@@ -193,10 +191,9 @@ def ensure_secret_secure(secret)
if secret.blank?
raise ArgumentError, "A secret is required to generate an " +
"integrity hash for cookie session data. Use " +
"config.action_controller.session = { :key => " +
"\"_myapp_session\", :secret => \"some secret phrase of at " +
"least #{SECRET_MIN_LENGTH} characters\" } " +
"in config/environment.rb"
"config.cookie_secret = \"some secret phrase of at " +
"least #{SECRET_MIN_LENGTH} characters\"" +
"in config/application.rb"
end
if secret.length < SECRET_MIN_LENGTH
......
......@@ -58,7 +58,7 @@ def ==(middleware)
if lazy_compare?(@klass) && lazy_compare?(middleware)
normalize(@klass) == normalize(middleware)
else
klass == ActiveSupport::Inflector.constantize(middleware.to_s)
klass.name == middleware.to_s
end
end
end
......@@ -122,7 +122,11 @@ def active
find_all { |middleware| middleware.active? }
end
def build(app)
def build(app = nil, &blk)
app ||= blk
raise "MiddlewareStack#build requires an app" unless app
active.reverse.inject(app) { |a, e| e.build(a) }
end
end
......
......@@ -5,6 +5,9 @@ module ActionDispatch
class Railtie < Rails::Railtie
railtie_name :action_dispatch
config.action_dispatch.x_sendfile_header = "X-Sendfile"
config.action_dispatch.ip_spoofing_check = true
# Prepare dispatcher callbacks and run 'prepare' callbacks
initializer "action_dispatch.prepare_dispatcher" do |app|
# TODO: This used to say unless defined?(Dispatcher). Find out why and fix.
......
......@@ -205,6 +205,7 @@ module Routing
autoload :Mapper, 'action_dispatch/routing/mapper'
autoload :Route, 'action_dispatch/routing/route'
autoload :RouteSet, 'action_dispatch/routing/route_set'
autoload :UrlFor, 'action_dispatch/routing/url_for'
SEPARATORS = %w( / . ? )
HTTP_METHODS = [:get, :head, :post, :put, :delete, :options]
......
module ActionDispatch
module Routing
class RouteSet
attr_accessor :controller_namespaces
CONTROLLER_REGEXP = /[_a-zA-Z0-9]+/
def controller_constraints
@controller_constraints ||= begin
namespaces = controller_namespaces + in_memory_controller_namespaces
source = namespaces.map { |ns| "#{Regexp.escape(ns)}/#{CONTROLLER_REGEXP.source}" }
source << CONTROLLER_REGEXP.source
Regexp.compile(source.sort.reverse.join('|'))
end
end
def in_memory_controller_namespaces
namespaces = Set.new
ActionController::Base.subclasses.each do |klass|
controller_name = klass.underscore
namespaces << controller_name.split('/')[0...-1].join('/')
end
namespaces.delete('')
namespaces
end
end
# Mapper instances are used to build routes. The object passed to the draw
# block in config/routes.rb is a Mapper instance.
#
......@@ -244,14 +269,15 @@ class Resource #:nodoc:
attr_reader :collection_methods, :member_methods, :new_methods
attr_reader :path_prefix, :name_prefix, :path_segment
attr_reader :plural, :singular
attr_reader :options
attr_reader :options, :defaults
def initialize(entities, options)
def initialize(entities, options, defaults)
@plural ||= entities
@singular ||= options[:singular] || plural.to_s.singularize
@path_segment = options.delete(:as) || @plural
@options = options
@defaults = defaults
arrange_actions
add_default_actions
......@@ -280,7 +306,7 @@ def path
def new_path
new_action = self.options[:path_names][:new] if self.options[:path_names]
new_action ||= ActionController::Base.resources_path_names[:new]
new_action ||= self.defaults[:path_names][:new]
@new_path ||= "#{path}/#{new_action}"
end
......@@ -370,7 +396,7 @@ def add_default_action(collection, method, action)
end
class SingletonResource < Resource #:nodoc:
def initialize(entity, options)
def initialize(entity, options, defaults)
@singular = @plural = entity
options[:controller] ||= @singular.to_s.pluralize
super
......@@ -717,7 +743,7 @@ def resource(*entities, &block)
private
def map_resource(entities, options = {}, &block)
resource = Resource.new(entities, options)
resource = Resource.new(entities, options, :path_names => @set.resources_path_names)
with_options :controller => resource.controller do |map|
map_associations(resource, options)
......@@ -734,7 +760,7 @@ def map_resource(entities, options = {}, &block)
end
def map_singleton_resource(entities, options = {}, &block)
resource = SingletonResource.new(entities, options)
resource = SingletonResource.new(entities, options, :path_names => @set.resources_path_names)
with_options :controller => resource.controller do |map|
map_associations(resource, options)
......@@ -826,7 +852,7 @@ def map_member_actions(map, resource)
actions.each do |action|
[method].flatten.each do |m|
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
action_path ||= ActionController::Base.resources_path_names[action] || action
action_path ||= @set.resources_path_names[action] || action
map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
end
......
require 'active_support/core_ext/hash/except'
module ActionDispatch
module Routing
class Mapper
......@@ -36,7 +38,7 @@ def initialize(set, scope, args)
end
def to_route
[ app, conditions, requirements, defaults, @options[:as] ]
[ app, conditions, requirements, defaults, @options[:as], @options[:anchor] ]
end
private
......@@ -64,7 +66,7 @@ def using_to_shorthand?(args, options)
# match "account/overview"
def using_match_shorthand?(args, options)
args.present? && options.except(:via).empty? && !args.first.include?(':')
args.present? && options.except(:via, :anchor).empty? && !args.first.include?(':')
end
def normalize_path(path)
......@@ -85,10 +87,9 @@ def conditions
end
def requirements
@requirements ||= returning(@options[:constraints] || {}) do |requirements|
@requirements ||= (@options[:constraints] || {}).tap do |requirements|
requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
@options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) }
requirements[:controller] ||= @set.controller_constraints
end
end
......@@ -175,9 +176,15 @@ def root(options = {})
end
def match(*args)
@set.add_route(*Mapping.new(@set, @scope, args).to_route)
mapping = Mapping.new(@set, @scope, args).to_route
@set.add_route(*mapping)
self
end
def default_url_options=(options)
@set.default_url_options = options
end
alias_method :default_url_options, :default_url_options=
end
module HttpHelpers
......@@ -283,7 +290,7 @@ def controller(controller)
end
def namespace(path)
scope(path.to_s, :name_prefix => path.to_s, :namespace => path.to_s) { yield }
scope(path.to_s, :name_prefix => path.to_s, :controller_namespace => path.to_s) { yield }
end
def constraints(constraints = {})
......@@ -294,6 +301,7 @@ def match(*args)
options = args.extract_options!
options = (@scope[:options] || {}).merge(options)
options[:anchor] = true unless options.key?(:anchor)
if @scope[:name_prefix] && !options[:as].blank?
options[:as] = "#{@scope[:name_prefix]}_#{options[:as]}"
......@@ -318,12 +326,12 @@ def merge_name_prefix_scope(parent, child)
parent ? "#{parent}_#{child}" : child
end
def merge_namespace_scope(parent, child)
def merge_controller_namespace_scope(parent, child)
parent ? "#{parent}/#{child}" : child
end
def merge_controller_scope(parent, child)
@scope[:namespace] ? "#{@scope[:namespace]}/#{child}" : child
@scope[:controller_namespace] ? "#{@scope[:controller_namespace]}/#{child}" : child
end
def merge_resources_path_names_scope(parent, child)
......@@ -367,9 +375,9 @@ def default_actions
def actions
if only = options[:only]
only.map(&:to_sym)
Array(only).map(&:to_sym)
elsif except = options[:except]
default_actions - except.map(&:to_sym)
default_actions - Array(except).map(&:to_sym)
else
default_actions
end
......@@ -443,7 +451,7 @@ def initialize(*args)
def resource(*resources, &block)
options = resources.extract_options!
if verify_common_behavior_for(:resource, resources, options, &block)
if apply_common_behavior_for(:resource, resources, options, &block)
return self
end
......@@ -451,7 +459,10 @@ def resource(*resources, &block)
scope(:path => resource.name.to_s, :controller => resource.controller) do
with_scope_level(:resource, resource) do
yield if block_given?
scope(:name_prefix => resource.name.to_s) do
yield if block_given?
end
get :show if resource.actions.include?(:show)
post :create if resource.actions.include?(:create)
......@@ -468,7 +479,7 @@ def resource(*resources, &block)
def resources(*resources, &block)
options = resources.extract_options!
if verify_common_behavior_for(:resources, resources, options, &block)
if apply_common_behavior_for(:resources, resources, options, &block)
return self
end
......@@ -534,6 +545,21 @@ def nested
end
end
def mount(app, options = nil)
if options
path = options.delete(:at)
else
options = app
app, path = options.find { |k, v| k.respond_to?(:call) }
options.delete(app) if app
end
raise "A rack application must be specified" unless path
match(path, options.merge(:to => app, :anchor => false))
self
end
def match(*args)
options = args.extract_options!
......@@ -591,7 +617,7 @@ def action_path(name, path_names = nil)
path_names[name.to_sym] || name.to_s
end
def verify_common_behavior_for(method, resources, options, &block)
def apply_common_behavior_for(method, resources, options, &block)
if resources.length > 1
resources.each { |r| send(method, r, options, &block) }
return true
......
......@@ -4,7 +4,7 @@ class Route #:nodoc:
attr_reader :app, :conditions, :defaults, :name
attr_reader :path, :requirements
def initialize(app, conditions = {}, requirements = {}, defaults = {}, name = nil)
def initialize(app, conditions, requirements, defaults, name, anchor)
@app = app
@defaults = defaults
@name = name
......@@ -17,7 +17,7 @@ def initialize(app, conditions = {}, requirements = {}, defaults = {}, name = ni
if path = conditions[:path_info]
@path = path
conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS)
conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS, anchor)
end
@conditions = conditions.inject({}) { |h, (k, v)|
......
require 'rack/mount'
require 'forwardable'
require 'action_dispatch/routing/deprecated_mapper'
module ActionDispatch
module Routing
......@@ -11,8 +12,8 @@ class RouteSet #:nodoc:
PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
class Dispatcher
def initialize(options = {})
defaults = options[:defaults]
def initialize(options={})
@defaults = options[:defaults]
@glob_param = options.delete(:glob)
end
......@@ -20,7 +21,8 @@ def call(env)
params = env[PARAMETERS_KEY]
prepare_params!(params)
unless controller = controller(params)
# Just raise undefined constant errors if a controller was specified as default.
unless controller = controller(params, @defaults.key?(:controller))
return [404, {'X-Cascade' => 'pass'}, []]
end
......@@ -39,14 +41,13 @@ def prepare_params!(params)
end
end
def controller(params)
def controller(params, raise_error=true)
if params && params.has_key?(:controller)
controller = "#{params[:controller].camelize}Controller"
ActiveSupport::Inflector.constantize(controller)
end
rescue NameError => e
raise unless e.message.include?(controller)
nil
raise ActionController::RoutingError, e.message, e.backtrace if raise_error
end
private
......@@ -59,7 +60,6 @@ def split_glob_param!(params)
end
end
# A NamedRouteCollection instance is a collection of named routes, and also
# maintains an anonymous module that can be used to install helpers for the
# named routes.
......@@ -168,63 +168,25 @@ def define_url_helper(route, name, kind, options)
selector = url_helper_name(name, kind)
hash_access_method = hash_access_name(name, kind)
# We use module_eval to avoid leaks.
#
# def users_url(*args)
# if args.empty? || Hash === args.first
# options = hash_for_users_url(args.first || {})
# else
# options = hash_for_users_url(args.extract_options!)
# default = default_url_options(options) if self.respond_to?(:default_url_options, true)
# options = (default ||= {}).merge(options)
#
# keys = []
# keys -= options.keys if args.size < keys.size - 1
#
# args = args.zip(keys).inject({}) do |h, (v, k)|
# h[k] = v
# h
# end
#
# # Tell url_for to skip default_url_options
# options[:use_defaults] = false
# options.merge!(args)
# end
#
# url_for(options)
# end
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
def #{selector}(*args)
if args.empty? || Hash === args.first
options = #{hash_access_method}(args.first || {})
else
options = #{hash_access_method}(args.extract_options!)
default = default_url_options(options) if self.respond_to?(:default_url_options, true)
options = (default ||= {}).merge(options)
keys = #{route.segment_keys.inspect}
keys -= options.keys if args.size < keys.size - 1 # take format into account
args = args.zip(keys).inject({}) do |h, (v, k)|
h[k] = v
h
end
# Tell url_for to skip default_url_options
options[:use_defaults] = false
options.merge!(args)
options = #{hash_access_method}(args.extract_options!)
if args.any?
options[:_positional_args] = args
options[:_positional_keys] = #{route.segment_keys.inspect}
end
url_for(options)
end
protected :#{selector}
END_EVAL
helpers << selector
end
end
attr_accessor :routes, :named_routes, :controller_namespaces
attr_accessor :routes, :named_routes
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options
def self.default_resources_path_names
{ :new => 'new', :edit => 'edit' }
......@@ -235,8 +197,10 @@ def initialize
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names.dup
self.controller_namespaces = Set.new
self.default_url_options = {}
@disable_clear_and_finalize = false
clear!
end
def draw(&block)
......@@ -273,61 +237,155 @@ def install_helpers(destinations = [ActionController::Base, ActionView::Base], r
named_routes.install(destinations, regenerate_code)
end
def empty?
routes.empty?
end
def url_helpers
@url_helpers ||= begin
router = self
CONTROLLER_REGEXP = /[_a-zA-Z0-9]+/
Module.new do
extend ActiveSupport::Concern
include UrlFor
def controller_constraints
@controller_constraints ||= begin
namespaces = controller_namespaces + in_memory_controller_namespaces
source = namespaces.map { |ns| "#{Regexp.escape(ns)}/#{CONTROLLER_REGEXP.source}" }
source << CONTROLLER_REGEXP.source
Regexp.compile(source.sort.reverse.join('|'))
# ROUTES TODO: install_helpers isn't great... can we make a module with the stuff that
# we can include?
# Yes plz - JP
included do
router.install_helpers(self)
end
define_method(:_router) { router }
end
end
end
def in_memory_controller_namespaces
namespaces = Set.new
ActionController::Base.subclasses.each do |klass|
controller_name = klass.underscore
namespaces << controller_name.split('/')[0...-1].join('/')
end
namespaces.delete('')
namespaces
def empty?
routes.empty?
end
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil)
route = Route.new(app, conditions, requirements, defaults, name)
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
route = Route.new(app, conditions, requirements, defaults, name, anchor)
@set.add_route(*route)
named_routes[name] = route if name
routes << route
route
end
def options_as_params(options)
# If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like
#
# generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
#
# (the above is from the unit tests). In the above case, because the
# controller was explicitly given, but no action, the action is implied to
# be "index", not the recalled action of "show".
#
# great fun, eh?
options_as_params = options.clone
options_as_params[:action] ||= 'index' if options[:controller]
options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
options_as_params
end
class Generator
attr_reader :options, :recall, :set, :script_name, :named_route
def initialize(options, recall, set, extras = false)
@script_name = options.delete(:script_name)
@named_route = options.delete(:use_route)
@options = options.dup
@recall = recall.dup
@set = set
@extras = extras
normalize_options!
normalize_controller_action_id!
use_relative_controller!
controller.sub!(%r{^/}, '') if controller
handle_nil_action!
end
def controller
@controller ||= @options[:controller]
end
def current_controller
@recall[:controller]
end
def use_recall_for(key)
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
@options[key] = @recall.delete(key)
end
end
def normalize_options!
# If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like
#
# generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
#
# (the above is from the unit tests). In the above case, because the
# controller was explicitly given, but no action, the action is implied to
# be "index", not the recalled action of "show".
if options[:controller]
options[:action] ||= 'index'
options[:controller] = options[:controller].to_s
end
if options[:action]
options[:action] = options[:action].to_s
end
end
# This pulls :controller, :action, and :id out of the recall.
# The recall key is only used if there is no key in the options
# or if the key in the options is identical. If any of
# :controller, :action or :id is not found, don't pull any
# more keys from the recall.
def normalize_controller_action_id!
@recall[:action] ||= 'index' if current_controller
use_recall_for(:controller) or return
use_recall_for(:action) or return
use_recall_for(:id)
end
# if the current controller is "foo/bar/baz" and :controller => "baz/bat"
# is specified, the controller becomes "foo/baz/bat"
def use_relative_controller!
if !named_route && different_controller?
old_parts = current_controller.split('/')
size = controller.count("/") + 1
parts = old_parts[0...-size] << controller
@controller = @options[:controller] = parts.join("/")
end
end
# This handles the case of :action => nil being explicitly passed.
# It is identical to :action => "index"
def handle_nil_action!
if options.has_key?(:action) && options[:action].nil?
options[:action] = 'index'
end
recall[:action] = options.delete(:action) if options[:action] == 'index'
end
def build_expiry(options, recall)
recall.inject({}) do |expiry, (key, recalled_value)|
expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
expiry
def generate
error = ActionController::RoutingError.new("No route matches #{options.inspect}")
path, params = @set.generate(:path_info, named_route, options, recall, opts)
raise error unless path
params.reject! {|k,v| !v }
return [path, params.keys] if @extras
path << "?#{params.to_query}" if params.any?
"#{script_name}#{path}"
rescue Rack::Mount::RoutingError
raise error
end
def opts
parameterize = lambda do |name, value|
if name == :controller
value
elsif value.is_a?(Array)
value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/')
else
Rack::Mount::Utils.escape_uri(value.to_param)
end
end
{:parameterize => parameterize}
end
def different_controller?
return false unless current_controller
controller.to_param != current_controller.to_param
end
end
......@@ -338,82 +396,44 @@ def extra_keys(options, recall={})
end
def generate_extras(options, recall={})
generate(options, recall, :generate_extras)
generate(options, recall, true)
end
def generate(options, recall = {}, method = :generate)
options, recall = options.dup, recall.dup
named_route = options.delete(:use_route)
def generate(options, recall = {}, extras = false)
Generator.new(options, recall, @set, extras).generate
end
options = options_as_params(options)
expire_on = build_expiry(options, recall)
RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash]
recall[:action] ||= 'index' if options[:controller] || recall[:controller]
def url_for(options)
options = default_url_options.merge(options || {})
if recall[:controller] && (!options.has_key?(:controller) || options[:controller] == recall[:controller])
options[:controller] = recall.delete(:controller)
handle_positional_args(options)
if recall[:action] && (!options.has_key?(:action) || options[:action] == recall[:action])
options[:action] = recall.delete(:action)
rewritten_url = ""
if recall[:id] && (!options.has_key?(:id) || options[:id] == recall[:id])
options[:id] = recall.delete(:id)
end
end
end
options[:controller] = options[:controller].to_s if options[:controller]
path_segments = options.delete(:_path_segments)
if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
old_parts = recall[:controller].split('/')
new_parts = options[:controller].split('/')
parts = old_parts[0..-(new_parts.length + 1)] + new_parts
options[:controller] = parts.join('/')
end
unless options[:only_path]
rewritten_url << (options[:protocol] || "http")
rewritten_url << "://" unless rewritten_url.match("://")
rewritten_url << rewrite_authentication(options)
options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
merged = options.merge(recall)
if options.has_key?(:action) && options[:action].nil?
options.delete(:action)
recall[:action] = 'index'
rewritten_url << options[:host]
rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
end
recall[:action] = options.delete(:action) if options[:action] == 'index'
opts = {}
opts[:parameterize] = lambda { |name, value|
if name == :controller
value
elsif value.is_a?(Array)
value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/')
else
Rack::Mount::Utils.escape_uri(value.to_param)
end
}
unless result = @set.generate(:path_info, named_route, options, recall, opts)
raise ActionController::RoutingError, "No route matches #{options.inspect}"
end
path_options = options.except(*RESERVED_OPTIONS)
path_options = yield(path_options) if block_given?
path = generate(path_options, path_segments || {})
path, params = result
params.each do |k, v|
if v
params[k] = v
else
params.delete(k)
end
end
# ROUTES TODO: This can be called directly, so script_name should probably be set in the router
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
rewritten_url << "##{Rack::Utils.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
if path && method == :generate_extras
[path, params.keys]
elsif path
path << "?#{params.to_query}" if params.any?
path
else
raise ActionController::RoutingError, "No route matches #{options.inspect}"
end
rescue Rack::Mount::RoutingError
raise ActionController::RoutingError, "No route matches #{options.inspect}"
rewritten_url
end
def call(env)
......@@ -431,9 +451,9 @@ def recognize_path(path, environment = {})
end
req = Rack::Request.new(env)
@set.recognize(req) do |route, params|
@set.recognize(req) do |route, matches, params|
dispatcher = route.app
if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params)
if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params, false)
dispatcher.prepare_params!(params)
return params
end
......@@ -441,6 +461,30 @@ def recognize_path(path, environment = {})
raise ActionController::RoutingError, "No route matches #{path.inspect}"
end
private
def handle_positional_args(options)
return unless args = options.delete(:_positional_args)
keys = options.delete(:_positional_keys)
keys -= options.keys if args.size < keys.size - 1 # take format into account
args = args.zip(keys).inject({}) do |h, (v, k)|
h[k] = v
h
end
# Tell url_for to skip default_url_options
options.merge!(args)
end
def rewrite_authentication(options)
if options[:user] && options[:password]
"#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@"
else
""
end
end
end
end
end
module ActionDispatch
module Routing
# In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
# is also possible: an URL can be generated from one of your routing definitions.
# URL generation functionality is centralized in this module.
#
# See ActionDispatch::Routing and ActionController::Resources for general
# information about routing and routes.rb.
#
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
# then ActionController::UrlFor is what you're looking for. Read on for
# an introduction.
#
# == URL generation from parameters
#
# As you may know, some functions - such as ActionController::Base#url_for
# and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
# of parameters. For example, you've probably had the chance to write code
# like this in one of your views:
#
# <%= link_to('Click here', :controller => 'users',
# :action => 'new', :message => 'Welcome!') %>
#
# #=> Generates a link to: /users/new?message=Welcome%21
#
# link_to, and all other functions that require URL generation functionality,
# actually use ActionController::UrlFor under the hood. And in particular,
# they use the ActionController::UrlFor#url_for method. One can generate
# the same path as the above example by using the following code:
#
# include UrlFor
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
# :only_path => true)
# # => "/users/new?message=Welcome%21"
#
# Notice the <tt>:only_path => true</tt> part. This is because UrlFor has no
# information about the website hostname that your Rails app is serving. So if you
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
# argument:
#
# include UrlFor
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
# :host => 'www.example.com') # Changed this.
# # => "http://www.example.com/users/new?message=Welcome%21"
#
# By default, all controllers and views have access to a special version of url_for,
# that already knows what the current hostname is. So if you use url_for in your
# controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
# argument.
#
# For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for.
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for'
# in full. However, mailers don't have hostname information, and what's why you'll still
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
#
#
# == URL generation for named routes
#
# UrlFor also allows one to access methods that have been auto-generated from
# named routes. For example, suppose that you have a 'users' resource in your
# <b>routes.rb</b>:
#
# map.resources :users
#
# This generates, among other things, the method <tt>users_path</tt>. By default,
# this method is accessible from your controllers, views and mailers. If you need
# to access this auto-generated method from other places (such as a model), then
# you can do that by including ActionController::UrlFor in your class:
#
# class User < ActiveRecord::Base
# include ActionController::UrlFor
#
# def base_uri
# user_path(self)
# end
# end
#
# User.find(1).base_uri # => "/users/1"
#
module UrlFor
extend ActiveSupport::Concern
included do
# TODO: with_routing extends @controller with url_helpers, trickling down to including this module which overrides its default_url_options
unless method_defined?(:default_url_options)
# Including in a class uses an inheritable hash. Modules get a plain hash.
if respond_to?(:class_attribute)
class_attribute :default_url_options
else
mattr_accessor :default_url_options
remove_method :default_url_options
end
self.default_url_options = {}
end
end
def url_options
default_url_options
end
# Generate a url based on the options provided, default_url_options and the
# routes defined in routes.rb. The following options are supported:
#
# * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
# * <tt>:host</tt> - Specifies the host the link should be targeted at.
# If <tt>:only_path</tt> is false, this option must be
# provided either explicitly, or via +default_url_options+.
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
#
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
# +url_for+ is forwarded to the Routes module.
#
# Examples:
#
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
# url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
def url_for(options = nil)
case options
when String
options
when nil, Hash
_router.url_for(url_options.merge(options || {}))
else
polymorphic_url(options)
end
end
end
end
end
......@@ -132,16 +132,21 @@ def parameterize(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
case fragment
when %r{^\w[\w\d+.-]*:.*}
fragment
when String
if fragment =~ %r{^\w[\w\d+.-]*:.*}
fragment
else
@request.protocol + @request.host_with_port + fragment
end
@request.protocol + @request.host_with_port + after_routing
end
when :back
raise RedirectBackError unless refer = @request.headers["Referer"]
refer
else
@controller.url_for(fragment)
end.gsub(/[\r\n]/, '')
end
def validate_request!
......
......@@ -80,7 +80,7 @@ def assert_generates(expected_path, options, defaults={}, extras = {}, message=n
expected_path = "/#{expected_path}" unless expected_path[0] == ?/
# Load routes.rb if it hasn't been loaded.
generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, defaults)
generated_path, extra_keys = @router.generate_extras(options, defaults)
found_extras = options.reject {|k, v| ! extra_keys.include? k}
msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
......@@ -125,7 +125,7 @@ def assert_routing(path, options, defaults={}, extras={}, message=nil)
end
# A helper to make it easier to test different route configurations.
# This method temporarily replaces ActionController::Routing::Routes
# This method temporarily replaces @router
# with a new RouteSet instance.
#
# The new instance is yielded to the passed block. Typically the block
......@@ -142,22 +142,19 @@ def assert_routing(path, options, defaults={}, extras={}, message=nil)
# end
#
def with_routing
real_routes = ActionController::Routing::Routes
ActionController::Routing.module_eval { remove_const :Routes }
temporary_routes = ActionController::Routing::RouteSet.new
ActionController::Routing.module_eval { const_set :Routes, temporary_routes }
yield temporary_routes
old_routes, @router = @router, ActionDispatch::Routing::RouteSet.new
old_controller, @controller = @controller, @controller.clone if @controller
_router = @router
@controller.singleton_class.send(:send, :include, @router.url_helpers) if @controller
yield @router
ensure
if ActionController::Routing.const_defined? :Routes
ActionController::Routing.module_eval { remove_const :Routes }
end
ActionController::Routing.const_set(:Routes, real_routes) if real_routes
@router = old_routes
@controller = old_controller if @controller
end
# ROUTES TODO: These assertions should really work in an integration context
def method_missing(selector, *args, &block)
if @controller && ActionController::Routing::Routes.named_routes.helpers.include?(selector)
if @controller && @router.named_routes.helpers.include?(selector)
@controller.send(selector, *args, &block)
else
super
......@@ -174,7 +171,7 @@ def recognized_request_for(path, request_method = nil)
request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
request.path = path
params = ActionController::Routing::Routes.recognize_path(path, { :method => request.method })
params = @router.recognize_path(path, { :method => request.method })
request.path_parameters = params.with_indifferent_access
request
......
require 'stringio'
require 'uri'
require 'active_support/core_ext/object/metaclass'
require 'active_support/core_ext/object/singleton_class'
require 'rack/test'
module ActionDispatch
......@@ -162,12 +162,31 @@ def cookies
# A running counter of the number of requests processed.
attr_accessor :request_count
include ActionDispatch::Routing::UrlFor
# Create and initialize a new Session instance.
def initialize(app)
@app = app
# If the app is a Rails app, make url_helpers available on the session
# This makes app.url_for and app.foo_path available in the console
if app.respond_to?(:routes) && app.routes.respond_to?(:url_helpers)
singleton_class.class_eval { include app.routes.url_helpers }
end
reset!
end
def url_options
opts = super.reverse_merge(
:host => host,
:protocol => https? ? "https" : "http"
)
opts.merge!(:port => 443) if !opts.key?(:port) && https?
opts
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.
......@@ -187,12 +206,10 @@ def reset!
unless defined? @named_routes_configured
# install the named routes in this session instance.
klass = metaclass
ActionController::Routing::Routes.install_helpers(klass)
klass = singleton_class
# the helpers are made protected by default--we make them public for
# easier access during testing and troubleshooting.
klass.module_eval { public *ActionController::Routing::Routes.named_routes.helpers }
@named_routes_configured = true
end
end
......@@ -221,14 +238,6 @@ 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)
controller ?
controller.url_for(options) :
generic_url_rewriter.rewrite(options)
end
private
# Performs the actual request.
......@@ -273,26 +282,14 @@ def process(method, path, parameters = nil, rack_environment = nil)
@request_count += 1
@request = ActionDispatch::Request.new(session.last_request.env)
@response = ActionDispatch::TestResponse.from_response(@mock_session.last_response)
response = @mock_session.last_response
@response = ActionDispatch::TestResponse.new(response.status, response.headers, response.body)
@html_document = nil
@controller = session.last_request.env['action_controller.instance']
return response.status
end
# Get a temporary URL writer object
def generic_url_rewriter
env = {
'REQUEST_METHOD' => "GET",
'QUERY_STRING' => "",
"REQUEST_URI" => "/",
"HTTP_HOST" => host,
"SERVER_PORT" => https? ? "443" : "80",
"HTTPS" => https? ? "on" : "off"
}
ActionController::UrlRewriter.new(ActionDispatch::Request.new(env), {})
end
end
module Runner
......@@ -364,6 +361,14 @@ def copy_session_variables! #:nodoc:
end
end
extend ActiveSupport::Concern
include ActionDispatch::Routing::UrlFor
def url_options
reset! unless @integration_session
@integration_session.url_options
end
# Delegate unhandled messages to the current session instance.
def method_missing(sym, *args, &block)
reset! unless @integration_session
......
require 'action_dispatch/middleware/flash'
module ActionDispatch
module TestProcess
def assigns(key = nil)
......
module ActionPack #:nodoc:
module ActionPack
module VERSION #:nodoc:
MAJOR = 3
MINOR = 0
TINY = "0.beta1"
TINY = 0
BUILD = "beta1"
STRING = [MAJOR, MINOR, TINY].join('.')
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
end
......@@ -37,15 +37,18 @@ module ActionView
autoload :Helpers
autoload_under "render" do
autoload :Layouts
autoload :Partials
autoload :Rendering
end
autoload :MissingTemplate, 'action_view/base'
autoload :Resolver, 'action_view/template/resolver'
autoload :PathResolver, 'action_view/template/resolver'
autoload :PathSet, 'action_view/paths'
autoload :FileSystemResolverWithFallback, 'action_view/template/resolver'
autoload :Base
autoload :LookupContext
autoload :MissingTemplate, 'action_view/base'
autoload :Resolver, 'action_view/template/resolver'
autoload :PathResolver, 'action_view/template/resolver'
autoload :FileSystemResolver, 'action_view/template/resolver'
autoload :PathSet, 'action_view/paths'
autoload :TemplateError, 'action_view/template/error'
autoload :TemplateHandler, 'action_view/template'
......@@ -55,7 +58,7 @@ module ActionView
autoload :TestCase, 'action_view/test_case'
end
require 'active_support/i18n'
require 'active_support/core_ext/string/output_safety'
require 'action_view/base'
I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"
......@@ -11,7 +11,7 @@ class MissingTemplate < ActionViewError #:nodoc:
def initialize(paths, path, details, partial)
@path = path
display_paths = paths.compact.join(":")
display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ")
template_type = if partial
"partial"
elsif path =~ /layouts/i
......@@ -20,7 +20,7 @@ def initialize(paths, path, details, partial)
'template'
end
super("Missing #{template_type} #{path} with #{details.inspect} in view path #{display_paths}")
super("Missing #{template_type} #{path} with #{details.inspect} in view paths #{display_paths}")
end
end
......@@ -173,81 +173,43 @@ class Base
module Subclasses
end
include Helpers, Rendering, Partials, ::ERB::Util
include Helpers, Rendering, Partials, Layouts, ::ERB::Util, Context
extend ActiveSupport::Memoizable
def config
self.config = DEFAULT_CONFIG unless @config
@config
end
def config=(config)
@config = ActiveSupport::OrderedOptions.new.merge(config)
end
extend ActiveSupport::Memoizable
attr_accessor :base_path, :assigns, :template_extension, :formats
attr_internal :captures
ActionView.run_base_hooks(self)
def reset_formats(formats)
@formats = formats
if defined?(AbstractController::HashKey)
# This is expensive, but we need to reset this when the format is updated,
# which currently only happens
Thread.current[:format_locale_key] =
AbstractController::HashKey.get(self.class, formats, I18n.locale)
end
end
class << self
delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'
delegate :logger, :to => 'ActionController::Base', :allow_nil => true
end
@@debug_rjs = false
##
# :singleton-method:
# Specify whether RJS responses should be wrapped in a try/catch block
# that alert()s the caught exception (and then re-raises it).
cattr_accessor :debug_rjs
@@debug_rjs = false
# Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed.
# Automatically reloading templates are not thread safe and should only be used in development mode.
@@cache_template_loading = nil
cattr_accessor :cache_template_loading
# :nodoc:
def self.xss_safe?
true
end
class_attribute :helpers
attr_reader :helpers
def self.cache_template_loading?
ActionController::Base.allow_concurrency || (cache_template_loading.nil? ? !ActiveSupport::Dependencies.load? : cache_template_loading)
class << self
delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'
delegate :logger, :to => 'ActionController::Base', :allow_nil => true
end
attr_internal :request, :layout
attr_accessor :base_path, :assigns, :template_extension, :lookup_context
attr_internal :captures, :request, :layout, :controller, :template, :config
def controller_path
@controller_path ||= controller && controller.controller_path
end
delegate :find, :exists?, :formats, :formats=, :locale, :locale=,
:view_paths, :view_paths=, :with_fallbacks, :update_details, :to => :lookup_context
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
:flash, :action_name, :controller_name, :to => :controller
delegate :logger, :to => :controller, :allow_nil => true
delegate :find, :to => :view_paths
include Context
def self.xss_safe? #:nodoc:
true
end
def self.process_view_paths(value)
ActionView::PathSet.new(Array(value))
end
class_attribute :helpers
attr_reader :helpers
def self.for_controller(controller)
@views ||= {}
......@@ -275,26 +237,26 @@ def inspect
klass = self
end
klass.new(controller.class.view_paths, {}, controller)
klass.new(controller.lookup_context, {}, controller)
end
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil, formats = nil)#:nodoc:
def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc:
@config = nil
@formats = formats
@assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) }
@helpers = self.class.helpers || Module.new
@_controller = controller
@_content_for = Hash.new {|h,k| h[k] = ActiveSupport::SafeBuffer.new }
@_config = ActiveSupport::InheritableOptions.new(controller.config) if controller
@_content_for = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
@_virtual_path = nil
self.view_paths = view_paths
end
attr_internal :controller, :template
attr_reader :view_paths
@lookup_context = lookup_context.is_a?(ActionView::LookupContext) ?
lookup_context : ActionView::LookupContext.new(lookup_context)
@lookup_context.formats = formats if formats
end
def view_paths=(paths)
@view_paths = self.class.process_view_paths(paths)
def controller_path
@controller_path ||= controller && controller.controller_path
end
def punctuate_body!(part)
......
......@@ -10,6 +10,7 @@ module Helpers #:nodoc:
autoload :CsrfHelper, 'action_view/helpers/csrf_helper'
autoload :DateHelper, 'action_view/helpers/date_helper'
autoload :DebugHelper, 'action_view/helpers/debug_helper'
autoload :DeprecatedBlockHelpers, 'action_view/helpers/deprecated_block_helpers'
autoload :FormHelper, 'action_view/helpers/form_helper'
autoload :FormOptionsHelper, 'action_view/helpers/form_options_helper'
autoload :FormTagHelper, 'action_view/helpers/form_tag_helper'
......
......@@ -5,9 +5,11 @@
require 'active_support/core_ext/kernel/reporting'
module ActionView
class Base
@@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>".html_safe }
cattr_accessor :field_error_proc
ActionView.base_hook do
class ActionView::Base
@@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>".html_safe }
cattr_accessor :field_error_proc
end
end
module Helpers
......@@ -80,13 +82,13 @@ def form(record_name, options = {})
record = convert_to_model(record)
options = options.symbolize_keys
options[:action] ||= record.new_record? ? "create" : "update"
options[:action] ||= record.persisted? ? "update" : "create"
action = url_for(:action => options[:action], :id => record)
submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/, '').capitalize
contents = form_tag({:action => action}, :method =>(options[:method] || 'post'), :enctype => options[:multipart] ? 'multipart/form-data': nil)
contents.safe_concat hidden_field(record_name, :id) unless record.new_record?
contents.safe_concat hidden_field(record_name, :id) if record.persisted?
contents.safe_concat all_input_tags(record, record_name, options)
yield contents if block_given?
contents.safe_concat submit_tag(submit_value)
......@@ -127,7 +129,7 @@ def error_message_on(object, method, *args)
if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
(errors = obj.errors[method])
content_tag("div",
"#{options[:prepend_text]}#{ERB::Util.html_escape(errors.first)}#{options[:append_text]}",
(options[:prepend_text].html_safe << errors.first).safe_concat(options[:append_text]),
:class => options[:css_class]
)
else
......@@ -226,16 +228,16 @@ def error_messages_for(*params)
error_messages = objects.sum do |object|
object.errors.full_messages.map do |msg|
content_tag(:li, ERB::Util.html_escape(msg))
content_tag(:li, msg)
end
end.join
end.join.html_safe
contents = ''
contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
contents << content_tag(:p, message) unless message.blank?
contents << content_tag(:ul, error_messages)
content_tag(:div, contents, html)
content_tag(:div, contents.html_safe, html)
end
else
''
......
......@@ -11,7 +11,7 @@ module Helpers #:nodoc:
# the assets exist before linking to them:
#
# image_tag("rails.png")
# # => <img alt="Rails src="/images/rails.png?1230601161" />
# # => <img alt="Rails" src="/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
# # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
......@@ -133,13 +133,6 @@ module Helpers #:nodoc:
# change. You can use something like Live HTTP Headers for Firefox to verify
# that the cache is indeed working.
module AssetTagHelper
assets_dir = defined?(Rails.public_path) ? Rails.public_path : "public"
ActionView::DEFAULT_CONFIG = {
:assets_dir => assets_dir,
:javascripts_dir => "#{assets_dir}/javascripts",
:stylesheets_dir => "#{assets_dir}/stylesheets",
}
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls', 'rails'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
# Returns a link tag that browsers and news readers can use to auto-detect
......@@ -530,7 +523,7 @@ def image_tag(source, options = {})
options.symbolize_keys!
src = options[:src] = path_to_image(source)
options[:alt] ||= File.basename(src, '.*').split('.').first.to_s.capitalize
options[:alt] ||= File.basename(src, '.*').capitalize
if size = options.delete(:size)
options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
......@@ -648,8 +641,8 @@ def compute_public_path(source, dir, ext = nil, include_host = true)
source = rewrite_asset_path(source)
if has_request && include_host
unless source =~ %r{^#{ActionController::Base.relative_url_root}/}
source = "#{ActionController::Base.relative_url_root}#{source}"
unless source =~ %r{^#{controller.config.relative_url_root}/}
source = "#{controller.config.relative_url_root}#{source}"
end
end
end
......@@ -677,7 +670,7 @@ def is_uri?(path)
# or the value returned from invoking the proc if it's a proc or the value from
# invoking call if it's an object responding to call.
def compute_asset_host(source)
if host = ActionController::Base.asset_host
if host = config.asset_host
if host.is_a?(Proc) || host.respond_to?(:call)
case host.is_a?(Proc) ? host.arity : host.method(:call).arity
when 2
......
......@@ -8,7 +8,7 @@ module AtomFeedHelper
# Full usage example:
#
# config/routes.rb:
# ActionController::Routing::Routes.draw do |map|
# Basecamp::Application.routes.draw do |map|
# map.resources :posts
# map.root :controller => "posts"
# end
......@@ -114,7 +114,7 @@ def atom_feed(options = {}, &block)
feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)}
xml.feed(feed_opts) do
xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}")
xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}")
xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port))
xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url)
......
......@@ -30,14 +30,10 @@ module CaptureHelper
# <b><%= @greeting %></b>
# </body></html>
#
def capture(*args, &block)
# Return captured buffer in erb.
if block_called_from_erb?(block)
with_output_buffer { block.call(*args) }
else
# Return block result otherwise, but protect buffer also.
with_output_buffer { return block.call(*args) }
end
def capture(*args)
value = nil
buffer = with_output_buffer { value = yield *args }
buffer.presence || value
end
# Calling content_for stores a block of markup in an identifier for later use.
......@@ -143,7 +139,7 @@ def content_for?(name)
# Defaults to a new empty string.
def with_output_buffer(buf = nil) #:nodoc:
unless buf
buf = ActiveSupport::SafeBuffer.new
buf = ActionView::OutputBuffer.new
buf.force_encoding(output_buffer.encoding) if buf.respond_to?(:force_encoding)
end
self.output_buffer, old_buffer = buf, output_buffer
......
......@@ -97,7 +97,9 @@ module Helpers
# </select>
#
module FormOptionsHelper
# ERB::Util can mask some helpers like textilize. Make sure to include them.
include ERB::Util
include TextHelper
# Create a select tag and a series of contained option tags for the provided object and method.
# The option currently held by the object will be selected, provided that the object is available.
......@@ -572,10 +574,9 @@ def add_options(option_tags, options, value = nil)
end
if value.blank? && options[:prompt]
prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
"<option value=\"\">#{prompt}</option>\n" + option_tags
else
option_tags
option_tags = "<option value=\"\">#{prompt}</option>\n" + option_tags
end
option_tags.html_safe
end
end
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册