aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
authorJoshua Peek <josh@joshpeek.com>2009-01-22 15:13:47 -0600
committerJoshua Peek <josh@joshpeek.com>2009-01-22 15:13:47 -0600
commitcc0b5fa9930dcc60914e21b518b3c54109243cfa (patch)
tree3b5c65d8d0329388730542093314028630b0945a /actionpack/lib
parente57cb2629ac4971a5dcb1cf8bb2f6d0509317928 (diff)
parentccda96093a3bf3fb360f7c6d61bbbf341b2ae034 (diff)
downloadrails-cc0b5fa9930dcc60914e21b518b3c54109243cfa.tar.gz
rails-cc0b5fa9930dcc60914e21b518b3c54109243cfa.tar.bz2
rails-cc0b5fa9930dcc60914e21b518b3c54109243cfa.zip
Merge branch 'master' into 3-0-unstable
Conflicts: ci/cruise_config.rb
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/action_controller.rb17
-rw-r--r--actionpack/lib/action_controller/assertions/selector_assertions.rb24
-rw-r--r--actionpack/lib/action_controller/base.rb68
-rw-r--r--actionpack/lib/action_controller/dispatcher.rb14
-rw-r--r--actionpack/lib/action_controller/flash.rb12
-rw-r--r--actionpack/lib/action_controller/helpers.rb6
-rw-r--r--actionpack/lib/action_controller/integration.rb25
-rw-r--r--actionpack/lib/action_controller/lock.rb16
-rw-r--r--actionpack/lib/action_controller/middleware_stack.rb28
-rw-r--r--actionpack/lib/action_controller/middlewares.rb8
-rw-r--r--actionpack/lib/action_controller/mime_responds.rb23
-rw-r--r--actionpack/lib/action_controller/params_parser.rb71
-rw-r--r--actionpack/lib/action_controller/polymorphic_routes.rb18
-rw-r--r--actionpack/lib/action_controller/rack_ext.rb3
-rw-r--r--actionpack/lib/action_controller/rack_ext/lock.rb21
-rw-r--r--actionpack/lib/action_controller/rack_ext/multipart.rb22
-rw-r--r--actionpack/lib/action_controller/rack_ext/parse_query.rb18
-rwxr-xr-xactionpack/lib/action_controller/request.rb488
-rw-r--r--actionpack/lib/action_controller/request_profiler.rb168
-rw-r--r--actionpack/lib/action_controller/rescue.rb8
-rw-r--r--actionpack/lib/action_controller/response.rb9
-rw-r--r--actionpack/lib/action_controller/rewindable_input.rb28
-rw-r--r--actionpack/lib/action_controller/routing.rb5
-rw-r--r--actionpack/lib/action_controller/routing/route_set.rb67
-rw-r--r--actionpack/lib/action_controller/session/cookie_store.rb10
-rw-r--r--actionpack/lib/action_controller/session_management.rb2
-rw-r--r--actionpack/lib/action_controller/test_case.rb22
-rw-r--r--actionpack/lib/action_controller/test_process.rb121
-rw-r--r--actionpack/lib/action_controller/uploaded_file.rb44
-rw-r--r--actionpack/lib/action_controller/url_encoded_pair_parser.rb155
-rw-r--r--actionpack/lib/action_controller/verb_piggybacking.rb24
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_view.rb2
-rw-r--r--actionpack/lib/action_view/base.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb598
-rw-r--r--actionpack/lib/action_view/helpers/benchmark_helper.rb29
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb10
-rw-r--r--actionpack/lib/action_view/inline_template.rb2
-rw-r--r--actionpack/lib/action_view/paths.rb107
-rw-r--r--actionpack/lib/action_view/renderable.rb9
-rw-r--r--actionpack/lib/action_view/renderable_partial.rb7
-rw-r--r--actionpack/lib/action_view/template.rb87
-rw-r--r--actionpack/lib/action_view/template_handlers.rb6
-rw-r--r--actionpack/lib/action_view/test_case.rb3
45 files changed, 1055 insertions, 1364 deletions
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 3bb755376f..3e77970367 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2008 David Heinemeier Hansson
+# Copyright (c) 2004-2009 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -31,8 +31,9 @@ rescue LoadError
end
end
-gem 'rack', '~> 0.4.0'
+gem 'rack', '>= 0.9.0'
require 'rack'
+require 'action_controller/rack_ext'
module ActionController
# TODO: Review explicit to see if they will automatically be handled by
@@ -55,16 +56,17 @@ module ActionController
autoload :Integration, 'action_controller/integration'
autoload :IntegrationTest, 'action_controller/integration'
autoload :Layout, 'action_controller/layout'
- autoload :Lock, 'action_controller/lock'
autoload :MiddlewareStack, 'action_controller/middleware_stack'
autoload :MimeResponds, 'action_controller/mime_responds'
+ autoload :ParamsParser, 'action_controller/params_parser'
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
- autoload :Request, 'action_controller/request'
autoload :RecordIdentifier, 'action_controller/record_identifier'
- autoload :Response, 'action_controller/response'
+ autoload :Request, 'action_controller/request'
autoload :RequestForgeryProtection, 'action_controller/request_forgery_protection'
autoload :Rescue, 'action_controller/rescue'
autoload :Resources, 'action_controller/resources'
+ autoload :Response, 'action_controller/response'
+ autoload :RewindableInput, 'action_controller/rewindable_input'
autoload :Routing, 'action_controller/routing'
autoload :SessionManagement, 'action_controller/session_management'
autoload :StatusCodes, 'action_controller/status_codes'
@@ -72,9 +74,12 @@ module ActionController
autoload :TestCase, 'action_controller/test_case'
autoload :TestProcess, 'action_controller/test_process'
autoload :Translation, 'action_controller/translation'
+ autoload :UploadedFile, 'action_controller/uploaded_file'
+ autoload :UploadedStringIO, 'action_controller/uploaded_file'
+ autoload :UploadedTempfile, 'action_controller/uploaded_file'
+ autoload :UrlEncodedPairParser, 'action_controller/url_encoded_pair_parser'
autoload :UrlRewriter, 'action_controller/url_rewriter'
autoload :UrlWriter, 'action_controller/url_rewriter'
- autoload :VerbPiggybacking, 'action_controller/verb_piggybacking'
autoload :Verification, 'action_controller/verification'
module Assertions
diff --git a/actionpack/lib/action_controller/assertions/selector_assertions.rb b/actionpack/lib/action_controller/assertions/selector_assertions.rb
index 248ca85994..0d56ea5ef7 100644
--- a/actionpack/lib/action_controller/assertions/selector_assertions.rb
+++ b/actionpack/lib/action_controller/assertions/selector_assertions.rb
@@ -109,20 +109,27 @@ module ActionController
# starting from (and including) that element and all its children in
# depth-first order.
#
- # If no element if specified, calling +assert_select+ will select from the
- # response HTML. Calling #assert_select inside an +assert_select+ block will
- # run the assertion for each element selected by the enclosing assertion.
+ # If no element if specified, calling +assert_select+ selects from the
+ # response HTML unless +assert_select+ is called from within an +assert_select+ block.
+ #
+ # When called with a block +assert_select+ passes an array of selected elements
+ # to the block. Calling +assert_select+ from the block, with no element specified,
+ # runs the assertion on the complete set of elements selected by the enclosing assertion.
+ # Alternatively the array may be iterated through so that +assert_select+ can be called
+ # separately for each element.
+ #
#
# ==== Example
- # assert_select "ol>li" do |elements|
+ # If the response contains two ordered lists, each with four list elements then:
+ # assert_select "ol" do |elements|
# elements.each do |element|
- # assert_select element, "li"
+ # assert_select element, "li", 4
# end
# end
#
- # Or for short:
- # assert_select "ol>li" do
- # assert_select "li"
+ # will pass, as will:
+ # assert_select "ol" do
+ # assert_select "li", 8
# end
#
# The selector may be a CSS selector expression (String), an expression
@@ -402,6 +409,7 @@ module ActionController
if rjs_type
if rjs_type == :insert
position = args.shift
+ id = args.shift
insertion = "insert_#{position}".to_sym
raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion]
statement = "(#{RJS_STATEMENTS[insertion]})"
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 4d4793c4e3..50b965ce4c 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -301,10 +301,7 @@ module ActionController #:nodoc:
# A YAML parser is also available and can be turned on with:
#
# ActionController::Base.param_parsers[Mime::YAML] = :yaml
- @@param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
- Mime::URL_ENCODED_FORM => :url_encoded_form,
- Mime::XML => :xml_simple,
- Mime::JSON => :json }
+ @@param_parsers = {}
cattr_accessor :param_parsers
# Controls the default charset for all renders.
@@ -382,6 +379,13 @@ module ActionController #:nodoc:
attr_accessor :action_name
class << self
+ def call(env)
+ # HACK: For global rescue to have access to the original request and response
+ request = env["action_controller.rescue.request"] ||= Request.new(env)
+ response = env["action_controller.rescue.response"] ||= Response.new
+ process(request, response)
+ end
+
# Factory for the standard create, process loop where the controller is discarded after processing.
def process(request, response) #:nodoc:
new.process(request, response)
@@ -640,7 +644,7 @@ module ActionController #:nodoc:
end
def session_enabled?
- request.session_options && request.session_options[:disabled] != false
+ ActiveSupport::Deprecation.warn("Sessions are now lazy loaded. So if you don't access them, consider them disabled.", caller)
end
self.view_paths = []
@@ -859,16 +863,23 @@ module ActionController #:nodoc:
def render(options = nil, extra_options = {}, &block) #:doc:
raise DoubleRenderError, "Can only render or redirect once per action" if performed?
+ validate_render_arguments(options, extra_options, block_given?)
+
if options.nil?
- return render(:file => default_template, :layout => true)
- elsif !extra_options.is_a?(Hash)
- raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}"
- else
- if options == :update
- options = extra_options.merge({ :update => true })
- elsif !options.is_a?(Hash)
- raise RenderError, "You called render with invalid options : #{options.inspect}"
+ options = { :template => default_template, :layout => true }
+ elsif options == :update
+ options = extra_options.merge({ :update => true })
+ elsif options.is_a?(String) || options.is_a?(Symbol)
+ case options.to_s.index('/')
+ when 0
+ extra_options[:file] = options
+ when nil
+ extra_options[:action] = options
+ else
+ extra_options[:template] = options
end
+
+ options = extra_options
end
layout = pick_layout(options)
@@ -1111,7 +1122,7 @@ module ActionController #:nodoc:
end
# Sets the etag, last_modified, or both on the response and renders a
- # "304 Not Modified" response if the request is already fresh.
+ # "304 Not Modified" response if the request is already fresh.
#
# Example:
#
@@ -1119,8 +1130,8 @@ module ActionController #:nodoc:
# @article = Article.find(params[:id])
# fresh_when(:etag => @article, :last_modified => @article.created_at.utc)
# end
- #
- # This will render the show template if the request isn't sending a matching etag or
+ #
+ # This will render the show template if the request isn't sending a matching etag or
# If-Modified-Since header and just a "304 Not Modified" response if there's a match.
def fresh_when(options)
options.assert_valid_keys(:etag, :last_modified)
@@ -1186,6 +1197,16 @@ module ActionController #:nodoc:
end
end
+ def validate_render_arguments(options, extra_options, has_block)
+ if options && (has_block && options != :update) && !options.is_a?(String) && !options.is_a?(Hash) && !options.is_a?(Symbol)
+ raise RenderError, "You called render with invalid options : #{options.inspect}"
+ end
+
+ if !extra_options.is_a?(Hash)
+ raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}"
+ end
+ end
+
def initialize_template_class(response)
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
response.template.helpers.send :include, self.class.master_helper_module
@@ -1215,7 +1236,7 @@ module ActionController #:nodoc:
log_processing_for_parameters
end
end
-
+
def log_processing_for_request_id
request_id = "\n\nProcessing #{self.class.name}\##{action_name} "
request_id << "to #{params[:format]} " if params[:format]
@@ -1227,7 +1248,7 @@ module ActionController #:nodoc:
def log_processing_for_parameters
parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
parameters = parameters.except!(:controller, :action, :format, :_method)
-
+
logger.info " Parameters: #{parameters.inspect}" unless parameters.empty?
end
@@ -1326,9 +1347,12 @@ module ActionController #:nodoc:
end
Base.class_eval do
- include Flash, Filters, Layout, Benchmarking, Rescue, MimeResponds, Helpers
- include Cookies, Caching, Verification, Streaming
- include SessionManagement, HttpAuthentication::Basic::ControllerMethods
- include RecordIdentifier, RequestForgeryProtection, Translation
+ [ Filters, Layout, Benchmarking, Rescue, Flash, MimeResponds, Helpers,
+ Cookies, Caching, Verification, Streaming, SessionManagement,
+ HttpAuthentication::Basic::ControllerMethods, RecordIdentifier,
+ RequestForgeryProtection, Translation
+ ].each do |mod|
+ include mod
+ end
end
end
diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb
index 4dc76e1b49..781bc48887 100644
--- a/actionpack/lib/action_controller/dispatcher.rb
+++ b/actionpack/lib/action_controller/dispatcher.rb
@@ -8,6 +8,8 @@ module ActionController
# Development mode callbacks
before_dispatch :reload_application
after_dispatch :cleanup_application
+
+ ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
end
if defined?(ActiveRecord)
@@ -60,11 +62,10 @@ module ActionController
def dispatch
begin
run_callbacks :before_dispatch
- controller = Routing::Routes.recognize(@request)
- controller.process(@request, @response).to_a
+ Routing::Routes.call(@env)
rescue Exception => exception
if controller ||= (::ApplicationController rescue Base)
- controller.process_with_exception(@request, @response, exception).to_a
+ controller.call_with_exception(@env, exception).to_a
else
raise exception
end
@@ -83,8 +84,7 @@ module ActionController
end
def _call(env)
- @request = Request.new(env)
- @response = Response.new
+ @env = env
dispatch
end
@@ -93,7 +93,6 @@ module ActionController
run_callbacks :prepare_dispatch
Routing::Routes.reload
- ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear
end
# Cleanup the application by clearing out loaded classes so they can
@@ -110,8 +109,7 @@ module ActionController
def checkin_connections
# Don't return connection (and peform implicit rollback) if this request is a part of integration test
- # TODO: This callback should have direct access to env
- return if @request.key?("rack.test")
+ return if @env.key?("rack.test")
ActiveRecord::Base.clear_active_connections!
end
end
diff --git a/actionpack/lib/action_controller/flash.rb b/actionpack/lib/action_controller/flash.rb
index 9856dbed2a..56ee9c67e2 100644
--- a/actionpack/lib/action_controller/flash.rb
+++ b/actionpack/lib/action_controller/flash.rb
@@ -4,20 +4,22 @@ module ActionController #:nodoc:
# action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
# then expose the flash to its template. Actually, that exposure is automatically done. Example:
#
- # class WeblogController < ActionController::Base
+ # class PostsController < ActionController::Base
# def create
# # save post
# flash[:notice] = "Successfully created post"
- # redirect_to :action => "display", :params => { :id => post.id }
+ # redirect_to posts_path(@post)
# end
#
- # def display
+ # def show
# # doesn't need to assign the flash notice to the template, that's done automatically
# end
# end
#
- # display.erb
- # <% if flash[:notice] %><div class="notice"><%= flash[:notice] %></div><% end %>
+ # show.html.erb
+ # <% if flash[:notice] %>
+ # <div class="notice"><%= flash[:notice] %></div>
+ # <% end %>
#
# This example just places a string in the flash, but you can put any object in there. And of course, you can put as
# many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
diff --git a/actionpack/lib/action_controller/helpers.rb b/actionpack/lib/action_controller/helpers.rb
index 402750c57d..ba65032f6a 100644
--- a/actionpack/lib/action_controller/helpers.rb
+++ b/actionpack/lib/action_controller/helpers.rb
@@ -163,9 +163,9 @@ module ActionController #:nodoc:
def helper_method(*methods)
methods.flatten.each do |method|
master_helper_module.module_eval <<-end_eval
- def #{method}(*args, &block)
- controller.send(%(#{method}), *args, &block)
- end
+ def #{method}(*args, &block) # def current_user(*args, &block)
+ controller.send(%(#{method}), *args, &block) # controller.send(%(current_user), *args, &block)
+ end # end
end_eval
end
end
diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb
index 71e2524e81..163ba84a3e 100644
--- a/actionpack/lib/action_controller/integration.rb
+++ b/actionpack/lib/action_controller/integration.rb
@@ -2,17 +2,6 @@ require 'stringio'
require 'uri'
require 'active_support/test_case'
-# Monkey patch Rack::Lint to support rewind
-module Rack
- class Lint
- class InputWrapper
- def rewind
- @input.rewind
- end
- end
- end
-end
-
module ActionController
module Integration #:nodoc:
# An integration Session instance represents a set of requests and responses
@@ -72,8 +61,8 @@ module ActionController
end
# Create and initialize a new Session instance.
- def initialize(app)
- @application = app
+ def initialize(app = nil)
+ @application = app || ActionController::Dispatcher.new
reset!
end
@@ -311,8 +300,10 @@ module ActionController
env[key] = value
end
- unless ActionController::Base.respond_to?(:clear_last_instantiation!)
- ActionController::Base.module_eval { include ControllerCapture }
+ [ControllerCapture, ActionController::ProcessWithTest].each do |mod|
+ unless ActionController::Base < mod
+ ActionController::Base.class_eval { include mod }
+ end
end
ActionController::Base.clear_last_instantiation!
@@ -340,6 +331,7 @@ module ActionController
if @controller = ActionController::Base.last_instantiation
@request = @controller.request
@response = @controller.response
+ @controller.send(:set_test_assigns)
else
# Decorate responses from Rack Middleware and Rails Metal
# as an Response for the purposes of integration testing
@@ -428,7 +420,7 @@ module ActionController
def multipart_body(params, boundary)
multipart_requestify(params).map do |key, value|
if value.respond_to?(:original_filename)
- File.open(value.path) do |f|
+ File.open(value.path, "rb") do |f|
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
<<-EOF
@@ -509,7 +501,6 @@ EOF
# can use this method to open multiple sessions that ought to be tested
# simultaneously.
def open_session(application = nil)
- application ||= ActionController::Dispatcher.new
session = Integration::Session.new(application)
# delegate the fixture accessors back to the test instance
diff --git a/actionpack/lib/action_controller/lock.rb b/actionpack/lib/action_controller/lock.rb
deleted file mode 100644
index c50762216e..0000000000
--- a/actionpack/lib/action_controller/lock.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module ActionController
- class Lock
- FLAG = 'rack.multithread'.freeze
-
- def initialize(app, lock = Mutex.new)
- @app, @lock = app, lock
- end
-
- def call(env)
- old, env[FLAG] = env[FLAG], false
- @lock.synchronize { @app.call(env) }
- ensure
- env[FLAG] = old
- end
- end
-end
diff --git a/actionpack/lib/action_controller/middleware_stack.rb b/actionpack/lib/action_controller/middleware_stack.rb
index 74f28565c0..dbc2fda41e 100644
--- a/actionpack/lib/action_controller/middleware_stack.rb
+++ b/actionpack/lib/action_controller/middleware_stack.rb
@@ -1,6 +1,14 @@
module ActionController
class MiddlewareStack < Array
class Middleware
+ def self.new(klass, *args, &block)
+ if klass.is_a?(self)
+ klass
+ else
+ super
+ end
+ end
+
attr_reader :args, :block
def initialize(klass, *args, &block)
@@ -24,6 +32,8 @@ module ActionController
else
@klass.to_s.constantize
end
+ rescue NameError
+ @klass
end
def active?
@@ -65,6 +75,24 @@ module ActionController
block.call(self) if block_given?
end
+ def insert(index, *args, &block)
+ index = self.index(index) unless index.is_a?(Integer)
+ middleware = Middleware.new(*args, &block)
+ super(index, middleware)
+ end
+
+ alias_method :insert_before, :insert
+
+ def insert_after(index, *args, &block)
+ index = self.index(index) unless index.is_a?(Integer)
+ insert(index + 1, *args, &block)
+ end
+
+ def swap(target, *args, &block)
+ insert_before(target, *args, &block)
+ delete(target)
+ end
+
def use(*args, &block)
middleware = Middleware.new(*args, &block)
push(middleware)
diff --git a/actionpack/lib/action_controller/middlewares.rb b/actionpack/lib/action_controller/middlewares.rb
index 793739723f..f9cfc2b18e 100644
--- a/actionpack/lib/action_controller/middlewares.rb
+++ b/actionpack/lib/action_controller/middlewares.rb
@@ -1,11 +1,9 @@
-use "ActionController::Lock", :if => lambda {
+use "Rack::Lock", :if => lambda {
!ActionController::Base.allow_concurrency
}
use "ActionController::Failsafe"
-use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) }
-
["ActionController::Session::CookieStore",
"ActionController::Session::MemCacheStore",
"ActiveRecord::SessionStore"].each do |store|
@@ -18,4 +16,6 @@ use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) }
)
end
-use ActionController::VerbPiggybacking
+use "ActionController::RewindableInput"
+use "ActionController::ParamsParser"
+use "Rack::MethodOverride"
diff --git a/actionpack/lib/action_controller/mime_responds.rb b/actionpack/lib/action_controller/mime_responds.rb
index 29294476f7..b755363873 100644
--- a/actionpack/lib/action_controller/mime_responds.rb
+++ b/actionpack/lib/action_controller/mime_responds.rb
@@ -143,12 +143,27 @@ module ActionController #:nodoc:
custom(@mime_type_priority.first, &block)
end
end
+
+ def self.generate_method_for_mime(mime)
+ sym = mime.is_a?(Symbol) ? mime : mime.to_sym
+ const = sym.to_s.upcase
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{sym}(&block) # def html(&block)
+ custom(Mime::#{const}, &block) # custom(Mime::HTML, &block)
+ end # end
+ RUBY
+ end
- def method_missing(symbol, &block)
- mime_constant = symbol.to_s.upcase
+ Mime::SET.each do |mime|
+ generate_method_for_mime(mime)
+ end
- if Mime::SET.include?(Mime.const_get(mime_constant))
- custom(Mime.const_get(mime_constant), &block)
+ def method_missing(symbol, &block)
+ mime_constant = Mime.const_get(symbol.to_s.upcase)
+
+ if Mime::SET.include?(mime_constant)
+ self.class.generate_method_for_mime(mime_constant)
+ send(symbol, &block)
else
super
end
diff --git a/actionpack/lib/action_controller/params_parser.rb b/actionpack/lib/action_controller/params_parser.rb
new file mode 100644
index 0000000000..d269fe07fa
--- /dev/null
+++ b/actionpack/lib/action_controller/params_parser.rb
@@ -0,0 +1,71 @@
+module ActionController
+ class ParamsParser
+ ActionController::Base.param_parsers[Mime::XML] = :xml_simple
+ ActionController::Base.param_parsers[Mime::JSON] = :json
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ if params = parse_formatted_parameters(env)
+ env["action_controller.request.request_parameters"] = params
+ end
+
+ @app.call(env)
+ end
+
+ private
+ def parse_formatted_parameters(env)
+ request = Request.new(env)
+
+ return false if request.content_length.zero?
+
+ mime_type = content_type_from_legacy_post_data_format_header(env) || request.content_type
+ strategy = ActionController::Base.param_parsers[mime_type]
+
+ return false unless strategy
+
+ case strategy
+ when Proc
+ strategy.call(request.raw_post)
+ when :xml_simple, :xml_node
+ body = request.raw_post
+ body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
+ when :yaml
+ YAML.load(request.raw_post)
+ when :json
+ body = request.raw_post
+ if body.blank?
+ {}
+ else
+ data = ActiveSupport::JSON.decode(body)
+ data = {:_json => data} unless data.is_a?(Hash)
+ data.with_indifferent_access
+ end
+ else
+ false
+ end
+ rescue Exception => e # YAML, XML or Ruby code block errors
+ raise
+ { "body" => request.raw_post,
+ "content_type" => request.content_type,
+ "content_length" => request.content_length,
+ "exception" => "#{e.message} (#{e.class})",
+ "backtrace" => e.backtrace }
+ end
+
+ def content_type_from_legacy_post_data_format_header(env)
+ if x_post_format = env['HTTP_X_POST_DATA_FORMAT']
+ case x_post_format.to_s.downcase
+ when 'yaml'
+ return Mime::YAML
+ when 'xml'
+ return Mime::XML
+ end
+ end
+
+ nil
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb
index dce50c6c3b..924d1aa6bd 100644
--- a/actionpack/lib/action_controller/polymorphic_routes.rb
+++ b/actionpack/lib/action_controller/polymorphic_routes.rb
@@ -118,13 +118,17 @@ module ActionController
%w(edit new).each do |action|
module_eval <<-EOT, __FILE__, __LINE__
- def #{action}_polymorphic_url(record_or_hash, options = {})
- polymorphic_url(record_or_hash, options.merge(:action => "#{action}"))
- end
-
- def #{action}_polymorphic_path(record_or_hash, options = {})
- polymorphic_url(record_or_hash, options.merge(:action => "#{action}", :routing_type => :path))
- end
+ def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
+ polymorphic_url( # polymorphic_url(
+ record_or_hash, # record_or_hash,
+ options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
+ end # end
+ #
+ def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
+ polymorphic_url( # polymorphic_url(
+ record_or_hash, # record_or_hash,
+ options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
+ end # end
EOT
end
diff --git a/actionpack/lib/action_controller/rack_ext.rb b/actionpack/lib/action_controller/rack_ext.rb
new file mode 100644
index 0000000000..2ba6654e3d
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext.rb
@@ -0,0 +1,3 @@
+require 'action_controller/rack_ext/lock'
+require 'action_controller/rack_ext/multipart'
+require 'action_controller/rack_ext/parse_query'
diff --git a/actionpack/lib/action_controller/rack_ext/lock.rb b/actionpack/lib/action_controller/rack_ext/lock.rb
new file mode 100644
index 0000000000..9bf1889065
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext/lock.rb
@@ -0,0 +1,21 @@
+module Rack
+ # Rack::Lock was commited to Rack core
+ # http://github.com/rack/rack/commit/7409b0c
+ # Remove this when Rack 1.0 is released
+ unless defined? Lock
+ class Lock
+ FLAG = 'rack.multithread'.freeze
+
+ def initialize(app, lock = Mutex.new)
+ @app, @lock = app, lock
+ end
+
+ def call(env)
+ old, env[FLAG] = env[FLAG], false
+ @lock.synchronize { @app.call(env) }
+ ensure
+ env[FLAG] = old
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/rack_ext/multipart.rb b/actionpack/lib/action_controller/rack_ext/multipart.rb
new file mode 100644
index 0000000000..3b142307e9
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext/multipart.rb
@@ -0,0 +1,22 @@
+module Rack
+ module Utils
+ module Multipart
+ class << self
+ def parse_multipart_with_rewind(env)
+ result = parse_multipart_without_rewind(env)
+
+ begin
+ env['rack.input'].rewind if env['rack.input'].respond_to?(:rewind)
+ rescue Errno::ESPIPE
+ # Handles exceptions raised by input streams that cannot be rewound
+ # such as when using plain CGI under Apache
+ end
+
+ result
+ end
+
+ alias_method_chain :parse_multipart, :rewind
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/rack_ext/parse_query.rb b/actionpack/lib/action_controller/rack_ext/parse_query.rb
new file mode 100644
index 0000000000..2f21a57770
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext/parse_query.rb
@@ -0,0 +1,18 @@
+# Rack does not automatically cleanup Safari 2 AJAX POST body
+# This has not yet been commited to Rack, please +1 this ticket:
+# http://rack.lighthouseapp.com/projects/22435/tickets/19
+
+module Rack
+ module Utils
+ alias_method :parse_query_without_ajax_body_cleanup, :parse_query
+ module_function :parse_query_without_ajax_body_cleanup
+
+ def parse_query(qs, d = '&;')
+ qs = qs.dup
+ qs.chop! if qs[-1] == 0
+ qs.gsub!(/&_=$/, '')
+ parse_query_without_ajax_body_cleanup(qs, d)
+ end
+ module_function :parse_query
+ end
+end
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
index d9eb5af849..09dcd684e8 100755
--- a/actionpack/lib/action_controller/request.rb
+++ b/actionpack/lib/action_controller/request.rb
@@ -6,24 +6,12 @@ require 'active_support/memoizable'
require 'action_controller/cgi_ext'
module ActionController
- # CgiRequest and TestRequest provide concrete implementations.
- class Request
+ class Request < Rack::Request
extend ActiveSupport::Memoizable
- class SessionFixationAttempt < StandardError #:nodoc:
- end
-
- # The hash of environment variables for this request,
- # such as { 'RAILS_ENV' => 'production' }.
- attr_reader :env
-
- def initialize(env)
- @env = env
- end
-
- %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
+ %w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
- REMOTE_IDENT REMOTE_USER SCRIPT_NAME
+ REMOTE_IDENT REMOTE_USER REMOTE_ADDR
SERVER_NAME SERVER_PROTOCOL
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
@@ -41,17 +29,18 @@ module ActionController
HTTP_METHODS = %w(get head put post delete options)
HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
- # The true HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
- # UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
+ # Returns the true HTTP request \method as a lowercase symbol, such as
+ # <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS
+ # constant above, an UnknownHttpMethod exception is raised.
def request_method
- method = @env['REQUEST_METHOD']
- HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
+ HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
end
memoize :request_method
- # The HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
- # Note, HEAD is returned as <tt>:get</tt> since the two are functionally
- # equivalent from the application's perspective.
+ # Returns the HTTP request \method used for action processing as a
+ # lowercase symbol, such as <tt>:post</tt>. (Unlike #request_method, this
+ # method returns <tt>:get</tt> for a HEAD request because the two are
+ # functionally equivalent from the application's perspective.)
def method
request_method == :head ? :get : request_method
end
@@ -92,16 +81,19 @@ module ActionController
# Returns the content length of the request as an integer.
def content_length
- @env['CONTENT_LENGTH'].to_i
+ super.to_i
end
- memoize :content_length
# The MIME type of the HTTP request, such as Mime::XML.
#
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_type
- Mime::Type.lookup(content_type_without_parameters)
+ if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
+ Mime::Type.lookup($1.strip.downcase)
+ else
+ nil
+ end
end
memoize :content_type
@@ -286,7 +278,7 @@ EOM
if forwarded = env["HTTP_X_FORWARDED_HOST"]
forwarded.split(/,\s?/).last
else
- env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
+ env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
end
end
@@ -389,20 +381,21 @@ EOM
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
- unless env.include? 'RAW_POST_DATA'
- env['RAW_POST_DATA'] = body.read(content_length)
+ unless @env.include? 'RAW_POST_DATA'
+ @env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i)
body.rewind if body.respond_to?(:rewind)
end
- env['RAW_POST_DATA']
+ @env['RAW_POST_DATA']
end
# Returns both GET and POST \parameters in a single hash.
def parameters
@parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
end
+ alias_method :params, :parameters
def path_parameters=(parameters) #:nodoc:
- @path_parameters = parameters
+ @env["rack.routing_args"] = parameters
@symbolized_path_parameters = @parameters = nil
end
@@ -418,51 +411,46 @@ EOM
#
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
def path_parameters
- @path_parameters ||= {}
+ @env["rack.routing_args"] ||= {}
end
# The request body is an IO input stream. If the RAW_POST_DATA environment
# variable is already set, wrap it in a StringIO.
def body
- if raw_post = env['RAW_POST_DATA']
+ if raw_post = @env['RAW_POST_DATA']
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
StringIO.new(raw_post)
else
- body_stream
+ @env['rack.input']
end
end
- def remote_addr
- @env['REMOTE_ADDR']
+ def form_data?
+ FORM_DATA_MEDIA_TYPES.include?(content_type.to_s)
end
- def referrer
- @env['HTTP_REFERER']
+ # Override Rack's GET method to support nested query strings
+ def GET
+ @env["action_controller.request.query_parameters"] ||= UrlEncodedPairParser.parse_query_parameters(query_string)
end
- alias referer referrer
+ alias_method :query_parameters, :GET
- def query_parameters
- @query_parameters ||= self.class.parse_query_parameters(query_string)
- end
-
- def request_parameters
- @request_parameters ||= parse_formatted_request_parameters
+ # Override Rack's POST method to support nested query strings
+ def POST
+ @env["action_controller.request.request_parameters"] ||= UrlEncodedPairParser.parse_hash_parameters(super)
end
+ alias_method :request_parameters, :POST
def body_stream #:nodoc:
@env['rack.input']
end
- def cookies
- Rack::Request.new(@env).cookies
- end
-
def session
@env['rack.session'] ||= {}
end
def session=(session) #:nodoc:
- @session = session
+ @env['rack.session'] = session
end
def reset_session
@@ -481,411 +469,9 @@ EOM
@env['SERVER_PORT'].to_i
end
- protected
- # The raw content type string. Use when you need parameters such as
- # charset or boundary which aren't included in the content_type MIME type.
- # Overridden by the X-POST_DATA_FORMAT header for backward compatibility.
- def content_type_with_parameters
- content_type_from_legacy_post_data_format_header ||
- env['CONTENT_TYPE'].to_s
- end
-
- # The raw content type string with its parameters stripped off.
- def content_type_without_parameters
- self.class.extract_content_type_without_parameters(content_type_with_parameters)
- end
- memoize :content_type_without_parameters
-
private
- def content_type_from_legacy_post_data_format_header
- if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
- case x_post_format.to_s.downcase
- when 'yaml'; 'application/x-yaml'
- when 'xml'; 'application/xml'
- end
- end
- end
-
- def parse_formatted_request_parameters
- return {} if content_length.zero?
-
- content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters)
-
- # Don't parse params for unknown requests.
- return {} if content_type.blank?
-
- mime_type = Mime::Type.lookup(content_type)
- strategy = ActionController::Base.param_parsers[mime_type]
-
- # Only multipart form parsing expects a stream.
- body = (strategy && strategy != :multipart_form) ? raw_post : self.body
-
- case strategy
- when Proc
- strategy.call(body)
- when :url_encoded_form
- self.class.clean_up_ajax_request_body! body
- self.class.parse_query_parameters(body)
- when :multipart_form
- self.class.parse_multipart_form_parameters(body, boundary, content_length, env)
- when :xml_simple, :xml_node
- body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
- when :yaml
- YAML.load(body)
- when :json
- if body.blank?
- {}
- else
- data = ActiveSupport::JSON.decode(body)
- data = {:_json => data} unless data.is_a?(Hash)
- data.with_indifferent_access
- end
- else
- {}
- end
- rescue Exception => e # YAML, XML or Ruby code block errors
- raise
- { "body" => body,
- "content_type" => content_type_with_parameters,
- "content_length" => content_length,
- "exception" => "#{e.message} (#{e.class})",
- "backtrace" => e.backtrace }
- end
-
def named_host?(host)
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
end
-
- class << self
- def parse_query_parameters(query_string)
- return {} if query_string.blank?
-
- pairs = query_string.split('&').collect do |chunk|
- next if chunk.empty?
- key, value = chunk.split('=', 2)
- next if key.empty?
- value = value.nil? ? nil : CGI.unescape(value)
- [ CGI.unescape(key), value ]
- end.compact
-
- UrlEncodedPairParser.new(pairs).result
- end
-
- def parse_request_parameters(params)
- parser = UrlEncodedPairParser.new
-
- params = params.dup
- until params.empty?
- for key, value in params
- if key.blank?
- params.delete key
- elsif !key.include?('[')
- # much faster to test for the most common case first (GET)
- # and avoid the call to build_deep_hash
- parser.result[key] = get_typed_value(value[0])
- params.delete key
- elsif value.is_a?(Array)
- parser.parse(key, get_typed_value(value.shift))
- params.delete key if value.empty?
- else
- raise TypeError, "Expected array, found #{value.inspect}"
- end
- end
- end
-
- parser.result
- end
-
- def parse_multipart_form_parameters(body, boundary, body_size, env)
- parse_request_parameters(read_multipart(body, boundary, body_size, env))
- end
-
- def extract_multipart_boundary(content_type_with_parameters)
- if content_type_with_parameters =~ MULTIPART_BOUNDARY
- ['multipart/form-data', $1.dup]
- else
- extract_content_type_without_parameters(content_type_with_parameters)
- end
- end
-
- def extract_content_type_without_parameters(content_type_with_parameters)
- $1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/
- end
-
- def clean_up_ajax_request_body!(body)
- body.chop! if body[-1] == 0
- body.gsub!(/&_=$/, '')
- end
-
-
- private
- def get_typed_value(value)
- case value
- when String
- value
- when NilClass
- ''
- when Array
- value.map { |v| get_typed_value(v) }
- else
- if value.respond_to? :original_filename
- # Uploaded file
- if value.original_filename
- value
- # Multipart param
- else
- result = value.read
- value.rewind
- result
- end
- # Unknown value, neither string nor multipart.
- else
- raise "Unknown form value: #{value.inspect}"
- end
- end
- end
-
- MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
-
- EOL = "\015\012"
-
- def read_multipart(body, boundary, body_size, env)
- params = Hash.new([])
- boundary = "--" + boundary
- quoted_boundary = Regexp.quote(boundary)
- buf = ""
- bufsize = 10 * 1024
- boundary_end=""
-
- # start multipart/form-data
- body.binmode if defined? body.binmode
- case body
- when File
- body.set_encoding(Encoding::BINARY) if body.respond_to?(:set_encoding)
- when StringIO
- body.string.force_encoding(Encoding::BINARY) if body.string.respond_to?(:force_encoding)
- end
- boundary_size = boundary.size + EOL.size
- body_size -= boundary_size
- status = body.read(boundary_size)
- if nil == status
- raise EOFError, "no content body"
- elsif boundary + EOL != status
- raise EOFError, "bad content body"
- end
-
- loop do
- head = nil
- content =
- if 10240 < body_size
- UploadedTempfile.new("CGI")
- else
- UploadedStringIO.new
- end
- content.binmode if defined? content.binmode
-
- until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
-
- if (not head) and /#{EOL}#{EOL}/n.match(buf)
- buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
- head = $1.dup
- ""
- end
- next
- end
-
- if head and ( (EOL + boundary + EOL).size < buf.size )
- content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
- buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
- end
-
- c = if bufsize < body_size
- body.read(bufsize)
- else
- body.read(body_size)
- end
- if c.nil? || c.empty?
- raise EOFError, "bad content body"
- end
- buf.concat(c)
- body_size -= c.size
- end
-
- buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
- content.print $1
- if "--" == $2
- body_size = -1
- end
- boundary_end = $2.dup
- ""
- end
-
- content.rewind
-
- head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni
- if filename = $1 || $2
- if /Mac/ni.match(env['HTTP_USER_AGENT']) and
- /Mozilla/ni.match(env['HTTP_USER_AGENT']) and
- (not /MSIE/ni.match(env['HTTP_USER_AGENT']))
- filename = CGI.unescape(filename)
- end
- content.original_path = filename.dup
- end
-
- head =~ /Content-Type: ([^\r]*)/ni
- content.content_type = $1.dup if $1
-
- head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni
- name = $1.dup if $1
-
- if params.has_key?(name)
- params[name].push(content)
- else
- params[name] = [content]
- end
- break if body_size == -1
- end
- raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
-
- begin
- body.rewind if body.respond_to?(:rewind)
- rescue Errno::ESPIPE
- # Handles exceptions raised by input streams that cannot be rewound
- # such as when using plain CGI under Apache
- end
-
- params
- end
- end
- end
-
- class UrlEncodedPairParser < StringScanner #:nodoc:
- attr_reader :top, :parent, :result
-
- def initialize(pairs = [])
- super('')
- @result = {}
- pairs.each { |key, value| parse(key, value) }
- end
-
- KEY_REGEXP = %r{([^\[\]=&]+)}
- BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
-
- # Parse the query string
- def parse(key, value)
- self.string = key
- @top, @parent = result, nil
-
- # First scan the bare key
- key = scan(KEY_REGEXP) or return
- key = post_key_check(key)
-
- # Then scan as many nestings as present
- until eos?
- r = scan(BRACKETED_KEY_REGEXP) or return
- key = self[1]
- key = post_key_check(key)
- end
-
- bind(key, value)
- end
-
- private
- # After we see a key, we must look ahead to determine our next action. Cases:
- #
- # [] follows the key. Then the value must be an array.
- # = follows the key. (A value comes next)
- # & or the end of string follows the key. Then the key is a flag.
- # otherwise, a hash follows the key.
- def post_key_check(key)
- if scan(/\[\]/) # a[b][] indicates that b is an array
- container(key, Array)
- nil
- elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
- container(key, Hash)
- nil
- else # End of key? We do nothing.
- key
- end
- end
-
- # Add a container to the stack.
- def container(key, klass)
- type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
- value = bind(key, klass.new)
- type_conflict! klass, value unless value.is_a?(klass)
- push(value)
- end
-
- # Push a value onto the 'stack', which is actually only the top 2 items.
- def push(value)
- @parent, @top = @top, value
- end
-
- # Bind a key (which may be nil for items in an array) to the provided value.
- def bind(key, value)
- if top.is_a? Array
- if key
- if top[-1].is_a?(Hash) && ! top[-1].key?(key)
- top[-1][key] = value
- else
- top << {key => value}.with_indifferent_access
- push top.last
- value = top[key]
- end
- else
- top << value
- end
- elsif top.is_a? Hash
- key = CGI.unescape(key)
- parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
- top[key] ||= value
- return top[key]
- else
- raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
- end
-
- return value
- end
-
- def type_conflict!(klass, value)
- raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
- end
- end
-
- module UploadedFile
- def self.included(base)
- base.class_eval do
- attr_accessor :original_path, :content_type
- alias_method :local_path, :path
- end
- end
-
- # Take the basename of the upload's original filename.
- # This handles the full Windows paths given by Internet Explorer
- # (and perhaps other broken user agents) without affecting
- # those which give the lone filename.
- # The Windows regexp is adapted from Perl's File::Basename.
- def original_filename
- unless defined? @original_filename
- @original_filename =
- unless original_path.blank?
- if original_path =~ /^(?:.*[:\\\/])?(.*)/m
- $1
- else
- File.basename original_path
- end
- end
- end
- @original_filename
- end
- end
-
- class UploadedStringIO < StringIO
- include UploadedFile
- end
-
- class UploadedTempfile < Tempfile
- include UploadedFile
end
end
diff --git a/actionpack/lib/action_controller/request_profiler.rb b/actionpack/lib/action_controller/request_profiler.rb
deleted file mode 100644
index 80cd55334f..0000000000
--- a/actionpack/lib/action_controller/request_profiler.rb
+++ /dev/null
@@ -1,168 +0,0 @@
-require 'optparse'
-
-module ActionController
- class RequestProfiler
- # Wrap up the integration session runner.
- class Sandbox
- include Integration::Runner
-
- def self.benchmark(n, script)
- new(script).benchmark(n)
- end
-
- def initialize(script_path)
- @quiet = false
- define_run_method(script_path)
- reset!
- end
-
- def benchmark(n, profiling = false)
- @quiet = true
- print ' '
-
- ms = Benchmark.ms do
- n.times do |i|
- run(profiling)
- print_progress(i)
- end
- end
-
- puts
- ms
- ensure
- @quiet = false
- end
-
- def say(message)
- puts " #{message}" unless @quiet
- end
-
- private
- def define_run_method(script_path)
- script = File.read(script_path)
-
- source = <<-end_source
- def run(profiling = false)
- if profiling
- RubyProf.resume do
- #{script}
- end
- else
- #{script}
- end
-
- old_request_count = request_count
- reset!
- self.request_count = old_request_count
- end
- end_source
-
- instance_eval source, script_path, 1
- end
-
- def print_progress(i)
- print "\n " if i % 60 == 0
- print ' ' if i % 10 == 0
- print '.'
- $stdout.flush
- end
- end
-
-
- attr_reader :options
-
- def initialize(options = {})
- @options = default_options.merge(options)
- end
-
-
- def self.run(args = nil, options = {})
- profiler = new(options)
- profiler.parse_options(args) if args
- profiler.run
- end
-
- def run
- sandbox = Sandbox.new(options[:script])
-
- puts 'Warming up once'
-
- elapsed = warmup(sandbox)
- puts '%.0f ms, %d requests, %d req/sec' % [elapsed, sandbox.request_count, 1000 * sandbox.request_count / elapsed]
- puts "\n#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x"
-
- options[:benchmark] ? benchmark(sandbox) : profile(sandbox)
- end
-
- def profile(sandbox)
- load_ruby_prof
-
- benchmark(sandbox, true)
- results = RubyProf.stop
-
- show_profile_results results
- results
- end
-
- def benchmark(sandbox, profiling = false)
- sandbox.request_count = 0
- elapsed = sandbox.benchmark(options[:n], profiling)
- count = sandbox.request_count.to_i
- puts '%.0f ms, %d requests, %d req/sec' % [elapsed, count, 1000 * count / elapsed]
- end
-
- def warmup(sandbox)
- Benchmark.ms { sandbox.run(false) }
- end
-
- def default_options
- { :n => 100, :open => 'open %s &' }
- end
-
- # Parse command-line options
- def parse_options(args)
- OptionParser.new do |opt|
- opt.banner = "USAGE: #{$0} [options] [session script path]"
-
- opt.on('-n', '--times [100]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i if v }
- opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v }
- opt.on('-m', '--measure [mode]', 'Which ruby-prof measure mode to use: process_time, wall_time, cpu_time, allocations, or memory. Defaults to process_time.') { |v| options[:measure] = v }
- opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v }
- opt.on('-h', '--help', 'Show this help') { puts opt; exit }
-
- opt.parse args
-
- if args.empty?
- puts opt
- exit
- end
- options[:script] = args.pop
- end
- end
-
- protected
- def load_ruby_prof
- begin
- gem 'ruby-prof', '>= 0.6.1'
- require 'ruby-prof'
- if mode = options[:measure]
- RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
- end
- rescue LoadError
- abort '`gem install ruby-prof` to use the profiler'
- end
- end
-
- def show_profile_results(results)
- File.open "#{RAILS_ROOT}/tmp/profile-graph.html", 'w' do |file|
- RubyProf::GraphHtmlPrinter.new(results).print(file)
- `#{options[:open] % file.path}` if options[:open]
- end
-
- File.open "#{RAILS_ROOT}/tmp/profile-flat.txt", 'w' do |file|
- RubyProf::FlatPrinter.new(results).print(file)
- `#{options[:open] % file.path}` if options[:open]
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb
index 5ef79a36ce..4b7d1e32fd 100644
--- a/actionpack/lib/action_controller/rescue.rb
+++ b/actionpack/lib/action_controller/rescue.rb
@@ -38,8 +38,8 @@ module ActionController #:nodoc:
'ActionView::TemplateError' => 'template_error'
}
- RESCUES_TEMPLATE_PATH = ActionView::PathSet::Path.new(
- File.join(File.dirname(__FILE__), "templates"), true)
+ RESCUES_TEMPLATE_PATH = ActionView::Template::EagerPath.new(
+ File.join(File.dirname(__FILE__), "templates"))
def self.included(base) #:nodoc:
base.cattr_accessor :rescue_responses
@@ -59,7 +59,9 @@ module ActionController #:nodoc:
end
module ClassMethods
- def process_with_exception(request, response, exception) #:nodoc:
+ def call_with_exception(env, exception) #:nodoc:
+ request = env["action_controller.rescue.request"] ||= Request.new(env)
+ response = env["action_controller.rescue.response"] ||= Response.new
new.process(request, response, :rescue_action, exception)
end
end
diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb
index 64319fe102..27860a6207 100644
--- a/actionpack/lib/action_controller/response.rb
+++ b/actionpack/lib/action_controller/response.rb
@@ -231,10 +231,13 @@ module ActionController # :nodoc:
# Don't set the Content-Length for block-based bodies as that would mean
# reading it all into memory. Not nice for, say, a 2GB streaming file.
def set_content_length!
- unless body.respond_to?(:call) || (status && status.to_s[0..2] == '304')
- self.headers["Content-Length"] ||= body.size
+ if status && status.to_s[0..2] == '204'
+ headers.delete('Content-Length')
+ elsif length = headers['Content-Length']
+ headers['Content-Length'] = length.to_s
+ elsif !body.respond_to?(:call) && (!status || status.to_s[0..2] != '304')
+ headers["Content-Length"] = body.size.to_s
end
- headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"]
end
def convert_language!
diff --git a/actionpack/lib/action_controller/rewindable_input.rb b/actionpack/lib/action_controller/rewindable_input.rb
new file mode 100644
index 0000000000..36f655c51e
--- /dev/null
+++ b/actionpack/lib/action_controller/rewindable_input.rb
@@ -0,0 +1,28 @@
+module ActionController
+ class RewindableInput
+ class RewindableIO < ActiveSupport::BasicObject
+ def initialize(io)
+ @io = io
+ @rewindable = io.is_a?(StringIO)
+ end
+
+ def method_missing(method, *args, &block)
+ unless @rewindable
+ @io = StringIO.new(@io.read)
+ @rewindable = true
+ end
+
+ @io.__send__(method, *args, &block)
+ end
+ end
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ env['rack.input'] = RewindableIO.new(env['rack.input'])
+ @app.call(env)
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb
index da9b56fdf9..a2141a77dc 100644
--- a/actionpack/lib/action_controller/routing.rb
+++ b/actionpack/lib/action_controller/routing.rb
@@ -193,9 +193,8 @@ module ActionController
#
# map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
#
- # will glob all remaining parts of the route that were not recognized earlier. This idiom
- # must appear at the end of the path. The globbed values are in <tt>params[:path]</tt> in
- # this case.
+ # will glob all remaining parts of the route that were not recognized earlier.
+ # The globbed values are in <tt>params[:path]</tt> as an array of path segments.
#
# == Route conditions
#
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb
index 13646aef61..044ace7de1 100644
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ b/actionpack/lib/action_controller/routing/route_set.rb
@@ -145,10 +145,10 @@ module ActionController
def define_hash_access(route, name, kind, options)
selector = hash_access_name(name, kind)
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
- def #{selector}(options = nil)
- options ? #{options.inspect}.merge(options) : #{options.inspect}
- end
- protected :#{selector}
+ def #{selector}(options = nil) # def hash_for_users_url(options = nil)
+ options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false}
+ end # end
+ protected :#{selector} # protected :hash_for_users_url
end_eval
helpers << selector
end
@@ -173,32 +173,33 @@ module ActionController
# foo_url(bar, baz, bang, :sort_by => 'baz')
#
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
- def #{selector}(*args)
-
- #{generate_optimisation_block(route, kind)}
-
- opts = if args.empty? || Hash === args.first
- args.first || {}
- else
- options = args.extract_options!
- args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
- h[k] = v
- h
- end
- options.merge(args)
- end
-
- url_for(#{hash_access_method}(opts))
-
- end
- #Add an alias to support the now deprecated formatted_* URL.
- def formatted_#{selector}(*args)
- ActiveSupport::Deprecation.warn(
- "formatted_#{selector}() has been deprecated. please pass format to the standard" +
- "#{selector}() method instead.", caller)
- #{selector}(*args)
- end
- protected :#{selector}
+ def #{selector}(*args) # def users_url(*args)
+ #
+ #{generate_optimisation_block(route, kind)} # #{generate_optimisation_block(route, kind)}
+ #
+ opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
+ args.first || {} # args.first || {}
+ else # else
+ options = args.extract_options! # options = args.extract_options!
+ args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)|
+ h[k] = v # h[k] = v
+ h # h
+ end # end
+ options.merge(args) # options.merge(args)
+ end # end
+ #
+ url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts))
+ #
+ end # end
+ #Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL.
+ def formatted_#{selector}(*args) # def formatted_users_url(*args)
+ ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn(
+ "formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " +
+ "Please pass format to the standard " + # "Please pass format to the standard " +
+ "#{selector} method instead.", caller) # "users_url method instead.", caller)
+ #{selector}(*args) # users_url(*args)
+ end # end
+ protected :#{selector} # protected :users_url
end_eval
helpers << selector
end
@@ -426,6 +427,12 @@ module ActionController
end
end
+ def call(env)
+ request = Request.new(env)
+ app = Routing::Routes.recognize(request)
+ app.call(env).to_a
+ end
+
def recognize(request)
params = recognize_path(request.path, extract_request_environment(request))
request.path_parameters = params.with_indifferent_access
diff --git a/actionpack/lib/action_controller/session/cookie_store.rb b/actionpack/lib/action_controller/session/cookie_store.rb
index 135bedaf50..6ad6369950 100644
--- a/actionpack/lib/action_controller/session/cookie_store.rb
+++ b/actionpack/lib/action_controller/session/cookie_store.rb
@@ -45,7 +45,7 @@ module ActionController
:domain => nil,
:path => "/",
:expire_after => nil,
- :httponly => false
+ :httponly => true
}.freeze
ENV_SESSION_KEY = "rack.session".freeze
@@ -56,8 +56,6 @@ module ActionController
class CookieOverflow < StandardError; end
def initialize(app, options = {})
- options = options.dup
-
# Process legacy CGI options
options = options.symbolize_keys
if options.has_key?(:session_path)
@@ -163,9 +161,9 @@ module ActionController
def ensure_session_key(key)
if key.blank?
- raise ArgumentError, 'A session_key is required to write a ' +
+ raise ArgumentError, 'A key is required to write a ' +
'cookie containing the session data. Use ' +
- 'config.action_controller.session = { :session_key => ' +
+ 'config.action_controller.session = { :key => ' +
'"_myapp_session", :secret => "some secret phrase" } in ' +
'config/environment.rb'
end
@@ -181,7 +179,7 @@ module ActionController
if secret.blank?
raise ArgumentError, "A secret is required to generate an " +
"integrity hash for cookie session data. Use " +
- "config.action_controller.session = { :session_key => " +
+ "config.action_controller.session = { :key => " +
"\"_myapp_session\", :secret => \"some secret phrase of at " +
"least #{SECRET_MIN_LENGTH} characters\" } " +
"in config/environment.rb"
diff --git a/actionpack/lib/action_controller/session_management.rb b/actionpack/lib/action_controller/session_management.rb
index f06a0da75c..b556f04649 100644
--- a/actionpack/lib/action_controller/session_management.rb
+++ b/actionpack/lib/action_controller/session_management.rb
@@ -37,7 +37,7 @@ module ActionController #:nodoc:
# Returns the hash used to configure the session. Example use:
#
- # ActionController::Base.session_options[:session_secure] = true # session only available over HTTPS
+ # ActionController::Base.session_options[:secure] = true # session only available over HTTPS
def session_options
@session_options ||= {}
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 7ed1a3e160..0b0d0c799b 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -1,4 +1,5 @@
require 'active_support/test_case'
+require 'action_controller/test_process'
module ActionController
# Superclass for ActionController functional tests. Functional tests allow you to
@@ -102,6 +103,8 @@ module ActionController
#
# assert_redirected_to page_url(:title => 'foo')
class TestCase < ActiveSupport::TestCase
+ include TestProcess
+
module Assertions
%w(response selector tag dom routing model).each do |kind|
include ActionController::Assertions.const_get("#{kind.camelize}Assertions")
@@ -124,17 +127,18 @@ module ActionController
#
# The exception is stored in the exception accessor for further inspection.
module RaiseActionExceptions
- attr_accessor :exception
+ protected
+ attr_accessor :exception
- def rescue_action_without_handler(e)
- self.exception = e
-
- if request.remote_addr == "0.0.0.0"
- raise(e)
- else
- super(e)
+ def rescue_action_without_handler(e)
+ self.exception = e
+
+ if request.remote_addr == "0.0.0.0"
+ raise(e)
+ else
+ super(e)
+ end
end
- end
end
setup :setup_controller_request_and_response
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
index 211e22ff58..22b97fc157 100644
--- a/actionpack/lib/action_controller/test_process.rb
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -1,46 +1,17 @@
-require 'action_controller/test_case'
-
module ActionController #:nodoc:
- class Base
- attr_reader :assigns
-
- # Process a test request called with a TestRequest object.
- def self.process_test(request)
- new.process_test(request)
- end
-
- def process_test(request) #:nodoc:
- process(request, TestResponse.new)
- end
-
- def process_with_test(*args)
- returning process_without_test(*args) do
- @assigns = {}
- (instance_variable_names - @@protected_instance_variables).each do |var|
- value = instance_variable_get(var)
- @assigns[var[1..-1]] = value
- response.template.assigns[var[1..-1]] = value if response
- end
- end
- end
-
- alias_method_chain :process, :test
- end
-
class TestRequest < Request #:nodoc:
attr_accessor :cookies, :session_options
- attr_accessor :query_parameters, :request_parameters, :path, :session
- attr_accessor :host, :user_agent
+ attr_accessor :query_parameters, :path, :session
+ attr_accessor :host
def initialize
- super(Rack::MockRequest.env_for('/'))
+ super(Rack::MockRequest.env_for("/"))
@query_parameters = {}
- @request_parameters = {}
@session = TestSession.new
- initialize_containers
initialize_default_values
+ initialize_containers
end
def reset_session
@@ -55,7 +26,11 @@ module ActionController #:nodoc:
# Either the RAW_POST_DATA environment variable or the URL-encoded request
# parameters.
def raw_post
- env['RAW_POST_DATA'] ||= returning(url_encoded_request_parameters) { |b| b.force_encoding(Encoding::BINARY) if b.respond_to?(:force_encoding) }
+ @env['RAW_POST_DATA'] ||= begin
+ data = url_encoded_request_parameters
+ data.force_encoding(Encoding::BINARY) if data.respond_to?(:force_encoding)
+ data
+ end
end
def port=(number)
@@ -125,26 +100,30 @@ module ActionController #:nodoc:
path_parameters[key.to_s] = value
end
end
+ raw_post # populate env['RAW_POST_DATA']
@parameters = nil # reset TestRequest#parameters to use the new path_parameters
end
def recycle!
- self.request_parameters = {}
self.query_parameters = {}
self.path_parameters = {}
unmemoize_all
end
+ def user_agent=(user_agent)
+ @env['HTTP_USER_AGENT'] = user_agent
+ end
+
private
def initialize_containers
- @env, @cookies = {}, {}
+ @cookies = {}
end
def initialize_default_values
@host = "test.host"
@request_uri = "/"
- @user_agent = "Rails Testing"
- self.remote_addr = "0.0.0.0"
+ @env['HTTP_USER_AGENT'] = "Rails Testing"
+ @env['REMOTE_ADDR'] = "0.0.0.0"
@env["SERVER_PORT"] = 80
@env['REQUEST_METHOD'] = "GET"
end
@@ -377,20 +356,33 @@ module ActionController #:nodoc:
module TestProcess
def self.included(base)
- # execute the request simulating a specific HTTP method and set/volley the response
- # TODO: this should be un-DRY'ed for the sake of API documentation.
- %w( get post put delete head ).each do |method|
- base.class_eval <<-EOV, __FILE__, __LINE__
- def #{method}(action, parameters = nil, session = nil, flash = nil)
- @request.env['REQUEST_METHOD'] = "#{method.upcase}" if defined?(@request)
- process(action, parameters, session, flash)
- end
- EOV
+ # Executes a request simulating GET HTTP method and set/volley the response
+ def get(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "GET")
+ end
+
+ # Executes a request simulating POST HTTP method and set/volley the response
+ def post(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "POST")
+ end
+
+ # Executes a request simulating PUT HTTP method and set/volley the response
+ def put(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "PUT")
+ end
+
+ # Executes a request simulating DELETE HTTP method and set/volley the response
+ def delete(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "DELETE")
+ end
+
+ # Executes a request simulating HEAD HTTP method and set/volley the response
+ def head(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "HEAD")
end
end
- # execute the request and set/volley the response
- def process(action, parameters = nil, session = nil, flash = nil)
+ 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|
@@ -403,7 +395,7 @@ module ActionController #:nodoc:
@response.recycle!
@html_document = nil
- @request.env['REQUEST_METHOD'] ||= "GET"
+ @request.env['REQUEST_METHOD'] = http_method
@request.action = action.to_s
@@ -413,7 +405,9 @@ module ActionController #:nodoc:
@request.session = ActionController::TestSession.new(session) unless session.nil?
@request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
build_request_uri(action, parameters)
- @controller.process(@request, @response)
+
+ Base.class_eval { include ProcessWithTest } unless Base < ProcessWithTest
+ @controller.process_with_test(@request, @response)
end
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@@ -490,7 +484,8 @@ module ActionController #:nodoc:
#
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
def fixture_file_upload(path, mime_type = nil, binary = false)
- ActionController::TestUploadedFile.new("#{ActionController::TestCase.try(:fixture_path)}#{path}", mime_type, binary)
+ fixture_path = ActionController::TestCase.send(:fixture_path) if ActionController::TestCase.respond_to?(:fixture_path)
+ ActionController::TestUploadedFile.new("#{fixture_path}#{path}", mime_type, binary)
end
# A helper to make it easier to test different route configurations.
@@ -525,12 +520,24 @@ module ActionController #:nodoc:
ActionController::Routing.const_set(:Routes, real_routes) if real_routes
end
end
-end
-module Test
- module Unit
- class TestCase #:nodoc:
- include ActionController::TestProcess
+ module ProcessWithTest #:nodoc:
+ def self.included(base)
+ base.class_eval { attr_reader :assigns }
end
+
+ def process_with_test(*args)
+ process(*args).tap { set_test_assigns }
+ end
+
+ private
+ def set_test_assigns
+ @assigns = {}
+ (instance_variable_names - self.class.protected_instance_variables).each do |var|
+ name, value = var[1..-1], instance_variable_get(var)
+ @assigns[name] = value
+ response.template.assigns[name] = value if response
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/uploaded_file.rb b/actionpack/lib/action_controller/uploaded_file.rb
new file mode 100644
index 0000000000..376ba3621a
--- /dev/null
+++ b/actionpack/lib/action_controller/uploaded_file.rb
@@ -0,0 +1,44 @@
+module ActionController
+ module UploadedFile
+ def self.included(base)
+ base.class_eval do
+ attr_accessor :original_path, :content_type
+ alias_method :local_path, :path
+ end
+ end
+
+ def self.extended(object)
+ object.class_eval do
+ attr_accessor :original_path, :content_type
+ alias_method :local_path, :path
+ end
+ end
+
+ # Take the basename of the upload's original filename.
+ # This handles the full Windows paths given by Internet Explorer
+ # (and perhaps other broken user agents) without affecting
+ # those which give the lone filename.
+ # The Windows regexp is adapted from Perl's File::Basename.
+ def original_filename
+ unless defined? @original_filename
+ @original_filename =
+ unless original_path.blank?
+ if original_path =~ /^(?:.*[:\\\/])?(.*)/m
+ $1
+ else
+ File.basename original_path
+ end
+ end
+ end
+ @original_filename
+ end
+ end
+
+ class UploadedStringIO < StringIO
+ include UploadedFile
+ end
+
+ class UploadedTempfile < Tempfile
+ include UploadedFile
+ end
+end
diff --git a/actionpack/lib/action_controller/url_encoded_pair_parser.rb b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
new file mode 100644
index 0000000000..57594c4259
--- /dev/null
+++ b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
@@ -0,0 +1,155 @@
+module ActionController
+ class UrlEncodedPairParser < StringScanner #:nodoc:
+ class << self
+ def parse_query_parameters(query_string)
+ return {} if query_string.blank?
+
+ pairs = query_string.split('&').collect do |chunk|
+ next if chunk.empty?
+ key, value = chunk.split('=', 2)
+ next if key.empty?
+ value = value.nil? ? nil : CGI.unescape(value)
+ [ CGI.unescape(key), value ]
+ end.compact
+
+ new(pairs).result
+ end
+
+ def parse_hash_parameters(params)
+ parser = new
+
+ params = params.dup
+ until params.empty?
+ for key, value in params
+ if key.blank?
+ params.delete(key)
+ elsif value.is_a?(Array)
+ parser.parse(key, get_typed_value(value.shift))
+ params.delete(key) if value.empty?
+ else
+ parser.parse(key, get_typed_value(value))
+ params.delete(key)
+ end
+ end
+ end
+
+ parser.result
+ end
+
+ private
+ def get_typed_value(value)
+ case value
+ when String
+ value
+ when NilClass
+ ''
+ when Array
+ value.map { |v| get_typed_value(v) }
+ when Hash
+ if value.has_key?(:tempfile) && value[:filename].any?
+ upload = value[:tempfile]
+ upload.extend(UploadedFile)
+ upload.original_path = value[:filename]
+ upload.content_type = value[:type]
+ upload
+ else
+ nil
+ end
+ else
+ raise "Unknown form value: #{value.inspect}"
+ end
+ end
+ end
+
+ attr_reader :top, :parent, :result
+
+ def initialize(pairs = [])
+ super('')
+ @result = {}
+ pairs.each { |key, value| parse(key, value) }
+ end
+
+ KEY_REGEXP = %r{([^\[\]=&]+)}
+ BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
+
+ # Parse the query string
+ def parse(key, value)
+ self.string = key
+ @top, @parent = result, nil
+
+ # First scan the bare key
+ key = scan(KEY_REGEXP) or return
+ key = post_key_check(key)
+
+ # Then scan as many nestings as present
+ until eos?
+ r = scan(BRACKETED_KEY_REGEXP) or return
+ key = self[1]
+ key = post_key_check(key)
+ end
+
+ bind(key, value)
+ end
+
+ private
+ # After we see a key, we must look ahead to determine our next action. Cases:
+ #
+ # [] follows the key. Then the value must be an array.
+ # = follows the key. (A value comes next)
+ # & or the end of string follows the key. Then the key is a flag.
+ # otherwise, a hash follows the key.
+ def post_key_check(key)
+ if scan(/\[\]/) # a[b][] indicates that b is an array
+ container(key, Array)
+ nil
+ elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
+ container(key, Hash)
+ nil
+ else # End of key? We do nothing.
+ key
+ end
+ end
+
+ # Add a container to the stack.
+ def container(key, klass)
+ type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
+ value = bind(key, klass.new)
+ type_conflict! klass, value unless value.is_a?(klass)
+ push(value)
+ end
+
+ # Push a value onto the 'stack', which is actually only the top 2 items.
+ def push(value)
+ @parent, @top = @top, value
+ end
+
+ # Bind a key (which may be nil for items in an array) to the provided value.
+ def bind(key, value)
+ if top.is_a? Array
+ if key
+ if top[-1].is_a?(Hash) && ! top[-1].key?(key)
+ top[-1][key] = value
+ else
+ top << {key => value}.with_indifferent_access
+ end
+ push top.last
+ return top[key]
+ else
+ top << value
+ return value
+ end
+ elsif top.is_a? Hash
+ key = CGI.unescape(key)
+ parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
+ top[key] ||= value
+ return top[key]
+ else
+ raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
+ end
+ end
+
+ def type_conflict!(klass, value)
+ raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/verb_piggybacking.rb b/actionpack/lib/action_controller/verb_piggybacking.rb
deleted file mode 100644
index 86cde304a0..0000000000
--- a/actionpack/lib/action_controller/verb_piggybacking.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-module ActionController
- # TODO: Use Rack::MethodOverride when it is released
- class VerbPiggybacking
- HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS)
-
- def initialize(app)
- @app = app
- end
-
- def call(env)
- if env["REQUEST_METHOD"] == "POST"
- req = Request.new(env)
- if method = (req.parameters[:_method] || env["HTTP_X_HTTP_METHOD_OVERRIDE"])
- method = method.to_s.upcase
- if HTTP_METHODS.include?(method)
- env["REQUEST_METHOD"] = method
- end
- end
- end
-
- @app.call(env)
- end
- end
-end
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index c7fd3092e7..b90f89be39 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2008 David Heinemeier Hansson
+# Copyright (c) 2004-2009 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 210a5f1a93..0b710bd8d9 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2008 David Heinemeier Hansson
+# Copyright (c) 2004-2009 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 8958e61e9d..70a0ba91a7 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -225,7 +225,7 @@ module ActionView #:nodoc:
end
# Returns the result of a render that's dictated by the options hash. The primary options are:
- #
+ #
# * <tt>:partial</tt> - See ActionView::Partials.
# * <tt>:update</tt> - Calls update_page with the block given.
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 0633d5414e..f6abea38ed 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -6,54 +6,70 @@ module ActionView
module Helpers #:nodoc:
# This module provides methods for generating HTML that links views to assets such
# as images, javascripts, stylesheets, and feeds. These methods do not verify
- # the assets exist before linking to them.
+ # the assets exist before linking to them:
+ #
+ # image_tag("rails.png")
+ # # => <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" />
#
# === Using asset hosts
+ #
# By default, Rails links to these assets on the current host in the public
- # folder, but you can direct Rails to link to assets from a dedicated assets server by
- # setting ActionController::Base.asset_host in your <tt>config/environment.rb</tt>. For example,
- # let's say your asset host is <tt>assets.example.com</tt>.
+ # folder, but you can direct Rails to link to assets from a dedicated asset
+ # server by setting ActionController::Base.asset_host in the application
+ # configuration, typically in <tt>config/environments/production.rb</tt>.
+ # For example, you'd define <tt>assets.example.com</tt> to be your asset
+ # host this way:
#
# ActionController::Base.asset_host = "assets.example.com"
+ #
+ # Helpers take that into account:
+ #
# image_tag("rails.png")
- # => <img src="http://assets.example.com/images/rails.png" alt="Rails" />
+ # # => <img alt="Rails" src="http://assets.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
- # This is useful since browsers typically open at most two connections to a single host,
- # which means your assets often wait in single file for their turn to load. You can
- # alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
- # to automatically distribute asset requests among four hosts (e.g., "assets0.example.com" through "assets3.example.com")
- # so browsers will open eight connections rather than two.
+ # Browsers typically open at most two simultaneous connections to a single
+ # host, which means your assets often have to wait for other assets to finish
+ # downloading. You can alleviate this by using a <tt>%d</tt> wildcard in the
+ # +asset_host+. For example, "assets%d.example.com". If that wildcard is
+ # present Rails distributes asset requests among the corresponding four hosts
+ # "assets0.example.com", ..., "assets3.example.com". With this trick browsers
+ # will open eight simultaneous connections rather than two.
#
# image_tag("rails.png")
- # => <img src="http://assets0.example.com/images/rails.png" alt="Rails" />
+ # # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # => <link href="http://assets3.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
- # To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
- # the wildcard to a single asset host. You can read more about setting up your DNS CNAME records from
- # your ISP.
+ # To do this, you can either setup four actual hosts, or you can use wildcard
+ # DNS to CNAME the wildcard to a single asset host. You can read more about
+ # setting up your DNS CNAME records from your ISP.
#
# Note: This is purely a browser performance optimization and is not meant
# for server load balancing. See http://www.die.net/musings/page_load_time/
# for background.
#
- # Alternatively, you can exert more control over the asset host by setting <tt>asset_host</tt> to a proc
- # that takes a single source argument. This is useful if you are unable to setup 4 actual hosts or have
- # fewer/more than 4 hosts. The example proc below generates http://assets1.example.com and
- # http://assets2.example.com randomly.
+ # Alternatively, you can exert more control over the asset host by setting
+ # +asset_host+ to a proc like this:
#
- # ActionController::Base.asset_host = Proc.new { |source| "http://assets#{rand(2) + 1}.example.com" }
+ # ActionController::Base.asset_host = Proc.new { |source|
+ # "http://assets#{rand(2) + 1}.example.com"
+ # }
# image_tag("rails.png")
- # => <img src="http://assets2.example.com/images/rails.png" alt="Rails" />
+ # # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # => <link href="http://assets1.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets1.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # The example above generates "http://assets1.example.com" and
+ # "http://assets2.example.com" randomly. This option is useful for example if
+ # you need fewer/more than four hosts, custom host names, etc.
#
- # The proc takes a <tt>source</tt> parameter (which is the path of the source asset) and an optional
- # <tt>request</tt> parameter (which is an entire instance of an <tt>ActionController::AbstractRequest</tt>
- # subclass). This can be used to generate a particular asset host depending on the asset path and the particular
- # request.
+ # As you see the proc takes a +source+ parameter. That's a string with the
+ # absolute path of the asset with any extensions and timestamps in place,
+ # for example "/images/rails.png?1230601161".
#
# ActionController::Base.asset_host = Proc.new { |source|
# if source.starts_with?('/images')
@@ -63,14 +79,16 @@ module ActionView
# end
# }
# image_tag("rails.png")
- # => <img src="http://images.example.com/images/rails.png" alt="Rails" />
+ # # => <img alt="Rails" src="http://images.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
- # The optional <tt>request</tt> parameter to the proc is useful in particular for serving assets from an
- # SSL-protected page. The example proc below disables asset hosting for HTTPS connections, while still sending
- # assets for plain HTTP requests from asset hosts. This is useful for avoiding mixed media warnings when serving
- # non-HTTP assets from HTTPS web pages when you don't have an SSL certificate for each of the asset hosts.
+ # Alternatively you may ask for a second parameter +request+. That one is
+ # particularly useful for serving assets from an SSL-protected page. The
+ # example proc below disables asset hosting for HTTPS connections, while
+ # still sending assets for plain HTTP requests from asset hosts. If you don't
+ # have SSL certificates for each of the asset hosts this technique allows you
+ # to avoid warnings in the client about mixed media.
#
# ActionController::Base.asset_host = Proc.new { |source, request|
# if request.ssl?
@@ -80,7 +98,8 @@ module ActionView
# end
# }
#
- # You can also implement a custom asset host object that responds to the call method and tasks one or two parameters just like the proc.
+ # You can also implement a custom asset host object that responds to +call+
+ # and takes either one or two parameters just like the proc.
#
# config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
# "http://asset%d.example.com", "https://asset1.example.com"
@@ -88,24 +107,29 @@ module ActionView
#
# === Using asset timestamps
#
- # By default, Rails will append all asset paths with that asset's timestamp. This allows you to set a cache-expiration date for the
- # asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp,
- # which then updates the URL as the timestamp is part of that, which in turn busts the cache).
+ # By default, Rails appends asset's timestamps to all asset paths. This allows
+ # you to set a cache-expiration date for the asset far into the future, but
+ # still be able to instantly invalidate it by simply updating the file (and
+ # hence updating the timestamp, which then updates the URL as the timestamp
+ # is part of that, which in turn busts the cache).
#
- # It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
- # advantage of this feature. Here's an example for Apache:
+ # It's the responsibility of the web server you use to set the far-future
+ # expiration date on cache assets that you need to take advantage of this
+ # feature. Here's an example for Apache:
#
- # # Asset Expiration
- # ExpiresActive On
- # <FilesMatch "\.(ico|gif|jpe?g|png|js|css)$">
- # ExpiresDefault "access plus 1 year"
- # </FilesMatch>
+ # # Asset Expiration
+ # ExpiresActive On
+ # <FilesMatch "\.(ico|gif|jpe?g|png|js|css)$">
+ # ExpiresDefault "access plus 1 year"
+ # </FilesMatch>
#
- # Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
- # have their clocks synchronized. If one of them drift out of sync, you'll see different timestamps at random and the cache won't
- # work. Which means that the browser will request the same assets over and over again even thought they didn't change. You can use
- # something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
- # requested over and over).
+ # Also note that in order for this to work, all your application servers must
+ # return the same timestamps. This means that they must have their clocks
+ # synchronized. If one of them drifts out of sync, you'll see different
+ # timestamps at random and the cache won't work. In that case the browser
+ # will request the same assets over and over again even thought they didn't
+ # 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"
JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts"
@@ -117,7 +141,7 @@ module ActionView
# <tt>:atom</tt>. Control the link options in url_for format using the
# +url_options+. You can modify the LINK tag itself in +tag_options+.
#
- # ==== Options:
+ # ==== Options
# * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
# * <tt>:type</tt> - Override the auto-generated mime type
# * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
@@ -157,7 +181,7 @@ module ActionView
# javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr.js
# javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
def javascript_path(source)
- JavaScriptTag.new(self, @controller, source).public_path
+ compute_public_path(source, 'javascripts', 'js')
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
@@ -255,17 +279,15 @@ module ActionView
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
- unless File.exists?(joined_javascript_path)
- JavaScriptSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_javascript_path)
- end
+ write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) unless File.exists?(joined_javascript_path)
javascript_src_tag(joined_javascript_name, options)
else
- JavaScriptSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
- javascript_src_tag(source, options)
- }.join("\n")
+ expand_javascript_sources(sources, recursive).collect { |source| javascript_src_tag(source, options) }.join("\n")
end
end
+ @@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
+
# Register one or more javascript files to be included when <tt>symbol</tt>
# is passed to <tt>javascript_include_tag</tt>. This method is typically intended
# to be called from plugin initialization to register javascript files
@@ -278,9 +300,11 @@ module ActionView
# <script type="text/javascript" src="/javascripts/body.js"></script>
# <script type="text/javascript" src="/javascripts/tail.js"></script>
def self.register_javascript_expansion(expansions)
- JavaScriptSources.expansions.merge!(expansions)
+ @@javascript_expansions.merge!(expansions)
end
+ @@stylesheet_expansions = {}
+
# Register one or more stylesheet files to be included when <tt>symbol</tt>
# is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended
# to be called from plugin initialization to register stylesheet files
@@ -293,7 +317,7 @@ module ActionView
# <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
def self.register_stylesheet_expansion(expansions)
- StylesheetSources.expansions.merge!(expansions)
+ @@stylesheet_expansions.merge!(expansions)
end
# Register one or more additional JavaScript files to be included when
@@ -301,11 +325,11 @@ module ActionView
# typically intended to be called from plugin initialization to register additional
# .js files that the plugin installed in <tt>public/javascripts</tt>.
def self.register_javascript_include_default(*sources)
- JavaScriptSources.expansions[:defaults].concat(sources)
+ @@javascript_expansions[:defaults].concat(sources)
end
def self.reset_javascript_include_default #:nodoc:
- JavaScriptSources.expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
+ @@javascript_expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
end
# Computes the path to a stylesheet asset in the public stylesheets directory.
@@ -320,7 +344,7 @@ module ActionView
# stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style.css
# stylesheet_path "http://www.railsapplication.com/css/style.js" # => http://www.railsapplication.com/css/style.css
def stylesheet_path(source)
- StylesheetTag.new(self, @controller, source).public_path
+ compute_public_path(source, 'stylesheets', 'css')
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
@@ -365,7 +389,6 @@ module ActionView
# compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
# is set to true (which is the case by default for the Rails production environment, but not for the development
# environment). Examples:
-
#
# ==== Examples
# stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
@@ -396,14 +419,10 @@ module ActionView
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
- unless File.exists?(joined_stylesheet_path)
- StylesheetSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_stylesheet_path)
- end
+ write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) unless File.exists?(joined_stylesheet_path)
stylesheet_tag(joined_stylesheet_name, options)
else
- StylesheetSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
- stylesheet_tag(source, options)
- }.join("\n")
+ expand_stylesheet_sources(sources, recursive).collect { |source| stylesheet_tag(source, options) }.join("\n")
end
end
@@ -418,7 +437,7 @@ module ActionView
# image_path("/icons/edit.png") # => /icons/edit.png
# image_path("http://www.railsapplication.com/img/edit.png") # => http://www.railsapplication.com/img/edit.png
def image_path(source)
- ImageTag.new(self, @controller, source).public_path
+ compute_public_path(source, 'images')
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
@@ -473,353 +492,190 @@ module ActionView
tag("img", options)
end
- private
- def javascript_src_tag(source, options)
- content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
- end
-
- def stylesheet_tag(source, options)
- tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
- end
-
- module ImageAsset
- DIRECTORY = 'images'.freeze
-
- def directory
- DIRECTORY
- end
-
- def extension
- nil
- end
- end
-
- module JavaScriptAsset
- DIRECTORY = 'javascripts'.freeze
- EXTENSION = 'js'.freeze
-
- def public_directory
- JAVASCRIPTS_DIR
- end
-
- def directory
- DIRECTORY
- end
+ def self.cache_asset_timestamps
+ @@cache_asset_timestamps
+ end
- def extension
- EXTENSION
- end
- end
+ # You can enable or disable the asset tag timestamps cache.
+ # With the cache enabled, the asset tag helper methods will make fewer
+ # expense file system calls. However this prevents you from modifying
+ # any asset files while the server is running.
+ #
+ # ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
+ def self.cache_asset_timestamps=(value)
+ @@cache_asset_timestamps = value
+ end
- module StylesheetAsset
- DIRECTORY = 'stylesheets'.freeze
- EXTENSION = 'css'.freeze
+ @@cache_asset_timestamps = true
- def public_directory
- STYLESHEETS_DIR
+ private
+ # Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
+ # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
+ # roots. Rewrite the asset path for cache-busting asset ids. Include
+ # asset host, if configured, with the correct request protocol.
+ def compute_public_path(source, dir, ext = nil, include_host = true)
+ has_request = @controller.respond_to?(:request)
+
+ if ext && (File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}")))
+ source += ".#{ext}"
end
- def directory
- DIRECTORY
- end
+ unless source =~ %r{^[-a-z]+://}
+ source = "/#{dir}/#{source}" unless source[0] == ?/
- def extension
- EXTENSION
- end
- end
+ source = rewrite_asset_path(source)
- class AssetTag
- extend ActiveSupport::Memoizable
-
- Cache = {}
- CacheGuard = Mutex.new
-
- ProtocolRegexp = %r{^[-a-z]+://}.freeze
-
- def initialize(template, controller, source, include_host = true)
- # NOTE: The template arg is temporarily needed for a legacy plugin
- # hook that is expected to call rewrite_asset_path on the
- # template. This should eventually be removed.
- @template = template
- @controller = controller
- @source = source
- @include_host = include_host
- @cache_key = if controller.respond_to?(:request)
- [self.class.name,controller.request.protocol,
- ActionController::Base.asset_host,
- ActionController::Base.relative_url_root,
- source, include_host]
- else
- [self.class.name,ActionController::Base.asset_host, source, include_host]
+ if has_request && include_host
+ unless source =~ %r{^#{ActionController::Base.relative_url_root}/}
+ source = "#{ActionController::Base.relative_url_root}#{source}"
+ end
end
end
-
- def public_path
- compute_public_path(@source)
- end
- memoize :public_path
-
- def asset_file_path
- File.join(ASSETS_DIR, public_path.split('?').first)
- end
- memoize :asset_file_path
-
- def contents
- File.read(asset_file_path)
- end
-
- def mtime
- File.mtime(asset_file_path)
- end
- private
- def request
- request? && @controller.request
- end
+ if include_host && source !~ %r{^[-a-z]+://}
+ host = compute_asset_host(source)
- def request?
- @controller.respond_to?(:request)
+ if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
+ host = "#{@controller.request.protocol}#{host}"
end
- # Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
- # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
- # roots. Rewrite the asset path for cache-busting asset ids. Include
- # asset host, if configured, with the correct request protocol.
- def compute_public_path(source)
- if source =~ ProtocolRegexp
- source += ".#{extension}" if missing_extension?(source)
- source = prepend_asset_host(source)
- source
- else
- CacheGuard.synchronize do
- Cache[@cache_key + [source]] ||= begin
- source += ".#{extension}" if missing_extension?(source) || file_exists_with_extension?(source)
- source = "/#{directory}/#{source}" unless source[0] == ?/
- source = rewrite_asset_path(source)
- source = prepend_relative_url_root(source)
- source = prepend_asset_host(source)
- source
- end
- end
- end
- end
-
- def missing_extension?(source)
- extension && File.extname(source).blank?
- end
-
- def file_exists_with_extension?(source)
- extension && File.exist?(File.join(ASSETS_DIR, directory, "#{source}.#{extension}"))
- end
-
- def prepend_relative_url_root(source)
- relative_url_root = ActionController::Base.relative_url_root
- if request? && @include_host && source !~ %r{^#{relative_url_root}/}
- "#{relative_url_root}#{source}"
- else
- source
- end
- end
+ "#{host}#{source}"
+ else
+ source
+ end
+ end
- def prepend_asset_host(source)
- if @include_host && source !~ ProtocolRegexp
- host = compute_asset_host(source)
- if request? && !host.blank? && host !~ ProtocolRegexp
- host = "#{request.protocol}#{host}"
- end
- "#{host}#{source}"
+ # Pick an asset host for this source. Returns +nil+ if no host is set,
+ # the host if no wildcard is set, the host interpolated with the
+ # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
+ # 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.is_a?(Proc) || host.respond_to?(:call)
+ case host.is_a?(Proc) ? host.arity : host.method(:call).arity
+ when 2
+ request = @controller.respond_to?(:request) && @controller.request
+ host.call(source, request)
else
- source
- end
- end
-
- # Pick an asset host for this source. Returns +nil+ if no host is set,
- # the host if no wildcard is set, the host interpolated with the
- # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
- # 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.is_a?(Proc) || host.respond_to?(:call)
- case host.is_a?(Proc) ? host.arity : host.method(:call).arity
- when 2
- host.call(source, request)
- else
- host.call(source)
- end
- else
- (host =~ /%d/) ? host % (source.hash % 4) : host
- end
+ host.call(source)
end
+ else
+ (host =~ /%d/) ? host % (source.hash % 4) : host
end
+ end
+ end
- # Use the RAILS_ASSET_ID environment variable or the source's
- # modification time as its cache-busting asset id.
- def rails_asset_id(source)
- if asset_id = ENV["RAILS_ASSET_ID"]
- asset_id
- else
- path = File.join(ASSETS_DIR, source)
+ @@asset_timestamps_cache = {}
+ @@asset_timestamps_cache_guard = Mutex.new
+
+ # Use the RAILS_ASSET_ID environment variable or the source's
+ # modification time as its cache-busting asset id.
+ def rails_asset_id(source)
+ if asset_id = ENV["RAILS_ASSET_ID"]
+ asset_id
+ else
+ if @@cache_asset_timestamps && (asset_id = @@asset_timestamps_cache[source])
+ asset_id
+ else
+ path = File.join(ASSETS_DIR, source)
+ asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
- if File.exist?(path)
- File.mtime(path).to_i.to_s
- else
- ''
+ if @@cache_asset_timestamps
+ @@asset_timestamps_cache_guard.synchronize do
+ @@asset_timestamps_cache[source] = asset_id
end
end
- end
- # Break out the asset path rewrite in case plugins wish to put the asset id
- # someplace other than the query string.
- def rewrite_asset_path(source)
- if @template.respond_to?(:rewrite_asset_path)
- # DEPRECATE: This way to override rewrite_asset_path
- @template.send(:rewrite_asset_path, source)
- else
- asset_id = rails_asset_id(source)
- if asset_id.blank?
- source
- else
- "#{source}?#{asset_id}"
- end
- end
+ asset_id
end
+ end
end
- class ImageTag < AssetTag
- include ImageAsset
+ # Break out the asset path rewrite in case plugins wish to put the asset id
+ # someplace other than the query string.
+ def rewrite_asset_path(source)
+ asset_id = rails_asset_id(source)
+ if asset_id.blank?
+ source
+ else
+ source + "?#{asset_id}"
+ end
end
- class JavaScriptTag < AssetTag
- include JavaScriptAsset
+ def javascript_src_tag(source, options)
+ content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
end
- class StylesheetTag < AssetTag
- include StylesheetAsset
+ def stylesheet_tag(source, options)
+ tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
end
- class AssetCollection
- extend ActiveSupport::Memoizable
-
- Cache = {}
- CacheGuard = Mutex.new
-
- def self.create(template, controller, sources, recursive)
- CacheGuard.synchronize do
- key = [self, sources, recursive]
- Cache[key] ||= new(template, controller, sources, recursive).freeze
- end
- end
+ def compute_javascript_paths(*args)
+ expand_javascript_sources(*args).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
+ end
- def initialize(template, controller, sources, recursive)
- # NOTE: The template arg is temporarily needed for a legacy plugin
- # hook. See NOTE under AssetTag#initialize for more details
- @template = template
- @controller = controller
- @sources = sources
- @recursive = recursive
- end
+ def compute_stylesheet_paths(*args)
+ expand_stylesheet_sources(*args).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
+ end
- def write_asset_file_contents(joined_asset_path)
- FileUtils.mkdir_p(File.dirname(joined_asset_path))
- File.open(joined_asset_path, "w+") { |cache| cache.write(joined_contents) }
- mt = latest_mtime
- File.utime(mt, mt, joined_asset_path)
+ def expand_javascript_sources(sources, recursive = false)
+ if sources.include?(:all)
+ all_javascript_files = collect_asset_files(JAVASCRIPTS_DIR, ('**' if recursive), '*.js')
+ ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
+ else
+ expanded_sources = sources.collect do |source|
+ determine_source(source, @@javascript_expansions)
+ end.flatten
+ expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
+ expanded_sources
end
-
- private
- def determine_source(source, collection)
- case source
- when Symbol
- collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
- else
- source
- end
- end
-
- def validate_sources!
- @sources.collect { |source| determine_source(source, self.class.expansions) }.flatten
- end
-
- def all_asset_files
- path = [public_directory, ('**' if @recursive), "*.#{extension}"].compact
- Dir[File.join(*path)].collect { |file|
- file[-(file.size - public_directory.size - 1)..-1].sub(/\.\w+$/, '')
- }.sort
- end
-
- def tag_sources
- expand_sources.collect { |source| tag_class.new(@template, @controller, source, false) }
- end
-
- def joined_contents
- tag_sources.collect { |source| source.contents }.join("\n\n")
- end
-
- # Set mtime to the latest of the combined files to allow for
- # consistent ETag without a shared filesystem.
- def latest_mtime
- tag_sources.map { |source| source.mtime }.max
- end
end
- class JavaScriptSources < AssetCollection
- include JavaScriptAsset
-
- EXPANSIONS = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
-
- def self.expansions
- EXPANSIONS
+ def expand_stylesheet_sources(sources, recursive)
+ if sources.first == :all
+ collect_asset_files(STYLESHEETS_DIR, ('**' if recursive), '*.css')
+ else
+ sources.collect do |source|
+ determine_source(source, @@stylesheet_expansions)
+ end.flatten
end
+ end
- APPLICATION_JS = "application".freeze
- APPLICATION_FILE = "application.js".freeze
-
- def expand_sources
- if @sources.include?(:all)
- assets = all_asset_files
- ((defaults.dup & assets) + assets).uniq!
- else
- expanded_sources = validate_sources!
- expanded_sources << APPLICATION_JS if include_application?
- expanded_sources
- end
+ def determine_source(source, collection)
+ case source
+ when Symbol
+ collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
+ else
+ source
end
- memoize :expand_sources
-
- private
- def tag_class
- JavaScriptTag
- end
-
- def defaults
- determine_source(:defaults, self.class.expansions)
- end
+ end
- def include_application?
- @sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, APPLICATION_FILE))
- end
+ def join_asset_file_contents(paths)
+ paths.collect { |path| File.read(asset_file_path(path)) }.join("\n\n")
end
- class StylesheetSources < AssetCollection
- include StylesheetAsset
+ def write_asset_file_contents(joined_asset_path, asset_paths)
+ FileUtils.mkdir_p(File.dirname(joined_asset_path))
+ File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
- EXPANSIONS = {}
+ # Set mtime to the latest of the combined files to allow for
+ # consistent ETag without a shared filesystem.
+ mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
+ File.utime(mt, mt, joined_asset_path)
+ end
- def self.expansions
- EXPANSIONS
- end
+ def asset_file_path(path)
+ File.join(ASSETS_DIR, path.split('?').first)
+ end
- def expand_sources
- @sources.first == :all ? all_asset_files : validate_sources!
- end
- memoize :expand_sources
+ def collect_asset_files(*path)
+ dir = path.first
- private
- def tag_class
- StylesheetTag
- end
+ Dir[File.join(*path.compact)].collect do |file|
+ file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '')
+ end.sort
end
end
end
-end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/benchmark_helper.rb b/actionpack/lib/action_view/helpers/benchmark_helper.rb
index 372d24a22e..61c2cecb04 100644
--- a/actionpack/lib/action_view/helpers/benchmark_helper.rb
+++ b/actionpack/lib/action_view/helpers/benchmark_helper.rb
@@ -18,12 +18,33 @@ module ActionView
# That would add something like "Process data files (345.2ms)" to the log,
# which you can then use to compare timings when optimizing your code.
#
- # You may give an optional logger level as the second argument
+ # You may give an optional logger level as the :level option.
# (:debug, :info, :warn, :error); the default value is :info.
- def benchmark(message = "Benchmarking", level = :info)
+ #
+ # <% benchmark "Low-level files", :level => :debug do %>
+ # <%= lowlevel_files_operation %>
+ # <% end %>
+ #
+ # Finally, you can pass true as the third argument to silence all log activity
+ # inside the block. This is great for boiling down a noisy block to just a single statement:
+ #
+ # <% benchmark "Process data files", :level => :info, :silence => true do %>
+ # <%= expensive_and_chatty_files_operation %>
+ # <% end %>
+ def benchmark(message = "Benchmarking", options = {})
if controller.logger
- ms = Benchmark.ms { yield }
- controller.logger.send(level, '%s (%.1fms)' % [message, ms])
+ if options.is_a?(Symbol)
+ ActiveSupport::Deprecation.warn("use benchmark('#{message}', :level => :#{options}) instead", caller)
+ options = { :level => options, :silence => false }
+ else
+ options.assert_valid_keys(:level, :silence)
+ options[:level] ||= :info
+ end
+
+ result = nil
+ ms = Benchmark.ms { result = options[:silence] ? controller.logger.silence { yield } : yield }
+ controller.logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
+ result
else
yield
end
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 4305617ac8..b4c1adbe76 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -860,7 +860,7 @@ module ActionView
# => post[written_on(1i)]
def input_name_from_type(type)
prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
- prefix += "[#{@options[:index]}]" if @options[:index]
+ prefix += "[#{@options[:index]}]" if @options.has_key?(:index)
field_name = @options[:field_name] || type
if @options[:include_position]
@@ -923,7 +923,7 @@ module ActionView
options[:field_name] = @method_name
options[:include_position] = true
options[:prefix] ||= @object_name
- options[:index] ||= @auto_index
+ options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
options[:datetime_separator] ||= ' &mdash; '
options[:time_separator] ||= ' : '
@@ -961,15 +961,15 @@ module ActionView
class FormBuilder
def date_select(method, options = {}, html_options = {})
- @template.date_select(@object_name, method, options.merge(:object => @object), html_options)
+ @template.date_select(@object_name, method, objectify_options(options), html_options)
end
def time_select(method, options = {}, html_options = {})
- @template.time_select(@object_name, method, options.merge(:object => @object), html_options)
+ @template.time_select(@object_name, method, objectify_options(options), html_options)
end
def datetime_select(method, options = {}, html_options = {})
- @template.datetime_select(@object_name, method, options.merge(:object => @object), html_options)
+ @template.datetime_select(@object_name, method, objectify_options(options), html_options)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 621e2946b5..a85751c657 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -737,9 +737,13 @@ module ActionView
(field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
src = <<-end_src
- def #{selector}(method, options = {})
- @template.send(#{selector.inspect}, @object_name, method, objectify_options(options))
- end
+ def #{selector}(method, options = {}) # def text_field(method, options = {})
+ @template.send( # @template.send(
+ #{selector.inspect}, # "text_field",
+ @object_name, # @object_name,
+ method, # method,
+ objectify_options(options)) # objectify_options(options))
+ end # end
end_src
class_eval src, __FILE__, __LINE__
end
diff --git a/actionpack/lib/action_view/inline_template.rb b/actionpack/lib/action_view/inline_template.rb
index 5e00cef13f..54efa543c8 100644
--- a/actionpack/lib/action_view/inline_template.rb
+++ b/actionpack/lib/action_view/inline_template.rb
@@ -12,7 +12,7 @@ module ActionView #:nodoc:
private
# Always recompile inline templates
- def recompile?(local_assigns)
+ def recompile?
true
end
end
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index b030156889..19207e7262 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -2,7 +2,7 @@ module ActionView #:nodoc:
class PathSet < Array #:nodoc:
def self.type_cast(obj)
if obj.is_a?(String)
- Path.new(obj)
+ Template::EagerPath.new(obj)
else
obj
end
@@ -32,111 +32,6 @@ module ActionView #:nodoc:
super(*objs.map { |obj| self.class.type_cast(obj) })
end
- class Path #:nodoc:
- attr_reader :path, :paths
- delegate :hash, :inspect, :to => :path
-
- def initialize(path, load = false)
- raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
- @path = path.freeze
- reload! if load
- end
-
- def to_s
- if defined?(RAILS_ROOT)
- path.to_s.sub(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '')
- else
- path.to_s
- end
- end
-
- def to_str
- path.to_str
- end
-
- def ==(path)
- to_str == path.to_str
- end
-
- def eql?(path)
- to_str == path.to_str
- end
-
- # Returns a ActionView::Template object for the given path string. The
- # input path should be relative to the view path directory,
- # +hello/index.html.erb+. This method also has a special exception to
- # match partial file names without a handler extension. So
- # +hello/index.html+ will match the first template it finds with a
- # known template extension, +hello/index.html.erb+. Template extensions
- # should not be confused with format extensions +html+, +js+, +xml+,
- # etc. A format must be supplied to match a formated file. +hello/index+
- # will never match +hello/index.html.erb+.
- #
- # This method also has two different implementations, one that is "lazy"
- # and makes file system calls every time and the other is cached,
- # "eager" which looks up the template in an in memory index. The "lazy"
- # version is designed for development where you want to automatically
- # find new templates between requests. The "eager" version is designed
- # for production mode and it is much faster but requires more time
- # upfront to build the file index.
- def [](path)
- if loaded?
- @paths[path]
- else
- Dir.glob("#{@path}/#{path}*").each do |file|
- template = create_template(file)
- if template.accessible_paths.include?(path)
- return template
- end
- end
- nil
- end
- end
-
- def loaded?
- @loaded ? true : false
- end
-
- def load
- reload! unless loaded?
- self
- end
-
- # Rebuild load path directory cache
- def reload!
- @paths = {}
-
- templates_in_path do |template|
- template.load!
- template.accessible_paths.each do |path|
- @paths[path] = template
- end
- end
-
- @paths.freeze
- @loaded = true
- end
-
- private
- def templates_in_path
- (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file|
- yield create_template(file) unless File.directory?(file)
- end
- end
-
- def create_template(file)
- Template.new(file.split("#{self}/").last, self)
- end
- end
-
- def load
- each { |path| path.load }
- end
-
- def reload!
- each { |path| path.reload! }
- end
-
def find_template(original_template_path, format = nil)
return original_template_path if original_template_path.respond_to?(:render)
template_path = original_template_path.sub(/^\//, '')
diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb
index d8e72f1179..153e14f68b 100644
--- a/actionpack/lib/action_view/renderable.rb
+++ b/actionpack/lib/action_view/renderable.rb
@@ -60,7 +60,7 @@ module ActionView
def compile(local_assigns)
render_symbol = method_name(local_assigns)
- if recompile?(render_symbol)
+ if !Base::CompiledTemplates.method_defined?(render_symbol) || recompile?
compile!(render_symbol, local_assigns)
end
end
@@ -89,11 +89,8 @@ module ActionView
end
end
- # Method to check whether template compilation is necessary.
- # The template will be compiled if the file has not been compiled yet, or
- # if local_assigns has a new key, which isn't supported by the compiled code yet.
- def recompile?(symbol)
- !Base::CompiledTemplates.method_defined?(symbol) || !loaded?
+ def recompile?
+ false
end
end
end
diff --git a/actionpack/lib/action_view/renderable_partial.rb b/actionpack/lib/action_view/renderable_partial.rb
index d92ff1b8d3..3ea836fa25 100644
--- a/actionpack/lib/action_view/renderable_partial.rb
+++ b/actionpack/lib/action_view/renderable_partial.rb
@@ -25,12 +25,11 @@ module ActionView
end
def render_partial(view, object = nil, local_assigns = {}, as = nil)
- object ||= local_assigns[:object] ||
- local_assigns[variable_name]
+ object ||= local_assigns[:object] || local_assigns[variable_name]
- if view.respond_to?(:controller)
+ if object.nil? && view.respond_to?(:controller)
ivar = :"@#{variable_name}"
- object ||=
+ object =
if view.controller.instance_variable_defined?(ivar)
ActiveSupport::Deprecation::DeprecatedObjectProxy.new(
view.controller.instance_variable_get(ivar),
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 5b384d0e4d..9d1e0d3ac5 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -1,5 +1,83 @@
module ActionView #:nodoc:
class Template
+ class Path
+ attr_reader :path, :paths
+ delegate :hash, :inspect, :to => :path
+
+ def initialize(path)
+ raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
+ @path = path.freeze
+ end
+
+ def to_s
+ if defined?(RAILS_ROOT)
+ path.to_s.sub(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '')
+ else
+ path.to_s
+ end
+ end
+
+ def to_str
+ path.to_str
+ end
+
+ def ==(path)
+ to_str == path.to_str
+ end
+
+ def eql?(path)
+ to_str == path.to_str
+ end
+
+ # Returns a ActionView::Template object for the given path string. The
+ # input path should be relative to the view path directory,
+ # +hello/index.html.erb+. This method also has a special exception to
+ # match partial file names without a handler extension. So
+ # +hello/index.html+ will match the first template it finds with a
+ # known template extension, +hello/index.html.erb+. Template extensions
+ # should not be confused with format extensions +html+, +js+, +xml+,
+ # etc. A format must be supplied to match a formated file. +hello/index+
+ # will never match +hello/index.html.erb+.
+ def [](path)
+ templates_in_path do |template|
+ if template.accessible_paths.include?(path)
+ return template
+ end
+ end
+ nil
+ end
+
+ private
+ def templates_in_path
+ (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file|
+ yield create_template(file) unless File.directory?(file)
+ end
+ end
+
+ def create_template(file)
+ Template.new(file.split("#{self}/").last, self)
+ end
+ end
+
+ class EagerPath < Path
+ def initialize(path)
+ super
+
+ @paths = {}
+ templates_in_path do |template|
+ template.load!
+ template.accessible_paths.each do |path|
+ @paths[path] = template
+ end
+ end
+ @paths.freeze
+ end
+
+ def [](path)
+ @paths[path]
+ end
+ end
+
extend TemplateHandlers
extend ActiveSupport::Memoizable
include Renderable
@@ -115,19 +193,18 @@ module ActionView #:nodoc:
File.mtime(filename) > mtime
end
- def loaded?
- @loaded
+ def recompile?
+ !@cached
end
def load!
- @loaded = true
- compile({})
+ @cached = true
freeze
end
private
def valid_extension?(extension)
- Template.template_handler_extensions.include?(extension)
+ !Template.registered_template_handler(extension).nil?
end
def find_full_path(path, load_paths)
diff --git a/actionpack/lib/action_view/template_handlers.rb b/actionpack/lib/action_view/template_handlers.rb
index d06ddd5fb5..205f8628f0 100644
--- a/actionpack/lib/action_view/template_handlers.rb
+++ b/actionpack/lib/action_view/template_handlers.rb
@@ -32,13 +32,17 @@ module ActionView #:nodoc:
@@template_handlers.keys.map(&:to_s).sort
end
+ def registered_template_handler(extension)
+ extension && @@template_handlers[extension.to_sym]
+ end
+
def register_default_template_handler(extension, klass)
register_template_handler(extension, klass)
@@default_template_handlers = klass
end
def handler_class_for_extension(extension)
- (extension && @@template_handlers[extension.to_sym]) || @@default_template_handlers
+ registered_template_handler(extension) || @@default_template_handlers
end
end
end
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 1a9ef983a5..ec337bb05b 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -1,3 +1,5 @@
+require 'active_support/test_case'
+
module ActionView
class Base
alias_method :initialize_without_template_tracking, :initialize
@@ -21,6 +23,7 @@ module ActionView
class TestCase < ActiveSupport::TestCase
include ActionController::TestCase::Assertions
+ include ActionController::TestProcess
class_inheritable_accessor :helper_class
@@helper_class = nil