aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
authorMikel Lindsaar <raasdnil@gmail.com>2010-01-19 21:05:37 +1100
committerMikel Lindsaar <raasdnil@gmail.com>2010-01-19 21:05:37 +1100
commit2107921000f4a186fed36e676d1ac089c8be1f99 (patch)
tree42f2188f058f8401d57f0d4c72063689a93e0419 /actionpack
parentccb7d9def3c20037c9ed5989d8cae1ed68763f4f (diff)
parented8501ef16fb2f5e4bd4d987740f5e5f62978400 (diff)
downloadrails-2107921000f4a186fed36e676d1ac089c8be1f99.tar.gz
rails-2107921000f4a186fed36e676d1ac089c8be1f99.tar.bz2
rails-2107921000f4a186fed36e676d1ac089c8be1f99.zip
Merge branch 'master' of git://github.com/rails/rails into rails
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/lib/abstract_controller/base.rb140
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb2
-rw-r--r--actionpack/lib/action_controller/deprecated/dispatcher.rb5
-rw-r--r--actionpack/lib/action_controller/metal/cookies.rb186
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb4
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb34
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb3
-rw-r--r--actionpack/lib/action_controller/railtie.rb50
-rw-r--r--actionpack/lib/action_controller/railties/subscriber.rb7
-rw-r--r--actionpack/lib/action_dispatch.rb12
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb123
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb101
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb50
-rwxr-xr-xactionpack/lib/action_dispatch/http/request.rb379
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb82
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb48
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb129
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb216
-rw-r--r--actionpack/lib/action_dispatch/middleware/notifications.rb24
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb32
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb2
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb29
-rw-r--r--actionpack/lib/action_dispatch/railties/subscriber.rb17
-rw-r--r--actionpack/lib/action_dispatch/routing.rb7
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb61
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb16
-rw-r--r--actionpack/lib/action_view/helpers/debug_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/sanitize_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb6
-rw-r--r--actionpack/test/abstract_unit.rb1
-rw-r--r--actionpack/test/activerecord/controller_runtime_test.rb4
-rw-r--r--actionpack/test/controller/cookie_test.rb37
-rw-r--r--actionpack/test/controller/filter_params_test.rb27
-rw-r--r--actionpack/test/controller/flash_test.rb2
-rw-r--r--actionpack/test/controller/new_base/base_test.rb28
-rw-r--r--actionpack/test/controller/subscriber_test.rb42
-rw-r--r--actionpack/test/dispatch/callbacks_test.rb12
-rw-r--r--actionpack/test/dispatch/response_test.rb9
-rw-r--r--actionpack/test/dispatch/routing_test.rb20
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb4
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb45
-rw-r--r--actionpack/test/dispatch/subscriber_test.rb112
-rw-r--r--actionpack/test/fixtures/reply.rb2
47 files changed, 1153 insertions, 983 deletions
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 48725ad82a..3119ee498b 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -62,15 +62,19 @@ module AbstractController
# Array[String]:: A list of all methods that should be considered
# actions.
def action_methods
- @action_methods ||=
+ @action_methods ||= begin
# All public instance methods of this class, including ancestors
- public_instance_methods(true).map { |m| m.to_s }.to_set -
- # Except for public instance methods of Base and its ancestors
- internal_methods.map { |m| m.to_s } +
- # Be sure to include shadowed public instance methods of this class
- public_instance_methods(false).map { |m| m.to_s } -
- # And always exclude explicitly hidden actions
- hidden_actions
+ methods = public_instance_methods(true).map { |m| m.to_s }.to_set -
+ # Except for public instance methods of Base and its ancestors
+ internal_methods.map { |m| m.to_s } +
+ # Be sure to include shadowed public instance methods of this class
+ public_instance_methods(false).map { |m| m.to_s } -
+ # And always exclude explicitly hidden actions
+ hidden_actions
+
+ # Clear out AS callback method pollution
+ methods.reject { |method| method =~ /_one_time_conditions/ }
+ end
end
# Returns the full controller name, underscored, without the ending Controller.
@@ -116,68 +120,72 @@ module AbstractController
self.class.controller_path
end
- private
- # Returns true if the name can be considered an action. This can
- # be overridden in subclasses to modify the semantics of what
- # can be considered an action.
- #
- # ==== Parameters
- # name<String>:: The name of an action to be tested
- #
- # ==== Returns
- # TrueClass, FalseClass
- def action_method?(name)
- self.class.action_methods.include?(name)
+ def action_methods
+ self.class.action_methods
end
- # Call the action. Override this in a subclass to modify the
- # behavior around processing an action. This, and not #process,
- # is the intended way to override action dispatching.
- def process_action(method_name, *args)
- send_action(method_name, *args)
- end
+ private
+ # Returns true if the name can be considered an action. This can
+ # be overridden in subclasses to modify the semantics of what
+ # can be considered an action.
+ #
+ # ==== Parameters
+ # name<String>:: The name of an action to be tested
+ #
+ # ==== Returns
+ # TrueClass, FalseClass
+ def action_method?(name)
+ self.class.action_methods.include?(name)
+ end
- # Actually call the method associated with the action. Override
- # this method if you wish to change how action methods are called,
- # not to add additional behavior around it. For example, you would
- # override #send_action if you want to inject arguments into the
- # method.
- alias send_action send
-
- # If the action name was not found, but a method called "action_missing"
- # was found, #method_for_action will return "_handle_action_missing".
- # This method calls #action_missing with the current action name.
- def _handle_action_missing
- action_missing(@_action_name)
- end
+ # Call the action. Override this in a subclass to modify the
+ # behavior around processing an action. This, and not #process,
+ # is the intended way to override action dispatching.
+ def process_action(method_name, *args)
+ send_action(method_name, *args)
+ end
- # Takes an action name and returns the name of the method that will
- # handle the action. In normal cases, this method returns the same
- # name as it receives. By default, if #method_for_action receives
- # a name that is not an action, it will look for an #action_missing
- # method and return "_handle_action_missing" if one is found.
- #
- # Subclasses may override this method to add additional conditions
- # that should be considered an action. For instance, an HTTP controller
- # with a template matching the action name is considered to exist.
- #
- # If you override this method to handle additional cases, you may
- # also provide a method (like _handle_method_missing) to handle
- # the case.
- #
- # If none of these conditions are true, and method_for_action
- # returns nil, an ActionNotFound exception will be raised.
- #
- # ==== Parameters
- # action_name<String>:: An action name to find a method name for
- #
- # ==== Returns
- # String:: The name of the method that handles the action
- # nil:: No method name could be found. Raise ActionNotFound.
- def method_for_action(action_name)
- if action_method?(action_name) then action_name
- elsif respond_to?(:action_missing, true) then "_handle_action_missing"
+ # Actually call the method associated with the action. Override
+ # this method if you wish to change how action methods are called,
+ # not to add additional behavior around it. For example, you would
+ # override #send_action if you want to inject arguments into the
+ # method.
+ alias send_action send
+
+ # If the action name was not found, but a method called "action_missing"
+ # was found, #method_for_action will return "_handle_action_missing".
+ # This method calls #action_missing with the current action name.
+ def _handle_action_missing
+ action_missing(@_action_name)
+ end
+
+ # Takes an action name and returns the name of the method that will
+ # handle the action. In normal cases, this method returns the same
+ # name as it receives. By default, if #method_for_action receives
+ # a name that is not an action, it will look for an #action_missing
+ # method and return "_handle_action_missing" if one is found.
+ #
+ # Subclasses may override this method to add additional conditions
+ # that should be considered an action. For instance, an HTTP controller
+ # with a template matching the action name is considered to exist.
+ #
+ # If you override this method to handle additional cases, you may
+ # also provide a method (like _handle_method_missing) to handle
+ # the case.
+ #
+ # If none of these conditions are true, and method_for_action
+ # returns nil, an ActionNotFound exception will be raised.
+ #
+ # ==== Parameters
+ # action_name<String>:: An action name to find a method name for
+ #
+ # ==== Returns
+ # String:: The name of the method that handles the action
+ # nil:: No method name could be found. Raise ActionNotFound.
+ def method_for_action(action_name)
+ if action_method?(action_name) then action_name
+ elsif respond_to?(:action_missing, true) then "_handle_action_missing"
+ end
end
- end
end
end
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index d57136dbb8..0dab4a3cc0 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -42,7 +42,7 @@ module AbstractController
# Delegates render_to_body and sticks the result in self.response_body.
def render(*args)
if response_body
- raise AbstractController::DoubleRenderError, "OMG"
+ raise AbstractController::DoubleRenderError, "Can only render or redirect once per action"
end
self.response_body = render_to_body(*args)
diff --git a/actionpack/lib/action_controller/deprecated/dispatcher.rb b/actionpack/lib/action_controller/deprecated/dispatcher.rb
index 3da3c8ce7d..8c21e375dd 100644
--- a/actionpack/lib/action_controller/deprecated/dispatcher.rb
+++ b/actionpack/lib/action_controller/deprecated/dispatcher.rb
@@ -1,8 +1,5 @@
module ActionController
class Dispatcher
- cattr_accessor :prepare_each_request
- self.prepare_each_request = false
-
class << self
def before_dispatch(*args, &block)
ActiveSupport::Deprecation.warn "ActionController::Dispatcher.before_dispatch is deprecated. " <<
@@ -18,7 +15,7 @@ module ActionController
def to_prepare(*args, &block)
ActiveSupport::Deprecation.warn "ActionController::Dispatcher.to_prepare is deprecated. " <<
- "Please use ActionDispatch::Callbacks.to_prepare instead.", caller
+ "Please use config.to_prepare instead", caller
ActionDispatch::Callbacks.after(*args, &block)
end
diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb
index 5b51bd21d0..7aa687b52c 100644
--- a/actionpack/lib/action_controller/metal/cookies.rb
+++ b/actionpack/lib/action_controller/metal/cookies.rb
@@ -1,48 +1,4 @@
module ActionController #:nodoc:
- # Cookies are read and written through ActionController#cookies.
- #
- # The cookies being read are the ones received along with the request, the cookies
- # being written will be sent out with the response. Reading a cookie does not get
- # the cookie object itself back, just the value it holds.
- #
- # Examples for writing:
- #
- # # Sets a simple session cookie.
- # cookies[:user_name] = "david"
- #
- # # Sets a cookie that expires in 1 hour.
- # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
- #
- # Examples for reading:
- #
- # cookies[:user_name] # => "david"
- # cookies.size # => 2
- #
- # Example for deleting:
- #
- # cookies.delete :user_name
- #
- # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
- #
- # cookies[:key] = {
- # :value => 'a yummy cookie',
- # :expires => 1.year.from_now,
- # :domain => 'domain.com'
- # }
- #
- # cookies.delete(:key, :domain => 'domain.com')
- #
- # The option symbols for setting cookies are:
- #
- # * <tt>:value</tt> - The cookie's value or list of values (as an array).
- # * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
- # of the application.
- # * <tt>:domain</tt> - The domain for which this cookie applies.
- # * <tt>:expires</tt> - The time at which this cookie expires, as a Time object.
- # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
- # Default is +false+.
- # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
- # only HTTP. Defaults to +false+.
module Cookies
extend ActiveSupport::Concern
@@ -52,146 +8,10 @@ module ActionController #:nodoc:
helper_method :cookies
cattr_accessor :cookie_verifier_secret
end
-
- protected
- # Returns the cookie container, which operates as described above.
+
+ private
def cookies
- @cookies ||= CookieJar.build(request, response)
- end
- end
-
- class CookieJar < Hash #:nodoc:
- def self.build(request, response)
- new.tap do |hash|
- hash.update(request.cookies)
- hash.response = response
- end
- end
-
- attr_accessor :response
-
- # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
- def [](name)
- super(name.to_s)
- end
-
- # Sets the cookie named +name+. The second argument may be the very cookie
- # value, or a hash of options as documented above.
- def []=(key, options)
- if options.is_a?(Hash)
- options.symbolize_keys!
- value = options[:value]
- else
- value = options
- options = { :value => value }
+ request.cookie_jar
end
-
- super(key.to_s, value)
-
- options[:path] ||= "/"
- response.set_cookie(key, options)
- end
-
- # Removes the cookie on the client machine by setting the value to an empty string
- # and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
- # an options hash to delete cookies with extra data such as a <tt>:path</tt>.
- def delete(key, options = {})
- options.symbolize_keys!
- options[:path] ||= "/"
- value = super(key.to_s)
- response.delete_cookie(key, options)
- value
- end
-
- # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
- #
- # cookies.permanent[:prefers_open_id] = true
- # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
- #
- # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
- #
- # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
- #
- # cookies.permanent.signed[:remember_me] = current_user.id
- # # => Set-Cookie: discount=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
- def permanent
- @permanent ||= PermanentCookieJar.new(self)
- end
-
- # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
- # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
- # cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
- # be raised.
- #
- # This jar requires that you set a suitable secret for the verification on ActionController::Base.cookie_verifier_secret.
- #
- # Example:
- #
- # cookies.signed[:discount] = 45
- # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
- #
- # cookies.signed[:discount] # => 45
- def signed
- @signed ||= SignedCookieJar.new(self)
- end
- end
-
- class PermanentCookieJar < CookieJar #:nodoc:
- def initialize(parent_jar)
- @parent_jar = parent_jar
- end
-
- def []=(key, options)
- if options.is_a?(Hash)
- options.symbolize_keys!
- else
- options = { :value => options }
- end
-
- options[:expires] = 20.years.from_now
- @parent_jar[key] = options
- end
-
- def signed
- @signed ||= SignedCookieJar.new(self)
- end
-
- def controller
- @parent_jar.controller
- end
-
- def method_missing(method, *arguments, &block)
- @parent_jar.send(method, *arguments, &block)
- end
- end
-
- class SignedCookieJar < CookieJar #:nodoc:
- def initialize(parent_jar)
- unless ActionController::Base.cookie_verifier_secret
- raise "You must set ActionController::Base.cookie_verifier_secret to use signed cookies"
- end
-
- @parent_jar = parent_jar
- @verifier = ActiveSupport::MessageVerifier.new(ActionController::Base.cookie_verifier_secret)
- end
-
- def [](name)
- @verifier.verify(@parent_jar[name])
- end
-
- def []=(key, options)
- if options.is_a?(Hash)
- options.symbolize_keys!
- options[:value] = @verifier.generate(options[:value])
- else
- options = { :value => @verifier.generate(options) }
- end
-
- @parent_jar[key] = options
- end
-
- def method_missing(method, *arguments, &block)
- @parent_jar.send(method, *arguments, &block)
- end
end
end
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 7222b7b2fa..876f778751 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -20,11 +20,7 @@ module ActionController
result = super
payload[:controller] = self.class.name
payload[:action] = self.action_name
- payload[:formats] = request.formats.map(&:to_s)
- payload[:remote_ip] = request.remote_ip
- payload[:method] = request.method
payload[:status] = response.status
- payload[:request_uri] = request.request_uri rescue "unknown"
append_info_to_payload(payload)
result
end
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index cb0e600871..6178a59029 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -1,7 +1,7 @@
module ActionController #:nodoc:
- # Responder is responsible to expose a resource for different mime requests,
+ # Responder is responsible for exposing a resource to different mime requests,
# usually depending on the HTTP verb. The responder is triggered when
- # respond_with is called. The simplest case to study is a GET request:
+ # <code>respond_with</code> is called. The simplest case to study is a GET request:
#
# class PeopleController < ApplicationController
# respond_to :html, :xml, :json
@@ -12,17 +12,17 @@ module ActionController #:nodoc:
# end
# end
#
- # When a request comes, for example with format :xml, three steps happen:
+ # When a request comes in, for example for an XML response, three steps happen:
#
- # 1) responder searches for a template at people/index.xml;
+ # 1) the responder searches for a template at people/index.xml;
#
- # 2) if the template is not available, it will invoke :to_xml in the given resource;
+ # 2) if the template is not available, it will invoke <code>#to_xml</code> on the given resource;
#
- # 3) if the responder does not respond_to :to_xml, call :to_format on it.
+ # 3) if the responder does not <code>respond_to :to_xml</code>, call <code>#to_format</code> on it.
#
# === Builtin HTTP verb semantics
#
- # Rails default responder holds semantics for each HTTP verb. Depending on the
+ # The default Rails responder holds semantics for each HTTP verb. Depending on the
# content type, verb and the resource status, it will behave differently.
#
# Using Rails default responder, a POST request for creating an object could
@@ -55,7 +55,7 @@ module ActionController #:nodoc:
#
# === Nested resources
#
- # You can given nested resource as you do in form_for and polymorphic_url.
+ # You can supply nested resources as you do in <code>form_for</code> and <code>polymorphic_url</code>.
# Consider the project has many tasks example. The create action for
# TasksController would be like:
#
@@ -67,15 +67,15 @@ module ActionController #:nodoc:
# end
#
# Giving an array of resources, you ensure that the responder will redirect to
- # project_task_url instead of task_url.
+ # <code>project_task_url</code> instead of <code>task_url</code>.
#
- # Namespaced and singleton resources requires a symbol to be given, as in
+ # Namespaced and singleton resources require a symbol to be given, as in
# polymorphic urls. If a project has one manager which has many tasks, it
# should be invoked as:
#
# respond_with(@project, :manager, @task)
#
- # Check polymorphic_url documentation for more examples.
+ # Check <code>polymorphic_url</code> documentation for more examples.
#
class Responder
attr_reader :controller, :request, :format, :resource, :resources, :options
@@ -126,7 +126,7 @@ module ActionController #:nodoc:
navigation_behavior(e)
end
- # All others formats follow the procedure below. First we try to render a
+ # All other formats follow the procedure below. First we try to render a
# template, if the template is not available, we verify if the resource
# responds to :to_format and display it.
#
@@ -183,11 +183,11 @@ module ActionController #:nodoc:
@default_response.call
end
- # display is just a shortcut to render a resource with the current format.
+ # Display is just a shortcut to render a resource with the current format.
#
# display @user, :status => :ok
#
- # For xml request is equivalent to:
+ # For XML requests it's equivalent to:
#
# render :xml => @user, :status => :ok
#
@@ -204,14 +204,14 @@ module ActionController #:nodoc:
controller.render given_options.merge!(options).merge!(format => resource)
end
- # Check if the resource has errors or not.
+ # Check whether the resource has errors.
#
def has_errors?
resource.respond_to?(:errors) && !resource.errors.empty?
end
- # By default, render the :edit action for html requests with failure, unless
- # the verb is post.
+ # By default, render the <code>:edit</code> action for HTML requests with failure, unless
+ # the verb is POST.
#
def default_action
@action ||= ACTIONS_FOR_VERBS[request.method]
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index c193a5eff4..d62269b9af 100644
--- a/actionpack/lib/action_controller/metal/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -10,6 +10,9 @@ module ActionController
@_response = response
@_response.request = request
ret = process(request.parameters[:action])
+ if cookies = @_request.env['action_dispatch.cookies']
+ cookies.write(@_response)
+ end
@_response.body ||= self.response_body
@_response.prepare!
set_test_assigns
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index 741101a210..7ea64c1923 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -23,7 +23,6 @@ module ActionController
initializer "action_controller.initialize_routing" do |app|
app.route_configuration_files << app.config.routes_configuration_file
app.route_configuration_files << app.config.builtin_routes_configuration_file
- app.reload_routes!
end
initializer "action_controller.initialize_framework_caches" do
@@ -40,54 +39,5 @@ module ActionController
ActionController::Base.view_paths = view_path if ActionController::Base.view_paths.blank?
end
- class MetalMiddlewareBuilder
- def initialize(metals)
- @metals = metals
- end
-
- def new(app)
- ActionDispatch::Cascade.new(@metals, app)
- end
-
- def name
- ActionDispatch::Cascade.name
- end
- alias_method :to_s, :name
- end
-
- initializer "action_controller.initialize_metal" do |app|
- metal_root = "#{Rails.root}/app/metal"
- load_list = app.config.metals || Dir["#{metal_root}/**/*.rb"]
-
- metals = load_list.map { |metal|
- metal = File.basename(metal.gsub("#{metal_root}/", ''), '.rb')
- require_dependency metal
- metal.camelize.constantize
- }.compact
-
- middleware = MetalMiddlewareBuilder.new(metals)
- app.config.middleware.insert_before(:"ActionDispatch::ParamsParser", middleware)
- end
-
- # Prepare dispatcher callbacks and run 'prepare' callbacks
- initializer "action_controller.prepare_dispatcher" do |app|
- # TODO: This used to say unless defined?(Dispatcher). Find out why and fix.
- # Notice that at this point, ActionDispatch::Callbacks were already loaded.
- require 'rails/dispatcher'
- ActionController::Dispatcher.prepare_each_request = true unless app.config.cache_classes
-
- unless app.config.cache_classes
- # Setup dev mode route reloading
- routes_last_modified = app.routes_changed_at
- reload_routes = lambda do
- unless app.routes_changed_at == routes_last_modified
- routes_last_modified = app.routes_changed_at
- app.reload_routes!
- end
- end
- ActionDispatch::Callbacks.before { |callbacks| reload_routes.call }
- end
- end
-
end
end
diff --git a/actionpack/lib/action_controller/railties/subscriber.rb b/actionpack/lib/action_controller/railties/subscriber.rb
index a9f5d16c58..6659e5df47 100644
--- a/actionpack/lib/action_controller/railties/subscriber.rb
+++ b/actionpack/lib/action_controller/railties/subscriber.rb
@@ -3,18 +3,13 @@ module ActionController
class Subscriber < Rails::Subscriber
def process_action(event)
payload = event.payload
-
- info "\nProcessed #{payload[:controller]}##{payload[:action]} " \
- "to #{payload[:formats].join(', ')} (for #{payload[:remote_ip]} at #{event.time.to_s(:db)}) " \
- "[#{payload[:method].to_s.upcase}]"
-
info " Parameters: #{payload[:params].inspect}" unless payload[:params].blank?
additions = ActionController::Base.log_process_action(payload)
message = "Completed in %.0fms" % event.duration
message << " (#{additions.join(" | ")})" unless additions.blank?
- message << " | #{payload[:status]} [#{payload[:request_uri]}]\n\n"
+ message << " by #{payload[:controller]}##{payload[:action]} [#{payload[:status]}]"
info(message)
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 71bb67d3ff..082562d921 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -43,8 +43,10 @@ module ActionDispatch
autoload_under 'middleware' do
autoload :Callbacks
autoload :Cascade
+ autoload :Cookies
autoload :Flash
autoload :Head
+ autoload :Notifications
autoload :ParamsParser
autoload :Rescue
autoload :ShowExceptions
@@ -55,7 +57,15 @@ module ActionDispatch
autoload :Routing
module Http
- autoload :Headers, 'action_dispatch/http/headers'
+ extend ActiveSupport::Autoload
+
+ autoload :Cache
+ autoload :Headers
+ autoload :MimeNegotiation
+ autoload :Parameters
+ autoload :Upload
+ autoload :UploadedFile, 'action_dispatch/http/upload'
+ autoload :URL
end
module Session
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
new file mode 100644
index 0000000000..428e62dc6b
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -0,0 +1,123 @@
+module ActionDispatch
+ module Http
+ module Cache
+ module Request
+ def if_modified_since
+ if since = env['HTTP_IF_MODIFIED_SINCE']
+ Time.rfc2822(since) rescue nil
+ end
+ end
+
+ def if_none_match
+ env['HTTP_IF_NONE_MATCH']
+ end
+
+ def not_modified?(modified_at)
+ if_modified_since && modified_at && if_modified_since >= modified_at
+ end
+
+ def etag_matches?(etag)
+ if_none_match && if_none_match == etag
+ end
+
+ # Check response freshness (Last-Modified and ETag) against request
+ # If-Modified-Since and If-None-Match conditions. If both headers are
+ # supplied, both must match, or the request is not considered fresh.
+ def fresh?(response)
+ last_modified = if_modified_since
+ etag = if_none_match
+
+ return false unless last_modified || etag
+
+ success = true
+ success &&= not_modified?(response.last_modified) if last_modified
+ success &&= etag_matches?(response.etag) if etag
+ success
+ end
+ end
+
+ module Response
+ def cache_control
+ @cache_control ||= {}
+ end
+
+ def last_modified
+ if last = headers['Last-Modified']
+ Time.httpdate(last)
+ end
+ end
+
+ def last_modified?
+ headers.include?('Last-Modified')
+ end
+
+ def last_modified=(utc_time)
+ headers['Last-Modified'] = utc_time.httpdate
+ end
+
+ def etag
+ @etag
+ end
+
+ def etag?
+ @etag
+ end
+
+ def etag=(etag)
+ key = ActiveSupport::Cache.expand_cache_key(etag)
+ @etag = %("#{Digest::MD5.hexdigest(key)}")
+ end
+
+ private
+
+ def handle_conditional_get!
+ if etag? || last_modified? || !@cache_control.empty?
+ set_conditional_cache_control!
+ elsif nonempty_ok_response?
+ self.etag = @body
+
+ if request && request.etag_matches?(etag)
+ self.status = 304
+ self.body = []
+ end
+
+ set_conditional_cache_control!
+ else
+ headers["Cache-Control"] = "no-cache"
+ end
+ end
+
+ def nonempty_ok_response?
+ @status == 200 && string_body?
+ end
+
+ def string_body?
+ !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) }
+ end
+
+ DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
+
+ def set_conditional_cache_control!
+ control = @cache_control
+
+ if control.empty?
+ headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
+ elsif @cache_control[:no_cache]
+ headers["Cache-Control"] = "no-cache"
+ else
+ extras = control[:extras]
+ max_age = control[:max_age]
+
+ options = []
+ options << "max-age=#{max_age.to_i}" if max_age
+ options << (control[:public] ? "public" : "private")
+ options << "must-revalidate" if control[:must_revalidate]
+ options.concat(extras) if extras
+
+ headers["Cache-Control"] = options.join(", ")
+ end
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
new file mode 100644
index 0000000000..40617e239a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -0,0 +1,101 @@
+module ActionDispatch
+ module Http
+ module MimeNegotiation
+ # 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
+ @env["action_dispatch.request.content_type"] ||= begin
+ if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
+ Mime::Type.lookup($1.strip.downcase)
+ else
+ nil
+ end
+ end
+ end
+
+ # Returns the accepted MIME type for the request.
+ def accepts
+ @env["action_dispatch.request.accepts"] ||= begin
+ header = @env['HTTP_ACCEPT'].to_s.strip
+
+ if header.empty?
+ [content_type]
+ else
+ Mime::Type.parse(header)
+ end
+ end
+ end
+
+ # Returns the Mime type for the \format used in the request.
+ #
+ # GET /posts/5.xml | request.format => Mime::XML
+ # GET /posts/5.xhtml | request.format => Mime::HTML
+ # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
+ #
+ def format(view_path = [])
+ formats.first
+ end
+
+ def formats
+ accept = @env['HTTP_ACCEPT']
+
+ @env["action_dispatch.request.formats"] ||=
+ if parameters[:format]
+ Array(Mime[parameters[:format]])
+ elsif xhr? || (accept && !accept.include?(?,))
+ accepts
+ else
+ [Mime::HTML]
+ end
+ end
+
+ # Sets the \format by string extension, which can be used to force custom formats
+ # that are not controlled by the extension.
+ #
+ # class ApplicationController < ActionController::Base
+ # before_filter :adjust_format_for_iphone
+ #
+ # private
+ # def adjust_format_for_iphone
+ # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
+ # end
+ # end
+ def format=(extension)
+ parameters[:format] = extension.to_s
+ @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
+ end
+
+ # Returns a symbolized version of the <tt>:format</tt> parameter of the request.
+ # If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
+ # otherwise.
+ def template_format
+ parameter_format = parameters[:format]
+
+ if parameter_format
+ parameter_format
+ elsif xhr?
+ :js
+ else
+ :html
+ end
+ end
+
+ # Receives an array of mimes and return the first user sent mime that
+ # matches the order array.
+ #
+ def negotiate_mime(order)
+ formats.each do |priority|
+ if priority == Mime::ALL
+ return order.first
+ elsif order.include?(priority)
+ return priority
+ end
+ end
+
+ order.include?(Mime::ALL) ? formats.first : nil
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
new file mode 100644
index 0000000000..97546d5f93
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -0,0 +1,50 @@
+require 'active_support/core_ext/hash/keys'
+
+module ActionDispatch
+ module Http
+ module Parameters
+ # Returns both GET and POST \parameters in a single hash.
+ def parameters
+ @env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
+ end
+ alias :params :parameters
+
+ def path_parameters=(parameters) #:nodoc:
+ @env.delete("action_dispatch.request.symbolized_path_parameters")
+ @env.delete("action_dispatch.request.parameters")
+ @env["action_dispatch.request.path_parameters"] = parameters
+ end
+
+ # The same as <tt>path_parameters</tt> with explicitly symbolized keys.
+ def symbolized_path_parameters
+ @env["action_dispatch.request.symbolized_path_parameters"] ||= path_parameters.symbolize_keys
+ end
+
+ # Returns a hash with the \parameters used to form the \path of the request.
+ # Returned hash keys are strings:
+ #
+ # {'action' => 'my_action', 'controller' => 'my_controller'}
+ #
+ # See <tt>symbolized_path_parameters</tt> for symbolized keys.
+ def path_parameters
+ @env["action_dispatch.request.path_parameters"] ||= {}
+ end
+
+ private
+
+ # Convert nested Hashs to HashWithIndifferentAccess
+ def normalize_parameters(value)
+ case value
+ when Hash
+ h = {}
+ value.each { |k, v| h[k] = normalize_parameters(v) }
+ h.with_indifferent_access
+ when Array
+ value.map { |e| normalize_parameters(e) }
+ else
+ value
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 22a08ec10d..187ce7c15d 100755
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -2,14 +2,17 @@ require 'tempfile'
require 'stringio'
require 'strscan'
-require 'active_support/memoizable'
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/string/access'
require 'action_dispatch/http/headers'
module ActionDispatch
class Request < Rack::Request
+ include ActionDispatch::Http::Cache::Request
+ include ActionDispatch::Http::MimeNegotiation
+ include ActionDispatch::Http::Parameters
+ include ActionDispatch::Http::Upload
+ include ActionDispatch::Http::URL
%w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
@@ -19,9 +22,11 @@ module ActionDispatch
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
HTTP_NEGOTIATE HTTP_PRAGMA ].each do |env|
- define_method(env.sub(/^HTTP_/n, '').downcase) do
- @env[env]
- end
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
+ def #{env.sub(/^HTTP_/n, '').downcase}
+ @env["#{env}"]
+ end
+ METHOD
end
def key?(key)
@@ -81,25 +86,6 @@ module ActionDispatch
Http::Headers.new(@env)
end
- # Returns the content length of the request as an integer.
- def content_length
- super.to_i
- end
-
- # The MIME type of the HTTP request, such as Mime::XML.
- #
- # For backward compatibility, the post \format is extracted from the
- # X-Post-Data-Format HTTP header if present.
- def content_type
- @env["action_dispatch.request.content_type"] ||= begin
- if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
- Mime::Type.lookup($1.strip.downcase)
- else
- nil
- end
- end
- end
-
def forgery_whitelisted?
method == :get || xhr? || content_type.nil? || !content_type.verify_request?
end
@@ -108,104 +94,9 @@ module ActionDispatch
content_type.to_s
end
- # Returns the accepted MIME type for the request.
- def accepts
- @env["action_dispatch.request.accepts"] ||= begin
- header = @env['HTTP_ACCEPT'].to_s.strip
-
- if header.empty?
- [content_type]
- else
- Mime::Type.parse(header)
- end
- end
- end
-
- def if_modified_since
- if since = env['HTTP_IF_MODIFIED_SINCE']
- Time.rfc2822(since) rescue nil
- end
- end
-
- def if_none_match
- env['HTTP_IF_NONE_MATCH']
- end
-
- def not_modified?(modified_at)
- if_modified_since && modified_at && if_modified_since >= modified_at
- end
-
- def etag_matches?(etag)
- if_none_match && if_none_match == etag
- end
-
- # Check response freshness (Last-Modified and ETag) against request
- # If-Modified-Since and If-None-Match conditions. If both headers are
- # supplied, both must match, or the request is not considered fresh.
- def fresh?(response)
- last_modified = if_modified_since
- etag = if_none_match
-
- return false unless last_modified || etag
-
- success = true
- success &&= not_modified?(response.last_modified) if last_modified
- success &&= etag_matches?(response.etag) if etag
- success
- end
-
- # Returns the Mime type for the \format used in the request.
- #
- # GET /posts/5.xml | request.format => Mime::XML
- # GET /posts/5.xhtml | request.format => Mime::HTML
- # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
- #
- def format(view_path = [])
- formats.first
- end
-
- def formats
- accept = @env['HTTP_ACCEPT']
-
- @env["action_dispatch.request.formats"] ||=
- if parameters[:format]
- Array.wrap(Mime[parameters[:format]])
- elsif xhr? || (accept && !accept.include?(?,))
- accepts
- else
- [Mime::HTML]
- end
- end
-
- # Sets the \format by string extension, which can be used to force custom formats
- # that are not controlled by the extension.
- #
- # class ApplicationController < ActionController::Base
- # before_filter :adjust_format_for_iphone
- #
- # private
- # def adjust_format_for_iphone
- # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
- # end
- # end
- def format=(extension)
- parameters[:format] = extension.to_s
- @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
- end
-
- # Returns a symbolized version of the <tt>:format</tt> parameter of the request.
- # If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
- # otherwise.
- def template_format
- parameter_format = parameters[:format]
-
- if parameter_format
- parameter_format
- elsif xhr?
- :js
- else
- :html
- end
+ # Returns the content length of the request as an integer.
+ def content_length
+ super.to_i
end
# Returns true if the request's "X-Requested-With" header contains
@@ -238,7 +129,7 @@ module ActionDispatch
if @env.include? 'HTTP_CLIENT_IP'
if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
# We don't know which came from the proxy, and which from the user
- raise ActionController::ActionControllerError.new(<<EOM)
+ raise ActionController::ActionControllerError.new <<EOM
IP spoofing attack?!
HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}
HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}
@@ -264,124 +155,6 @@ EOM
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
end
- # Returns the complete URL used for this request.
- def url
- protocol + host_with_port + request_uri
- end
-
- # Returns 'https://' if this is an SSL request and 'http://' otherwise.
- def protocol
- ssl? ? 'https://' : 'http://'
- end
-
- # Is this an SSL request?
- def ssl?
- @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
- end
-
- # Returns the \host for this request, such as "example.com".
- def raw_host_with_port
- if forwarded = env["HTTP_X_FORWARDED_HOST"]
- forwarded.split(/,\s?/).last
- else
- env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
- end
- end
-
- # Returns the host for this request, such as example.com.
- def host
- raw_host_with_port.sub(/:\d+$/, '')
- end
-
- # Returns a \host:\port string for this request, such as "example.com" or
- # "example.com:8080".
- def host_with_port
- "#{host}#{port_string}"
- end
-
- # Returns the port number of this request as an integer.
- def port
- if raw_host_with_port =~ /:(\d+)$/
- $1.to_i
- else
- standard_port
- end
- end
-
- # Returns the standard \port number for this request's protocol.
- def standard_port
- case protocol
- when 'https://' then 443
- else 80
- end
- end
-
- # Returns a \port suffix like ":8080" if the \port number of this request
- # is not the default HTTP \port 80 or HTTPS \port 443.
- def port_string
- port == standard_port ? '' : ":#{port}"
- end
-
- def server_port
- @env['SERVER_PORT'].to_i
- end
-
- # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
- # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
- def domain(tld_length = 1)
- return nil unless named_host?(host)
-
- host.split('.').last(1 + tld_length).join('.')
- end
-
- # Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
- # returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
- # such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
- # in "www.rubyonrails.co.uk".
- def subdomains(tld_length = 1)
- return [] unless named_host?(host)
- parts = host.split('.')
- parts[0..-(tld_length+2)]
- end
-
- # Returns the query string, accounting for server idiosyncrasies.
- def query_string
- @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '')
- end
-
- # Returns the request URI, accounting for server idiosyncrasies.
- # WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
- def request_uri
- if uri = @env['REQUEST_URI']
- # Remove domain, which webrick puts into the request_uri.
- (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
- else
- # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
- uri = @env['PATH_INFO'].to_s
-
- if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
- uri = uri.sub(/#{script_filename}\//, '')
- end
-
- env_qs = @env['QUERY_STRING'].to_s
- uri += "?#{env_qs}" unless env_qs.empty?
-
- if uri.blank?
- @env.delete('REQUEST_URI')
- else
- @env['REQUEST_URI'] = uri
- end
- end
- end
-
- # Returns the interpreted \path to requested resource after all the installation
- # directory of this application was taken into account.
- def path
- path = request_uri.to_s[/\A[^\?]*/]
- path.sub!(/\A#{ActionController::Base.relative_url_root}/, '')
- path
- end
-
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
@@ -392,33 +165,6 @@ EOM
@env['RAW_POST_DATA']
end
- # Returns both GET and POST \parameters in a single hash.
- def parameters
- @env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
- end
- alias_method :params, :parameters
-
- def path_parameters=(parameters) #:nodoc:
- @env.delete("action_dispatch.request.symbolized_path_parameters")
- @env.delete("action_dispatch.request.parameters")
- @env["action_dispatch.request.path_parameters"] = parameters
- end
-
- # The same as <tt>path_parameters</tt> with explicitly symbolized keys.
- def symbolized_path_parameters
- @env["action_dispatch.request.symbolized_path_parameters"] ||= path_parameters.symbolize_keys
- end
-
- # Returns a hash with the \parameters used to form the \path of the request.
- # Returned hash keys are strings:
- #
- # {'action' => 'my_action', 'controller' => 'my_controller'}
- #
- # See <tt>symbolized_path_parameters</tt> for symbolized keys.
- def path_parameters
- @env["action_dispatch.request.path_parameters"] ||= {}
- 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
@@ -434,18 +180,6 @@ EOM
FORM_DATA_MEDIA_TYPES.include?(content_type.to_s)
end
- # Override Rack's GET method to support indifferent access
- def GET
- @env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super)
- end
- alias_method :query_parameters, :GET
-
- # Override Rack's POST method to support indifferent access
- def POST
- @env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super)
- end
- alias_method :request_parameters, :POST
-
def body_stream #:nodoc:
@env['rack.input']
end
@@ -463,6 +197,19 @@ EOM
@env['rack.session.options'] = options
end
+ # Override Rack's GET method to support indifferent access
+ def GET
+ @env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super)
+ end
+ alias :query_parameters :GET
+
+ # Override Rack's POST method to support indifferent access
+ def POST
+ @env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super)
+ end
+ alias :request_parameters :POST
+
+
# Returns the authorization header regardless of whether it was specified directly or through one of the
# proxy alternatives.
def authorization
@@ -471,77 +218,5 @@ EOM
@env['X_HTTP_AUTHORIZATION'] ||
@env['REDIRECT_X_HTTP_AUTHORIZATION']
end
-
- # Receives an array of mimes and return the first user sent mime that
- # matches the order array.
- #
- def negotiate_mime(order)
- formats.each do |priority|
- if priority == Mime::ALL
- return order.first
- elsif order.include?(priority)
- return priority
- end
- end
-
- order.include?(Mime::ALL) ? formats.first : nil
- end
-
- private
-
- def named_host?(host)
- !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
- end
-
- module UploadedFile
- def self.extended(object)
- object.class_eval do
- attr_accessor :original_path, :content_type
- alias_method :local_path, :path if method_defined?(: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
-
- # Convert nested Hashs to HashWithIndifferentAccess and replace
- # file upload hashs with UploadedFile objects
- def normalize_parameters(value)
- case value
- when Hash
- if value.has_key?(:tempfile)
- upload = value[:tempfile]
- upload.extend(UploadedFile)
- upload.original_path = value[:filename]
- upload.content_type = value[:type]
- upload
- else
- h = {}
- value.each { |k, v| h[k] = normalize_parameters(v) }
- h.with_indifferent_access
- end
- when Array
- value.map { |e| normalize_parameters(e) }
- else
- value
- end
- end
end
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 8524bbd993..f299306ff4 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -32,6 +32,8 @@ module ActionDispatch # :nodoc:
# end
# end
class Response < Rack::Response
+ include ActionDispatch::Http::Cache::Response
+
attr_accessor :request, :blank
attr_writer :header, :sending_file
@@ -55,10 +57,6 @@ module ActionDispatch # :nodoc:
yield self if block_given?
end
- def cache_control
- @cache_control ||= {}
- end
-
def status=(status)
@status = Rack::Utils.status_code(status)
end
@@ -114,33 +112,6 @@ module ActionDispatch # :nodoc:
# information.
attr_accessor :charset, :content_type
- def last_modified
- if last = headers['Last-Modified']
- Time.httpdate(last)
- end
- end
-
- def last_modified?
- headers.include?('Last-Modified')
- end
-
- def last_modified=(utc_time)
- headers['Last-Modified'] = utc_time.httpdate
- end
-
- def etag
- @etag
- end
-
- def etag?
- @etag
- end
-
- def etag=(etag)
- key = ActiveSupport::Cache.expand_cache_key(etag)
- @etag = %("#{Digest::MD5.hexdigest(key)}")
- end
-
CONTENT_TYPE = "Content-Type"
cattr_accessor(:default_charset) { "utf-8" }
@@ -148,7 +119,7 @@ module ActionDispatch # :nodoc:
def to_a
assign_default_content_type_and_charset!
handle_conditional_get!
- self["Set-Cookie"] = @cookie.join("\n")
+ self["Set-Cookie"] = @cookie.join("\n") unless @cookie.blank?
self["ETag"] = @etag if @etag
super
end
@@ -222,31 +193,6 @@ module ActionDispatch # :nodoc:
end
private
- def handle_conditional_get!
- if etag? || last_modified? || !@cache_control.empty?
- set_conditional_cache_control!
- elsif nonempty_ok_response?
- self.etag = @body
-
- if request && request.etag_matches?(etag)
- self.status = 304
- self.body = []
- end
-
- set_conditional_cache_control!
- else
- headers["Cache-Control"] = "no-cache"
- end
- end
-
- def nonempty_ok_response?
- @status == 200 && string_body?
- end
-
- def string_body?
- !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) }
- end
-
def assign_default_content_type_and_charset!
return if headers[CONTENT_TYPE].present?
@@ -259,27 +205,5 @@ module ActionDispatch # :nodoc:
headers[CONTENT_TYPE] = type
end
- DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
-
- def set_conditional_cache_control!
- control = @cache_control
-
- if control.empty?
- headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
- elsif @cache_control[:no_cache]
- headers["Cache-Control"] = "no-cache"
- else
- extras = control[:extras]
- max_age = control[:max_age]
-
- options = []
- options << "max-age=#{max_age.to_i}" if max_age
- options << (control[:public] ? "public" : "private")
- options << "must-revalidate" if control[:must_revalidate]
- options.concat(extras) if extras
-
- headers["Cache-Control"] = options.join(", ")
- end
- end
end
end
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
new file mode 100644
index 0000000000..dc6121b911
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -0,0 +1,48 @@
+module ActionDispatch
+ module Http
+ module UploadedFile
+ def self.extended(object)
+ object.class_eval do
+ attr_accessor :original_path, :content_type
+ alias_method :local_path, :path if method_defined?(: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
+
+ module Upload
+ # Convert nested Hashs to HashWithIndifferentAccess and replace
+ # file upload hashs with UploadedFile objects
+ def normalize_parameters(value)
+ if Hash === value && value.has_key?(:tempfile)
+ upload = value[:tempfile]
+ upload.extend(UploadedFile)
+ upload.original_path = value[:filename]
+ upload.content_type = value[:type]
+ upload
+ else
+ super
+ end
+ end
+ private :normalize_parameters
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
new file mode 100644
index 0000000000..40ceb5a9b6
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -0,0 +1,129 @@
+module ActionDispatch
+ module Http
+ module URL
+ # Returns the complete URL used for this request.
+ def url
+ protocol + host_with_port + request_uri
+ end
+
+ # Returns 'https://' if this is an SSL request and 'http://' otherwise.
+ def protocol
+ ssl? ? 'https://' : 'http://'
+ end
+
+ # Is this an SSL request?
+ def ssl?
+ @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
+ end
+
+ # Returns the \host for this request, such as "example.com".
+ def raw_host_with_port
+ if forwarded = env["HTTP_X_FORWARDED_HOST"]
+ forwarded.split(/,\s?/).last
+ else
+ env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
+ end
+ end
+
+ # Returns the host for this request, such as example.com.
+ def host
+ raw_host_with_port.sub(/:\d+$/, '')
+ end
+
+ # Returns a \host:\port string for this request, such as "example.com" or
+ # "example.com:8080".
+ def host_with_port
+ "#{host}#{port_string}"
+ end
+
+ # Returns the port number of this request as an integer.
+ def port
+ if raw_host_with_port =~ /:(\d+)$/
+ $1.to_i
+ else
+ standard_port
+ end
+ end
+
+ # Returns the standard \port number for this request's protocol.
+ def standard_port
+ case protocol
+ when 'https://' then 443
+ else 80
+ end
+ end
+
+ # Returns a \port suffix like ":8080" if the \port number of this request
+ # is not the default HTTP \port 80 or HTTPS \port 443.
+ def port_string
+ port == standard_port ? '' : ":#{port}"
+ end
+
+ def server_port
+ @env['SERVER_PORT'].to_i
+ end
+
+ # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
+ # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
+ def domain(tld_length = 1)
+ return nil unless named_host?(host)
+
+ host.split('.').last(1 + tld_length).join('.')
+ end
+
+ # Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
+ # returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
+ # such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
+ # in "www.rubyonrails.co.uk".
+ def subdomains(tld_length = 1)
+ return [] unless named_host?(host)
+ parts = host.split('.')
+ parts[0..-(tld_length+2)]
+ end
+
+ # Returns the query string, accounting for server idiosyncrasies.
+ def query_string
+ @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '')
+ end
+
+ # Returns the request URI, accounting for server idiosyncrasies.
+ # WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
+ def request_uri
+ if uri = @env['REQUEST_URI']
+ # Remove domain, which webrick puts into the request_uri.
+ (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
+ else
+ # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
+ uri = @env['PATH_INFO'].to_s
+
+ if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
+ uri = uri.sub(/#{script_filename}\//, '')
+ end
+
+ env_qs = @env['QUERY_STRING'].to_s
+ uri += "?#{env_qs}" unless env_qs.empty?
+
+ if uri.blank?
+ @env.delete('REQUEST_URI')
+ else
+ @env['REQUEST_URI'] = uri
+ end
+ end
+ end
+
+ # Returns the interpreted \path to requested resource after all the installation
+ # directory of this application was taken into account.
+ def path
+ path = request_uri.to_s[/\A[^\?]*/]
+ path.sub!(/\A#{ActionController::Base.relative_url_root}/, '')
+ path
+ end
+
+ private
+
+ def named_host?(host)
+ !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index 5ec406e134..d07841218a 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -45,8 +45,6 @@ module ActionDispatch
run_callbacks(:prepare) if @prepare_each_request
@app.call(env)
end
- ensure
- ActiveSupport::Notifications.instrument "action_dispatch.callback"
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
new file mode 100644
index 0000000000..0dc03a1a7e
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -0,0 +1,216 @@
+module ActionDispatch
+ class Request
+ def cookie_jar
+ env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self)
+ end
+ end
+
+ # Cookies are read and written through ActionController#cookies.
+ #
+ # The cookies being read are the ones received along with the request, the cookies
+ # being written will be sent out with the response. Reading a cookie does not get
+ # the cookie object itself back, just the value it holds.
+ #
+ # Examples for writing:
+ #
+ # # Sets a simple session cookie.
+ # cookies[:user_name] = "david"
+ #
+ # # Sets a cookie that expires in 1 hour.
+ # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
+ #
+ # Examples for reading:
+ #
+ # cookies[:user_name] # => "david"
+ # cookies.size # => 2
+ #
+ # Example for deleting:
+ #
+ # cookies.delete :user_name
+ #
+ # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
+ #
+ # cookies[:key] = {
+ # :value => 'a yummy cookie',
+ # :expires => 1.year.from_now,
+ # :domain => 'domain.com'
+ # }
+ #
+ # cookies.delete(:key, :domain => 'domain.com')
+ #
+ # The option symbols for setting cookies are:
+ #
+ # * <tt>:value</tt> - The cookie's value or list of values (as an array).
+ # * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
+ # of the application.
+ # * <tt>:domain</tt> - The domain for which this cookie applies.
+ # * <tt>:expires</tt> - The time at which this cookie expires, as a Time object.
+ # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
+ # Default is +false+.
+ # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
+ # only HTTP. Defaults to +false+.
+ class Cookies
+ class CookieJar < Hash #:nodoc:
+ def self.build(request)
+ new.tap do |hash|
+ hash.update(request.cookies)
+ end
+ end
+
+ def initialize
+ @set_cookies = {}
+ @delete_cookies = {}
+
+ super
+ end
+
+ # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
+ def [](name)
+ super(name.to_s)
+ end
+
+ # Sets the cookie named +name+. The second argument may be the very cookie
+ # value, or a hash of options as documented above.
+ def []=(key, options)
+ if options.is_a?(Hash)
+ options.symbolize_keys!
+ value = options[:value]
+ else
+ value = options
+ options = { :value => value }
+ end
+
+ value = super(key.to_s, value)
+
+ options[:path] ||= "/"
+ @set_cookies[key] = options
+ value
+ end
+
+ # Removes the cookie on the client machine by setting the value to an empty string
+ # and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
+ # an options hash to delete cookies with extra data such as a <tt>:path</tt>.
+ def delete(key, options = {})
+ options.symbolize_keys!
+ options[:path] ||= "/"
+ value = super(key.to_s)
+ @delete_cookies[key] = options
+ value
+ end
+
+ # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
+ #
+ # cookies.permanent[:prefers_open_id] = true
+ # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
+ #
+ # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
+ #
+ # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
+ #
+ # cookies.permanent.signed[:remember_me] = current_user.id
+ # # => Set-Cookie: discount=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
+ def permanent
+ @permanent ||= PermanentCookieJar.new(self)
+ end
+
+ # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
+ # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
+ # cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
+ # be raised.
+ #
+ # This jar requires that you set a suitable secret for the verification on ActionController::Base.cookie_verifier_secret.
+ #
+ # Example:
+ #
+ # cookies.signed[:discount] = 45
+ # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
+ #
+ # cookies.signed[:discount] # => 45
+ def signed
+ @signed ||= SignedCookieJar.new(self)
+ end
+
+ def write(response)
+ @set_cookies.each { |k, v| response.set_cookie(k, v) }
+ @delete_cookies.each { |k, v| response.delete_cookie(k, v) }
+ end
+ end
+
+ class PermanentCookieJar < CookieJar #:nodoc:
+ def initialize(parent_jar)
+ @parent_jar = parent_jar
+ end
+
+ def []=(key, options)
+ if options.is_a?(Hash)
+ options.symbolize_keys!
+ else
+ options = { :value => options }
+ end
+
+ options[:expires] = 20.years.from_now
+ @parent_jar[key] = options
+ end
+
+ def signed
+ @signed ||= SignedCookieJar.new(self)
+ end
+
+ def controller
+ @parent_jar.controller
+ end
+
+ def method_missing(method, *arguments, &block)
+ @parent_jar.send(method, *arguments, &block)
+ end
+ end
+
+ class SignedCookieJar < CookieJar #:nodoc:
+ def initialize(parent_jar)
+ unless ActionController::Base.cookie_verifier_secret
+ raise "You must set ActionController::Base.cookie_verifier_secret to use signed cookies"
+ end
+
+ @parent_jar = parent_jar
+ @verifier = ActiveSupport::MessageVerifier.new(ActionController::Base.cookie_verifier_secret)
+ end
+
+ def [](name)
+ if value = @parent_jar[name]
+ @verifier.verify(value)
+ end
+ end
+
+ def []=(key, options)
+ if options.is_a?(Hash)
+ options.symbolize_keys!
+ options[:value] = @verifier.generate(options[:value])
+ else
+ options = { :value => @verifier.generate(options) }
+ end
+
+ @parent_jar[key] = options
+ end
+
+ def method_missing(method, *arguments, &block)
+ @parent_jar.send(method, *arguments, &block)
+ end
+ end
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ status, headers, body = @app.call(env)
+
+ if cookie_jar = env['action_dispatch.cookies']
+ response = Rack::Response.new(body, status, headers)
+ cookie_jar.write(response)
+ response.to_a
+ else
+ [status, headers, body]
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/notifications.rb b/actionpack/lib/action_dispatch/middleware/notifications.rb
new file mode 100644
index 0000000000..01d2cbb435
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/notifications.rb
@@ -0,0 +1,24 @@
+module ActionDispatch
+ # Provide notifications in the middleware stack. Notice that for the before_dispatch
+ # and after_dispatch notifications, we just send the original env, so we don't pile
+ # up large env hashes in the queue. However, in exception cases, the whole env hash
+ # is actually useful, so we send it all.
+ class Notifications
+ def initialize(app)
+ @app = app
+ end
+
+ def call(stack_env)
+ env = stack_env.dup
+ ActiveSupport::Notifications.instrument("action_dispatch.before_dispatch", :env => env)
+
+ ActiveSupport::Notifications.instrument!("action_dispatch.after_dispatch", :env => env) do
+ @app.call(stack_env)
+ end
+ rescue Exception => exception
+ ActiveSupport::Notifications.instrument('action_dispatch.exception',
+ :env => stack_env, :exception => exception)
+ raise exception
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 10f04dcdf6..3bcd004e12 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -20,7 +20,7 @@ module ActionDispatch
# * :exception - The exception raised;
#
class ShowExceptions
- LOCALHOST = '127.0.0.1'.freeze
+ LOCALHOST = ['127.0.0.1', '::1'].freeze
RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
@@ -61,11 +61,8 @@ module ActionDispatch
def call(env)
@app.call(env)
rescue Exception => exception
- ActiveSupport::Notifications.instrument 'action_dispatch.show_exception',
- :env => env, :exception => exception do
- raise exception if env['action_dispatch.show_exceptions'] == false
- render_exception(env, exception)
- end
+ raise exception if env['action_dispatch.show_exceptions'] == false
+ render_exception(env, exception)
end
private
@@ -88,7 +85,10 @@ module ActionDispatch
def rescue_action_locally(request, exception)
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
:request => request,
- :exception => exception
+ :exception => exception,
+ :application_trace => application_trace(exception),
+ :framework_trace => framework_trace(exception),
+ :full_trace => full_trace(exception)
)
file = "rescues/#{@@rescue_templates[exception.class.name]}.erb"
body = template.render(:file => file, :layout => 'rescues/layout.erb')
@@ -118,7 +118,7 @@ module ActionDispatch
# True if the request came from localhost, 127.0.0.1.
def local_request?(request)
- request.remote_addr == LOCALHOST && request.remote_ip == LOCALHOST
+ LOCALHOST.any?{ |local_ip| request.remote_addr == local_ip && request.remote_ip == local_ip }
end
def status_code(exception)
@@ -148,9 +148,21 @@ module ActionDispatch
end
end
- def clean_backtrace(exception)
+ def application_trace(exception)
+ clean_backtrace(exception, :silent)
+ end
+
+ def framework_trace(exception)
+ clean_backtrace(exception, :noise)
+ end
+
+ def full_trace(exception)
+ clean_backtrace(exception, :all)
+ end
+
+ def clean_backtrace(exception, *args)
defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
- Rails.backtrace_cleaner.clean(exception.backtrace) :
+ Rails.backtrace_cleaner.clean(exception.backtrace, *args) :
exception.backtrace
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
index 5224403dab..839df50999 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
@@ -11,13 +11,20 @@
clean_params.delete("controller")
request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
+
+ def debug_hash(hash)
+ hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
+ end
%>
<h2 style="margin-top: 30px">Request</h2>
<p><b>Parameters</b>: <pre><%=h request_dump %></pre></p>
<p><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></p>
-<div id="session_dump" style="display:none"><%= debug(@request.session.instance_variable_get("@data")) %></div>
+<div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div>
+
+<p><a href="#" onclick="document.getElementById('env_dump').style.display='block'; return false;">Show env dump</a></p>
+<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env %></pre></div>
<h2 style="margin-top: 30px">Response</h2>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
index 07b4919934..d18b162a93 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
@@ -1,8 +1,8 @@
<%
traces = [
- ["Application Trace", @exception.application_backtrace],
- ["Framework Trace", @exception.framework_backtrace],
- ["Full Trace", @exception.clean_backtrace]
+ ["Application Trace", @application_trace],
+ ["Framework Trace", @framework_trace],
+ ["Full Trace", @full_trace]
]
names = traces.collect {|name, trace| name}
%>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
index 693e56270a..bd6ffbab5d 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
@@ -4,7 +4,7 @@
in <%=h @request.parameters['controller'].humanize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
<% end %>
</h1>
-<pre><%=h @exception.clean_message %></pre>
+<pre><%=h @exception.message %></pre>
<%= render :file => "rescues/_trace.erb" %>
<%= render :file => "rescues/_request_and_response.erb" %>
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
new file mode 100644
index 0000000000..18978bfb39
--- /dev/null
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -0,0 +1,29 @@
+require "action_dispatch"
+require "rails"
+
+module ActionDispatch
+ class Railtie < Rails::Railtie
+ plugin_name :action_dispatch
+
+ require "action_dispatch/railties/subscriber"
+ subscriber ActionDispatch::Railties::Subscriber.new
+
+ # Prepare dispatcher callbacks and run 'prepare' callbacks
+ initializer "action_dispatch.prepare_dispatcher" do |app|
+ # TODO: This used to say unless defined?(Dispatcher). Find out why and fix.
+ require 'rails/dispatcher'
+
+ unless app.config.cache_classes
+ # Setup dev mode route reloading
+ routes_last_modified = app.routes_changed_at
+ reload_routes = lambda do
+ unless app.routes_changed_at == routes_last_modified
+ routes_last_modified = app.routes_changed_at
+ app.reload_routes!
+ end
+ end
+ ActionDispatch::Callbacks.before { |callbacks| reload_routes.call }
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/railties/subscriber.rb b/actionpack/lib/action_dispatch/railties/subscriber.rb
new file mode 100644
index 0000000000..c08a844c6a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/railties/subscriber.rb
@@ -0,0 +1,17 @@
+module ActionDispatch
+ module Railties
+ class Subscriber < Rails::Subscriber
+ def before_dispatch(event)
+ request = Request.new(event.payload[:env])
+ path = request.request_uri.inspect rescue "unknown"
+
+ info "\n\nProcessing #{path} to #{request.formats.join(', ')} " <<
+ "(for #{request.remote_ip} at #{event.time.to_s(:db)}) [#{request.method.to_s.upcase}]"
+ end
+
+ def logger
+ ActionController::Base.logger
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index e99f979197..b598d6f7e2 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -193,9 +193,10 @@ module ActionDispatch
#
# With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
#
- # * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>,
- # <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
- # <tt>:any</tt> means that any method can access the route.
+ # * <tt>:method</tt> - Allows you to specify which HTTP method(s) can access the route. Possible values are
+ # <tt>:post</tt>, <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. Use an array to specify more
+ # than one method, e.g. <tt>[ :get, :post ]</tt>. The default value is <tt>:any</tt>, <tt>:any</tt> means that any
+ # method can access the route.
#
# Example:
#
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index ce5c56ae1c..811c287355 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -380,7 +380,7 @@ module ActionDispatch
end
def controller
- plural
+ options[:controller] || plural
end
def member_name
@@ -418,28 +418,12 @@ module ActionDispatch
def resource(*resources, &block)
options = resources.extract_options!
- if resources.length > 1
- raise ArgumentError if block_given?
- resources.each { |r| resource(r, options) }
- return self
- end
-
- if path_names = options.delete(:path_names)
- scope(:resources_path_names => path_names) do
- resource(resources, options)
- end
+ if verify_common_behavior_for(:resource, resources, options, &block)
return self
end
resource = SingletonResource.new(resources.pop, options)
- if @scope[:scope_level] == :resources
- nested do
- resource(resource.name, options, &block)
- end
- return self
- end
-
scope(:path => resource.name.to_s, :controller => resource.controller) do
with_scope_level(:resource, resource) do
yield if block_given?
@@ -459,28 +443,12 @@ module ActionDispatch
def resources(*resources, &block)
options = resources.extract_options!
- if resources.length > 1
- raise ArgumentError if block_given?
- resources.each { |r| resources(r, options) }
- return self
- end
-
- if path_names = options.delete(:path_names)
- scope(:resources_path_names => path_names) do
- resources(resources, options)
- end
+ if verify_common_behavior_for(:resources, resources, options, &block)
return self
end
resource = Resource.new(resources.pop, options)
- if @scope[:scope_level] == :resources
- nested do
- resources(resource.name, options, &block)
- end
- return self
- end
-
scope(:path => resource.name.to_s, :controller => resource.controller) do
with_scope_level(:resources, resource) do
yield if block_given?
@@ -595,6 +563,29 @@ module ActionDispatch
path_names[name.to_sym] || name.to_s
end
+ def verify_common_behavior_for(method, resources, options, &block)
+ if resources.length > 1
+ resources.each { |r| send(method, r, options, &block) }
+ return true
+ end
+
+ if path_names = options.delete(:path_names)
+ scope(:resources_path_names => path_names) do
+ send(method, resources.pop, options, &block)
+ end
+ return true
+ end
+
+ if @scope[:scope_level] == :resources
+ nested do
+ send(method, resources.pop, options, &block)
+ end
+ return true
+ end
+
+ false
+ end
+
def with_exclusive_name_prefix(prefix)
begin
old_name_prefix = @scope[:name_prefix]
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index fc477afb17..0c33539b4a 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -12,29 +12,29 @@ module ActionDispatch
# and a :method containing the required HTTP verb.
#
# # assert that POSTing to /items will call the create action on ItemsController
- # assert_recognizes {:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post}
+ # assert_recognizes({:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post})
#
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
# to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
# extras argument, appending the query string on the path directly will not work. For example:
#
# # assert that a path of '/items/list/1?view=print' returns the correct options
- # assert_recognizes {:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" }
+ # assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
#
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
#
# ==== Examples
# # Check the default route (i.e., the index action)
- # assert_recognizes {:controller => 'items', :action => 'index'}, 'items'
+ # assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
#
# # Test a specific action
- # assert_recognizes {:controller => 'items', :action => 'list'}, 'items/list'
+ # assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list')
#
# # Test an action with a parameter
- # assert_recognizes {:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1'
+ # assert_recognizes({:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1')
#
# # Test a custom route
- # assert_recognizes {:controller => 'items', :action => 'show', :id => '1'}, 'view/item1'
+ # assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1')
#
# # Check a Simply RESTful generated route
# assert_recognizes list_items_url, 'items/list'
@@ -103,7 +103,7 @@ module ActionDispatch
# assert_routing '/home', :controller => 'home', :action => 'index'
#
# # Test a route generated with a specific controller, action, and parameter (id)
- # assert_routing '/entries/show/23', :controller => 'entries', :action => 'show', id => 23
+ # assert_routing '/entries/show/23', :controller => 'entries', :action => 'show', :id => 23
#
# # Assert a basic route (controller + default action), with an error message if it fails
# assert_routing '/store', { :controller => 'store', :action => 'index' }, {}, {}, 'Route for store index not generated properly'
@@ -112,7 +112,7 @@ module ActionDispatch
# assert_routing 'controller/action/9', {:id => "9", :item => "square"}, {:controller => "controller", :action => "action"}, {}, {:item => "square"}
#
# # Tests a route with a HTTP method
- # assert_routing { :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" }
+ # assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" })
def assert_routing(path, options, defaults={}, extras={}, message=nil)
assert_recognizes(options, path, extras, message)
diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb
index 90863fca08..885945fde3 100644
--- a/actionpack/lib/action_view/helpers/debug_helper.rb
+++ b/actionpack/lib/action_view/helpers/debug_helper.rb
@@ -27,10 +27,10 @@ module ActionView
def debug(object)
begin
Marshal::dump(object)
- "<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", "&nbsp; ")}</pre>"
+ "<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", "&nbsp; ")}</pre>".html_safe!
rescue Exception => e # errors from Marshal or YAML
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
- "<code class='debug_dump'>#{h(object.inspect)}</code>"
+ "<code class='debug_dump'>#{h(object.inspect)}</code>".html_safe!
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 7688e786b1..048bedc7ba 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -55,6 +55,9 @@ module ActionView
# * Any other key creates standard HTML attributes for the tag.
#
# ==== Examples
+ # select_tag "people", options_from_collection_for_select(@people, "name", "id")
+ # # <select id="people" name="people"><option value="1">David</option></select>
+ #
# select_tag "people", "<option>David</option>"
# # => <select id="people" name="people"><option>David</option></select>
#
diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb
index f03ffe5ef4..657d26f0a2 100644
--- a/actionpack/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb
@@ -22,7 +22,7 @@ module ActionView
#
# Custom Use (only the mentioned tags and attributes are allowed, nothing else)
#
- # <%= sanitize @article.body, :tags => %w(table tr td), :attributes => %w(id class style)
+ # <%= sanitize @article.body, :tags => %w(table tr td), :attributes => %w(id class style) %>
#
# Add table tags to the default allowed tags
#
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index be15e227b9..814d86812d 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -226,8 +226,7 @@ module ActionView
# Returns the text with all the Textile[http://www.textism.com/tools/textile] codes turned into HTML tags.
#
# You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile].
- # <i>This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/]
- # is available</i>.
+ # <i>This method is only available if RedCloth[http://redcloth.org/] is available</i>.
#
# ==== Examples
# textilize("*This is Textile!* Rejoice!")
@@ -263,8 +262,7 @@ module ActionView
# but without the bounding <p> tag that RedCloth adds.
#
# You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile].
- # <i>This method is requires RedCloth[http://whytheluckystiff.net/ruby/redcloth/]
- # to be available</i>.
+ # <i>This method is only available if RedCloth[http://redcloth.org/] is available</i>.
#
# ==== Examples
# textilize_without_paragraph("*This is Textile!* Rejoice!")
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 10913c0fdb..7b04638ccc 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -92,6 +92,7 @@ class ActionController::IntegrationTest < ActiveSupport::TestCase
middleware.use "ActionDispatch::ShowExceptions"
middleware.use "ActionDispatch::Callbacks"
middleware.use "ActionDispatch::ParamsParser"
+ middleware.use "ActionDispatch::Cookies"
middleware.use "ActionDispatch::Flash"
middleware.use "ActionDispatch::Head"
}.build(routes || ActionController::Routing::Routes)
diff --git a/actionpack/test/activerecord/controller_runtime_test.rb b/actionpack/test/activerecord/controller_runtime_test.rb
index 37c7738301..d6f7cd80ab 100644
--- a/actionpack/test/activerecord/controller_runtime_test.rb
+++ b/actionpack/test/activerecord/controller_runtime_test.rb
@@ -37,8 +37,8 @@ module ControllerRuntimeSubscriberTest
get :show
wait
- assert_equal 2, @logger.logged(:info).size
- assert_match /\(Views: [\d\.]+ms | ActiveRecord: [\d\.]+ms\)/, @logger.logged(:info)[1]
+ assert_equal 1, @logger.logged(:info).size
+ assert_match /\(Views: [\d\.]+ms | ActiveRecord: [\d\.]+ms\)/, @logger.logged(:info)[0]
end
class SyncSubscriberTest < ActionController::TestCase
diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb
index c8e8b3857e..f5ccef8aaf 100644
--- a/actionpack/test/controller/cookie_test.rb
+++ b/actionpack/test/controller/cookie_test.rb
@@ -54,12 +54,12 @@ class CookieTest < ActionController::TestCase
cookies.permanent[:user_name] = "Jamie"
head :ok
end
-
+
def set_signed_cookie
cookies.signed[:user_id] = 45
head :ok
end
-
+
def set_permanent_signed_cookie
cookies.permanent.signed[:remember_me] = 100
head :ok
@@ -120,28 +120,6 @@ class CookieTest < ActionController::TestCase
assert_equal({"user_name" => nil}, @response.cookies)
end
- def test_cookiejar_accessor
- @request.cookies["user_name"] = "david"
- @controller.request = @request
- jar = ActionController::CookieJar.build(@controller.request, @controller.response)
- assert_equal "david", jar["user_name"]
- assert_equal nil, jar["something_else"]
- end
-
- def test_cookiejar_accessor_with_array_value
- @request.cookies["pages"] = %w{1 2 3}
- @controller.request = @request
- jar = ActionController::CookieJar.build(@controller.request, @controller.response)
- assert_equal %w{1 2 3}, jar["pages"]
- end
-
- def test_cookiejar_delete_removes_item_and_returns_its_value
- @request.cookies["user_name"] = "david"
- @controller.response = @response
- jar = ActionController::CookieJar.build(@controller.request, @controller.response)
- assert_equal "david", jar.delete("user_name")
- end
-
def test_delete_cookie_with_path
get :delete_cookie_with_path
assert_cookie_header "user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT"
@@ -157,19 +135,24 @@ class CookieTest < ActionController::TestCase
assert_match /Jamie/, @response.headers["Set-Cookie"]
assert_match %r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"]
end
-
+
def test_signed_cookie
get :set_signed_cookie
assert_equal 45, @controller.send(:cookies).signed[:user_id]
end
-
+
+ def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature
+ get :set_signed_cookie
+ assert_nil @controller.send(:cookies).signed[:non_existant_attribute]
+ end
+
def test_permanent_signed_cookie
get :set_permanent_signed_cookie
assert_match %r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"]
assert_equal 100, @controller.send(:cookies).signed[:remember_me]
end
-
+
private
def assert_cookie_header(expected)
header = @response.headers["Set-Cookie"]
diff --git a/actionpack/test/controller/filter_params_test.rb b/actionpack/test/controller/filter_params_test.rb
index d0635669c2..45949636c3 100644
--- a/actionpack/test/controller/filter_params_test.rb
+++ b/actionpack/test/controller/filter_params_test.rb
@@ -9,23 +9,6 @@ end
class FilterParamTest < ActionController::TestCase
tests FilterParamController
- class MockLogger
- attr_reader :logged
- attr_accessor :level
-
- def initialize
- @level = Logger::DEBUG
- end
-
- def method_missing(method, *args)
- @logged ||= []
- @logged << args.first unless block_given?
- @logged << yield if block_given?
- end
- end
-
- setup :set_logger
-
def test_filter_parameters_must_have_one_word
assert_raises RuntimeError do
FilterParamController.filter_parameter_logging
@@ -65,14 +48,4 @@ class FilterParamTest < ActionController::TestCase
assert !FilterParamController.action_methods.include?('filter_parameters')
assert_raise(NoMethodError) { @controller.filter_parameters([{'password' => '[FILTERED]'}]) }
end
-
- private
-
- def set_logger
- @controller.logger = MockLogger.new
- end
-
- def logs
- @logs ||= @controller.logger.logged.compact.map {|l| l.to_s.strip}
- end
end
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 85a2e7f44b..3c651ebebc 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -220,7 +220,7 @@ class FlashIntegrationTest < ActionController::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do |map|
- match ':action', :to => ActionDispatch::Session::CookieStore.new(TestController, :key => SessionKey, :secret => SessionSecret)
+ match ':action', :to => ActionDispatch::Session::CookieStore.new(FlashIntegrationTest::TestController, :key => FlashIntegrationTest::SessionKey, :secret => FlashIntegrationTest::SessionSecret)
end
yield
end
diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb
index 1f9bf7f0fb..964780eaf2 100644
--- a/actionpack/test/controller/new_base/base_test.rb
+++ b/actionpack/test/controller/new_base/base_test.rb
@@ -3,6 +3,8 @@ require 'abstract_unit'
# Tests the controller dispatching happy path
module Dispatching
class SimpleController < ActionController::Base
+ before_filter :authenticate
+
def index
render :text => "success"
end
@@ -12,12 +14,20 @@ module Dispatching
end
def modify_response_body_twice
- ret = (self.response_body = "success")
+ ret = (self.response_body = "success")
self.response_body = "#{ret}!"
end
def modify_response_headers
end
+
+ def show_actions
+ render :text => "actions: #{action_methods.to_a.join(', ')}"
+ end
+
+ protected
+ def authenticate
+ end
end
class EmptyController < ActionController::Base ; end
@@ -64,5 +74,21 @@ module Dispatching
assert_equal 'empty', EmptyController.controller_name
assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
end
+
+ test "action methods" do
+ assert_equal Set.new(%w(
+ modify_response_headers
+ modify_response_body_twice
+ index
+ modify_response_body
+ show_actions
+ )), SimpleController.action_methods
+
+ assert_equal Set.new, EmptyController.action_methods
+ assert_equal Set.new, Submodule::ContainedEmptyController.action_methods
+
+ get "/dispatching/simple/show_actions"
+ assert_body "actions: modify_response_headers, modify_response_body_twice, index, modify_response_body, show_actions"
+ end
end
end
diff --git a/actionpack/test/controller/subscriber_test.rb b/actionpack/test/controller/subscriber_test.rb
index ef1a325799..24132ee928 100644
--- a/actionpack/test/controller/subscriber_test.rb
+++ b/actionpack/test/controller/subscriber_test.rb
@@ -66,15 +66,10 @@ module ActionControllerSubscriberTest
def test_process_action
get :show
wait
- assert_equal 2, logs.size
- assert_match /Processed\sAnother::SubscribersController#show/, logs[0]
- end
-
- def test_process_action_formats
- get :show
- wait
- assert_equal 2, logs.size
- assert_match /text\/html/, logs[0]
+ assert_equal 1, logs.size
+ assert_match /Completed/, logs.first
+ assert_match /\[200\]/, logs.first
+ assert_match /Another::SubscribersController#show/, logs.first
end
def test_process_action_without_parameters
@@ -87,23 +82,14 @@ module ActionControllerSubscriberTest
get :show, :id => '10'
wait
- assert_equal 3, logs.size
- assert_equal 'Parameters: {"id"=>"10"}', logs[1]
+ assert_equal 2, logs.size
+ assert_equal 'Parameters: {"id"=>"10"}', logs[0]
end
def test_process_action_with_view_runtime
get :show
wait
- assert_match /\(Views: [\d\.]+ms\)/, logs[1]
- end
-
- def test_process_action_with_status_and_request_uri
- get :show
- wait
- last = logs.last
- assert_match /Completed/, last
- assert_match /200/, last
- assert_match /another\/subscribers\/show/, last
+ assert_match /\(Views: [\d\.]+ms\)/, logs[0]
end
def test_process_action_with_filter_parameters
@@ -112,7 +98,7 @@ module ActionControllerSubscriberTest
get :show, :lifo => 'Pratik', :amount => '420', :step => '1'
wait
- params = logs[1]
+ params = logs[0]
assert_match /"amount"=>"\[FILTERED\]"/, params
assert_match /"lifo"=>"\[FILTERED\]"/, params
assert_match /"step"=>"1"/, params
@@ -122,7 +108,7 @@ module ActionControllerSubscriberTest
get :redirector
wait
- assert_equal 3, logs.size
+ assert_equal 2, logs.size
assert_equal "Redirected to http://foo.bar/", logs[0]
end
@@ -130,7 +116,7 @@ module ActionControllerSubscriberTest
get :data_sender
wait
- assert_equal 3, logs.size
+ assert_equal 2, logs.size
assert_match /Sent data omg\.txt/, logs[0]
end
@@ -138,7 +124,7 @@ module ActionControllerSubscriberTest
get :file_sender
wait
- assert_equal 3, logs.size
+ assert_equal 2, logs.size
assert_match /Sent file/, logs[0]
assert_match /test\/fixtures\/company\.rb/, logs[0]
end
@@ -147,7 +133,7 @@ module ActionControllerSubscriberTest
get :xfile_sender
wait
- assert_equal 3, logs.size
+ assert_equal 2, logs.size
assert_match /Sent X\-Sendfile header/, logs[0]
assert_match /test\/fixtures\/company\.rb/, logs[0]
end
@@ -157,7 +143,7 @@ module ActionControllerSubscriberTest
get :with_fragment_cache
wait
- assert_equal 4, logs.size
+ assert_equal 3, logs.size
assert_match /Exist fragment\? views\/foo/, logs[0]
assert_match /Write fragment views\/foo/, logs[1]
ensure
@@ -169,7 +155,7 @@ module ActionControllerSubscriberTest
get :with_page_cache
wait
- assert_equal 3, logs.size
+ assert_equal 2, logs.size
assert_match /Write page/, logs[0]
assert_match /\/index\.html/, logs[0]
ensure
diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb
index f3ea5209f4..9df882ce75 100644
--- a/actionpack/test/dispatch/callbacks_test.rb
+++ b/actionpack/test/dispatch/callbacks_test.rb
@@ -85,18 +85,6 @@ class DispatcherTest < Test::Unit::TestCase
assert_equal 4, Foo.b
end
- def test_should_send_an_instrumentation_callback_for_async_processing
- ActiveSupport::Notifications.expects(:instrument).with("action_dispatch.callback")
- dispatch
- end
-
- def test_should_send_an_instrumentation_callback_for_async_processing_even_on_failure
- ActiveSupport::Notifications.notifier.expects(:publish)
- assert_raise RuntimeError do
- dispatch { |env| raise "OMG" }
- end
- end
-
private
def dispatch(cache_classes = true, &block)
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 02f63f7006..4697fa3e2b 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -13,8 +13,7 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal({
"Content-Type" => "text/html; charset=utf-8",
"Cache-Control" => "max-age=0, private, must-revalidate",
- "ETag" => '"65a8e27d8879283831b664bd8b7f0ad4"',
- "Set-Cookie" => ""
+ "ETag" => '"65a8e27d8879283831b664bd8b7f0ad4"'
}, headers)
parts = []
@@ -30,8 +29,7 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal({
"Content-Type" => "text/html; charset=utf-8",
"Cache-Control" => "max-age=0, private, must-revalidate",
- "ETag" => '"ebb5e89e8a94e9dd22abf5d915d112b2"',
- "Set-Cookie" => ""
+ "ETag" => '"ebb5e89e8a94e9dd22abf5d915d112b2"'
}, headers)
end
@@ -44,8 +42,7 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal 200, status
assert_equal({
"Content-Type" => "text/html; charset=utf-8",
- "Cache-Control" => "no-cache",
- "Set-Cookie" => ""
+ "Cache-Control" => "no-cache"
}, headers)
parts = []
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 5845c791a0..6dccabdb3f 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -65,7 +65,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resources :companies do
resources :people
- resource :avatar
+ resource :avatar, :controller => :avatar
end
resources :images do
@@ -294,34 +294,34 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_projects
with_test_routes do
get '/projects'
- assert_equal 'projects#index', @response.body
+ assert_equal 'project#index', @response.body
assert_equal '/projects', projects_path
post '/projects'
- assert_equal 'projects#create', @response.body
+ assert_equal 'project#create', @response.body
get '/projects.xml'
- assert_equal 'projects#index', @response.body
+ assert_equal 'project#index', @response.body
assert_equal '/projects.xml', projects_path(:format => 'xml')
get '/projects/new'
- assert_equal 'projects#new', @response.body
+ assert_equal 'project#new', @response.body
assert_equal '/projects/new', new_project_path
get '/projects/new.xml'
- assert_equal 'projects#new', @response.body
+ assert_equal 'project#new', @response.body
assert_equal '/projects/new.xml', new_project_path(:format => 'xml')
get '/projects/1'
- assert_equal 'projects#show', @response.body
+ assert_equal 'project#show', @response.body
assert_equal '/projects/1', project_path(:id => '1')
get '/projects/1.xml'
- assert_equal 'projects#show', @response.body
+ assert_equal 'project#show', @response.body
assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml')
get '/projects/1/edit'
- assert_equal 'projects#edit', @response.body
+ assert_equal 'project#edit', @response.body
assert_equal '/projects/1/edit', edit_project_path(:id => '1')
end
end
@@ -383,7 +383,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1')
get '/projects/1/companies/1/avatar'
- assert_equal 'avatars#show', @response.body
+ assert_equal 'avatar#show', @response.body
assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1')
end
end
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index ab7b9bc31b..d2c1758af1 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -137,7 +137,7 @@ class CookieStoreTest < ActionController::IntegrationTest
with_test_route_set do
get '/no_session_access'
assert_response :success
- assert_equal "", headers['Set-Cookie']
+ assert_equal nil, headers['Set-Cookie']
end
end
@@ -147,7 +147,7 @@ class CookieStoreTest < ActionController::IntegrationTest
"fef868465920f415f2c0652d6910d3af288a0367"
get '/no_session_access'
assert_response :success
- assert_equal "", headers['Set-Cookie']
+ assert_equal nil, headers['Set-Cookie']
end
end
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 951fb4a22e..def86c8323 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -53,19 +53,21 @@ class ShowExceptionsTest < ActionController::IntegrationTest
test "rescue locally from a local request" do
@app = ProductionApp
- self.remote_addr = '127.0.0.1'
+ ['127.0.0.1', '::1'].each do |ip_address|
+ self.remote_addr = ip_address
- get "/"
- assert_response 500
- assert_match /puke/, body
+ get "/"
+ assert_response 500
+ assert_match /puke/, body
- get "/not_found"
- assert_response 404
- assert_match /#{ActionController::UnknownAction.name}/, body
+ get "/not_found"
+ assert_response 404
+ assert_match /#{ActionController::UnknownAction.name}/, body
- get "/method_not_allowed"
- assert_response 405
- assert_match /ActionController::MethodNotAllowed/, body
+ get "/method_not_allowed"
+ assert_response 405
+ assert_match /ActionController::MethodNotAllowed/, body
+ end
end
test "localize public rescue message" do
@@ -104,27 +106,4 @@ class ShowExceptionsTest < ActionController::IntegrationTest
assert_response 405
assert_match /ActionController::MethodNotAllowed/, body
end
-
- test "publishes notifications" do
- # Wait pending notifications to be published
- ActiveSupport::Notifications.notifier.wait
-
- @app, event = ProductionApp, nil
- self.remote_addr = '127.0.0.1'
-
- ActiveSupport::Notifications.subscribe('action_dispatch.show_exception') do |*args|
- event = args
- end
-
- get "/"
- assert_response 500
- assert_match /puke/, body
-
- ActiveSupport::Notifications.notifier.wait
-
- assert_equal 'action_dispatch.show_exception', event.first
- assert_kind_of Hash, event.last[:env]
- assert_equal 'GET', event.last[:env]["REQUEST_METHOD"]
- assert_kind_of RuntimeError, event.last[:exception]
- end
end
diff --git a/actionpack/test/dispatch/subscriber_test.rb b/actionpack/test/dispatch/subscriber_test.rb
new file mode 100644
index 0000000000..a7f1a2659a
--- /dev/null
+++ b/actionpack/test/dispatch/subscriber_test.rb
@@ -0,0 +1,112 @@
+require "abstract_unit"
+require "rails/subscriber/test_helper"
+require "action_dispatch/railties/subscriber"
+
+module DispatcherSubscriberTest
+ Boomer = lambda do |env|
+ req = ActionDispatch::Request.new(env)
+ case req.path
+ when "/"
+ [200, {}, []]
+ else
+ raise "puke!"
+ end
+ end
+
+ App = ActionDispatch::Notifications.new(Boomer)
+
+ def setup
+ Rails::Subscriber.add(:action_dispatch, ActionDispatch::Railties::Subscriber.new)
+ @app = App
+ super
+
+ @events = []
+ ActiveSupport::Notifications.subscribe do |*args|
+ @events << args
+ end
+ end
+
+ def set_logger(logger)
+ ActionController::Base.logger = logger
+ end
+
+ def test_publishes_notifications
+ get "/"
+ wait
+
+ assert_equal 2, @events.size
+ before, after = @events
+
+ assert_equal 'action_dispatch.before_dispatch', before[0]
+ assert_kind_of Hash, before[4][:env]
+ assert_equal 'GET', before[4][:env]["REQUEST_METHOD"]
+
+ assert_equal 'action_dispatch.after_dispatch', after[0]
+ assert_kind_of Hash, after[4][:env]
+ assert_equal 'GET', after[4][:env]["REQUEST_METHOD"]
+ end
+
+ def test_publishes_notifications_even_on_failures
+ begin
+ get "/puke"
+ rescue
+ end
+
+ wait
+
+ assert_equal 3, @events.size
+ before, after, exception = @events
+
+ assert_equal 'action_dispatch.before_dispatch', before[0]
+ assert_kind_of Hash, before[4][:env]
+ assert_equal 'GET', before[4][:env]["REQUEST_METHOD"]
+
+ assert_equal 'action_dispatch.after_dispatch', after[0]
+ assert_kind_of Hash, after[4][:env]
+ assert_equal 'GET', after[4][:env]["REQUEST_METHOD"]
+
+ assert_equal 'action_dispatch.exception', exception[0]
+ assert_kind_of Hash, exception[4][:env]
+ assert_equal 'GET', exception[4][:env]["REQUEST_METHOD"]
+ assert_kind_of RuntimeError, exception[4][:exception]
+ end
+
+ def test_subscriber_logs_notifications
+ get "/"
+ wait
+
+ log = @logger.logged(:info).first
+ assert_equal 1, @logger.logged(:info).size
+
+ assert_match %r{^Processing "/" to text/html}, log
+ assert_match %r{\(for 127\.0\.0\.1}, log
+ assert_match %r{\[GET\]}, log
+ end
+
+ def test_subscriber_has_its_logged_flushed_after_request
+ assert_equal 0, @logger.flush_count
+ get "/"
+ wait
+ assert_equal 1, @logger.flush_count
+ end
+
+ def test_subscriber_has_its_logged_flushed_even_after_busted_requests
+ assert_equal 0, @logger.flush_count
+ begin
+ get "/puke"
+ rescue
+ end
+ wait
+ assert_equal 1, @logger.flush_count
+ end
+
+ class SyncSubscriberTest < ActionController::IntegrationTest
+ include Rails::Subscriber::SyncTestHelper
+ include DispatcherSubscriberTest
+ end
+
+ class AsyncSubscriberTest < ActionController::IntegrationTest
+ include Rails::Subscriber::AsyncTestHelper
+ include DispatcherSubscriberTest
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/fixtures/reply.rb b/actionpack/test/fixtures/reply.rb
index 04598437c2..19cba93673 100644
--- a/actionpack/test/fixtures/reply.rb
+++ b/actionpack/test/fixtures/reply.rb
@@ -1,5 +1,5 @@
class Reply < ActiveRecord::Base
- named_scope :base
+ scope :base
belongs_to :topic, :include => [:replies]
belongs_to :developer