diff options
author | Mikel Lindsaar <raasdnil@gmail.com> | 2010-01-19 19:41:15 +1100 |
---|---|---|
committer | Mikel Lindsaar <raasdnil@gmail.com> | 2010-01-19 19:41:15 +1100 |
commit | eaae58ce0c79aa5b4d6de16e2e67034b7fd971bb (patch) | |
tree | d2d3fc90f977020d4e389f526f7ce494a23c3e69 /actionpack/lib | |
parent | c5acbcbb0f72b9968decd702041dcb5b72574a28 (diff) | |
parent | c71120e29caddda295c133adfb279870733a3f81 (diff) | |
download | rails-eaae58ce0c79aa5b4d6de16e2e67034b7fd971bb.tar.gz rails-eaae58ce0c79aa5b4d6de16e2e67034b7fd971bb.tar.bz2 rails-eaae58ce0c79aa5b4d6de16e2e67034b7fd971bb.zip |
Merge branch 'master' of github.com:lifo/docrails
Diffstat (limited to 'actionpack/lib')
20 files changed, 432 insertions, 367 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/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/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 7b44212310..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 diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 65df9b1f03..f299306ff4 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -119,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 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/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 9aaa4355f2..811c287355 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -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_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(" ", " ")}</pre>" + "<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", " ")}</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 |