diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index 24c25abc8b..ebcca0d246 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -4,7 +4,6 @@ require 'rake/testtask' require 'rake/rdoctask' require 'rake/packagetask' require 'rake/gempackagetask' -require 'rake/contrib/sshpublisher' require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version') PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' @@ -61,7 +60,7 @@ spec = Gem::Specification.new do |s| s.rubyforge_project = "actionmailer" s.homepage = "http://www.rubyonrails.org" - s.add_dependency('actionpack', '= 2.3.2' + PKG_BUILD) + s.add_dependency('actionpack', '= 3.0.pre' + PKG_BUILD) s.has_rdoc = true s.requirements << 'none' @@ -82,12 +81,14 @@ end desc "Publish the API documentation" task :pgem => [:package] do + require 'rake/contrib/sshpublisher' Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload `ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'` end desc "Publish the API documentation" task :pdoc => [:rdoc] do + require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/am", "doc").upload end diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index 02c536c8ad..a427376579 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -21,16 +21,9 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -begin - require 'action_controller' -rescue LoadError - actionpack_path = "#{File.dirname(__FILE__)}/../../actionpack/lib" - if File.directory?(actionpack_path) - $:.unshift actionpack_path - require 'action_controller' - end -end - +actionpack_path = "#{File.dirname(__FILE__)}/../../actionpack/lib" +$:.unshift(actionpack_path) if File.directory?(actionpack_path) +require 'action_controller' require 'action_view' module ActionMailer diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index af2cc2ee24..b5a0d0ab96 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -1,3 +1,7 @@ +require "active_support/core_ext/class" +# Use the old layouts until actionmailer gets refactored +require "action_controller/legacy/layout" + module ActionMailer #:nodoc: # Action Mailer allows you to send email from your application using a mailer model and views. # @@ -697,7 +701,7 @@ def create_mail def perform_delivery_smtp(mail) destinations = mail.destinations mail.ready_to_send - sender = (mail['return-path'] && mail['return-path'].spec) || mail.from + sender = (mail['return-path'] && mail['return-path'].spec) || mail['from'] smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port]) smtp.enable_starttls_auto if smtp_settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto) diff --git a/actionmailer/lib/action_mailer/helpers.rb b/actionmailer/lib/action_mailer/helpers.rb index 31f7de8d60..1bb8682315 100644 --- a/actionmailer/lib/action_mailer/helpers.rb +++ b/actionmailer/lib/action_mailer/helpers.rb @@ -48,13 +48,14 @@ def helper(*args, &block) file_name = arg.to_s.underscore + '_helper' class_name = file_name.camelize - begin - require_dependency(file_name) - rescue LoadError => load_error - requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1] - msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}" - raise LoadError.new(msg).copy_blame!(load_error) - end + require_dependency(file_name, "Missing helper file helpers/%s.rb") + # begin + # require_dependency(file_name) + # rescue LoadError => load_error + # requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1] + # msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}" + # raise LoadError.new(msg).copy_blame!(load_error) + # end add_template_helper(class_name.constantize) else @@ -97,7 +98,7 @@ def inherited_with_helper(child) child.master_helper_module.__send__(:include, master_helper_module) child.helper child.name.to_s.underscore rescue MissingSourceFile => e - raise unless e.is_missing?("helpers/#{child.name.to_s.underscore}_helper") + raise unless e.is_missing?("#{child.name.to_s.underscore}_helper") end end end diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index 08ff0d2ffb..0f2b58552b 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -1,8 +1,8 @@ module ActionMailer module VERSION #:nodoc: - MAJOR = 2 - MINOR = 3 - TINY = 2 + MAJOR = 3 + MINOR = 0 + TINY = "pre" STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index 51b375fef3..3d4d0fb995 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -1,9 +1,6 @@ require 'rubygems' require 'test/unit' -gem 'mocha', '>= 0.9.5' -require 'mocha' - $:.unshift "#{File.dirname(__FILE__)}/../lib" $:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib" $:.unshift "#{File.dirname(__FILE__)}/../../actionpack/lib" diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 5f5614e58f..1fc5018561 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -22,9 +22,11 @@ task :default => [ :test ] # Run the unit tests desc "Run all unit tests" -task :test => [:test_action_pack, :test_active_record_integration, :test_new_base, :test_new_base_on_old_tests] +task :test => [:test_action_pack, :test_active_record_integration, :test_new_base] -test_lib_dirs = [ENV["NEW"] ? "test/new_base" : "test", "test/lib"] +test_lib_dirs = ENV["NEW"] ? ["test/new_base"] : [] +test_lib_dirs.push "test", "test/lib" +# test_lib_dirs = [ENV["NEW"] ? "test/new_base" : "test", "test/lib"] Rake::TestTask.new(:test_action_pack) do |t| t.libs.concat test_lib_dirs @@ -35,6 +37,7 @@ Rake::TestTask.new(:test_action_pack) do |t| t.verbose = true #t.warning = true end + task :isolated_test do ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) Dir.glob("test/{controller,dispatch,template}/**/*_test.rb").all? do |file| @@ -112,7 +115,7 @@ spec = Gem::Specification.new do |s| s.has_rdoc = true s.requirements << 'none' - s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD) + s.add_dependency('activesupport', '= 3.0.pre' + PKG_BUILD) s.require_path = 'lib' s.autorequire = 'action_controller' diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 4033bbcbad..e822a11d14 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -1,84 +1,62 @@ -#-- -# Copyright (c) 2004-2009 David Heinemeier Hansson -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#++ - -activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" -$:.unshift activesupport_path if File.directory?(activesupport_path) -require 'active_support' - -require File.join(File.dirname(__FILE__), "action_pack") - module ActionController - # TODO: Review explicit to see if they will automatically be handled by - # the initializer if they are really needed. - def self.load_all! - [Base, Request, Response, UrlRewriter, UrlWriter] - [ActionDispatch::Http::Headers] - end + autoload :Base, "action_controller/base/base" + autoload :ConditionalGet, "action_controller/base/conditional_get" + autoload :HideActions, "action_controller/base/hide_actions" + autoload :Http, "action_controller/base/http" + autoload :Layouts, "action_controller/base/layouts" + autoload :RackConvenience, "action_controller/base/rack_convenience" + autoload :Rails2Compatibility, "action_controller/base/compatibility" + autoload :Redirector, "action_controller/base/redirector" + autoload :Renderer, "action_controller/base/renderer" + autoload :RenderOptions, "action_controller/base/render_options" + autoload :Renderers, "action_controller/base/render_options" + autoload :Rescue, "action_controller/base/rescuable" + autoload :Testing, "action_controller/base/testing" + autoload :UrlFor, "action_controller/base/url_for" + autoload :Session, "action_controller/base/session" + autoload :Helpers, "action_controller/base/helpers" - autoload :Base, 'action_controller/base/base' - autoload :Benchmarking, 'action_controller/base/chained/benchmarking' - autoload :Caching, 'action_controller/caching' - autoload :Cookies, 'action_controller/base/cookies' - autoload :Dispatcher, 'action_controller/dispatch/dispatcher' - autoload :Filters, 'action_controller/base/chained/filters' - autoload :Flash, 'action_controller/base/chained/flash' - autoload :Helpers, 'action_controller/base/helpers' - autoload :HttpAuthentication, 'action_controller/base/http_authentication' - autoload :Integration, 'action_controller/testing/integration' - autoload :IntegrationTest, 'action_controller/testing/integration' - autoload :Layout, 'action_controller/base/layout' - autoload :MimeResponds, 'action_controller/base/mime_responds' + # Ported modules + # require 'action_controller/routing' + autoload :Caching, 'action_controller/caching' + autoload :Dispatcher, 'action_controller/dispatch/dispatcher' + autoload :Integration, 'action_controller/testing/integration' + autoload :MimeResponds, 'action_controller/base/mime_responds' autoload :PolymorphicRoutes, 'action_controller/routing/generation/polymorphic_routes' - autoload :RecordIdentifier, 'action_controller/record_identifier' - autoload :Redirector, 'action_controller/base/redirect' - autoload :Renderer, 'action_controller/base/render' - autoload :RequestForgeryProtection, 'action_controller/base/request_forgery_protection' - autoload :Rescue, 'action_controller/base/rescue' - autoload :Resources, 'action_controller/routing/resources' - autoload :Responder, 'action_controller/base/responder' - autoload :Routing, 'action_controller/routing' + autoload :RecordIdentifier, 'action_controller/record_identifier' + autoload :Resources, 'action_controller/routing/resources' autoload :SessionManagement, 'action_controller/base/session_management' - autoload :Streaming, 'action_controller/base/streaming' - autoload :TestCase, 'action_controller/testing/test_case' - autoload :TestProcess, 'action_controller/testing/process' - autoload :Translation, 'action_controller/translation' - autoload :UrlEncodedPairParser, 'action_controller/dispatch/url_encoded_pair_parser' - autoload :UrlRewriter, 'action_controller/routing/generation/url_rewriter' - autoload :UrlWriter, 'action_controller/routing/generation/url_rewriter' - autoload :Verification, 'action_controller/base/verification' - autoload :FilterParameterLogging, 'action_controller/base/filter_parameter_logging' + autoload :TestCase, 'action_controller/testing/test_case' + autoload :TestProcess, 'action_controller/testing/process' + autoload :UrlRewriter, 'action_controller/routing/generation/url_rewriter' + autoload :UrlWriter, 'action_controller/routing/generation/url_rewriter' - module Assertions - autoload :DomAssertions, 'action_controller/testing/assertions/dom' - autoload :ModelAssertions, 'action_controller/testing/assertions/model' - autoload :ResponseAssertions, 'action_controller/testing/assertions/response' - autoload :RoutingAssertions, 'action_controller/testing/assertions/routing' - autoload :SelectorAssertions, 'action_controller/testing/assertions/selector' - autoload :TagAssertions, 'action_controller/testing/assertions/tag' - end + autoload :Verification, 'action_controller/base/verification' + autoload :Flash, 'action_controller/base/flash' + autoload :RequestForgeryProtection, 'action_controller/base/request_forgery_protection' + autoload :Streaming, 'action_controller/base/streaming' + autoload :HttpAuthentication, 'action_controller/base/http_authentication' + autoload :FilterParameterLogging, 'action_controller/base/filter_parameter_logging' + autoload :Translation, 'action_controller/translation' + autoload :Cookies, 'action_controller/base/cookies' + + autoload :ActionControllerError, 'action_controller/base/exceptions' + autoload :SessionRestoreError, 'action_controller/base/exceptions' + autoload :RenderError, 'action_controller/base/exceptions' + autoload :RoutingError, 'action_controller/base/exceptions' + autoload :MethodNotAllowed, 'action_controller/base/exceptions' + autoload :NotImplemented, 'action_controller/base/exceptions' + autoload :UnknownController, 'action_controller/base/exceptions' + autoload :MissingFile, 'action_controller/base/exceptions' + autoload :RenderError, 'action_controller/base/exceptions' + autoload :SessionOverflowError, 'action_controller/base/exceptions' + autoload :UnknownHttpMethod, 'action_controller/base/exceptions' + + require 'action_controller/routing' end autoload :HTML, 'action_controller/vendor/html-scanner' +autoload :AbstractController, 'action_controller/abstract' require 'action_dispatch' require 'action_view' diff --git a/actionpack/lib/action_controller/abstract.rb b/actionpack/lib/action_controller/abstract.rb index f46b91627f..d0eba253b8 100644 --- a/actionpack/lib/action_controller/abstract.rb +++ b/actionpack/lib/action_controller/abstract.rb @@ -2,13 +2,15 @@ require "active_support/core_ext/module/delegation" module AbstractController - autoload :Base, "action_controller/abstract/base" - autoload :Benchmarker, "action_controller/abstract/benchmarker" - autoload :Callbacks, "action_controller/abstract/callbacks" - autoload :Helpers, "action_controller/abstract/helpers" - autoload :Layouts, "action_controller/abstract/layouts" - autoload :Logger, "action_controller/abstract/logger" - autoload :Renderer, "action_controller/abstract/renderer" + autoload :Base, "action_controller/abstract/base" + autoload :Benchmarker, "action_controller/abstract/benchmarker" + autoload :Callbacks, "action_controller/abstract/callbacks" + autoload :Helpers, "action_controller/abstract/helpers" + autoload :Layouts, "action_controller/abstract/layouts" + autoload :Logger, "action_controller/abstract/logger" + autoload :Renderer, "action_controller/abstract/renderer" # === Exceptions - autoload :ActionNotFound, "action_controller/abstract/exceptions" + autoload :ActionNotFound, "action_controller/abstract/exceptions" + autoload :DoubleRenderError, "action_controller/abstract/exceptions" + autoload :Error, "action_controller/abstract/exceptions" end diff --git a/actionpack/lib/action_controller/abstract/base.rb b/actionpack/lib/action_controller/abstract/base.rb index 87083a4d79..a19a236ef7 100644 --- a/actionpack/lib/action_controller/abstract/base.rb +++ b/actionpack/lib/action_controller/abstract/base.rb @@ -1,53 +1,65 @@ require 'active_support/core_ext/module/attr_internal' module AbstractController - class Error < StandardError; end - - class DoubleRenderError < Error - DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"." - - def initialize(message = nil) - super(message || DEFAULT_MESSAGE) - end - end class Base attr_internal :response_body - attr_internal :response_obj attr_internal :action_name class << self attr_reader :abstract + alias_method :abstract?, :abstract + # Define a controller as abstract. See internal_methods for more + # details. def abstract! @abstract = true end - alias_method :abstract?, :abstract - def inherited(klass) - ::AbstractController::Base.subclasses << klass.to_s + ::AbstractController::Base.descendants << klass.to_s super end - def subclasses - @subclasses ||= [] + # A list of all descendents of AbstractController::Base. This is + # useful for initializers which need to add behavior to all controllers. + def descendants + @descendants ||= [] end + # A list of all internal methods for a controller. This finds the first + # abstract superclass of a controller, and gets a list of all public + # instance methods on that abstract class. Public instance methods of + # a controller would normally be considered action methods, so we + # are removing those methods on classes declared as abstract + # (ActionController::Http and ActionController::Base are defined + # as abstract) def internal_methods controller = self controller = controller.superclass until controller.abstract? controller.public_instance_methods(true) end - def process(action) - new.process(action.to_s) - end - + # The list of hidden actions to an empty Array. Defaults to an + # empty Array. This can be modified by other modules or subclasses + # to specify particular actions as hidden. + # + # ==== Returns + # Array[String]:: An array of method names that should not be + # considered actions. def hidden_actions [] end + # A list of method names that should be considered actions. This + # includes all public instance methods on a controller, less + # any internal methods (see #internal_methods), adding back in + # any methods that are internal, but still exist on the class + # itself. Finally, #hidden_actions are removed. + # + # ==== Returns + # Array[String]:: A list of all methods that should be considered + # actions. def action_methods @action_methods ||= # All public instance methods of this class, including ancestors @@ -63,10 +75,14 @@ def action_methods abstract! - def initialize - self.response_obj = {} - end - + # Calls the action going through the entire action dispatch stack. + # + # The actual method that is called is determined by calling + # #method_for_action. If no method can handle the action, then an + # ActionNotFound error is raised. + # + # ==== Returns + # self def process(action) @_action_name = action_name = action.to_s @@ -79,33 +95,63 @@ def process(action) end private - def action_methods - self.class.action_methods + # 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:: The name of an action to be tested + # + # ==== Returns + # TrueClass, FalseClass + def action_method?(name) + self.class.action_methods.include?(name) end - def action_method?(action) - action_methods.include?(action) - end - - # It is possible for respond_to?(action_name) to be false and - # respond_to?(:action_missing) to be false if respond_to_action? - # is overridden in a subclass. For instance, ActionController::Base - # overrides it to include the case where a template matching the - # action_name is found. + # 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) send_action(method_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 - # Override this to change the conditions that will raise an - # ActionNotFound error. If you accept a difference case, - # you must handle it by also overriding process_action and - # handling the case. + # 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:: 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" diff --git a/actionpack/lib/action_controller/abstract/benchmarker.rb b/actionpack/lib/action_controller/abstract/benchmarker.rb index 07294cede3..58e9564c2f 100644 --- a/actionpack/lib/action_controller/abstract/benchmarker.rb +++ b/actionpack/lib/action_controller/abstract/benchmarker.rb @@ -5,6 +5,16 @@ module Benchmarker include Logger module ClassMethods + # Execute the passed in block, timing the duration of the block in ms. + # + # ==== Parameters + # title<#to_s>:: The title of block to benchmark + # log_level:: A valid log level. Defaults to Logger::DEBUG + # use_silence:: Whether or not to silence the + # logger for the duration of the block. + # + # ==== Returns + # Object:: The result of the block def benchmark(title, log_level = ::Logger::DEBUG, use_silence = true) if logger && logger.level >= log_level result = nil diff --git a/actionpack/lib/action_controller/abstract/callbacks.rb b/actionpack/lib/action_controller/abstract/callbacks.rb index c6d3413c30..0d5161c80e 100644 --- a/actionpack/lib/action_controller/abstract/callbacks.rb +++ b/actionpack/lib/action_controller/abstract/callbacks.rb @@ -2,12 +2,17 @@ module AbstractController module Callbacks extend ActiveSupport::Concern + # Uses ActiveSupport::NewCallbacks as the base functionality. For + # more details on the whole callback system, read the documentation + # for ActiveSupport::NewCallbacks. include ActiveSupport::NewCallbacks included do define_callbacks :process_action, "response_body" end + # Override AbstractController::Base's process_action to run the + # process_action callbacks around the normal behavior. def process_action(method_name) _run_process_action_callbacks(method_name) do super @@ -15,6 +20,17 @@ def process_action(method_name) end module ClassMethods + # If :only or :accept are used, convert the options into the + # primitive form (:per_key) used by ActiveSupport::Callbacks. + # The basic idea is that :only => :index gets converted to + # :if => proc {|c| c.action_name == "index" }, but that the + # proc is only evaluated once per action for the lifetime of + # a Rails process. + # + # ==== Options + # :only<#to_s>:: The callback should be run only for this action + # :except<#to_s>:: The callback should be run for all actions + # except this action def _normalize_callback_options(options) if only = options[:only] only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ") @@ -26,35 +42,69 @@ def _normalize_callback_options(options) end end + # Skip before, after, and around filters matching any of the names + # + # ==== Parameters + # *names:: A list of valid names that could be used for + # callbacks. Note that skipping uses Ruby equality, so it's + # impossible to skip a callback defined using an anonymous proc + # using #skip_filter + def skip_filter(*names, &blk) + skip_before_filter(*names) + skip_after_filter(*names) + skip_around_filter(*names) + end + + # Take callback names and an optional callback proc, normalize them, + # then call the block with each callback. This allows us to abstract + # the normalization across several methods that use it. + # + # ==== Parameters + # callbacks:: A list of callbacks, with an optional + # options hash as the last parameter. + # block:: A proc that should be added to the callbacks. + # + # ==== Block Parameters + # name:: The callback to be added + # options:: A list of options to be used when adding the callback + def _insert_callbacks(callbacks, block) + options = callbacks.last.is_a?(Hash) ? callbacks.pop : {} + _normalize_callback_options(options) + callbacks.push(block) if block + callbacks.each do |callback| + yield callback, options + end + end + + # set up before_filter, prepend_before_filter, skip_before_filter, etc. + # for each of before, after, and around. [:before, :after, :around].each do |filter| class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + # Append a before, after or around filter. See _insert_callbacks + # for details on the allowed parameters. def #{filter}_filter(*names, &blk) - options = names.last.is_a?(Hash) ? names.pop : {} - _normalize_callback_options(options) - names.push(blk) if block_given? - names.each do |name| - process_action_callback(:#{filter}, name, options) + _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, :#{filter}, name, options) end end + # Prepend a before, after or around filter. See _insert_callbacks + # for details on the allowed parameters. def prepend_#{filter}_filter(*names, &blk) - options = names.last.is_a?(Hash) ? names.pop : {} - _normalize_callback_options(options) - names.push(blk) if block_given? - names.each do |name| - process_action_callback(:#{filter}, name, options.merge(:prepend => true)) + _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) end end + # Skip a before, after or around filter. See _insert_callbacks + # for details on the allowed parameters. def skip_#{filter}_filter(*names, &blk) - options = names.last.is_a?(Hash) ? names.pop : {} - _normalize_callback_options(options) - names.push(blk) if block_given? - names.each do |name| - skip_process_action_callback(:#{filter}, name, options) + _insert_callbacks(names, blk) do |name, options| + skip_callback(:process_action, :#{filter}, name, options) end end + # *_filter is the same as append_*_filter alias_method :append_#{filter}_filter, :#{filter}_filter RUBY_EVAL end diff --git a/actionpack/lib/action_controller/abstract/exceptions.rb b/actionpack/lib/action_controller/abstract/exceptions.rb index 2f6c55f068..b671516de1 100644 --- a/actionpack/lib/action_controller/abstract/exceptions.rb +++ b/actionpack/lib/action_controller/abstract/exceptions.rb @@ -1,3 +1,12 @@ module AbstractController + class Error < StandardError; end class ActionNotFound < StandardError; end + + class DoubleRenderError < Error + DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"." + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end end diff --git a/actionpack/lib/action_controller/abstract/helpers.rb b/actionpack/lib/action_controller/abstract/helpers.rb index 0a2776de9c..6b73f887c1 100644 --- a/actionpack/lib/action_controller/abstract/helpers.rb +++ b/actionpack/lib/action_controller/abstract/helpers.rb @@ -5,33 +5,26 @@ module Helpers include Renderer included do - extlib_inheritable_accessor :master_helper_module - self.master_helper_module = Module.new + extlib_inheritable_accessor(:_helpers) { Module.new } end + # Override AbstractController::Renderer's _action_view to include the + # helper module for this class into its helpers module. def _action_view - @_action_view ||= begin - av = super - av.helpers.send(:include, master_helper_module) - av - end + @_action_view ||= super.tap { |av| av.helpers.include(_helpers) } end module ClassMethods + # When a class is inherited, wrap its helper module in a new module. + # This ensures that the parent class's module can be changed + # independently of the child class's. def inherited(klass) - klass.master_helper_module = Module.new - klass.master_helper_module.__send__ :include, master_helper_module + helpers = _helpers + klass._helpers = Module.new { include helpers } super end - # Makes all the (instance) methods in the helper module available to templates rendered through this controller. - # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules - # available to the templates. - def add_template_helper(mod) - master_helper_module.module_eval { include mod } - end - # Declare a controller method as a helper. For example, the following # makes the +current_user+ controller method available to the view: # class ApplicationController < ActionController::Base @@ -48,9 +41,13 @@ def add_template_helper(mod) # # In a view: # <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%> + # + # ==== Parameters + # meths:: The name of a method on the controller + # to be made available on the view. def helper_method(*meths) meths.flatten.each do |meth| - master_helper_module.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 + _helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 def #{meth}(*args, &blk) controller.send(%(#{meth}), *args, &blk) end @@ -58,6 +55,14 @@ def #{meth}(*args, &blk) end end + # Make a number of helper modules part of this class' default + # helpers. + # + # ==== Parameters + # *args:: Modules to be included + # block:: Evalulate the block in the context + # of the helper module. Any methods defined in the block + # will be helpers. def helper(*args, &block) args.flatten.each do |arg| case arg @@ -65,7 +70,18 @@ def helper(*args, &block) add_template_helper(arg) end end - master_helper_module.module_eval(&block) if block_given? + _helpers.module_eval(&block) if block_given? + end + + private + # Makes all the (instance) methods in the helper module available to templates + # rendered through this controller. + # + # ==== Parameters + # mod:: The module to include into the current helper module + # for the class + def add_template_helper(mod) + _helpers.module_eval { include mod } end end end diff --git a/actionpack/lib/action_controller/abstract/layouts.rb b/actionpack/lib/action_controller/abstract/layouts.rb index 273063f74b..2ac4e6068a 100644 --- a/actionpack/lib/action_controller/abstract/layouts.rb +++ b/actionpack/lib/action_controller/abstract/layouts.rb @@ -5,16 +5,31 @@ module Layouts include Renderer included do - extlib_inheritable_accessor :_layout_conditions - self._layout_conditions = {} + extlib_inheritable_accessor(:_layout_conditions) { Hash.new } end module ClassMethods - def layout(layout, conditions = {}) - unless [String, Symbol, FalseClass, NilClass].include?(layout.class) - raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil" - end + def inherited(klass) + super + klass._write_layout_method + end + # Specify the layout to use for this class. + # + # If the specified layout is a: + # String:: the String is the template name + # Symbol:: call the method specified by the symbol, which will return + # the template name + # false:: There is no layout + # true:: raise an ArgumentError + # + # ==== Parameters + # layout:: The layout to use. + # + # ==== Options (conditions) + # :only<#to_s, Array[#to_s]>:: A list of actions to apply this layout to. + # :except<#to_s, Array[#to_s]>:: Apply this layout to all actions but this one + def layout(layout, conditions = {}) conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} } self._layout_conditions = conditions @@ -22,6 +37,11 @@ def layout(layout, conditions = {}) _write_layout_method end + # If no layout is supplied, look for a template named the return + # value of this method. + # + # ==== Returns + # String:: A template name def _implied_layout_name name.underscore end @@ -29,23 +49,31 @@ def _implied_layout_name # Takes the specified layout and creates a _layout method to be called # by _default_layout # - # If the specified layout is a: - # String:: return the string - # Symbol:: call the method specified by the symbol - # false:: return nil - # none:: If a layout is found in the view paths with the controller's - # name, return that string. Otherwise, use the superclass' - # layout (which might also be implied) + # If there is no explicit layout specified: + # If a layout is found in the view paths with the controller's + # name, return that string. Otherwise, use the superclass' + # layout (which might also be implied) def _write_layout_method case @_layout when String self.class_eval %{def _layout(details) #{@_layout.inspect} end} when Symbol - self.class_eval %{def _layout(details) #{@_layout} end} + self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 + def _layout(details) + #{@_layout}.tap do |layout| + unless layout.is_a?(String) || !layout + raise ArgumentError, "Your layout method :#{@_layout} returned \#{layout}. It " \ + "should have returned a String, false, or nil" + end + end + end + ruby_eval when false self.class_eval %{def _layout(details) end} - else - self.class_eval %{ + when true + raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil" + when nil + self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 def _layout(details) if view_paths.find_by_parts?("#{_implied_layout_name}", details, "layouts") "#{_implied_layout_name}" @@ -53,33 +81,55 @@ def _layout(details) super end end - } + ruby_eval end + self.class_eval { private :_layout } end end private - # This will be overwritten - def _layout(details) - end + # This will be overwritten by _write_layout_method + def _layout(details) end - # :api: plugin - # ==== - # Override this to mutate the inbound layout name + # Determine the layout for a given name and details. + # + # ==== Parameters + # name:: The name of the template + # details Object}>:: A list of details to restrict + # the lookup to. By default, layout lookup is limited to the + # formats specified for the current request. def _layout_for_name(name, details = {:formats => formats}) - unless [String, FalseClass, NilClass].include?(name.class) - raise ArgumentError, "String, false, or nil expected; you passed #{name.inspect}" - end - - name && view_paths.find_by_parts(name, details, _layout_prefix(name)) + name && _find_by_parts(name, details) end - # TODO: Decide if this is the best hook point for the feature - def _layout_prefix(name) - "layouts" + # Take in the name and details and find a Template. + # + # ==== Parameters + # name:: The name of the template to retrieve + # details:: A list of details to restrict the search by. This + # might include details like the format or locale of the template. + # + # ==== Returns + # Template:: A template object matching the name and details + def _find_by_parts(name, details) + # TODO: Make prefix actually part of details in ViewPath#find_by_parts + prefix = details.key?(:prefix) ? details.delete(:prefix) : "layouts" + view_paths.find_by_parts(name, details, prefix) end - def _default_layout(require_layout = false, details = {:formats => formats}) + # Returns the default layout for this controller and a given set of details. + # Optionally raises an exception if the layout could not be found. + # + # ==== Parameters + # details:: A list of details to restrict the search by. This + # might include details like the format or locale of the template. + # require_layout:: If this is true, raise an ArgumentError + # with details about the fact that the exception could not be + # found (defaults to false) + # + # ==== Returns + # Template:: The template object for the default layout (or nil) + def _default_layout(details, require_layout = false) if require_layout && _action_has_layout? && !_layout(details) raise ArgumentError, "There was no default layout for #{self.class} in #{view_paths.inspect}" @@ -93,6 +143,12 @@ def _default_layout(require_layout = false, details = {:formats => formats}) end end + # Determines whether the current action has a layout by checking the + # action name against the :only and :except conditions set on the + # layout. + # + # ==== Returns + # Boolean:: True if the action has a layout, false otherwise. def _action_has_layout? conditions = _layout_conditions if only = conditions[:only] diff --git a/actionpack/lib/action_controller/abstract/logger.rb b/actionpack/lib/action_controller/abstract/logger.rb index d6fa843485..b960e152e3 100644 --- a/actionpack/lib/action_controller/abstract/logger.rb +++ b/actionpack/lib/action_controller/abstract/logger.rb @@ -5,6 +5,13 @@ module AbstractController module Logger extend ActiveSupport::Concern + # A class that allows you to defer expensive processing + # until the logger actually tries to log. Otherwise, you are + # forced to do the processing in advance, and send the + # entire processed String to the logger, which might + # just discard the String if the log level is too low. + # + # TODO: Require that Rails loggers accept a block. class DelayedLog def initialize(&blk) @blk = blk @@ -20,8 +27,10 @@ def to_s cattr_accessor :logger end - def process(action) - ret = super + # Override process_action in the AbstractController::Base + # to log details about the method. + def process_action(action) + super if logger log = DelayedLog.new do @@ -32,10 +41,9 @@ def process(action) logger.info(log) end - - ret end + private def request_origin # this *needs* to be cached! # otherwise you'd get different results if calling it more than once diff --git a/actionpack/lib/action_controller/abstract/renderer.rb b/actionpack/lib/action_controller/abstract/renderer.rb index dd58c7cb64..611d3a16ce 100644 --- a/actionpack/lib/action_controller/abstract/renderer.rb +++ b/actionpack/lib/action_controller/abstract/renderer.rb @@ -14,10 +14,32 @@ module Renderer self._view_paths ||= ActionView::PathSet.new end + # An instance of a view class. The default view class is ActionView::Base + # + # The view class must have the following methods: + # initialize[paths, assigns_for_first_render, controller] + # paths:: A list of resolvers to look for templates in + # controller A controller + # _render_partial_from_controller[options] + # options:: see _render_partial in ActionView::Base + # _render_template_from_controller[template, layout, options, partial] + # template:: The template to render + # layout:: The layout to render around the template + # options:: See _render_template_with_layout in ActionView::Base + # partial:: Whether or not the template to render is a partial + # _partial:: If a partial, rather than a template, was rendered, return + # the partial. + # helpers:: A module containing the helpers to be used in the view. This + # module should respond_to include. + # controller:: The controller that initialized the ActionView + # + # Override this method in a to change the default behavior. def _action_view @_action_view ||= ActionView::Base.new(self.class.view_paths, {}, self) end + # Mostly abstracts the fact that calling render twice is a DoubleRenderError. + # Delegates render_to_body and sticks the result in self.response_body. def render(*args) if response_body raise AbstractController::DoubleRenderError, "OMG" @@ -27,9 +49,10 @@ def render(*args) end # Raw rendering of a template to a Rack-compatible body. - # ==== - # @option _prefix The template's path prefix - # @option _layout The relative path to the layout template to use + # + # ==== Options + # _partial_object:: The object that is being rendered. If this + # exists, we are in the special case of rendering an object as a partial. # # :api: plugin def render_to_body(options = {}) @@ -42,21 +65,27 @@ def render_to_body(options = {}) end end - # Raw rendering of a template to a string. - # ==== - # @option _prefix The template's path prefix - # @option _layout The relative path to the layout template to use + # Raw rendering of a template to a string. Just convert the results of + # render_to_body into a String. # # :api: plugin def render_to_string(options = {}) AbstractController::Renderer.body_to_s(render_to_body(options)) end + # Renders the template from an object. + # + # ==== Options + # _template:: The template to render + # _layout:: The layout to wrap the template in (optional) + # _partial:: Whether or not the template to be rendered is a partial def _render_template(options) _action_view._render_template_from_controller(options[:_template], options[:_layout], options, options[:_partial]) end - def view_paths() + # The list of view paths for this controller. See ActionView::ViewPathSet for + # more details about writing custom view paths. + def view_paths _view_paths end @@ -73,6 +102,15 @@ def self.body_to_s(body) end private + # Take in a set of options and determine the template to render + # + # ==== Options + # _template:: If this is provided, the search is over + # _template_name<#to_s>:: The name of the template to look up. Otherwise, + # use the current action name. + # _prefix:: The prefix to look inside of. In a file system, this corresponds + # to a directory. + # _partial:: Whether or not the file to look up is a partial def _determine_template(options) name = (options[:_template_name] || action_name).to_s @@ -82,18 +120,36 @@ def _determine_template(options) end module ClassMethods + # Append a path to the list of view paths for this controller. + # + # ==== Parameters + # path:: If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::ViewPathSet for more information) def append_view_path(path) self.view_paths << path end + # Prepend a path to the list of view paths for this controller. + # + # ==== Parameters + # path:: If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::ViewPathSet for more information) def prepend_view_path(path) self.view_paths.unshift(path) end + # A list of all of the default view paths for this controller. def view_paths self._view_paths end + # Set the view paths. + # + # ==== Parameters + # paths:: If a ViewPathSet is provided, use that; + # otherwise, process the parameter into a ViewPathSet. def view_paths=(paths) self._view_paths = paths.is_a?(ActionView::PathSet) ? paths : ActionView::Base.process_view_paths(paths) diff --git a/actionpack/lib/action_controller/base/base.rb b/actionpack/lib/action_controller/base/base.rb index 1b400d8ed3..e541d24e31 100644 --- a/actionpack/lib/action_controller/base/base.rb +++ b/actionpack/lib/action_controller/base/base.rb @@ -1,884 +1,167 @@ -require 'action_controller/deprecated' -require 'set' -require 'active_support/core_ext/class/inheritable_attributes' -require 'active_support/core_ext/module/attr_internal' +module ActionController + class Base < Http + abstract! -module ActionController #:nodoc: - class ActionControllerError < StandardError #:nodoc: - end + include AbstractController::Benchmarker + include AbstractController::Callbacks + include AbstractController::Logger - class SessionRestoreError < ActionControllerError #:nodoc: - end - - class RenderError < ActionControllerError #:nodoc: - end - - class RoutingError < ActionControllerError #:nodoc: - attr_reader :failures - def initialize(message, failures=[]) - super(message) - @failures = failures - end - end - - class MethodNotAllowed < ActionControllerError #:nodoc: - attr_reader :allowed_methods - - def initialize(*allowed_methods) - super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.") - @allowed_methods = allowed_methods - end - - def allowed_methods_header - allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', ' - end - end - - class NotImplemented < MethodNotAllowed #:nodoc: - end - - class UnknownController < ActionControllerError #:nodoc: - end - - class UnknownAction < ActionControllerError #:nodoc: - end - - class MissingFile < ActionControllerError #:nodoc: - end - - class RenderError < ActionControllerError #:nodoc: - end - - class SessionOverflowError < ActionControllerError #:nodoc: - DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.' - - def initialize(message = nil) - super(message || DEFAULT_MESSAGE) - end - end - - class UnknownHttpMethod < ActionControllerError #:nodoc: - end - - # Action Controllers are the core of a web request in Rails. They are made up of one or more actions that are executed - # on request and then either render a template or redirect to another action. An action is defined as a public method - # on the controller, which will automatically be made accessible to the web-server through Rails Routes. - # - # A sample controller could look like this: - # - # class GuestBookController < ActionController::Base - # def index - # @entries = Entry.find(:all) - # end - # - # def sign - # Entry.create(params[:entry]) - # redirect_to :action => "index" - # end - # end - # - # Actions, by default, render a template in the app/views directory corresponding to the name of the controller and action - # after executing code in the action. For example, the +index+ action of the GuestBookController would render the - # template app/views/guestbook/index.erb by default after populating the @entries instance variable. - # - # Unlike index, the sign action will not render a template. After performing its main purpose (creating a - # new entry in the guest book), it initiates a redirect instead. This redirect works by returning an external - # "302 Moved" HTTP response that takes the user to the index action. - # - # The index and sign represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect. - # Most actions are variations of these themes. - # - # == Requests - # - # Requests are processed by the Action Controller framework by extracting the value of the "action" key in the request parameters. - # This value should hold the name of the action to be performed. Once the action has been identified, the remaining - # request parameters, the session (if one is available), and the full request with all the HTTP headers are made available to - # the action through instance variables. Then the action is performed. - # - # The full request object is available with the request accessor and is primarily used to query for HTTP headers. These queries - # are made by accessing the environment hash, like this: - # - # def server_ip - # location = request.env["SERVER_ADDR"] - # render :text => "This server hosted at #{location}" - # end - # - # == Parameters - # - # All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method - # which returns a hash. For example, an action that was performed through /weblog/list?category=All&limit=5 will include - # { "category" => "All", "limit" => 5 } in params. - # - # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as: - # - # - # - # - # A request stemming from a form holding these inputs will include { "post" => { "name" => "david", "address" => "hyacintvej" } }. - # If the address input had been named "post[address][street]", the params would have included - # { "post" => { "address" => { "street" => "hyacintvej" } } }. There's no limit to the depth of the nesting. - # - # == Sessions - # - # Sessions allows you to store objects in between requests. This is useful for objects that are not yet ready to be persisted, - # such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such - # as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely - # they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at. - # - # You can place objects in the session by using the session method, which accesses a hash: - # - # session[:person] = Person.authenticate(user_name, password) - # - # And retrieved again through the same hash: - # - # Hello #{session[:person]} - # - # For removing objects from the session, you can either assign a single key to +nil+: - # - # # removes :person from session - # session[:person] = nil - # - # or you can remove the entire session with +reset_session+. - # - # Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted. - # This prevents the user from tampering with the session but also allows him to see its contents. - # - # Do not put secret information in cookie-based sessions! - # - # Other options for session storage are: - # - # * ActiveRecord::SessionStore - Sessions are stored in your database, which works better than PStore with multiple app servers and, - # unlike CookieStore, hides your session contents from the user. To use ActiveRecord::SessionStore, set - # - # config.action_controller.session_store = :active_record_store - # - # in your config/environment.rb and run rake db:sessions:create. - # - # * MemCacheStore - Sessions are stored as entries in your memcached cache. - # Set the session store type in config/environment.rb: - # - # config.action_controller.session_store = :mem_cache_store - # - # This assumes that memcached has been installed and configured properly. - # See the MemCacheStore docs for more information. - # - # == Responses - # - # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response - # object is generated automatically through the use of renders and redirects and requires no user intervention. - # - # == Renders - # - # Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering - # of a template. Included in the Action Pack is the Action View, which enables rendering of ERb templates. It's automatically configured. - # The controller passes objects to the view by assigning instance variables: - # - # def show - # @post = Post.find(params[:id]) - # end - # - # Which are then automatically available to the view: - # - # Title: <%= @post.title %> - # - # You don't have to rely on the automated rendering. Especially actions that could result in the rendering of different templates will use - # the manual rendering methods: - # - # def search - # @results = Search.find(params[:query]) - # case @results - # when 0 then render :action => "no_results" - # when 1 then render :action => "show" - # when 2..10 then render :action => "show_many" - # end - # end - # - # Read more about writing ERb and Builder templates in link:classes/ActionView/Base.html. - # - # == Redirects - # - # Redirects are used to move from one action to another. For example, after a create action, which stores a blog entry to a database, - # we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're going to reuse (and redirect to) - # a show action that we'll assume has already been created. The code might look like this: - # - # def create - # @entry = Entry.new(params[:entry]) - # if @entry.save - # # The entry was saved correctly, redirect to show - # redirect_to :action => 'show', :id => @entry.id - # else - # # things didn't go so well, do something else - # end - # end - # - # In this case, after saving our new entry to the database, the user is redirected to the show method which is then executed. - # - # == Calling multiple redirects or renders - # - # An action may contain only a single render or a single redirect. Attempting to try to do either again will result in a DoubleRenderError: - # - # def do_something - # redirect_to :action => "elsewhere" - # render :action => "overthere" # raises DoubleRenderError - # end - # - # If you need to redirect on the condition of something, then be sure to add "and return" to halt execution. - # - # def do_something - # redirect_to(:action => "elsewhere") and return if monkeys.nil? - # render :action => "overthere" # won't be called if monkeys is nil - # end - # - class Base + include ActionController::Helpers + include ActionController::HideActions + include ActionController::UrlFor + include ActionController::Redirector + include ActionController::Renderer + include ActionController::Renderers::All + include ActionController::Layouts + include ActionController::ConditionalGet + include ActionController::RackConvenience + # Legacy modules + include SessionManagement include ActionDispatch::StatusCodes + include ActionController::Caching + include ActionController::MimeResponds - cattr_reader :protected_instance_variables - # Controller specific instance variables which will not be accessible inside views. - @@protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller - @action_name @before_filter_chain_aborted @action_cache_path @_headers @_params - @_flash @_response) + # Rails 2.x compatibility + include ActionController::Rails2Compatibility - # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, - # and images to a dedicated asset server away from the main web server. Example: - # ActionController::Base.asset_host = "http://assets.example.com" - cattr_accessor :asset_host + include ActionController::Cookies + include ActionController::Session + include ActionController::Flash + include ActionController::Verification + include ActionController::RequestForgeryProtection + include ActionController::Streaming + include ActionController::HttpAuthentication::Basic::ControllerMethods + include ActionController::HttpAuthentication::Digest::ControllerMethods + include ActionController::FilterParameterLogging + include ActionController::Translation - # All requests are considered local by default, so everyone will be exposed to detailed debugging screens on errors. - # When the application is ready to go public, this should be set to false, and the protected method local_request? - # should instead be implemented in the controller to determine when debugging screens should be shown. - @@consider_all_requests_local = true - cattr_accessor :consider_all_requests_local - - # Indicates whether to allow concurrent action processing. Your - # controller actions and any other code they call must also behave well - # when called from concurrent threads. Turned off by default. - @@allow_concurrency = false - cattr_accessor :allow_concurrency - - # Modern REST web services often need to submit complex data to the web application. - # The @@param_parsers hash lets you register handlers which will process the HTTP body and add parameters to the - # params hash. These handlers are invoked for POST and PUT requests. - # - # By default application/xml is enabled. A XmlSimple class with the same param name as the root will be instantiated - # in the params. This allows XML requests to mask themselves as regular form submissions, so you can have one - # action serve both regular forms and web service requests. - # - # Example of doing your own parser for a custom content type: - # - # ActionController::Base.param_parsers[Mime::Type.lookup('application/atom+xml')] = Proc.new do |data| - # node = REXML::Document.new(post) - # { node.root.name => node.root } - # end - # - # Note: Up until release 1.1 of Rails, Action Controller would default to using XmlSimple configured to discard the - # root node for such requests. The new default is to keep the root, such that "David" results - # in params[:r][:name] for "David" instead of params[:name]. To get the old behavior, you can - # re-register XmlSimple as application/xml handler ike this: - # - # ActionController::Base.param_parsers[Mime::XML] = - # Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) } - # - # A YAML parser is also available and can be turned on with: - # - # ActionController::Base.param_parsers[Mime::YAML] = :yaml - @@param_parsers = { Mime::MULTIPART_FORM => :multipart_form, - Mime::URL_ENCODED_FORM => :url_encoded_form, - Mime::XML => :xml_simple, - Mime::JSON => :json } - cattr_accessor :param_parsers - - # Controls the default charset for all renders. - @@default_charset = "utf-8" - cattr_accessor :default_charset - - # The logger is used for generating information on the action run-time (including benchmarking) if available. - # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers. - cattr_accessor :logger - - # Controls the resource action separator - @@resource_action_separator = "/" - cattr_accessor :resource_action_separator - - # Allow to override path names for default resources' actions - @@resources_path_names = { :new => 'new', :edit => 'edit' } - cattr_accessor :resources_path_names - - # Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ - # sets it to :authenticity_token by default. - cattr_accessor :request_forgery_protection_token - - # Controls the IP Spoofing check when determining the remote IP. - @@ip_spoofing_check = true - cattr_accessor :ip_spoofing_check - - # Indicates whether or not optimise the generated named - # route helper methods - cattr_accessor :optimise_named_routes - self.optimise_named_routes = true - - # Indicates whether the response format should be determined by examining the Accept HTTP header, - # or by using the simpler params + ajax rules. - # - # If this is set to +true+ (the default) then +respond_to+ and +Request#format+ will take the Accept - # header into account. If it is set to false then the request format will be determined solely - # by examining params[:format]. If params format is missing, the format will be either HTML or - # Javascript depending on whether the request is an AJAX request. - cattr_accessor :use_accept_header - self.use_accept_header = true - - # Controls whether request forgery protection is turned on or not. Turned off by default only in test mode. - class_inheritable_accessor :allow_forgery_protection - self.allow_forgery_protection = true - - # If you are deploying to a subdirectory, you will need to set - # config.action_controller.relative_url_root - # This defaults to ENV['RAILS_RELATIVE_URL_ROOT'] - cattr_accessor :relative_url_root - self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT'] - - # Holds the request object that's primarily used to get environment variables through access like - # request.env["REQUEST_URI"]. - attr_internal :request - - # Holds a hash of all the GET, POST, and Url parameters passed to the action. Accessed like params["post_id"] - # to get the post_id. No type casts are made, so all values are returned as strings. - attr_internal :params - - # Holds the response object that's primarily used to set additional HTTP headers through access like - # response.headers["Cache-Control"] = "no-cache". Can also be used to access the final body HTML after a template - # has been rendered through response.body -- useful for after_filters that wants to manipulate the output, - # such as a OutputCompressionFilter. - attr_internal :response - - # Holds a hash of objects in the session. Accessed like session[:person] to get the object tied to the "person" - # key. The session will hold any type of object as values, but the key should be a string or symbol. - def session - request.session - end - - # Holds a hash of header names and values. Accessed like headers["Cache-Control"] to get the value of the Cache-Control - # directive. Values should always be specified as strings. - attr_internal :headers - - # Returns the name of the action this controller is processing. - attr_accessor :action_name - - attr_reader :template - - def action(name, env) - request = ActionDispatch::Request.new(env) - response = ActionDispatch::Response.new - self.action_name = name && name.to_s - process(request, response).to_a - end - - - class << self - def action(name = nil) - @actions ||= {} - @actions[name] ||= proc do |env| - new.action(name, env) - end - end - - # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController". - def controller_class_name - @controller_class_name ||= name.demodulize + # TODO: Extract into its own module + # This should be moved together with other normalizing behavior + module ImplicitRender + def send_action(*) + ret = super + default_render unless performed? + ret end - # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat". - def controller_name - @controller_name ||= controller_class_name.sub(/Controller$/, '').underscore - end - - # Converts the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat". - def controller_path - @controller_path ||= name.gsub(/Controller$/, '').underscore - end - - # Return an array containing the names of public methods that have been marked hidden from the action processor. - # By default, all methods defined in ActionController::Base and included modules are hidden. - # More methods can be hidden using hide_action. - def hidden_actions - read_inheritable_attribute(:hidden_actions) || write_inheritable_attribute(:hidden_actions, []) - end - - # Hide each of the given methods from being callable as actions. - def hide_action(*names) - write_inheritable_attribute(:hidden_actions, hidden_actions | names.map { |name| name.to_s }) - end - - # View load paths determine the bases from which template references can be made. So a call to - # render("test/template") will be looked up in the view load paths array and the closest match will be - # returned. - def view_paths - if defined? @view_paths - @view_paths - else - superclass.view_paths - end - end - - def view_paths=(value) - @view_paths = ActionView::Base.process_view_paths(value) if value - end - - # Adds a view_path to the front of the view_paths array. - # If the current class has no view paths, copy them from - # the superclass. This change will be visible for all future requests. - # - # ArticleController.prepend_view_path("views/default") - # ArticleController.prepend_view_path(["views/default", "views/custom"]) - # - def prepend_view_path(path) - @view_paths = superclass.view_paths.dup if !defined?(@view_paths) || @view_paths.nil? - @view_paths.unshift(*path) - end - - # Adds a view_path to the end of the view_paths array. - # If the current class has no view paths, copy them from - # the superclass. This change will be visible for all future requests. - # - # ArticleController.append_view_path("views/default") - # ArticleController.append_view_path(["views/default", "views/custom"]) - # - def append_view_path(path) - @view_paths = superclass.view_paths.dup if @view_paths.nil? - @view_paths.push(*path) - end - - @@exempt_from_layout = [ActionView::TemplateHandlers::RJS] - - def exempt_from_layout(*types) - types.each do |type| - @@exempt_from_layout << - ActionView::Template.handler_class_for_extension(type) - end - - @@exempt_from_layout - end - - end - - public - def call(env) - request = ActionDispatch::Request.new(env) - response = ActionDispatch::Response.new - process(request, response).to_a - end - - # Extracts the action_name from the request parameters and performs that action. - def process(request, response, method = :perform_action, *arguments) #:nodoc: - response.request = request - - assign_shortcuts(request, response) - initialize_template_class(response) - initialize_current_url - - log_processing - send(method, *arguments) - - send_response - ensure - process_cleanup - end - - def send_response - response.prepare! - response - end - - # Returns a URL that has been rewritten according to the options hash and the defined routes. - # (For doing a complete redirect, use +redirect_to+). - # - # url_for is used to: - # - # All keys given to +url_for+ are forwarded to the Route module, save for the following: - # * :anchor - Specifies the anchor name to be appended to the path. For example, - # url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments' - # will produce "/posts/show/10#comments". - # * :only_path - If true, returns the relative URL (omitting the protocol, host name, and port) (false by default). - # * :trailing_slash - If true, adds a trailing slash, as in "/archive/2005/". Note that this - # is currently not recommended since it breaks caching. - # * :host - Overrides the default (current) host if provided. - # * :protocol - Overrides the default (current) protocol if provided. - # * :port - Optionally specify the port to connect to. - # * :user - Inline HTTP authentication (only plucked out if :password is also present). - # * :password - Inline HTTP authentication (only plucked out if :user is also present). - # * :skip_relative_url_root - If true, the url is not constructed using the +relative_url_root+ - # of the request so the path will include the web server relative installation directory. - # - # The URL is generated from the remaining keys in the hash. A URL contains two key parts: the and a query string. - # Routes composes a query string as the key/value pairs not included in the . - # - # The default Routes setup supports a typical Rails path of "controller/action/id" where action and id are optional, with - # action defaulting to 'index' when not given. Here are some typical url_for statements and their corresponding URLs: - # - # url_for :controller => 'posts', :action => 'recent' # => 'proto://host.com/posts/recent' - # url_for :controller => 'posts', :action => 'index' # => 'proto://host.com/posts' - # url_for :controller => 'posts', :action => 'index', :port=>'8033' # => 'proto://host.com:8033/posts' - # url_for :controller => 'posts', :action => 'show', :id => 10 # => 'proto://host.com/posts/show/10' - # url_for :controller => 'posts', :user => 'd', :password => '123' # => 'proto://d:123@host.com/posts' - # - # When generating a new URL, missing values may be filled in from the current request's parameters. For example, - # url_for :action => 'some_action' will retain the current controller, as expected. This behavior extends to - # other parameters, including :controller, :id, and any other parameters that are placed into a Route's - # path. - #   - # The URL helpers such as url_for have a limited form of memory: when generating a new URL, they can look for - # missing values in the current request's parameters. Routes attempts to guess when a value should and should not be - # taken from the defaults. There are a few simple rules on how this is performed: - # - # * If the controller name begins with a slash no defaults are used: - # - # url_for :controller => '/home' - # - # In particular, a leading slash ensures no namespace is assumed. Thus, - # while url_for :controller => 'users' may resolve to - # Admin::UsersController if the current controller lives under - # that module, url_for :controller => '/users' ensures you link - # to ::UsersController no matter what. - # * If the controller changes, the action will default to index unless provided - # - # The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the - # route given by map.connect 'people/:last/:first/:action', :action => 'bio', :controller => 'people'. - # - # Suppose that the current URL is "people/hh/david/contacts". Let's consider a few different cases of URLs which are generated - # from this page. - # - # * url_for :action => 'bio' -- During the generation of this URL, default values will be used for the first and - # last components, and the action shall change. The generated URL will be, "people/hh/david/bio". - # * url_for :first => 'davids-little-brother' This generates the URL 'people/hh/davids-little-brother' -- note - # that this URL leaves out the assumed action of 'bio'. - # - # However, you might ask why the action from the current request, 'contacts', isn't carried over into the new URL. The - # answer has to do with the order in which the parameters appear in the generated path. In a nutshell, since the - # value that appears in the slot for :first is not equal to default value for :first we stop using - # defaults. On its own, this rule can account for much of the typical Rails URL behavior. - #   - # Although a convenience, defaults can occasionally get in your way. In some cases a default persists longer than desired. - # The default may be cleared by adding :name => nil to url_for's options. - # This is often required when writing form helpers, since the defaults in play may vary greatly depending upon where the - # helper is used from. The following line will redirect to PostController's default action, regardless of the page it is - # displayed on: - # - # url_for :controller => 'posts', :action => nil - # - # If you explicitly want to create a URL that's almost the same as the current URL, you can do so using the - # :overwrite_params options. Say for your posts you have different views for showing and printing them. - # Then, in the show view, you get the URL for the print view like this - # - # url_for :overwrite_params => { :action => 'print' } - # - # This takes the current URL as is and only exchanges the action. In contrast, url_for :action => 'print' - # would have slashed-off the path components after the changed action. - def url_for(options = {}) - options ||= {} - case options - when String - options - when Hash - @url.rewrite(rewrite_options(options)) - else - polymorphic_url(options) - end - end - - # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController". - def controller_class_name - self.class.controller_class_name - end - - # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat". - def controller_name - self.class.controller_name - end - - # Converts the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat". - def controller_path - self.class.controller_path - end - - def session_enabled? - ActiveSupport::Deprecation.warn("Sessions are now lazy loaded. So if you don't access them, consider them disabled.", caller) - end - - self.view_paths = [] - - # View load paths for controller. - def view_paths - @template.view_paths - end - - def view_paths=(value) - @template.view_paths = ActionView::Base.process_view_paths(value) - end - - # Adds a view_path to the front of the view_paths array. - # This change affects the current request only. - # - # self.prepend_view_path("views/default") - # self.prepend_view_path(["views/default", "views/custom"]) - # - def prepend_view_path(path) - @template.view_paths.unshift(*path) - end - - # Adds a view_path to the end of the view_paths array. - # This change affects the current request only. - # - # self.append_view_path("views/default") - # self.append_view_path(["views/default", "views/custom"]) - # - def append_view_path(path) - @template.view_paths.push(*path) - end - - def rewrite_options(options) #:nodoc: - if defaults = default_url_options(options) - defaults.merge(options) - else - options - end - end - - # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in - # the form of a hash, just like the one you would use for url_for directly. Example: - # - # def default_url_options(options) - # { :project => @project.active? ? @project.url_name : "unknown" } - # end - # - # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the - # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set - # by this method. - def default_url_options(options = nil) - end - - # Sets the etag and/or last_modified on the response and checks it against - # the client request. If the request doesn't match the options provided, the - # request is considered stale and should be generated from scratch. Otherwise, - # it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent. - # - # Parameters: - # * :etag - # * :last_modified - # * :public By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches). - # - # Example: - # - # def show - # @article = Article.find(params[:id]) - # - # if stale?(:etag => @article, :last_modified => @article.created_at.utc) - # @statistics = @article.really_expensive_call - # respond_to do |format| - # # all the supported formats - # end - # end - # end - def stale?(options) - fresh_when(options) - !request.fresh?(response) - end - - # Sets the etag, last_modified, or both on the response and renders a - # "304 Not Modified" response if the request is already fresh. - # - # Parameters: - # * :etag - # * :last_modified - # * :public By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches). - # - # Example: - # - # def show - # @article = Article.find(params[:id]) - # fresh_when(:etag => @article, :last_modified => @article.created_at.utc, :public => true) - # end - # - # This will render the show template if the request isn't sending a matching etag or - # If-Modified-Since header and just a "304 Not Modified" response if there's a match. - # - def fresh_when(options) - options.assert_valid_keys(:etag, :last_modified, :public) - - response.etag = options[:etag] if options[:etag] - response.last_modified = options[:last_modified] if options[:last_modified] - - if options[:public] - cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip } - cache_control.delete("private") - cache_control.delete("no-cache") - cache_control << "public" - response.headers["Cache-Control"] = cache_control.join(', ') - end - - if request.fresh?(response) - head :not_modified - end - end - - # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that - # intermediate caches shouldn't cache the response. - # - # Examples: - # expires_in 20.minutes - # expires_in 3.hours, :public => true - # expires in 3.hours, 'max-stale' => 5.hours, :public => true - # - # This method will overwrite an existing Cache-Control header. - # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. - def expires_in(seconds, options = {}) #:doc: - cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip } - - cache_control << "max-age=#{seconds}" - cache_control.delete("no-cache") - if options[:public] - cache_control.delete("private") - cache_control << "public" - else - cache_control << "private" - end - - # This allows for additional headers to be passed through like 'max-stale' => 5.hours - cache_control += options.symbolize_keys.reject{|k,v| k == :public || k == :private }.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"} - - response.headers["Cache-Control"] = cache_control.join(', ') - end - - # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or - # intermediate caches (like caching proxy servers). - def expires_now #:doc: - response.headers["Cache-Control"] = "no-cache" - end - - # Resets the session by clearing out all the objects stored within and initializing a new session object. - def reset_session #:doc: - request.reset_session - end - - private - def _process_options(options) - if content_type = options[:content_type] - response.content_type = content_type.to_s - end - - if location = options[:location] - response.headers["Location"] = url_for(location) - end - - response.status = interpret_status(options[:status] || DEFAULT_RENDER_STATUS_CODE) - end - - def initialize_template_class(response) - @template = ActionView::Base.new(self.class.view_paths, {}, self, formats) - response.template = @template if response.respond_to?(:template=) - @template.helpers.send :include, self.class.master_helper_module - @performed_render = @performed_redirect = false - end - - def assign_shortcuts(request, response) - @_request, @_response, @_params = request, response, request.parameters - @_headers = @_response.headers - end - - def initialize_current_url - @url = UrlRewriter.new(request, params.clone) - end - - def log_processing - if logger && logger.info? - log_processing_for_request_id - log_processing_for_parameters - end - end - - def log_processing_for_request_id - request_id = "\n\nProcessing #{self.class.name}\##{action_name} " - request_id << "to #{params[:format]} " if params[:format] - request_id << "(for #{request_origin}) [#{request.method.to_s.upcase}]" - - logger.info(request_id) - end - - def default_render #:nodoc: + def default_render render end - def perform_action - if called = action_methods.include?(action_name) - ret = send(action_name) - elsif called = respond_to?(:method_missing) - ret = method_missing(action_name) - end - - return (performed? ? ret : default_render) if called - - begin - view_paths.find_by_parts(action_name, {:formats => formats, :locales => [I18n.locale]}, controller_path) - rescue => e - raise UnknownAction, "No action responded to #{action_name}. Actions: " + - "#{action_methods.sort.to_sentence}", caller - end - - default_render - end - - # Returns true if a render or redirect has already been performed. - def performed? - @performed_render || @performed_redirect - end - - def reset_variables_added_to_assigns - @template.instance_variable_set("@assigns_added", nil) - end - - def request_origin - # this *needs* to be cached! - # otherwise you'd get different results if calling it more than once - @request_origin ||= "#{request.remote_ip} at #{Time.now.to_s(:db)}" - end - - # Returns the request URI used to get to the current location - def complete_request_uri - "#{request.protocol}#{request.host}#{request.request_uri}" - end - - def default_template(action_name = self.action_name) - self.view_paths.find_template(default_template_name(action_name), default_template_format) - end - - def default_template_name(action_name = self.action_name) - if action_name - action_name = action_name.to_s - if action_name.include?('/') && template_path_includes_controller?(action_name) - action_name = strip_out_controller(action_name) + def method_for_action(action_name) + super || begin + if view_paths.find_by_parts?(action_name.to_s, {:formats => formats, :locales => [I18n.locale]}, controller_path) + "default_render" end end - "#{controller_path}/#{action_name}" + end + end + + include ImplicitRender + + include ActionController::Rescue + + def self.inherited(klass) + ::ActionController::Base.subclasses << klass.to_s + super + end + + def self.subclasses + @subclasses ||= [] + end + + def _normalize_options(action = nil, options = {}, &blk) + if action.is_a?(Hash) + options, action = action, nil + elsif action.is_a?(String) || action.is_a?(Symbol) + key = case action = action.to_s + when %r{^/} then :file + when %r{/} then :template + else :action + end + options.merge! key => action + elsif action + options.merge! :partial => action end - def strip_out_controller(path) - path.split('/', 2).last + if options.key?(:action) && options[:action].to_s.index("/") + options[:template] = options.delete(:action) end - def template_path_includes_controller?(path) - self.controller_path.split('/')[-1] == path.split('/')[0] + if options[:status] + options[:status] = interpret_status(options[:status]).to_i end - def process_cleanup - end - end + options[:update] = blk if block_given? + options + end - Base.class_eval do - [ Filters, Layout, Renderer, Redirector, Responder, Benchmarking, Rescue, Flash, MimeResponds, Helpers, - Cookies, Caching, Verification, Streaming, SessionManagement, - HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods, RecordIdentifier, - RequestForgeryProtection, Translation, FilterParameterLogging - ].each do |mod| - include mod + def render(action = nil, options = {}, &blk) + options = _normalize_options(action, options, &blk) + super(options) + end + + def render_to_string(action = nil, options = {}, &blk) + options = _normalize_options(action, options, &blk) + super(options) + end + + # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: + # + # * Hash - The URL will be generated by calling url_for with the +options+. + # * Record - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. + # * String starting with protocol:// (like http://) - Is passed straight through as the target for redirection. + # * String not containing a protocol - The current protocol and host is prepended to the string. + # * :back - Back to the page that issued the request. Useful for forms that are triggered from multiple places. + # Short-hand for redirect_to(request.env["HTTP_REFERER"]) + # + # Examples: + # redirect_to :action => "show", :id => 5 + # redirect_to post + # redirect_to "http://www.rubyonrails.org" + # redirect_to "/images/screenshot.jpg" + # redirect_to articles_url + # redirect_to :back + # + # The redirection happens as a "302 Moved" header unless otherwise specified. + # + # Examples: + # redirect_to post_url(@post), :status=>:found + # redirect_to :action=>'atom', :status=>:moved_permanently + # redirect_to post_url(@post), :status=>301 + # redirect_to :action=>'atom', :status=>302 + # + # When using redirect_to :back, if there is no referrer, + # RedirectBackError will be raised. You may specify some fallback + # behavior for this case by rescuing RedirectBackError. + def redirect_to(options = {}, response_status = {}) #:doc: + raise ActionControllerError.new("Cannot redirect to nil!") if options.nil? + + status = if options.is_a?(Hash) && options.key?(:status) + interpret_status(options.delete(:status)) + elsif response_status.key?(:status) + interpret_status(response_status[:status]) + else + 302 + end + + url = case options + # The scheme name consist of a letter followed by any combination of + # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") + # characters; and is terminated by a colon (":"). + when %r{^\w[\w\d+.-]*:.*} + options + when String + request.protocol + request.host_with_port + options + when :back + raise RedirectBackError unless refer = request.headers["Referer"] + refer + else + url_for(options) + end + + super(url, status) end end end diff --git a/actionpack/lib/action_controller/base/chained/benchmarking.rb b/actionpack/lib/action_controller/base/chained/benchmarking.rb deleted file mode 100644 index 57a1ac8314..0000000000 --- a/actionpack/lib/action_controller/base/chained/benchmarking.rb +++ /dev/null @@ -1,107 +0,0 @@ -require 'active_support/core_ext/benchmark' - -module ActionController #:nodoc: - # The benchmarking module times the performance of actions and reports to the logger. If the Active Record - # package has been included, a separate timing section for database calls will be added as well. - module Benchmarking #:nodoc: - def self.included(base) - base.extend(ClassMethods) - - base.class_eval do - alias_method_chain :perform_action, :benchmark - alias_method_chain :render, :benchmark - end - end - - module ClassMethods - # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it - # (unless use_silence is set to false). - # - # The benchmark is only recorded if the current level of the logger matches the log_level, which makes it - # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark - # will only be conducted if the log level is low enough. - def benchmark(title, log_level = Logger::DEBUG, use_silence = true) - if logger && logger.level >= log_level - result = nil - ms = Benchmark.ms { result = use_silence ? silence { yield } : yield } - logger.add(log_level, "#{title} (#{('%.1f' % ms)}ms)") - result - else - yield - end - end - - # Silences the logger for the duration of the block. - def silence - old_logger_level, logger.level = logger.level, Logger::ERROR if logger - yield - ensure - logger.level = old_logger_level if logger - end - end - - protected - def render_with_benchmark(options = nil, extra_options = {}, &block) - if logger - if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? - db_runtime = ActiveRecord::Base.connection.reset_runtime - end - - render_output = nil - @view_runtime = Benchmark.ms { render_output = render_without_benchmark(options, extra_options, &block) } - - if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? - @db_rt_before_render = db_runtime - @db_rt_after_render = ActiveRecord::Base.connection.reset_runtime - @view_runtime -= @db_rt_after_render - end - - render_output - else - render_without_benchmark(options, extra_options, &block) - end - end - - private - def perform_action_with_benchmark - if logger && logger.info? - ms = [Benchmark.ms { perform_action_without_benchmark }, 0.01].max - logging_view = defined?(@view_runtime) - logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? - - log_message = 'Completed in %.0fms' % ms - - if logging_view || logging_active_record - log_message << " (" - log_message << view_runtime if logging_view - - if logging_active_record - log_message << ", " if logging_view - log_message << active_record_runtime + ")" - else - ")" - end - end - - log_message << " | #{response.status}" - log_message << " [#{complete_request_uri rescue "unknown"}]" - - logger.info(log_message) - response.headers["X-Runtime"] = "%.0f" % ms - else - perform_action_without_benchmark - end - end - - def view_runtime - "View: %.0f" % @view_runtime - end - - def active_record_runtime - db_runtime = ActiveRecord::Base.connection.reset_runtime - db_runtime += @db_rt_before_render if @db_rt_before_render - db_runtime += @db_rt_after_render if @db_rt_after_render - "DB: %.0f" % db_runtime - end - end -end diff --git a/actionpack/lib/action_controller/base/chained/filters.rb b/actionpack/lib/action_controller/base/chained/filters.rb deleted file mode 100644 index f528dd0686..0000000000 --- a/actionpack/lib/action_controller/base/chained/filters.rb +++ /dev/null @@ -1,670 +0,0 @@ -module ActionController #:nodoc: - module Filters #:nodoc: - extend ActiveSupport::Concern - - class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc: - def append_filter_to_chain(filters, filter_type, &block) - pos = find_filter_append_position(filters, filter_type) - update_filter_chain(filters, filter_type, pos, &block) - end - - def prepend_filter_to_chain(filters, filter_type, &block) - pos = find_filter_prepend_position(filters, filter_type) - update_filter_chain(filters, filter_type, pos, &block) - end - - def create_filters(filters, filter_type, &block) - filters, conditions = extract_options(filters, &block) - filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) } - filters - end - - def skip_filter_in_chain(*filters, &test) - filters, conditions = extract_options(filters) - filters.each do |filter| - if callback = find(filter) then delete(callback) end - end if conditions.empty? - update_filter_in_chain(filters, :skip => conditions, &test) - end - - private - def update_filter_chain(filters, filter_type, pos, &block) - new_filters = create_filters(filters, filter_type, &block) - insert(pos, new_filters).flatten! - end - - def find_filter_append_position(filters, filter_type) - # appending an after filter puts it at the end of the call chain - # before and around filters go before the first after filter in the chain - unless filter_type == :after - each_with_index do |f,i| - return i if f.after? - end - end - return -1 - end - - def find_filter_prepend_position(filters, filter_type) - # prepending a before or around filter puts it at the front of the call chain - # after filters go before the first after filter in the chain - if filter_type == :after - each_with_index do |f,i| - return i if f.after? - end - return -1 - end - return 0 - end - - def find_or_create_filter(filter, filter_type, options = {}) - update_filter_in_chain([filter], options) - - if found_filter = find(filter) { |f| f.type == filter_type } - found_filter - else - filter_kind = case - when filter.respond_to?(:before) && filter_type == :before - :before - when filter.respond_to?(:after) && filter_type == :after - :after - else - :filter - end - - case filter_type - when :before - BeforeFilter.new(filter_kind, filter, options) - when :after - AfterFilter.new(filter_kind, filter, options) - else - AroundFilter.new(filter_kind, filter, options) - end - end - end - - def update_filter_in_chain(filters, options, &test) - filters.map! { |f| block_given? ? find(f, &test) : find(f) } - filters.compact! - - map! do |filter| - if filters.include?(filter) - new_filter = filter.dup - new_filter.update_options!(options) - new_filter - else - filter - end - end - end - end - - class Filter < ActiveSupport::Callbacks::Callback #:nodoc: - def initialize(kind, method, options = {}) - super - update_options! options - end - - # override these to return true in appropriate subclass - def before? - false - end - - def after? - false - end - - def around? - false - end - - # Make sets of strings from :only/:except options - def update_options!(other) - if other - convert_only_and_except_options_to_sets_of_strings(other) - if other[:skip] - convert_only_and_except_options_to_sets_of_strings(other[:skip]) - end - end - - options.update(other) - end - - private - def should_not_skip?(controller) - if options[:skip] - !included_in_action?(controller, options[:skip]) - else - true - end - end - - def included_in_action?(controller, options) - if options[:only] - options[:only].include?(controller.action_name) - elsif options[:except] - !options[:except].include?(controller.action_name) - else - true - end - end - - def should_run_callback?(controller) - should_not_skip?(controller) && included_in_action?(controller, options) && super - end - - def convert_only_and_except_options_to_sets_of_strings(opts) - [:only, :except].each do |key| - if values = opts[key] - opts[key] = Array(values).map {|val| val.to_s }.to_set - end - end - end - end - - class AroundFilter < Filter #:nodoc: - def type - :around - end - - def around? - true - end - - def call(controller, &block) - if should_run_callback?(controller) - method = filter_responds_to_before_and_after? ? around_proc : self.method - - # For around_filter do |controller, action| - if method.is_a?(Proc) && method.arity == 2 - evaluate_method(method, controller, block) - else - evaluate_method(method, controller, &block) - end - else - block.call - end - end - - private - def filter_responds_to_before_and_after? - method.respond_to?(:before) && method.respond_to?(:after) - end - - def around_proc - Proc.new do |controller, action| - method.before(controller) - - if controller.__send__(:performed?) - controller.__send__(:halt_filter_chain, method, :rendered_or_redirected) - else - begin - action.call - ensure - method.after(controller) - end - end - end - end - end - - class BeforeFilter < Filter #:nodoc: - def type - :before - end - - def before? - true - end - - def call(controller, &block) - super - if controller.__send__(:performed?) - controller.__send__(:halt_filter_chain, method, :rendered_or_redirected) - end - end - end - - class AfterFilter < Filter #:nodoc: - def type - :after - end - - def after? - true - end - end - - # Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do - # authentication, caching, or auditing before the intended action is performed. Or to do localization or output - # compression after the action has been performed. Filters have access to the request, response, and all the instance - # variables set by other filters in the chain or by the action (in the case of after filters). - # - # == Filter inheritance - # - # Controller inheritance hierarchies share filters downwards, but subclasses can also add or skip filters without - # affecting the superclass. For example: - # - # class BankController < ActionController::Base - # before_filter :audit - # - # private - # def audit - # # record the action and parameters in an audit log - # end - # end - # - # class VaultController < BankController - # before_filter :verify_credentials - # - # private - # def verify_credentials - # # make sure the user is allowed into the vault - # end - # end - # - # Now any actions performed on the BankController will have the audit method called before. On the VaultController, - # first the audit method is called, then the verify_credentials method. If the audit method renders or redirects, then - # verify_credentials and the intended action are never called. - # - # == Filter types - # - # A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first - # is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of - # the controller by use of a symbol. In the bank example above, both BankController and VaultController use this form. - # - # Using an external class makes for more easily reused generic filters, such as output compression. External filter classes - # are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example: - # - # class OutputCompressionFilter - # def self.filter(controller) - # controller.response.body = compress(controller.response.body) - # end - # end - # - # class NewspaperController < ActionController::Base - # after_filter OutputCompressionFilter - # end - # - # The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can - # manipulate them as it sees fit. - # - # The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation. - # Or just as a quick test. It works like this: - # - # class WeblogController < ActionController::Base - # before_filter { |controller| head(400) if controller.params["stop_action"] } - # end - # - # As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables. - # This means that the block has access to both the request and response objects complete with convenience methods for params, - # session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call - # and returns 1 or -1 on arity will do (such as a Proc or an Method object). - # - # Please note that around_filters function a little differently than the normal before and after filters with regard to filter - # types. Please see the section dedicated to around_filters below. - # - # == Filter chain ordering - # - # Using before_filter and after_filter appends the specified filters to the existing chain. That's usually - # just fine, but some times you care more about the order in which the filters are executed. When that's the case, you - # can use prepend_before_filter and prepend_after_filter. Filters added by these methods will be put at the - # beginning of their respective chain and executed before the rest. For example: - # - # class ShoppingController < ActionController::Base - # before_filter :verify_open_shop - # - # class CheckoutController < ShoppingController - # prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock - # - # The filter chain for the CheckoutController is now :ensure_items_in_cart, :ensure_items_in_stock, - # :verify_open_shop. So if either of the ensure filters renders or redirects, we'll never get around to see if the shop - # is open or not. - # - # You may pass multiple filter arguments of each type as well as a filter block. - # If a block is given, it is treated as the last argument. - # - # == Around filters - # - # Around filters wrap an action, executing code both before and after. - # They may be declared as method references, blocks, or objects responding - # to +filter+ or to both +before+ and +after+. - # - # To use a method as an +around_filter+, pass a symbol naming the Ruby method. - # Yield (or block.call) within the method to run the action. - # - # around_filter :catch_exceptions - # - # private - # def catch_exceptions - # yield - # rescue => exception - # logger.debug "Caught exception! #{exception}" - # raise - # end - # - # To use a block as an +around_filter+, pass a block taking as args both - # the controller and the action block. You can't call yield directly from - # an +around_filter+ block; explicitly call the action block instead: - # - # around_filter do |controller, action| - # logger.debug "before #{controller.action_name}" - # action.call - # logger.debug "after #{controller.action_name}" - # end - # - # To use a filter object with +around_filter+, pass an object responding - # to :filter or both :before and :after. With a - # filter method, yield to the block as above: - # - # around_filter BenchmarkingFilter - # - # class BenchmarkingFilter - # def self.filter(controller, &block) - # Benchmark.measure(&block) - # end - # end - # - # With +before+ and +after+ methods: - # - # around_filter Authorizer.new - # - # class Authorizer - # # This will run before the action. Redirecting aborts the action. - # def before(controller) - # unless user.authorized? - # redirect_to(login_url) - # end - # end - # - # # This will run after the action if and only if before did not render or redirect. - # def after(controller) - # end - # end - # - # If the filter has +before+ and +after+ methods, the +before+ method will be - # called before the action. If +before+ renders or redirects, the filter chain is - # halted and +after+ will not be run. See Filter Chain Halting below for - # an example. - # - # == Filter chain skipping - # - # Declaring a filter on a base class conveniently applies to its subclasses, - # but sometimes a subclass should skip some of its superclass' filters: - # - # class ApplicationController < ActionController::Base - # before_filter :authenticate - # around_filter :catch_exceptions - # end - # - # class WeblogController < ApplicationController - # # Will run the :authenticate and :catch_exceptions filters. - # end - # - # class SignupController < ApplicationController - # # Skip :authenticate, run :catch_exceptions. - # skip_before_filter :authenticate - # end - # - # class ProjectsController < ApplicationController - # # Skip :catch_exceptions, run :authenticate. - # skip_filter :catch_exceptions - # end - # - # class ClientsController < ApplicationController - # # Skip :catch_exceptions and :authenticate unless action is index. - # skip_filter :catch_exceptions, :authenticate, :except => :index - # end - # - # == Filter conditions - # - # Filters may be limited to specific actions by declaring the actions to - # include or exclude. Both options accept single actions - # (:only => :index) or arrays of actions - # (:except => [:foo, :bar]). - # - # class Journal < ActionController::Base - # # Require authentication for edit and delete. - # before_filter :authorize, :only => [:edit, :delete] - # - # # Passing options to a filter with a block. - # around_filter(:except => :index) do |controller, action_block| - # results = Profiler.run(&action_block) - # controller.response.sub! "", "#{results}" - # end - # - # private - # def authorize - # # Redirect to login unless authenticated. - # end - # end - # - # == Filter Chain Halting - # - # before_filter and around_filter may halt the request - # before a controller action is run. This is useful, for example, to deny - # access to unauthenticated users or to redirect from HTTP to HTTPS. - # Simply call render or redirect. After filters will not be executed if the filter - # chain is halted. - # - # Around filters halt the request unless the action block is called. - # Given these filters - # after_filter :after - # around_filter :around - # before_filter :before - # - # The filter chain will look like: - # - # ... - # . \ - # . #around (code before yield) - # . . \ - # . . #before (actual filter code is run) - # . . . \ - # . . . execute controller action - # . . . / - # . . ... - # . . / - # . #around (code after yield) - # . / - # #after (actual filter code is run, unless the around filter does not yield) - # - # If +around+ returns before yielding, +after+ will still not be run. The +before+ - # filter and controller action will not be run. If +before+ renders or redirects, - # the second half of +around+ and will still run but +after+ and the - # action will not. If +around+ fails to yield, +after+ will not be run. - module ClassMethods - # The passed filters will be appended to the filter_chain and - # will execute before the action on this controller is performed. - def append_before_filter(*filters, &block) - filter_chain.append_filter_to_chain(filters, :before, &block) - end - - # The passed filters will be prepended to the filter_chain and - # will execute before the action on this controller is performed. - def prepend_before_filter(*filters, &block) - filter_chain.prepend_filter_to_chain(filters, :before, &block) - end - - # Shorthand for append_before_filter since it's the most common. - alias :before_filter :append_before_filter - - # The passed filters will be appended to the array of filters - # that run _after_ actions on this controller are performed. - def append_after_filter(*filters, &block) - filter_chain.append_filter_to_chain(filters, :after, &block) - end - - # The passed filters will be prepended to the array of filters - # that run _after_ actions on this controller are performed. - def prepend_after_filter(*filters, &block) - filter_chain.prepend_filter_to_chain(filters, :after, &block) - end - - # Shorthand for append_after_filter since it's the most common. - alias :after_filter :append_after_filter - - # If you append_around_filter A.new, B.new, the filter chain looks like - # - # B#before - # A#before - # # run the action - # A#after - # B#after - # - # With around filters which yield to the action block, +before+ and +after+ - # are the code before and after the yield. - def append_around_filter(*filters, &block) - filter_chain.append_filter_to_chain(filters, :around, &block) - end - - # If you prepend_around_filter A.new, B.new, the filter chain looks like: - # - # A#before - # B#before - # # run the action - # B#after - # A#after - # - # With around filters which yield to the action block, +before+ and +after+ - # are the code before and after the yield. - def prepend_around_filter(*filters, &block) - filter_chain.prepend_filter_to_chain(filters, :around, &block) - end - - # Shorthand for +append_around_filter+ since it's the most common. - alias :around_filter :append_around_filter - - # Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference - # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out - # of many sub-controllers need a different hierarchy. - # - # You can control the actions to skip the filter for with the :only and :except options, - # just like when you apply the filters. - def skip_before_filter(*filters) - filter_chain.skip_filter_in_chain(*filters, &:before?) - end - - # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference - # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out - # of many sub-controllers need a different hierarchy. - # - # You can control the actions to skip the filter for with the :only and :except options, - # just like when you apply the filters. - def skip_after_filter(*filters) - filter_chain.skip_filter_in_chain(*filters, &:after?) - end - - # Removes the specified filters from the filter chain. This only works for method reference (symbol) - # filters, not procs. This method is different from skip_after_filter and skip_before_filter in that - # it will match any before, after or yielding around filter. - # - # You can control the actions to skip the filter for with the :only and :except options, - # just like when you apply the filters. - def skip_filter(*filters) - filter_chain.skip_filter_in_chain(*filters) - end - - # Returns an array of Filter objects for this controller. - def filter_chain - read_inheritable_attribute('filter_chain') || write_inheritable_attribute('filter_chain', FilterChain.new) - end - - # Returns all the before filters for this class and all its ancestors. - # This method returns the actual filter that was assigned in the controller to maintain existing functionality. - def before_filters #:nodoc: - filter_chain.select(&:before?).map(&:method) - end - - # Returns all the after filters for this class and all its ancestors. - # This method returns the actual filter that was assigned in the controller to maintain existing functionality. - def after_filters #:nodoc: - filter_chain.select(&:after?).map(&:method) - end - end - - module InstanceMethods # :nodoc: - def self.included(base) - base.class_eval do - alias_method_chain :perform_action, :filters - alias_method_chain :process, :filters - end - end - - protected - def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc: - @before_filter_chain_aborted = false - process_without_filters(request, response, method, *arguments) - end - - def perform_action_with_filters - call_filters(self.class.filter_chain, 0, 0) - end - - private - def call_filters(chain, index, nesting) - index = run_before_filters(chain, index, nesting) - aborted = @before_filter_chain_aborted - perform_action_without_filters unless performed? || aborted - return index if nesting != 0 || aborted - run_after_filters(chain, index) - end - - def run_before_filters(chain, index, nesting) - while chain[index] - filter, index = chain[index], index - break unless filter # end of call chain reached - - case filter - when BeforeFilter - filter.call(self) # invoke before filter - index = index.next - break if @before_filter_chain_aborted - when AroundFilter - yielded = false - - filter.call(self) do - yielded = true - # all remaining before and around filters will be run in this call - index = call_filters(chain, index.next, nesting.next) - end - - halt_filter_chain(filter, :did_not_yield) unless yielded - - break - else - break # no before or around filters left - end - end - - index - end - - def run_after_filters(chain, index) - seen_after_filter = false - - while chain[index] - filter, index = chain[index], index - break unless filter # end of call chain reached - - case filter - when AfterFilter - seen_after_filter = true - filter.call(self) # invoke after filter - else - # implementation error or someone has mucked with the filter chain - raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter - end - - index = index.next - end - - index.next - end - - def halt_filter_chain(filter, reason) - @before_filter_chain_aborted = true - logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger - end - end - end -end diff --git a/actionpack/lib/action_controller/new_base/compatibility.rb b/actionpack/lib/action_controller/base/compatibility.rb similarity index 92% rename from actionpack/lib/action_controller/new_base/compatibility.rb rename to actionpack/lib/action_controller/base/compatibility.rb index f278c2da14..cd4b72b1c6 100644 --- a/actionpack/lib/action_controller/new_base/compatibility.rb +++ b/actionpack/lib/action_controller/base/compatibility.rb @@ -114,8 +114,17 @@ def method_for_action(action_name) super || (respond_to?(:method_missing) && "_handle_method_missing") end - def _layout_prefix(name) - super unless name =~ /\blayouts/ + def _find_by_parts(name, details) + details[:prefix] = nil if name =~ /\blayouts/ + super + end + + # Move this into a "don't run in production" module + def _default_layout(details, require_layout = false) + super + rescue ActionView::MissingTemplate + _find_by_parts(_layout({}), {}) + nil end def performed? diff --git a/actionpack/lib/action_controller/new_base/conditional_get.rb b/actionpack/lib/action_controller/base/conditional_get.rb similarity index 100% rename from actionpack/lib/action_controller/new_base/conditional_get.rb rename to actionpack/lib/action_controller/base/conditional_get.rb diff --git a/actionpack/lib/action_controller/base/exceptions.rb b/actionpack/lib/action_controller/base/exceptions.rb new file mode 100644 index 0000000000..d0811254cb --- /dev/null +++ b/actionpack/lib/action_controller/base/exceptions.rb @@ -0,0 +1,58 @@ +module ActionController + class ActionControllerError < StandardError #:nodoc: + end + + class SessionRestoreError < ActionControllerError #:nodoc: + end + + class RenderError < ActionControllerError #:nodoc: + end + + class RoutingError < ActionControllerError #:nodoc: + attr_reader :failures + def initialize(message, failures=[]) + super(message) + @failures = failures + end + end + + class MethodNotAllowed < ActionControllerError #:nodoc: + attr_reader :allowed_methods + + def initialize(*allowed_methods) + super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.") + @allowed_methods = allowed_methods + end + + def allowed_methods_header + allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', ' + end + + def handle_response!(response) + response.headers['Allow'] ||= allowed_methods_header + end + end + + class NotImplemented < MethodNotAllowed #:nodoc: + end + + class UnknownController < ActionControllerError #:nodoc: + end + + class MissingFile < ActionControllerError #:nodoc: + end + + class RenderError < ActionControllerError #:nodoc: + end + + class SessionOverflowError < ActionControllerError #:nodoc: + DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.' + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + class UnknownHttpMethod < ActionControllerError #:nodoc: + end +end \ No newline at end of file diff --git a/actionpack/lib/action_controller/base/filter_parameter_logging.rb b/actionpack/lib/action_controller/base/filter_parameter_logging.rb index 8370ba6fc0..065e62a37f 100644 --- a/actionpack/lib/action_controller/base/filter_parameter_logging.rb +++ b/actionpack/lib/action_controller/base/filter_parameter_logging.rb @@ -2,10 +2,7 @@ module ActionController module FilterParameterLogging extend ActiveSupport::Concern - # TODO : Remove the defined? check when new base is the main base - if defined?(ActionController::Http) - include AbstractController::Logger - end + include AbstractController::Logger included do include InstanceMethodsForNewBase @@ -46,6 +43,10 @@ def filter_parameter_logging(*filter_words, &block) filtered_parameters[key] = '[FILTERED]' elsif value.is_a?(Hash) filtered_parameters[key] = filter_parameters(value) + elsif value.is_a?(Array) + filtered_parameters[key] = value.collect do |item| + filter_parameters(item) + end elsif block_given? key = key.dup value = value.dup if value diff --git a/actionpack/lib/action_controller/base/chained/flash.rb b/actionpack/lib/action_controller/base/flash.rb similarity index 74% rename from actionpack/lib/action_controller/base/chained/flash.rb rename to actionpack/lib/action_controller/base/flash.rb index 42c6e430ca..590f9be3ac 100644 --- a/actionpack/lib/action_controller/base/chained/flash.rb +++ b/actionpack/lib/action_controller/base/flash.rb @@ -28,20 +28,7 @@ module ActionController #:nodoc: module Flash extend ActiveSupport::Concern - # TODO : Remove the defined? check when new base is the main base - include Session if defined?(ActionController::Http) - - included do - # TODO : Remove the defined? check when new base is the main base - if defined?(ActionController::Http) - include InstanceMethodsForNewBase - else - include InstanceMethodsForBase - - alias_method_chain :perform_action, :flash - alias_method_chain :reset_session, :flash - end - end + include Session class FlashNow #:nodoc: def initialize(flash) @@ -148,49 +135,30 @@ def use(key = nil, used = true) end end - module InstanceMethodsForBase #:nodoc: - protected - def perform_action_with_flash - perform_action_without_flash - if defined? @_flash - @_flash.store(session) - remove_instance_variable(:@_flash) - end - end - - def reset_session_with_flash - reset_session_without_flash - remove_instance_variable(:@_flash) if defined?(@_flash) - end - end - - module InstanceMethodsForNewBase #:nodoc: - protected - def process_action(method_name) - super - if defined? @_flash - @_flash.store(session) - remove_instance_variable(:@_flash) - end - end - - def reset_session - super - remove_instance_variable(:@_flash) if defined?(@_flash) - end - end - - protected - # Access the contents of the flash. Use flash["notice"] to - # read a notice you put there or flash["notice"] = "hello" - # to put a new one. - def flash #:doc: - if !defined?(@_flash) - @_flash = session["flash"] || FlashHash.new - @_flash.sweep - end - - @_flash + protected + def process_action(method_name) + super + if defined? @_flash + @_flash.store(session) + remove_instance_variable(:@_flash) end + end + + def reset_session + super + remove_instance_variable(:@_flash) if defined?(@_flash) + end + + # Access the contents of the flash. Use flash["notice"] to + # read a notice you put there or flash["notice"] = "hello" + # to put a new one. + def flash #:doc: + if !defined?(@_flash) + @_flash = session["flash"] || FlashHash.new + @_flash.sweep + end + + @_flash + end end end diff --git a/actionpack/lib/action_controller/base/helpers.rb b/actionpack/lib/action_controller/base/helpers.rb index f74158bc13..2fa5ea6519 100644 --- a/actionpack/lib/action_controller/base/helpers.rb +++ b/actionpack/lib/action_controller/base/helpers.rb @@ -1,85 +1,80 @@ +require 'active_support/core_ext/load_error' +require 'active_support/core_ext/name_error' require 'active_support/dependencies' -# FIXME: helper { ... } is broken on Ruby 1.9 -module ActionController #:nodoc: - module Helpers #:nodoc: +module ActionController + # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+, + # +numbers+ and model objects, to name a few. These helpers are available to all templates + # by default. + # + # In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to + # extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will + # include a helper whose name matches that of the controller, e.g., MyController will automatically + # include MyHelper. + # + # Additional helpers can be specified using the +helper+ class method in ActionController::Base or any + # controller which inherits from it. + # + # ==== Examples + # The +to_s+ method from the Time class can be wrapped in a helper method to display a custom message if + # the Time object is blank: + # + # module FormattedTimeHelper + # def format_time(time, format=:long, blank_message=" ") + # time.blank? ? blank_message : time.to_s(format) + # end + # end + # + # FormattedTimeHelper can now be included in a controller, using the +helper+ class method: + # + # class EventsController < ActionController::Base + # helper FormattedTimeHelper + # def index + # @events = Event.find(:all) + # end + # end + # + # Then, in any view rendered by EventController, the format_time method can be called: + # + # <% @events.each do |event| -%> + #

+ # <% format_time(event.time, :short, "N/A") %> | <%= event.name %> + #

+ # <% end -%> + # + # Finally, assuming we have two event instances, one which has a time and one which does not, + # the output might look like this: + # + # 23 Aug 11:30 | Carolina Railhawks Soccer Match + # N/A | Carolina Railhaws Training Workshop + # + module Helpers extend ActiveSupport::Concern + include AbstractController::Helpers + included do - # Initialize the base module to aggregate its helpers. - class_inheritable_accessor :master_helper_module - self.master_helper_module = Module.new - # Set the default directory for helpers - class_inheritable_accessor :helpers_dir - self.helpers_dir = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers") - - class << self - alias_method_chain :inherited, :helper + extlib_inheritable_accessor(:helpers_dir) do + defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers" end end - # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+, - # +numbers+ and Active Record objects, to name a few. These helpers are available to all templates - # by default. - # - # In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to - # extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will - # include a helper whose name matches that of the controller, e.g., MyController will automatically - # include MyHelper. - # - # Additional helpers can be specified using the +helper+ class method in ActionController::Base or any - # controller which inherits from it. - # - # ==== Examples - # The +to_s+ method from the Time class can be wrapped in a helper method to display a custom message if - # the Time object is blank: - # - # module FormattedTimeHelper - # def format_time(time, format=:long, blank_message=" ") - # time.blank? ? blank_message : time.to_s(format) - # end - # end - # - # FormattedTimeHelper can now be included in a controller, using the +helper+ class method: - # - # class EventsController < ActionController::Base - # helper FormattedTimeHelper - # def index - # @events = Event.find(:all) - # end - # end - # - # Then, in any view rendered by EventController, the format_time method can be called: - # - # <% @events.each do |event| -%> - #

- # <% format_time(event.time, :short, "N/A") %> | <%= event.name %> - #

- # <% end -%> - # - # Finally, assuming we have two event instances, one which has a time and one which does not, - # the output might look like this: - # - # 23 Aug 11:30 | Carolina Railhawks Soccer Match - # N/A | Carolina Railhaws Training Workshop - # module ClassMethods - # Makes all the (instance) methods in the helper module available to templates rendered through this controller. - # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules - # available to the templates. - def add_template_helper(helper_module) #:nodoc: - master_helper_module.module_eval { include helper_module } + def inherited(klass) + klass.class_eval { default_helper_module! unless name.blank? } + super end # The +helper+ class method can take a series of helper module names, a block, or both. # - # * *args: One or more modules, strings or symbols, or the special symbol :all. - # * &block: A block defining helper methods. - # + # ==== Parameters + # *args + # block:: A block defining helper methods + # # ==== Examples - # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file - # and include the module in the template class. The second form illustrates how to include custom helpers + # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file + # and include the module in the template class. The second form illustrates how to include custom helpers # when working with namespaced controllers, or other cases where the file containing the helper definition is not # in one of Rails' standard load paths: # helper :foo # => requires 'foo_helper' and includes FooHelper @@ -92,78 +87,23 @@ def add_template_helper(helper_module) #:nodoc: # ActionController::Base.helpers_dir (defaults to app/helpers/**/*.rb under RAILS_ROOT). # helper :all # - # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available + # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available # to the template. # # One line # helper { def hello() "Hello, world!" end } # # Multi-line # helper do - # def foo(bar) - # "#{bar} is the very best" + # def foo(bar) + # "#{bar} is the very best" # end # end - # + # # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of # +symbols+, +strings+, +modules+ and blocks. # helper(:three, BlindHelper) { def mice() 'mice' end } # def helper(*args, &block) - args.flatten.each do |arg| - case arg - when Module - add_template_helper(arg) - when :all - helper(all_application_helpers) - when String, Symbol - file_name = arg.to_s.underscore + '_helper' - class_name = file_name.camelize - - begin - require_dependency(file_name) - rescue LoadError => load_error - requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1] - if requiree == file_name - msg = "Missing helper file helpers/#{file_name}.rb" - raise LoadError.new(msg).copy_blame!(load_error) - else - raise - end - end - - add_template_helper(class_name.constantize) - else - raise ArgumentError, "helper expects String, Symbol, or Module argument (was: #{args.inspect})" - end - end - - # Evaluate block in template class if given. - master_helper_module.module_eval(&block) if block_given? - end - - # Declare a controller method as a helper. For example, the following - # makes the +current_user+ controller method available to the view: - # class ApplicationController < ActionController::Base - # helper_method :current_user, :logged_in? - # - # def current_user - # @current_user ||= User.find_by_id(session[:user]) - # end - # - # def logged_in? - # current_user != nil - # end - # end - # - # In a view: - # <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%> - def helper_method(*methods) - methods.flatten.each do |method| - master_helper_module.module_eval <<-end_eval - def #{method}(*args, &block) # def current_user(*args, &block) - controller.send(%(#{method}), *args, &block) # controller.send(%(current_user), *args, &block) - end # end - end_eval - end + super(*_modules_for_helpers(args), &block) end # Declares helper accessors for controller attributes. For example, the @@ -171,51 +111,68 @@ def #{method}(*args, &block) # def current_user(*args, &block # controller and makes them available to the view: # helper_attr :name # attr_accessor :name + # + # ==== Parameters + # *attrs:: Names of attributes to be converted + # into helpers. def helper_attr(*attrs) attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } end # Provides a proxy to access helpers methods from outside the view. def helpers - unless @helper_proxy - @helper_proxy = ActionView::Base.new - @helper_proxy.extend master_helper_module - else - @helper_proxy + @helper_proxy ||= ActionView::Base.new.extend(_helpers) + end + + private + # Returns a list of modules, normalized from the acceptable kinds of + # helpers with the following behavior: + # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper", + # and "foo_bar_helper.rb" is loaded using require_dependency. + # :all:: Loads all modules in the #helpers_dir + # Module:: No further processing + # + # After loading the appropriate files, the corresponding modules + # are returned. + # + # ==== Parameters + # args:: A list of helpers + # + # ==== Returns + # Array[Module]:: A normalized list of modules for the list of + # helpers provided. + def _modules_for_helpers(args) + args.flatten.map! do |arg| + case arg + when :all + _modules_for_helpers all_application_helpers + when String, Symbol + file_name = "#{arg.to_s.underscore}_helper" + require_dependency(file_name, "Missing helper file helpers/%s.rb") + file_name.camelize.constantize + when Module + arg + else + raise ArgumentError, "helper must be a String, Symbol, or Module" + end end end - private - def default_helper_module! - unless name.blank? - module_name = name.sub(/Controller$|$/, 'Helper') - module_path = module_name.split('::').map { |m| m.underscore }.join('/') - require_dependency module_path - helper module_name.constantize - end - rescue MissingSourceFile => e - raise unless e.is_missing? module_path - rescue NameError => e - raise unless e.missing_name? module_name - end + def default_helper_module! + module_name = name.sub(/Controller$/, '') + module_path = module_name.underscore + helper module_path + rescue MissingSourceFile => e + raise e unless e.is_missing? "#{module_path}_helper" + rescue NameError => e + raise e unless e.missing_name? "#{module_name}Helper" + end - def inherited_with_helper(child) - inherited_without_helper(child) - - begin - child.master_helper_module = Module.new - child.master_helper_module.__send__ :include, master_helper_module - child.__send__ :default_helper_module! - rescue MissingSourceFile => e - raise unless e.is_missing?("helpers/#{child.controller_path}_helper") - end - end - - # Extract helper names from files in app/helpers/**/*.rb - def all_application_helpers - extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/ - Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' } - end + # Extract helper names from files in app/helpers/**/*.rb + def all_application_helpers + extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/ + Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' } + end end end end diff --git a/actionpack/lib/action_controller/base/hide_actions.rb b/actionpack/lib/action_controller/base/hide_actions.rb new file mode 100644 index 0000000000..af68c772b1 --- /dev/null +++ b/actionpack/lib/action_controller/base/hide_actions.rb @@ -0,0 +1,35 @@ +module ActionController + # ActionController::HideActions adds the ability to prevent public methods on a controller + # to be called as actions. + module HideActions + extend ActiveSupport::Concern + + included do + extlib_inheritable_accessor(:hidden_actions) { Set.new } + end + + private + + # Overrides AbstractController::Base#action_method? to return false if the + # action name is in the list of hidden actions. + def action_method?(action_name) + !hidden_actions.include?(action_name) && super + end + + module ClassMethods + # Sets all of the actions passed in as hidden actions. + # + # ==== Parameters + # *args<#to_s>:: A list of actions + def hide_action(*args) + hidden_actions.merge(args.map! {|a| a.to_s }) + end + + # Overrides AbstractController::Base#action_methods to remove any methods + # that are listed as hidden methods. + def action_methods + @action_methods ||= Set.new(super.reject {|name| hidden_actions.include?(name)}) + end + end + end +end diff --git a/actionpack/lib/action_controller/new_base/http.rb b/actionpack/lib/action_controller/base/http.rb similarity index 50% rename from actionpack/lib/action_controller/new_base/http.rb rename to actionpack/lib/action_controller/base/http.rb index c96aaaa865..ec78bc15a8 100644 --- a/actionpack/lib/action_controller/new_base/http.rb +++ b/actionpack/lib/action_controller/base/http.rb @@ -2,48 +2,48 @@ require 'active_support/core_ext/module/delegation' module ActionController + # ActionController::Http provides a way to get a valid Rack application from a controller. + # + # In AbstractController, dispatching is triggered directly by calling #process on a new controller. + # ActionController::Http provides an #action method that returns a valid Rack application for a + # given action. Other rack builders, such as Rack::Builder, Rack::URLMap, and the Rails router, + # can dispatch directly to the action returned by FooController.action(:index). class Http < AbstractController::Base abstract! # :api: public attr_internal :params, :env - # :api: public + # Returns the last part of the controller's name, underscored, without the ending + # "Controller". For instance, MyApp::MyPostsController would return "my_posts" for + # controller_name + # + # ==== Returns + # String def self.controller_name @controller_name ||= controller_path.split("/").last end - # :api: public + # Delegates to the class' #controller_name def controller_name self.class.controller_name end - # :api: public + # Returns the full controller name, underscored, without the ending Controller. + # For instance, MyApp::MyPostsController would return "my_app/my_posts" for + # controller_name. + # + # ==== Returns + # String def self.controller_path - @controller_path ||= self.name.sub(/Controller$/, '').underscore + @controller_path ||= name && name.sub(/Controller$/, '').underscore end - # :api: public + # Delegates to the class' #controller_path def controller_path self.class.controller_path end - # :api: private - def self.action_names - action_methods - end - - # :api: private - def action_names - action_methods - end - - # :api: plugin - def self.call(env) - controller = new - controller.call(env).to_rack - end - # The details below can be overridden to support a specific # Request and Response object. The default ActionController::Base # implementation includes RackConvenience, which makes a request @@ -57,7 +57,7 @@ def initialize(*) super end - # Basic implements for content_type=, location=, and headers are + # Basic implementations for content_type=, location=, and headers are # provided to reduce the dependency on the RackConvenience module # in Renderer and Redirector. @@ -81,6 +81,15 @@ def to_rack [status, headers, response_body] end + # Return a rack endpoint for the given action. Memoize the endpoint, so + # multiple calls into MyController.action will return the same object + # for the same action. + # + # ==== Parameters + # action<#to_s>:: An action name + # + # ==== Returns + # Proc:: A rack application def self.action(name) @actions ||= {} @actions[name.to_s] ||= proc do |env| diff --git a/actionpack/lib/action_controller/base/http_authentication.rb b/actionpack/lib/action_controller/base/http_authentication.rb index 680900446c..525787bf92 100644 --- a/actionpack/lib/action_controller/base/http_authentication.rb +++ b/actionpack/lib/action_controller/base/http_authentication.rb @@ -185,7 +185,7 @@ def authorization(request) request.env['REDIRECT_X_HTTP_AUTHORIZATION'] end - # Raises error unless the request credentials response value matches the expected value. + # Returns false unless the request credentials response value matches the expected value. # First try the password as a ha1 digest password. If this fails, then try it as a plain # text password. def validate_digest_response(request, realm, &password_procedure) @@ -194,6 +194,8 @@ def validate_digest_response(request, realm, &password_procedure) if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque] password = password_procedure.call(credentials[:username]) + return false unless password + method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD'] [true, false].any? do |password_is_ha1| diff --git a/actionpack/lib/action_controller/base/layouts.rb b/actionpack/lib/action_controller/base/layouts.rb new file mode 100644 index 0000000000..365351b421 --- /dev/null +++ b/actionpack/lib/action_controller/base/layouts.rb @@ -0,0 +1,192 @@ +module ActionController + # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in + # repeated setups. The inclusion pattern has pages that look like this: + # + # <%= render "shared/header" %> + # Hello World + # <%= render "shared/footer" %> + # + # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose + # and if you ever want to change the structure of these two includes, you'll have to change all the templates. + # + # With layouts, you can flip it around and have the common structure know where to insert changing content. This means + # that the header and footer are only mentioned in one place, like this: + # + # // The header part of this layout + # <%= yield %> + # // The footer part of this layout + # + # And then you have content pages that look like this: + # + # hello world + # + # At rendering time, the content page is computed and then inserted in the layout, like this: + # + # // The header part of this layout + # hello world + # // The footer part of this layout + # + # == Accessing shared variables + # + # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with + # references that won't materialize before rendering time: + # + #

<%= @page_title %>

+ # <%= yield %> + # + # ...and content pages that fulfill these references _at_ rendering time: + # + # <% @page_title = "Welcome" %> + # Off-world colonies offers you a chance to start a new life + # + # The result after rendering is: + # + #

Welcome

+ # Off-world colonies offers you a chance to start a new life + # + # == Layout assignment + # + # You can either specify a layout declaratively (using the #layout class method) or give + # it the same name as your controller, and place it in app/views/layouts. + # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance. + # + # For instance, if you have PostsController and a template named app/views/layouts/posts.html.erb, + # that template will be used for all actions in PostsController and controllers inheriting + # from PostsController. + # + # If you use a module, for instance Weblog::PostsController, you will need a template named + # app/views/layouts/weblog/posts.html.erb. + # + # Since all your controllers inherit from ApplicationController, they will use + # app/views/layouts/application.html.erb if no other layout is specified + # or provided. + # + # == Inheritance Examples + # + # class BankController < ActionController::Base + # layout "bank_standard" + # + # class InformationController < BankController + # + # class TellerController < BankController + # # teller.html.erb exists + # + # class TillController < TellerController + # + # class VaultController < BankController + # layout :access_level_layout + # + # class EmployeeController < BankController + # layout nil + # + # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites + # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all. + # + # The TellerController uses +teller.html.erb+, and TillController inherits that layout and + # uses it as well. + # + # == Types of layouts + # + # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes + # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can + # be done either by specifying a method reference as a symbol or using an inline method (as a proc). + # + # The method reference is the preferred approach to variable layouts and is used like this: + # + # class WeblogController < ActionController::Base + # layout :writers_and_readers + # + # def index + # # fetching posts + # end + # + # private + # def writers_and_readers + # logged_in? ? "writer_layout" : "reader_layout" + # end + # + # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing + # is logged in or not. + # + # If you want to use an inline method, such as a proc, do something like this: + # + # class WeblogController < ActionController::Base + # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } + # + # Of course, the most common way of specifying a layout is still just as a plain template name: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard" + # + # If no directory is specified for the template name, the template will by default be looked for in app/views/layouts/. + # Otherwise, it will be looked up relative to the template root. + # + # == Conditional layouts + # + # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering + # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The + # :only and :except options can be passed to the layout call. For example: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard", :except => :rss + # + # # ... + # + # end + # + # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout + # around the rendered view. + # + # Both the :only and :except condition can accept an arbitrary number of method references, so + # #:except => [ :rss, :text_only ] is valid, as is :except => :rss. + # + # == Using a different layout in the action render call + # + # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. + # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller. + # You can do this by passing a :layout option to the render call. For example: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard" + # + # def help + # render :action => "help", :layout => "help" + # end + # end + # + # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout. + module Layouts + extend ActiveSupport::Concern + + include ActionController::Renderer + include AbstractController::Layouts + + module ClassMethods + # If no layout is provided, look for a layout with this name. + def _implied_layout_name + controller_path + end + end + + private + def _determine_template(options) + super + + return if (options.key?(:text) || options.key?(:inline) || options.key?(:partial)) && !options.key?(:layout) + layout = options.key?(:layout) ? options[:layout] : :default + options[:_layout] = _layout_for_option(layout, options[:_template].details) + end + + def _layout_for_option(name, details) + case name + when String then _layout_for_name(name, details) + when true then _default_layout(details, true) + when :default then _default_layout(details, false) + when false, nil then nil + else + raise ArgumentError, + "String, true, or false, expected for `layout'; you passed #{name.inspect}" + end + end + end +end diff --git a/actionpack/lib/action_controller/base/mime_responds.rb b/actionpack/lib/action_controller/base/mime_responds.rb index 3c17dda1a1..ed0d58dba1 100644 --- a/actionpack/lib/action_controller/base/mime_responds.rb +++ b/actionpack/lib/action_controller/base/mime_responds.rb @@ -120,12 +120,9 @@ def custom(mime_type, &block) @responses[mime_type] ||= Proc.new do # TODO: Remove this when new base is merged in - if defined?(Http) - @controller.formats = [mime_type.to_sym] - end - + @controller.formats = [mime_type.to_sym] + @controller.content_type = mime_type @controller.template.formats = [mime_type.to_sym] - @response.content_type = mime_type.to_s block_given? ? block.call : @controller.send(:render, :action => @controller.action_name) end diff --git a/actionpack/lib/action_controller/new_base/rack_convenience.rb b/actionpack/lib/action_controller/base/rack_convenience.rb similarity index 91% rename from actionpack/lib/action_controller/new_base/rack_convenience.rb rename to actionpack/lib/action_controller/base/rack_convenience.rb index 5dfa7d12f3..805157b0e3 100644 --- a/actionpack/lib/action_controller/new_base/rack_convenience.rb +++ b/actionpack/lib/action_controller/base/rack_convenience.rb @@ -3,7 +3,7 @@ module RackConvenience extend ActiveSupport::Concern included do - delegate :headers, :status=, :location=, + delegate :headers, :status=, :location=, :content_type=, :status, :location, :content_type, :to => "@_response" attr_internal :request, :response end diff --git a/actionpack/lib/action_controller/base/redirect.rb b/actionpack/lib/action_controller/base/redirect.rb deleted file mode 100644 index 7e10f614e2..0000000000 --- a/actionpack/lib/action_controller/base/redirect.rb +++ /dev/null @@ -1,89 +0,0 @@ -module ActionController - class RedirectBackError < ActionControllerError #:nodoc: - DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].' - - def initialize(message = nil) - super(message || DEFAULT_MESSAGE) - end - end - - module Redirector - - # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: - # - # * Hash - The URL will be generated by calling url_for with the +options+. - # * Record - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. - # * String starting with protocol:// (like http://) - Is passed straight through as the target for redirection. - # * String not containing a protocol - The current protocol and host is prepended to the string. - # * :back - Back to the page that issued the request. Useful for forms that are triggered from multiple places. - # Short-hand for redirect_to(request.env["HTTP_REFERER"]) - # - # Examples: - # redirect_to :action => "show", :id => 5 - # redirect_to post - # redirect_to "http://www.rubyonrails.org" - # redirect_to "/images/screenshot.jpg" - # redirect_to articles_url - # redirect_to :back - # - # The redirection happens as a "302 Moved" header unless otherwise specified. - # - # Examples: - # redirect_to post_url(@post), :status=>:found - # redirect_to :action=>'atom', :status=>:moved_permanently - # redirect_to post_url(@post), :status=>301 - # redirect_to :action=>'atom', :status=>302 - # - # When using redirect_to :back, if there is no referrer, - # RedirectBackError will be raised. You may specify some fallback - # behavior for this case by rescuing RedirectBackError. - def redirect_to(options = {}, response_status = {}) #:doc: - raise ActionControllerError.new("Cannot redirect to nil!") if options.nil? - - if options.is_a?(Hash) && options[:status] - status = options.delete(:status) - elsif response_status[:status] - status = response_status[:status] - else - status = 302 - end - - case options - # The scheme name consist of a letter followed by any combination of - # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") - # characters; and is terminated by a colon (":"). - when %r{^\w[\w\d+.-]*:.*} - redirect_to_full_url(options, status) - when String - redirect_to_full_url(request.protocol + request.host_with_port + options, status) - when :back - if referer = request.headers["Referer"] - redirect_to(referer, :status=>status) - else - raise RedirectBackError - end - else - redirect_to_full_url(url_for(options), status) - end - end - - def redirect_to_full_url(url, status) - raise DoubleRenderError if performed? - logger.info("Redirected to #{url}") if logger && logger.info? - response.status = interpret_status(status) - response.location = url.gsub(/[\r\n]/, '') - response.body = "You are being redirected." - @performed_redirect = true - end - - # Clears the redirected results from the headers, resets the status to 200 and returns - # the URL that was used to redirect or nil if there was no redirected URL - # Note that +redirect_to+ will change the body of the response to indicate a redirection. - # The response body is not reset here, see +erase_render_results+ - def erase_redirect_results #:nodoc: - @performed_redirect = false - response.status = DEFAULT_RENDER_STATUS_CODE - response.headers.delete('Location') - end - end -end \ No newline at end of file diff --git a/actionpack/lib/action_controller/new_base/redirector.rb b/actionpack/lib/action_controller/base/redirector.rb similarity index 100% rename from actionpack/lib/action_controller/new_base/redirector.rb rename to actionpack/lib/action_controller/base/redirector.rb diff --git a/actionpack/lib/action_controller/base/render.rb b/actionpack/lib/action_controller/base/render.rb deleted file mode 100644 index cc0d878e01..0000000000 --- a/actionpack/lib/action_controller/base/render.rb +++ /dev/null @@ -1,403 +0,0 @@ -require 'action_controller/abstract/renderer' - -module ActionController - DEFAULT_RENDER_STATUS_CODE = "200 OK" - - class DoubleRenderError < ActionControllerError #:nodoc: - DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"." - - def initialize(message = nil) - super(message || DEFAULT_MESSAGE) - end - end - - module Renderer - - protected - # Renders the content that will be returned to the browser as the response body. - # - # === Rendering an action - # - # Action rendering is the most common form and the type used automatically by Action Controller when nothing else is - # specified. By default, actions are rendered within the current layout (if one exists). - # - # # Renders the template for the action "goal" within the current controller - # render :action => "goal" - # - # # Renders the template for the action "short_goal" within the current controller, - # # but without the current active layout - # render :action => "short_goal", :layout => false - # - # # Renders the template for the action "long_goal" within the current controller, - # # but with a custom layout - # render :action => "long_goal", :layout => "spectacular" - # - # === Rendering partials - # - # Partial rendering in a controller is most commonly used together with Ajax calls that only update one or a few elements on a page - # without reloading. Rendering of partials from the controller makes it possible to use the same partial template in - # both the full-page rendering (by calling it from within the template) and when sub-page updates happen (from the - # controller action responding to Ajax calls). By default, the current layout is not used. - # - # # Renders the same partial with a local variable. - # render :partial => "person", :locals => { :name => "david" } - # - # # Renders the partial, making @new_person available through - # # the local variable 'person' - # render :partial => "person", :object => @new_person - # - # # Renders a collection of the same partial by making each element - # # of @winners available through the local variable "person" as it - # # builds the complete response. - # render :partial => "person", :collection => @winners - # - # # Renders a collection of partials but with a custom local variable name - # render :partial => "admin_person", :collection => @winners, :as => :person - # - # # Renders the same collection of partials, but also renders the - # # person_divider partial between each person partial. - # render :partial => "person", :collection => @winners, :spacer_template => "person_divider" - # - # # Renders a collection of partials located in a view subfolder - # # outside of our current controller. In this example we will be - # # rendering app/views/shared/_note.r(html|xml) Inside the partial - # # each element of @new_notes is available as the local var "note". - # render :partial => "shared/note", :collection => @new_notes - # - # # Renders the partial with a status code of 500 (internal error). - # render :partial => "broken", :status => 500 - # - # Note that the partial filename must also be a valid Ruby variable name, - # so e.g. 2005 and register-user are invalid. - # - # - # == Automatic etagging - # - # Rendering will automatically insert the etag header on 200 OK responses. The etag is calculated using MD5 of the - # response body. If a request comes in that has a matching etag, the response will be changed to a 304 Not Modified - # and the response body will be set to an empty string. No etag header will be inserted if it's already set. - # - # === Rendering a template - # - # Template rendering works just like action rendering except that it takes a path relative to the template root. - # The current layout is automatically applied. - # - # # Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.erb) - # render :template => "weblog/show" - # - # # Renders the template with a local variable - # render :template => "weblog/show", :locals => {:customer => Customer.new} - # - # === Rendering a file - # - # File rendering works just like action rendering except that it takes a filesystem path. By default, the path - # is assumed to be absolute, and the current layout is not applied. - # - # # Renders the template located at the absolute filesystem path - # render :file => "/path/to/some/template.erb" - # render :file => "c:/path/to/some/template.erb" - # - # # Renders a template within the current layout, and with a 404 status code - # render :file => "/path/to/some/template.erb", :layout => true, :status => 404 - # render :file => "c:/path/to/some/template.erb", :layout => true, :status => 404 - # - # === Rendering text - # - # Rendering of text is usually used for tests or for rendering prepared content, such as a cache. By default, text - # rendering is not done within the active layout. - # - # # Renders the clear text "hello world" with status code 200 - # render :text => "hello world!" - # - # # Renders the clear text "Explosion!" with status code 500 - # render :text => "Explosion!", :status => 500 - # - # # Renders the clear text "Hi there!" within the current active layout (if one exists) - # render :text => "Hi there!", :layout => true - # - # # Renders the clear text "Hi there!" within the layout - # # placed in "app/views/layouts/special.r(html|xml)" - # render :text => "Hi there!", :layout => "special" - # - # The :text option can also accept a Proc object, which can be used to manually control the page generation. This should - # generally be avoided, as it violates the separation between code and content, and because almost everything that can be - # done with this method can also be done more cleanly using one of the other rendering methods, most notably templates. - # - # # Renders "Hello from code!" - # render :text => proc { |response, output| output.write("Hello from code!") } - # - # === Rendering XML - # - # Rendering XML sets the content type to application/xml. - # - # # Renders 'David' - # render :xml => {:name => "David"}.to_xml - # - # It's not necessary to call to_xml on the object you want to render, since render will - # automatically do that for you: - # - # # Also renders 'David' - # render :xml => {:name => "David"} - # - # === Rendering JSON - # - # Rendering JSON sets the content type to application/json and optionally wraps the JSON in a callback. It is expected - # that the response will be parsed (or eval'd) for use as a data structure. - # - # # Renders '{"name": "David"}' - # render :json => {:name => "David"}.to_json - # - # It's not necessary to call to_json on the object you want to render, since render will - # automatically do that for you: - # - # # Also renders '{"name": "David"}' - # render :json => {:name => "David"} - # - # Sometimes the result isn't handled directly by a script (such as when the request comes from a SCRIPT tag), - # so the :callback option is provided for these cases. - # - # # Renders 'show({"name": "David"})' - # render :json => {:name => "David"}.to_json, :callback => 'show' - # - # === Rendering an inline template - # - # Rendering of an inline template works as a cross between text and action rendering where the source for the template - # is supplied inline, like text, but its interpreted with ERb or Builder, like action. By default, ERb is used for rendering - # and the current layout is not used. - # - # # Renders "hello, hello, hello, again" - # render :inline => "<%= 'hello, ' * 3 + 'again' %>" - # - # # Renders "

Good seeing you!

" using Builder - # render :inline => "xml.p { 'Good seeing you!' }", :type => :builder - # - # # Renders "hello david" - # render :inline => "<%= 'hello ' + name %>", :locals => { :name => "david" } - # - # === Rendering inline JavaScriptGenerator page updates - # - # In addition to rendering JavaScriptGenerator page updates with Ajax in RJS templates (see ActionView::Base for details), - # you can also pass the :update parameter to +render+, along with a block, to render page updates inline. - # - # render :update do |page| - # page.replace_html 'user_list', :partial => 'user', :collection => @users - # page.visual_effect :highlight, 'user_list' - # end - # - # === Rendering vanilla JavaScript - # - # In addition to using RJS with render :update, you can also just render vanilla JavaScript with :js. - # - # # Renders "alert('hello')" and sets the mime type to text/javascript - # render :js => "alert('hello')" - # - # === Rendering with status and location headers - # All renders take the :status and :location options and turn them into headers. They can even be used together: - # - # render :xml => post.to_xml, :status => :created, :location => post_url(post) - def render(options = nil, extra_options = {}, &block) #:doc: - raise DoubleRenderError, "Can only render or redirect once per action" if performed? - - options = { :layout => true } if options.nil? - - # This handles render "string", render :symbol, and render object - # render string and symbol are handled by render_for_name - # render object becomes render :partial => object - unless options.is_a?(Hash) - if options.is_a?(String) || options.is_a?(Symbol) - original, options = options, extra_options - else - extra_options[:partial], options = options, extra_options - end - end - - layout_name = options.delete(:layout) - - _process_options(options) - - if block_given? - @template.send(:_evaluate_assigns_and_ivars) - - generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block) - response.content_type = Mime::JS - return render_for_text(generator.to_s) - end - - if original - return render_for_name(original, layout_name, options) unless block_given? - end - - if options.key?(:text) - return render_for_text(@template._render_text(options[:text], - _pick_layout(layout_name), options)) - end - - file, template = options.values_at(:file, :template) - if file || template - file = template.sub(/^\//, '') if template - return render_for_file(file, [layout_name, !!template], options) - end - - if action_option = options[:action] - return render_for_action(action_option, [layout_name, true], options) - end - - if inline = options[:inline] - render_for_text(@template._render_inline(inline, _pick_layout(layout_name), options)) - - elsif xml = options[:xml] - response.content_type ||= Mime::XML - render_for_text(xml.respond_to?(:to_xml) ? xml.to_xml : xml) - - elsif js = options[:js] - response.content_type ||= Mime::JS - render_for_text(js) - - elsif options.include?(:json) - json = options[:json] - json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str) - json = "#{options[:callback]}(#{json})" unless options[:callback].blank? - response.content_type ||= Mime::JSON - render_for_text(json) - - elsif partial = options[:partial] - if partial == true - parts = [action_name_base, formats, controller_name, true] - elsif partial.is_a?(String) - parts = partial_parts(partial, options) - else - return render_for_text(@template._render_partial(options)) - end - - render_for_parts(parts, layout_name, options) - - elsif options[:nothing] - render_for_text(nil) - - else - render_for_parts([action_name, formats, controller_path], layout_name, options) - end - end - - def partial_parts(name, options) - segments = name.split("/") - parts = segments.pop.split(".") - - case parts.size - when 1 - parts - when 2, 3 - extension = parts.delete_at(1).to_sym - if formats.include?(extension) - self.formats.replace [extension] - end - parts.pop if parts.size == 2 - end - - path = parts.join(".") - prefix = segments[0..-1].join("/") - prefix = prefix.blank? ? controller_path : prefix - parts = [path, formats, prefix] - parts.push options[:object] || true - end - - def formats - @_request.formats.map {|f| f.symbol }.compact - end - - def action_name_base(name = action_name) - (name.is_a?(String) ? name.sub(/^#{controller_path}\//, '') : name).to_s - end - - # Same rules as render, but returns a Rack-compatible body - # instead of sending the response. - def render_to_body(options = nil, &block) #:doc: - render(options, &block) - response.body - ensure - response.content_type = nil - erase_render_results - reset_variables_added_to_assigns - end - - def render_to_string(options = {}) - AbstractController::Renderer.body_to_s(render_to_body(options)) - end - - # Clears the rendered results, allowing for another render to be performed. - def erase_render_results #:nodoc: - response.body = [] - @performed_render = false - end - - # Erase both render and redirect results - def erase_results #:nodoc: - erase_render_results - erase_redirect_results - end - - # Return a response that has no content (merely headers). The options - # argument is interpreted to be a hash of header names and values. - # This allows you to easily return a response that consists only of - # significant headers: - # - # head :created, :location => person_path(@person) - # - # It can also be used to return exceptional conditions: - # - # return head(:method_not_allowed) unless request.post? - # return head(:bad_request) unless valid_request? - # render - def head(*args) - if args.length > 2 - raise ArgumentError, "too many arguments to head" - elsif args.empty? - raise ArgumentError, "too few arguments to head" - end - options = args.extract_options! - status = interpret_status(args.shift || options.delete(:status) || :ok) - - options.each do |key, value| - headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s - end - - render :nothing => true, :status => status - end - - private - def render_for_name(name, layout, options) - case name.to_s.index('/') - when 0 - render_for_file(name, layout, options) - when nil - render_for_action(name, layout, options) - else - render_for_file(name.sub(/^\//, ''), [layout, true], options) - end - end - - # ==== Arguments - # parts:: - # Example: ["show", [:html, :xml], "users", false] - def render_for_parts(parts, layout_details, options = {}) - parts[1] = {:formats => parts[1], :locales => [I18n.locale]} - - tmp = view_paths.find_by_parts(*parts) - - layout = _pick_layout(*layout_details) unless - self.class.exempt_from_layout.include?(tmp.handler) - - render_for_text( - @template._render_template_with_layout(tmp, layout, options, parts[3])) - end - - def render_for_file(file, layout, options) - render_for_parts([file, [request.format.to_sym]], layout, options) - end - - def render_for_action(name, layout, options) - parts = [action_name_base(name), formats, controller_name] - render_for_parts(parts, layout, options) - end - end -end diff --git a/actionpack/lib/action_controller/new_base/render_options.rb b/actionpack/lib/action_controller/base/render_options.rb similarity index 100% rename from actionpack/lib/action_controller/new_base/render_options.rb rename to actionpack/lib/action_controller/base/render_options.rb diff --git a/actionpack/lib/action_controller/new_base/renderer.rb b/actionpack/lib/action_controller/base/renderer.rb similarity index 100% rename from actionpack/lib/action_controller/new_base/renderer.rb rename to actionpack/lib/action_controller/base/renderer.rb diff --git a/actionpack/lib/action_controller/base/request_forgery_protection.rb b/actionpack/lib/action_controller/base/request_forgery_protection.rb index c580280a0c..ad06657f86 100644 --- a/actionpack/lib/action_controller/base/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/base/request_forgery_protection.rb @@ -6,20 +6,16 @@ module RequestForgeryProtection extend ActiveSupport::Concern # TODO : Remove the defined? check when new base is the main base - if defined?(ActionController::Http) - include AbstractController::Helpers, Session - end + include AbstractController::Helpers, Session included do - if defined?(ActionController::Http) - # Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ - # sets it to :authenticity_token by default. - cattr_accessor :request_forgery_protection_token + # Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ + # sets it to :authenticity_token by default. + cattr_accessor :request_forgery_protection_token - # Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode. - class_inheritable_accessor :allow_forgery_protection - self.allow_forgery_protection = true - end + # Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode. + class_inheritable_accessor :allow_forgery_protection + self.allow_forgery_protection = true helper_method :form_authenticity_token helper_method :protect_against_forgery? diff --git a/actionpack/lib/action_controller/new_base/rescuable.rb b/actionpack/lib/action_controller/base/rescuable.rb similarity index 100% rename from actionpack/lib/action_controller/new_base/rescuable.rb rename to actionpack/lib/action_controller/base/rescuable.rb diff --git a/actionpack/lib/action_controller/base/rescue.rb b/actionpack/lib/action_controller/base/rescue.rb deleted file mode 100644 index 2717a06a37..0000000000 --- a/actionpack/lib/action_controller/base/rescue.rb +++ /dev/null @@ -1,50 +0,0 @@ -module ActionController #:nodoc: - # Actions that fail to perform as expected throw exceptions. These - # exceptions can either be rescued for the public view (with a nice - # user-friendly explanation) or for the developers view (with tons of - # debugging information). The developers view is already implemented by - # the Action Controller, but the public view should be tailored to your - # specific application. - # - # The default behavior for public exceptions is to render a static html - # file with the name of the error code thrown. If no such file exists, an - # empty response is sent with the correct status code. - # - # You can override what constitutes a local request by overriding the - # local_request? method in your own controller. Custom rescue - # behavior is achieved by overriding the rescue_action_in_public - # and rescue_action_locally methods. - module Rescue - def self.included(base) #:nodoc: - base.send :include, ActiveSupport::Rescuable - base.extend(ClassMethods) - - base.class_eval do - alias_method_chain :perform_action, :rescue - end - end - - module ClassMethods - def rescue_action(env) - exception = env.delete('action_dispatch.rescue.exception') - request = ActionDispatch::Request.new(env) - response = ActionDispatch::Response.new - new.process(request, response, :rescue_action, exception).to_a - end - end - - protected - # Exception handler called when the performance of an action raises - # an exception. - def rescue_action(exception) - rescue_with_handler(exception) || raise(exception) - end - - private - def perform_action_with_rescue - perform_action_without_rescue - rescue Exception => exception - rescue_action(exception) - end - end -end diff --git a/actionpack/lib/action_controller/base/responder.rb b/actionpack/lib/action_controller/base/responder.rb deleted file mode 100644 index 1aee980da6..0000000000 --- a/actionpack/lib/action_controller/base/responder.rb +++ /dev/null @@ -1,43 +0,0 @@ -module ActionController - module Responder - def self.included(klass) - klass.extend ClassMethods - end - - private - def render_for_text(text) #:nodoc: - @performed_render = true - - case text - when Proc - response.body = text - when nil - # Safari 2 doesn't pass response headers if the response is zero-length - if response.body_parts.empty? - response.body_parts << ' ' - end - else - response.body_parts << text - end - end - - # Returns a set of the methods defined as actions in your controller - def action_methods - self.class.action_methods - end - - module ClassMethods - def action_methods - @action_methods ||= - # All public instance methods of this class, including ancestors - public_instance_methods(true).map { |m| m.to_s }.to_set - - # Except for public instance methods of Base and its ancestors - Base.public_instance_methods(true).map { |m| m.to_s } + - # Be sure to include shadowed public instance methods of this class - public_instance_methods(false).map { |m| m.to_s } - - # And always exclude explicitly hidden actions - hidden_actions - end - end - end -end diff --git a/actionpack/lib/action_controller/new_base/session.rb b/actionpack/lib/action_controller/base/session.rb similarity index 100% rename from actionpack/lib/action_controller/new_base/session.rb rename to actionpack/lib/action_controller/base/session.rb diff --git a/actionpack/lib/action_controller/base/streaming.rb b/actionpack/lib/action_controller/base/streaming.rb index 73d4bde6c1..9ff4f25f43 100644 --- a/actionpack/lib/action_controller/base/streaming.rb +++ b/actionpack/lib/action_controller/base/streaming.rb @@ -4,10 +4,7 @@ module ActionController #:nodoc: module Streaming extend ActiveSupport::Concern - # TODO : Remove the defined? check when new base is the main base - if defined?(ActionController::Http) - include ActionController::Renderer - end + include ActionController::Renderer DEFAULT_SEND_FILE_OPTIONS = { :type => 'application/octet-stream'.freeze, @@ -162,15 +159,16 @@ def send_file_headers!(options) disposition <<= %(; filename="#{options[:filename]}") if options[:filename] content_type = options[:type] + if content_type.is_a?(Symbol) - raise ArgumentError, "Unknown MIME type #{options[:type]}" unless Mime::EXTENSION_LOOKUP.has_key?(content_type.to_s) - content_type = Mime::Type.lookup_by_extension(content_type.to_s) + raise ArgumentError, "Unknown MIME type #{options[:type]}" unless Mime::EXTENSION_LOOKUP.key?(content_type.to_s) + self.content_type = Mime::Type.lookup_by_extension(content_type.to_s) + else + self.content_type = content_type end - content_type = content_type.to_s.strip # fixes a problem with extra '\r' with some browsers headers.merge!( - 'Content-Length' => options[:length], - 'Content-Type' => content_type, + 'Content-Length' => options[:length].to_s, 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary' ) diff --git a/actionpack/lib/action_controller/new_base/testing.rb b/actionpack/lib/action_controller/base/testing.rb similarity index 100% rename from actionpack/lib/action_controller/new_base/testing.rb rename to actionpack/lib/action_controller/base/testing.rb diff --git a/actionpack/lib/action_controller/new_base/url_for.rb b/actionpack/lib/action_controller/base/url_for.rb similarity index 100% rename from actionpack/lib/action_controller/new_base/url_for.rb rename to actionpack/lib/action_controller/base/url_for.rb diff --git a/actionpack/lib/action_controller/base/verification.rb b/actionpack/lib/action_controller/base/verification.rb index d87b348ed4..951ae1bee1 100644 --- a/actionpack/lib/action_controller/base/verification.rb +++ b/actionpack/lib/action_controller/base/verification.rb @@ -2,10 +2,7 @@ module ActionController #:nodoc: module Verification #:nodoc: extend ActiveSupport::Concern - # TODO : Remove the defined? check when new base is the main base - if defined?(ActionController::Http) - include AbstractController::Callbacks, Session, Flash, Renderer - end + include AbstractController::Callbacks, Session, Flash, Renderer # This module provides a class-level method for specifying that certain # actions are guarded against being called without certain prerequisites diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index 54148b55d8..d8a1662acc 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -62,14 +62,7 @@ def caches_action(*actions) cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options) - # TODO: Remove this once new base is swapped in. - if defined?(ActionController::Http) - around_filter cache_filter, filter_options - else - around_filter(filter_options) do |controller, action| - cache_filter.filter(controller, action) - end - end + around_filter cache_filter, filter_options end end @@ -91,19 +84,10 @@ def initialize(options, &block) @options = options end - # TODO: Remove once New Base is merged - if defined?(ActionController::Http) - def filter(controller) - should_continue = before(controller) - yield if should_continue - after(controller) - end - else - def filter(controller, action) - should_continue = before(controller) - action.call if should_continue - after(controller) - end + def filter(controller) + should_continue = before(controller) + yield if should_continue + after(controller) end def before(controller) diff --git a/actionpack/lib/action_controller/base/layout.rb b/actionpack/lib/action_controller/legacy/layout.rb similarity index 97% rename from actionpack/lib/action_controller/base/layout.rb rename to actionpack/lib/action_controller/legacy/layout.rb index cf5f46a32b..3f3d20b95f 100644 --- a/actionpack/lib/action_controller/base/layout.rb +++ b/actionpack/lib/action_controller/legacy/layout.rb @@ -1,8 +1,16 @@ require 'active_support/core_ext/enumerable' +require 'active_support/core_ext/class' require 'active_support/core_ext/class/delegating_attributes' require 'active_support/core_ext/class/inheritable_attributes' module ActionController #:nodoc: + # MegasuperultraHAX + # plz refactor ActionMailer + class Base + @@exempt_from_layout = [ActionView::TemplateHandlers::RJS] + cattr_accessor :exempt_from_layout + end + module Layout #:nodoc: def self.included(base) base.extend(ClassMethods) @@ -36,9 +44,6 @@ def self.included(base) # hello world # // The footer part of this layout # - # NOTE: The old notation for rendering the view from a layout was to expose the magic @content_for_layout instance - # variable. The preferred notation now is to use yield, as documented above. - # # == Accessing shared variables # # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with diff --git a/actionpack/lib/action_controller/new_base.rb b/actionpack/lib/action_controller/new_base.rb deleted file mode 100644 index df256985ac..0000000000 --- a/actionpack/lib/action_controller/new_base.rb +++ /dev/null @@ -1,47 +0,0 @@ -module ActionController - autoload :Base, "action_controller/new_base/base" - autoload :ConditionalGet, "action_controller/new_base/conditional_get" - autoload :HideActions, "action_controller/new_base/hide_actions" - autoload :Http, "action_controller/new_base/http" - autoload :Layouts, "action_controller/new_base/layouts" - autoload :RackConvenience, "action_controller/new_base/rack_convenience" - autoload :Rails2Compatibility, "action_controller/new_base/compatibility" - autoload :Redirector, "action_controller/new_base/redirector" - autoload :Renderer, "action_controller/new_base/renderer" - autoload :RenderOptions, "action_controller/new_base/render_options" - autoload :Renderers, "action_controller/new_base/render_options" - autoload :Rescue, "action_controller/new_base/rescuable" - autoload :Testing, "action_controller/new_base/testing" - autoload :UrlFor, "action_controller/new_base/url_for" - autoload :Session, "action_controller/new_base/session" - autoload :Helpers, "action_controller/new_base/helpers" - - # Ported modules - # require 'action_controller/routing' - autoload :Caching, 'action_controller/caching' - autoload :Dispatcher, 'action_controller/dispatch/dispatcher' - autoload :MimeResponds, 'action_controller/base/mime_responds' - autoload :PolymorphicRoutes, 'action_controller/routing/generation/polymorphic_routes' - autoload :RecordIdentifier, 'action_controller/record_identifier' - autoload :Resources, 'action_controller/routing/resources' - autoload :SessionManagement, 'action_controller/base/session_management' - autoload :TestCase, 'action_controller/testing/test_case' - autoload :UrlRewriter, 'action_controller/routing/generation/url_rewriter' - autoload :UrlWriter, 'action_controller/routing/generation/url_rewriter' - - autoload :Verification, 'action_controller/base/verification' - autoload :Flash, 'action_controller/base/chained/flash' - autoload :RequestForgeryProtection, 'action_controller/base/request_forgery_protection' - autoload :Streaming, 'action_controller/base/streaming' - autoload :HttpAuthentication, 'action_controller/base/http_authentication' - autoload :FilterParameterLogging, 'action_controller/base/filter_parameter_logging' - autoload :Translation, 'action_controller/translation' - autoload :Cookies, 'action_controller/base/cookies' - - require 'action_controller/routing' -end - -autoload :HTML, 'action_controller/vendor/html-scanner' - -require 'action_dispatch' -require 'action_view' diff --git a/actionpack/lib/action_controller/new_base/base.rb b/actionpack/lib/action_controller/new_base/base.rb deleted file mode 100644 index d7b65d37fa..0000000000 --- a/actionpack/lib/action_controller/new_base/base.rb +++ /dev/null @@ -1,173 +0,0 @@ -module ActionController - class Base < Http - abstract! - - include AbstractController::Benchmarker - include AbstractController::Callbacks - include AbstractController::Logger - - include ActionController::Helpers - include ActionController::HideActions - include ActionController::UrlFor - include ActionController::Redirector - include ActionController::Renderer - include ActionController::Renderers::All - include ActionController::Layouts - include ActionController::ConditionalGet - include ActionController::RackConvenience - - # Legacy modules - include SessionManagement - include ActionDispatch::StatusCodes - include ActionController::Caching - include ActionController::MimeResponds - - # Rails 2.x compatibility - include ActionController::Rails2Compatibility - - include ActionController::Cookies - include ActionController::Session - include ActionController::Flash - include ActionController::Verification - include ActionController::RequestForgeryProtection - include ActionController::Streaming - include ActionController::HttpAuthentication::Basic::ControllerMethods - include ActionController::HttpAuthentication::Digest::ControllerMethods - include ActionController::FilterParameterLogging - include ActionController::Translation - - # TODO: Extract into its own module - # This should be moved together with other normalizing behavior - module ImplicitRender - def send_action(method_name) - ret = super - default_render unless performed? - ret - end - - def default_render - render - end - - def method_for_action(action_name) - super || begin - if view_paths.find_by_parts?(action_name.to_s, {:formats => formats, :locales => [I18n.locale]}, controller_path) - "default_render" - end - end - end - end - - include ImplicitRender - - include ActionController::Rescue - - def self.inherited(klass) - ::ActionController::Base.subclasses << klass.to_s - super - end - - def self.subclasses - @subclasses ||= [] - end - - def self.app_loaded! - @subclasses.each do |subclass| - subclass.constantize._write_layout_method - end - end - - def _normalize_options(action = nil, options = {}, &blk) - if action.is_a?(Hash) - options, action = action, nil - elsif action.is_a?(String) || action.is_a?(Symbol) - key = case action = action.to_s - when %r{^/} then :file - when %r{/} then :template - else :action - end - options.merge! key => action - elsif action - options.merge! :partial => action - end - - if options.key?(:action) && options[:action].to_s.index("/") - options[:template] = options.delete(:action) - end - - if options[:status] - options[:status] = interpret_status(options[:status]).to_i - end - - options[:update] = blk if block_given? - options - end - - def render(action = nil, options = {}, &blk) - options = _normalize_options(action, options, &blk) - super(options) - end - - def render_to_string(action = nil, options = {}, &blk) - options = _normalize_options(action, options, &blk) - super(options) - end - - # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: - # - # * Hash - The URL will be generated by calling url_for with the +options+. - # * Record - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. - # * String starting with protocol:// (like http://) - Is passed straight through as the target for redirection. - # * String not containing a protocol - The current protocol and host is prepended to the string. - # * :back - Back to the page that issued the request. Useful for forms that are triggered from multiple places. - # Short-hand for redirect_to(request.env["HTTP_REFERER"]) - # - # Examples: - # redirect_to :action => "show", :id => 5 - # redirect_to post - # redirect_to "http://www.rubyonrails.org" - # redirect_to "/images/screenshot.jpg" - # redirect_to articles_url - # redirect_to :back - # - # The redirection happens as a "302 Moved" header unless otherwise specified. - # - # Examples: - # redirect_to post_url(@post), :status=>:found - # redirect_to :action=>'atom', :status=>:moved_permanently - # redirect_to post_url(@post), :status=>301 - # redirect_to :action=>'atom', :status=>302 - # - # When using redirect_to :back, if there is no referrer, - # RedirectBackError will be raised. You may specify some fallback - # behavior for this case by rescuing RedirectBackError. - def redirect_to(options = {}, response_status = {}) #:doc: - raise ActionControllerError.new("Cannot redirect to nil!") if options.nil? - - status = if options.is_a?(Hash) && options.key?(:status) - interpret_status(options.delete(:status)) - elsif response_status.key?(:status) - interpret_status(response_status[:status]) - else - 302 - end - - url = case options - # The scheme name consist of a letter followed by any combination of - # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") - # characters; and is terminated by a colon (":"). - when %r{^\w[\w\d+.-]*:.*} - options - when String - request.protocol + request.host_with_port + options - when :back - raise RedirectBackError unless refer = request.headers["Referer"] - refer - else - url_for(options) - end - - super(url, status) - end - end -end diff --git a/actionpack/lib/action_controller/new_base/helpers.rb b/actionpack/lib/action_controller/new_base/helpers.rb deleted file mode 100644 index e8000be87b..0000000000 --- a/actionpack/lib/action_controller/new_base/helpers.rb +++ /dev/null @@ -1,129 +0,0 @@ -require 'active_support/core_ext/load_error' -require 'active_support/core_ext/name_error' -require 'active_support/dependencies' - -module ActionController - module Helpers - extend ActiveSupport::Concern - - include AbstractController::Helpers - - included do - # Set the default directory for helpers - class_inheritable_accessor :helpers_dir - self.helpers_dir = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers") - end - - module ClassMethods - def inherited(klass) - klass.__send__ :default_helper_module! - super - end - - # The +helper+ class method can take a series of helper module names, a block, or both. - # - # * *args: One or more modules, strings or symbols, or the special symbol :all. - # * &block: A block defining helper methods. - # - # ==== Examples - # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file - # and include the module in the template class. The second form illustrates how to include custom helpers - # when working with namespaced controllers, or other cases where the file containing the helper definition is not - # in one of Rails' standard load paths: - # helper :foo # => requires 'foo_helper' and includes FooHelper - # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper - # - # When the argument is a module it will be included directly in the template class. - # helper FooHelper # => includes FooHelper - # - # When the argument is the symbol :all, the controller will include all helpers beneath - # ActionController::Base.helpers_dir (defaults to app/helpers/**/*.rb under RAILS_ROOT). - # helper :all - # - # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available - # to the template. - # # One line - # helper { def hello() "Hello, world!" end } - # # Multi-line - # helper do - # def foo(bar) - # "#{bar} is the very best" - # end - # end - # - # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of - # +symbols+, +strings+, +modules+ and blocks. - # helper(:three, BlindHelper) { def mice() 'mice' end } - # - def helper(*args, &block) - args.flatten.each do |arg| - case arg - when :all - helper all_application_helpers - when String, Symbol - file_name = arg.to_s.underscore + '_helper' - class_name = file_name.camelize - - begin - require_dependency(file_name) - rescue LoadError => load_error - requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1] - if requiree == file_name - msg = "Missing helper file helpers/#{file_name}.rb" - raise LoadError.new(msg).copy_blame!(load_error) - else - raise - end - end - - super class_name.constantize - else - super args - end - end - - # Evaluate block in template class if given. - master_helper_module.module_eval(&block) if block_given? - end - - # Declares helper accessors for controller attributes. For example, the - # following adds new +name+ and name= instance methods to a - # controller and makes them available to the view: - # helper_attr :name - # attr_accessor :name - def helper_attr(*attrs) - attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } - end - - # Provides a proxy to access helpers methods from outside the view. - def helpers - unless @helper_proxy - @helper_proxy = ActionView::Base.new - @helper_proxy.extend master_helper_module - else - @helper_proxy - end - end - - private - def default_helper_module! - unless name.blank? - module_name = name.sub(/Controller$|$/, 'Helper') - module_path = module_name.split('::').map { |m| m.underscore }.join('/') - require_dependency module_path - helper module_name.constantize - end - rescue MissingSourceFile => e - raise unless e.is_missing? module_path - rescue NameError => e - raise unless e.missing_name? module_name - end - - # Extract helper names from files in app/helpers/**/*.rb - def all_application_helpers - extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/ - Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' } - end - end - end -end diff --git a/actionpack/lib/action_controller/new_base/hide_actions.rb b/actionpack/lib/action_controller/new_base/hide_actions.rb deleted file mode 100644 index b45e520bee..0000000000 --- a/actionpack/lib/action_controller/new_base/hide_actions.rb +++ /dev/null @@ -1,39 +0,0 @@ -module ActionController - module HideActions - extend ActiveSupport::Concern - - included do - extlib_inheritable_accessor :hidden_actions - self.hidden_actions ||= Set.new - end - - def action_methods - self.class.action_names - end - - def action_names - action_methods - end - - private - def action_method?(action_name) - !hidden_actions.include?(action_name) && super - end - - module ClassMethods - def hide_action(*args) - args.each do |arg| - self.hidden_actions << arg.to_s - end - end - - def action_methods - @action_names ||= Set.new(super.reject {|name| self.hidden_actions.include?(name.to_s)}) - end - - def self.action_names - action_methods - end - end - end -end diff --git a/actionpack/lib/action_controller/new_base/layouts.rb b/actionpack/lib/action_controller/new_base/layouts.rb deleted file mode 100644 index 0ff71587d6..0000000000 --- a/actionpack/lib/action_controller/new_base/layouts.rb +++ /dev/null @@ -1,34 +0,0 @@ -module ActionController - module Layouts - extend ActiveSupport::Concern - - include ActionController::Renderer - include AbstractController::Layouts - - module ClassMethods - def _implied_layout_name - controller_path - end - end - - private - def _determine_template(options) - super - if (!options.key?(:text) && !options.key?(:inline) && !options.key?(:partial)) || options.key?(:layout) - options[:_layout] = _layout_for_option(options.key?(:layout) ? options[:layout] : :none, options[:_template].details) - end - end - - def _layout_for_option(name, details) - case name - when String then _layout_for_name(name, details) - when true then _default_layout(true, details) - when :none then _default_layout(false, details) - when false, nil then nil - else - raise ArgumentError, - "String, true, or false, expected for `layout'; you passed #{name.inspect}" - end - end - end -end diff --git a/actionpack/lib/action_controller/old_base.rb b/actionpack/lib/action_controller/old_base.rb new file mode 100644 index 0000000000..dd22bfd617 --- /dev/null +++ b/actionpack/lib/action_controller/old_base.rb @@ -0,0 +1,84 @@ +#-- +# Copyright (c) 2004-2009 David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" +$:.unshift activesupport_path if File.directory?(activesupport_path) +require 'active_support' + +require File.join(File.dirname(__FILE__), "action_pack") + +module ActionController + # TODO: Review explicit to see if they will automatically be handled by + # the initilizer if they are really needed. + def self.load_all! + [Base, Request, Response, UrlRewriter, UrlWriter] + [ActionDispatch::Http::Headers] + end + + autoload :Base, 'action_controller/base/base' + autoload :Benchmarking, 'action_controller/base/chained/benchmarking' + autoload :Caching, 'action_controller/caching' + autoload :Cookies, 'action_controller/base/cookies' + autoload :Dispatcher, 'action_controller/dispatch/dispatcher' + autoload :Filters, 'action_controller/base/chained/filters' + autoload :Flash, 'action_controller/base/chained/flash' + autoload :Helpers, 'action_controller/base/helpers' + autoload :HttpAuthentication, 'action_controller/base/http_authentication' + autoload :Integration, 'action_controller/testing/integration' + autoload :IntegrationTest, 'action_controller/testing/integration' + autoload :Layout, 'action_controller/base/layout' + autoload :MimeResponds, 'action_controller/base/mime_responds' + autoload :PolymorphicRoutes, 'action_controller/routing/generation/polymorphic_routes' + autoload :RecordIdentifier, 'action_controller/record_identifier' + autoload :Redirector, 'action_controller/base/redirect' + autoload :Renderer, 'action_controller/base/render' + autoload :RequestForgeryProtection, 'action_controller/base/request_forgery_protection' + autoload :Rescue, 'action_controller/base/rescue' + autoload :Resources, 'action_controller/routing/resources' + autoload :Responder, 'action_controller/base/responder' + autoload :Routing, 'action_controller/routing' + autoload :SessionManagement, 'action_controller/base/session_management' + autoload :Streaming, 'action_controller/base/streaming' + autoload :TestCase, 'action_controller/testing/test_case' + autoload :TestProcess, 'action_controller/testing/process' + autoload :Translation, 'action_controller/translation' + autoload :UrlEncodedPairParser, 'action_controller/dispatch/url_encoded_pair_parser' + autoload :UrlRewriter, 'action_controller/routing/generation/url_rewriter' + autoload :UrlWriter, 'action_controller/routing/generation/url_rewriter' + autoload :Verification, 'action_controller/base/verification' + autoload :FilterParameterLogging, 'action_controller/base/filter_parameter_logging' + + module Assertions + autoload :DomAssertions, 'action_controller/testing/assertions/dom' + autoload :ModelAssertions, 'action_controller/testing/assertions/model' + autoload :ResponseAssertions, 'action_controller/testing/assertions/response' + autoload :RoutingAssertions, 'action_controller/testing/assertions/routing' + autoload :SelectorAssertions, 'action_controller/testing/assertions/selector' + autoload :TagAssertions, 'action_controller/testing/assertions/tag' + end +end + +autoload :HTML, 'action_controller/vendor/html-scanner' + +require 'action_dispatch' +require 'action_view' diff --git a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb b/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb index d9b614c237..c6f7de17bd 100644 --- a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb @@ -112,8 +112,7 @@ def polymorphic_url(record_or_hash_or_array, options = {}) # Returns the path component of a URL for the given record. It uses # polymorphic_url with :routing_type => :path. def polymorphic_path(record_or_hash_or_array, options = {}) - options[:routing_type] = :path - polymorphic_url(record_or_hash_or_array, options) + polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path)) end %w(edit new).each do |action| diff --git a/actionpack/lib/action_controller/routing/resources.rb b/actionpack/lib/action_controller/routing/resources.rb index 05c782d226..2dee0a3d87 100644 --- a/actionpack/lib/action_controller/routing/resources.rb +++ b/actionpack/lib/action_controller/routing/resources.rb @@ -150,9 +150,9 @@ def set_allowed_actions end if only - @allowed_actions[:only] = Array(only).map(&:to_sym) + @allowed_actions[:only] = Array(only).map {|a| a.to_sym } elsif except - @allowed_actions[:except] = Array(except).map(&:to_sym) + @allowed_actions[:except] = Array(except).map {|a| a.to_sym } end end diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb index fb344f6c6b..040a7e2cb6 100644 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ b/actionpack/lib/action_controller/routing/route_set.rb @@ -305,6 +305,7 @@ def routes_changed_at end def add_route(path, options = {}) + options.each { |k, v| options[k] = v.to_s if [:controller, :action].include?(k) && v.is_a?(Symbol) } route = builder.build(path, options) routes << route route @@ -436,7 +437,7 @@ def call(env) def recognize(request) params = recognize_path(request.path, extract_request_environment(request)) request.path_parameters = params.with_indifferent_access - "#{params[:controller].camelize}Controller".constantize + "#{params[:controller].to_s.camelize}Controller".constantize end def recognize_path(path, environment={}) diff --git a/actionpack/lib/action_controller/routing/routing_ext.rb b/actionpack/lib/action_controller/routing/routing_ext.rb index 4a82b2af5f..5e5b22b6c2 100644 --- a/actionpack/lib/action_controller/routing/routing_ext.rb +++ b/actionpack/lib/action_controller/routing/routing_ext.rb @@ -1,49 +1,4 @@ -class Object - def to_param - to_s - end -end - -class TrueClass - def to_param - self - end -end - -class FalseClass - def to_param - self - end -end - -class NilClass - def to_param - self - end -end - -class Regexp #:nodoc: - def number_of_captures - Regexp.new("|#{source}").match('').captures.length - end - - def multiline? - options & MULTILINE == MULTILINE - end - - class << self - def optionalize(pattern) - case unoptionalize(pattern) - when /\A(.|\(.*\))\Z/ then "#{pattern}?" - else "(?:#{pattern})?" - end - end - - def unoptionalize(pattern) - [/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp| - return $1 if regexp =~ pattern - end - return pattern - end - end -end +require 'active_support/core_ext/object/conversions' +require 'active_support/core_ext/boolean/conversions' +require 'active_support/core_ext/nil/conversions' +require 'active_support/core_ext/regexp' diff --git a/actionpack/lib/action_controller/testing/integration.rb b/actionpack/lib/action_controller/testing/integration.rb index af4ccb7837..5cb0f48f82 100644 --- a/actionpack/lib/action_controller/testing/integration.rb +++ b/actionpack/lib/action_controller/testing/integration.rb @@ -1,6 +1,7 @@ require 'stringio' require 'uri' require 'active_support/test_case' +require 'active_support/core_ext/object/metaclass' require 'rack/mock_session' require 'rack/test/cookie_jar' @@ -191,7 +192,7 @@ def reset! unless defined? @named_routes_configured # install the named routes in this session instance. - klass = class << self; self; end + klass = metaclass Routing::Routes.install_helpers(klass) # the helpers are made protected by default--we make them public for @@ -244,7 +245,7 @@ def process(method, path, parameters = nil, rack_environment = nil) path = location.query ? "#{location.path}?#{location.query}" : location.path end - [ControllerCapture, ActionController::ProcessWithTest].each do |mod| + [ControllerCapture, ActionController::Testing].each do |mod| unless ActionController::Base < mod ActionController::Base.class_eval { include mod } end diff --git a/actionpack/lib/action_controller/testing/performance.rb b/actionpack/lib/action_controller/testing/performance_test.rb similarity index 100% rename from actionpack/lib/action_controller/testing/performance.rb rename to actionpack/lib/action_controller/testing/performance_test.rb diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb index 9647f8ce45..7634290ea1 100644 --- a/actionpack/lib/action_controller/testing/process.rb +++ b/actionpack/lib/action_controller/testing/process.rb @@ -56,6 +56,8 @@ def recycle! @block = nil @length = 0 @body = [] + @charset = nil + @content_type = nil @request = @template = nil end @@ -122,30 +124,24 @@ def process(action, parameters = nil, session = nil, flash = nil, http_method = @request.recycle! @response.recycle! + @controller.response_body = nil + @controller.formats = nil + @controller.params = nil @html_document = nil - @request.request_method = http_method + @request.env['REQUEST_METHOD'] = http_method parameters ||= {} @request.assign_parameters(@controller.class.controller_path, action.to_s, parameters) @request.session = ActionController::TestSession.new(session) unless session.nil? @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash + + @controller.request = @request + @controller.params.merge!(parameters) build_request_uri(action, parameters) - - Base.class_eval { include ProcessWithTest } unless Base < ProcessWithTest - - env = @request.env - app = @controller - - # TODO: Enable Lint - # app = Rack::Lint.new(app) - - status, headers, body = app.action(action, env) - response = Rack::MockResponse.new(status, headers, body) - - @response.request, @response.template = @request, @controller.template - @response.status, @response.headers, @response.body = response.status, response.headers, response.body + Base.class_eval { include Testing } + @controller.process_with_new_base_test(@request, @response) @response end @@ -165,7 +161,7 @@ def assigns(key = nil) next if ActionController::Base.protected_instance_variables.include?(ivar) assigns[ivar[1..-1]] = @controller.instance_variable_get(ivar) end - + key.nil? ? assigns : assigns[key.to_s] end @@ -261,27 +257,4 @@ def with_routing ActionController::Routing.const_set(:Routes, real_routes) if real_routes end end - - module ProcessWithTest #:nodoc: - def self.included(base) - base.class_eval { - attr_reader :assigns - alias_method_chain :process, :test - } - end - - def process_with_test(*args) - process_without_test(*args).tap { set_test_assigns } - end - - private - def set_test_assigns - @assigns = {} - (instance_variable_names - self.class.protected_instance_variables).each do |var| - name, value = var[1..-1], instance_variable_get(var) - @assigns[name] = value - @template.assigns[name] = value if response - end - end - end -end +end \ No newline at end of file diff --git a/actionpack/lib/action_controller/testing/process2.rb b/actionpack/lib/action_controller/testing/process2.rb deleted file mode 100644 index 1c6fd2d80a..0000000000 --- a/actionpack/lib/action_controller/testing/process2.rb +++ /dev/null @@ -1,74 +0,0 @@ -require "action_controller/testing/process" - -module ActionController - module TestProcess - - # Executes a request simulating GET HTTP method and set/volley the response - def get(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "GET") - end - - # Executes a request simulating POST HTTP method and set/volley the response - def post(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "POST") - end - - # Executes a request simulating PUT HTTP method and set/volley the response - def put(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "PUT") - end - - # Executes a request simulating DELETE HTTP method and set/volley the response - def delete(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "DELETE") - end - - # Executes a request simulating HEAD HTTP method and set/volley the response - def head(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "HEAD") - end - - def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET') - # Sanity check for required instance variables so we can give an - # understandable error message. - %w(@controller @request @response).each do |iv_name| - if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil? - raise "#{iv_name} is nil: make sure you set it in your test's setup method." - end - end - - @request.recycle! - @response.recycle! - @controller.response_body = nil - @controller.formats = nil - @controller.params = nil - - @html_document = nil - @request.env['REQUEST_METHOD'] = http_method - - parameters ||= {} - @request.assign_parameters(@controller.class.controller_path, action.to_s, parameters) - - @request.session = ActionController::TestSession.new(session) unless session.nil? - @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash - - @controller.request = @request - @controller.params.merge!(parameters) - build_request_uri(action, parameters) - # Base.class_eval { include ProcessWithTest } unless Base < ProcessWithTest - @controller.process_with_new_base_test(@request, @response) - @response - end - - def build_request_uri(action, parameters) - unless @request.env['REQUEST_URI'] - options = @controller.__send__(:rewrite_options, parameters) - options.update(:only_path => true, :action => action) - - url = ActionController::UrlRewriter.new(@request, parameters) - @request.request_uri = url.rewrite(options) - end - end - - end -end \ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 25156a4c75..27f27e27fe 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -2,7 +2,19 @@ require 'active_support/core_ext/class/attribute_accessors' module Mime - SET = [] + class Mimes < Array + def symbols + @symbols ||= map {|m| m.to_sym } + end + + %w(<< concat shift unshift push pop []= clear compact! collect! + delete delete_at delete_if flatten! map! insert reject! reverse! + replace slice! sort! uniq!).each do |method| + define_method(method) {|*args| @symbols = nil; super(*args) } + end + end + + SET = Mimes.new EXTENSION_LOOKUP = {} LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? } diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 140feb9a68..3f23a5af7a 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -180,12 +180,10 @@ def formats else accepts.dup end.tap do |ret| - if defined?(ActionController::Http) - if ret == ONLY_ALL - ret.replace Mime::SET - elsif all = ret.index(Mime::ALL) - ret.delete_at(all) && ret.insert(all, *Mime::SET) - end + if ret == ONLY_ALL + ret.replace Mime::SET + elsif all = ret.index(Mime::ALL) + ret.delete_at(all) && ret.insert(all, *Mime::SET) end end else diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index b9db7a4508..e58b4b5e19 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -67,12 +67,7 @@ def body end def body=(body) - @body = - if body.respond_to?(:to_str) - [body] - else - body - end + @body = body.respond_to?(:to_str) ? [body] : body end def body_parts @@ -96,36 +91,7 @@ def location=(url) # If a character set has been defined for this response (see charset=) then # the character set information will also be included in the content type # information. - def content_type=(mime_type) - self.headers["Content-Type"] = - if mime_type =~ /charset/ || (c = charset).nil? - mime_type.to_s - else - "#{mime_type}; charset=#{c}" - end - end - - # Returns the response's content MIME type, or nil if content type has been set. - def content_type - content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0] - content_type.blank? ? nil : content_type - end - - # Set the charset of the Content-Type header. Set to nil to remove it. - # If no content type is set, it defaults to HTML. - def charset=(charset) - headers["Content-Type"] = - if charset - "#{content_type || Mime::HTML}; charset=#{charset}" - else - content_type || Mime::HTML.to_s - end - end - - def charset - charset = String(headers["Content-Type"] || headers["type"]).split(";")[1] - charset.blank? ? nil : charset.strip.split("=")[1] - end + attr_accessor :charset, :content_type def last_modified if last = headers['Last-Modified'] @@ -162,15 +128,15 @@ def sending_file? end def assign_default_content_type_and_charset! - if type = headers['Content-Type'] || headers['type'] - unless type =~ /charset=/ || sending_file? - headers['Content-Type'] = "#{type}; charset=#{default_charset}" - end - else - type = Mime::HTML.to_s - type += "; charset=#{default_charset}" unless sending_file? - headers['Content-Type'] = type - end + return if !headers["Content-Type"].blank? + + @content_type ||= Mime::HTML + @charset ||= default_charset + + type = @content_type.to_s.dup + type << "; charset=#{@charset}" unless sending_file? + + headers["Content-Type"] = type end def prepare! diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb index 8f448970d9..1d9efc2b36 100644 --- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb @@ -1,3 +1,4 @@ +require "active_support/core_ext/kernel/requires" begin require_library_or_gem 'memcache' diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index bfff307669..036deec6d2 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/exception" + module ActionDispatch class ShowExceptions include StatusCodes diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index e0aa2a5f2f..ed0cdf38ee 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,8 +1,8 @@ module ActionPack #:nodoc: module VERSION #:nodoc: - MAJOR = 2 - MINOR = 3 - TINY = 2 + MAJOR = 3 + MINOR = 0 + TINY = "pre" STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 94138097e3..27a06db5bb 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -33,21 +33,22 @@ def self.load_all! [Base, InlineTemplate, TemplateError] end - autoload :Base, 'action_view/base' - autoload :Helpers, 'action_view/helpers' - autoload :InlineTemplate, 'action_view/template/inline' - autoload :Partials, 'action_view/render/partials' - autoload :Path, 'action_view/template/path' - autoload :PathSet, 'action_view/paths' - autoload :Rendering, 'action_view/render/rendering' - autoload :Renderable, 'action_view/template/renderable' + autoload :Base, 'action_view/base' + autoload :Helpers, 'action_view/helpers' + autoload :InlineTemplate, 'action_view/template/inline' + autoload :Partials, 'action_view/render/partials' + autoload :Resolver, 'action_view/template/resolver' + autoload :PathSet, 'action_view/paths' + autoload :Rendering, 'action_view/render/rendering' + autoload :Renderable, 'action_view/template/renderable' autoload :RenderablePartial, 'action_view/template/partial' - autoload :Template, 'action_view/template/template' - autoload :TemplateError, 'action_view/template/error' - autoload :TemplateHandler, 'action_view/template/handler' - autoload :TemplateHandlers, 'action_view/template/handlers' - autoload :TextTemplate, 'action_view/template/text' - autoload :Helpers, 'action_view/helpers' + autoload :Template, 'action_view/template/template' + autoload :TemplateError, 'action_view/template/error' + autoload :TemplateHandler, 'action_view/template/handler' + autoload :TemplateHandlers, 'action_view/template/handlers' + autoload :TextTemplate, 'action_view/template/text' + autoload :Helpers, 'action_view/helpers' + autoload :FileSystemResolverWithFallback, 'action_view/template/resolver' end class ERB diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 4ab568b44c..45184f58fb 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -170,12 +170,13 @@ class Base attr_accessor :base_path, :assigns, :template_extension, :formats attr_accessor :controller + attr_internal :captures attr_accessor :output_buffer class << self delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB' - delegate :logger, :to => 'ActionController::Base' + delegate :logger, :to => 'ActionController::Base', :allow_nil => true end @@debug_rjs = false @@ -229,38 +230,27 @@ def include(*args) def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil, formats = nil)#:nodoc: @formats = formats || [:html] - @assigns = assigns_for_first_render - @assigns_added = nil + @assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) } @controller = controller @helpers = ProxyModule.new(self) + @_content_for = Hash.new {|h,k| h[k] = "" } self.view_paths = view_paths - - @_first_render = nil - @_current_render = nil end + attr_internal :template attr_reader :view_paths def view_paths=(paths) @view_paths = self.class.process_view_paths(paths) end - # Access the current template being rendered. - # Returns a ActionView::Template object. - def template - @_current_render - end - - def template=(template) #:nodoc: - @_first_render ||= template - @_current_render = template - end - def with_template(current_template) + _evaluate_assigns_and_ivars last_template, self.template = template, current_template + last_formats, self.formats = formats, [current_template.mime_type.to_sym] + Mime::SET.symbols yield ensure - self.template = last_template + self.template, self.formats = last_template, last_formats end def punctuate_body!(part) @@ -271,30 +261,19 @@ def punctuate_body!(part) # Evaluates the local assigns and controller ivars, pushes them to the view. def _evaluate_assigns_and_ivars #:nodoc: - unless @assigns_added - @assigns.each { |key, value| instance_variable_set("@#{key}", value) } - _copy_ivars_from_controller - @assigns_added = true - end + @assigns_added ||= _copy_ivars_from_controller end - private + private - def _copy_ivars_from_controller #:nodoc: - if @controller - variables = @controller.instance_variable_names - variables -= @controller.protected_instance_variables if @controller.respond_to?(:protected_instance_variables) - variables.each { |name| instance_variable_set(name, @controller.instance_variable_get(name)) } - end + def _copy_ivars_from_controller #:nodoc: + if @controller + variables = @controller.instance_variable_names + variables -= @controller.protected_instance_variables if @controller.respond_to?(:protected_instance_variables) + variables.each { |name| instance_variable_set(name, @controller.instance_variable_get(name)) } end + true + end - def _set_controller_content_type(content_type) #:nodoc: - # TODO: Remove this method when new base is switched - unless defined?(ActionController::Http) - if controller.respond_to?(:response) - controller.response.content_type ||= content_type - end - end - end end end diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index 693ab7c2e0..97fa2d80e9 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -11,7 +11,7 @@ module Helpers #:nodoc: autoload :FormHelper, 'action_view/helpers/form_helper' autoload :FormOptionsHelper, 'action_view/helpers/form_options_helper' autoload :FormTagHelper, 'action_view/helpers/form_tag_helper' - autoload :JavascriptHelper, 'action_view/helpers/javascript_helper' + autoload :JavaScriptHelper, 'action_view/helpers/javascript_helper' autoload :NumberHelper, 'action_view/helpers/number_helper' autoload :PrototypeHelper, 'action_view/helpers/prototype_helper' autoload :RecordIdentificationHelper, 'action_view/helpers/record_identification_helper' diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb index b4b9f6e34b..75cc651968 100644 --- a/actionpack/lib/action_view/helpers/active_record_helper.rb +++ b/actionpack/lib/action_view/helpers/active_record_helper.rb @@ -1,6 +1,8 @@ require 'cgi' require 'action_view/helpers/form_helper' require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/enumerable' +require 'active_support/core_ext/kernel/reporting' module ActionView class Base @@ -120,9 +122,9 @@ def error_message_on(object, method, *args) options.reverse_merge!(:prepend_text => '', :append_text => '', :css_class => 'formError') if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) && - (errors = obj.errors.on(method)) + (errors = obj.errors[method]) content_tag("div", - "#{options[:prepend_text]}#{ERB::Util.html_escape(errors.is_a?(Array) ? errors.first : errors)}#{options[:append_text]}", + "#{options[:prepend_text]}#{ERB::Util.html_escape(errors.first)}#{options[:append_text]}", :class => options[:css_class] ) else @@ -245,59 +247,22 @@ def to_tag(options = {}) end end - alias_method :tag_without_error_wrapping, :tag - def tag(name, options) - if object.respond_to?(:errors) && object.errors.respond_to?(:on) - error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name)) + %w(tag content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth| + without = "#{meth}_without_error_wrapping" + define_method "#{meth}_with_error_wrapping" do |*args| + error_wrapping(send(without, *args)) + end + alias_method_chain meth, :error_wrapping + end + + def error_wrapping(html_tag) + if object.respond_to?(:errors) && object.errors.respond_to?(:full_messages) && object.errors[@method_name].any? + Base.field_error_proc.call(html_tag, self) else - tag_without_error_wrapping(name, options) + html_tag end end - alias_method :content_tag_without_error_wrapping, :content_tag - def content_tag(name, value, options) - if object.respond_to?(:errors) && object.errors.respond_to?(:on) - error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name)) - else - content_tag_without_error_wrapping(name, value, options) - end - end - - alias_method :to_date_select_tag_without_error_wrapping, :to_date_select_tag - def to_date_select_tag(options = {}, html_options = {}) - if object.respond_to?(:errors) && object.errors.respond_to?(:on) - error_wrapping(to_date_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name)) - else - to_date_select_tag_without_error_wrapping(options, html_options) - end - end - - alias_method :to_datetime_select_tag_without_error_wrapping, :to_datetime_select_tag - def to_datetime_select_tag(options = {}, html_options = {}) - if object.respond_to?(:errors) && object.errors.respond_to?(:on) - error_wrapping(to_datetime_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name)) - else - to_datetime_select_tag_without_error_wrapping(options, html_options) - end - end - - alias_method :to_time_select_tag_without_error_wrapping, :to_time_select_tag - def to_time_select_tag(options = {}, html_options = {}) - if object.respond_to?(:errors) && object.errors.respond_to?(:on) - error_wrapping(to_time_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name)) - else - to_time_select_tag_without_error_wrapping(options, html_options) - end - end - - def error_wrapping(html_tag, has_error) - has_error ? Base.field_error_proc.call(html_tag, self) : html_tag - end - - def error_message - object.errors.on(@method_name) - end - def column_type object.send(:column_for_attribute, @method_name).type end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 63b181509a..898da3c7e4 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -1,6 +1,7 @@ require 'cgi' require 'action_view/helpers/url_helper' require 'action_view/helpers/tag_helper' +require 'active_support/core_ext/file' module ActionView module Helpers #:nodoc: @@ -273,17 +274,20 @@ def javascript_path(source) # javascript_include_tag :all, :cache => true, :recursive => true def javascript_include_tag(*sources) options = sources.extract_options!.stringify_keys - cache = options.delete("cache") + concat = options.delete("concat") + cache = concat || options.delete("cache") recursive = options.delete("recursive") - if ActionController::Base.perform_caching && cache + if concat || (ActionController::Base.perform_caching && cache) joined_javascript_name = (cache == true ? "all" : cache) + ".js" - joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name) + joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? ASSETS_DIR : JAVASCRIPTS_DIR, joined_javascript_name) - write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) unless File.exists?(joined_javascript_path) + unless ActionController::Base.perform_caching && File.exists?(joined_javascript_path) + write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) + end javascript_src_tag(joined_javascript_name, options) else - expand_javascript_sources(sources, recursive).collect { |source| javascript_src_tag(source, options) }.join("\n") + ensure_javascript_sources!(expand_javascript_sources(sources, recursive)).collect { |source| javascript_src_tag(source, options) }.join("\n") end end @@ -412,19 +416,28 @@ def stylesheet_path(source) # The :recursive option is also available for caching: # # stylesheet_link_tag :all, :cache => true, :recursive => true + # + # To force concatenation (even in development mode) set :concat to true. This is useful if + # you have too many stylesheets for IE to load. + # + # stylesheet_link_tag :all, :concat => true + # def stylesheet_link_tag(*sources) options = sources.extract_options!.stringify_keys - cache = options.delete("cache") + concat = options.delete("concat") + cache = concat || options.delete("cache") recursive = options.delete("recursive") - if ActionController::Base.perform_caching && cache + if concat || (ActionController::Base.perform_caching && cache) joined_stylesheet_name = (cache == true ? "all" : cache) + ".css" - joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name) + joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? ASSETS_DIR : STYLESHEETS_DIR, joined_stylesheet_name) - write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) unless File.exists?(joined_stylesheet_path) + unless ActionController::Base.perform_caching && File.exists?(joined_stylesheet_path) + write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) + end stylesheet_tag(joined_stylesheet_name, options) else - expand_stylesheet_sources(sources, recursive).collect { |source| stylesheet_tag(source, options) }.join("\n") + ensure_stylesheet_sources!(expand_stylesheet_sources(sources, recursive)).collect { |source| stylesheet_tag(source, options) }.join("\n") end end @@ -444,6 +457,21 @@ def image_path(source) end alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route + # Computes the path to a video asset in the public videos directory. + # Full paths from the document root will be passed through. + # Used internally by +video_tag+ to build the video path. + # + # ==== Examples + # video_path("hd") # => /videos/hd + # video_path("hd.avi") # => /videos/hd.avi + # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi + # video_path("/trailers/hd.avi") # => /videos/hd.avi + # video_path("http://www.railsapplication.com/vid/hd.avi") # => http://www.railsapplication.com/vid/hd.avi + def video_path(source) + compute_public_path(source, 'videos') + end + alias_method :path_to_video, :video_path # aliased to avoid conflicts with an video_path named route + # Returns an html image tag for the +source+. The +source+ can be a full # path or a file that exists in your public images directory. # @@ -480,8 +508,8 @@ def image_path(source) def image_tag(source, options = {}) options.symbolize_keys! - options[:src] = path_to_image(source) - options[:alt] ||= File.basename(options[:src], '.*').split('.').first.to_s.capitalize + src = options[:src] = path_to_image(source) + options[:alt] ||= File.basename(src, '.*').split('.').first.to_s.capitalize if size = options.delete(:size) options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} @@ -489,12 +517,64 @@ def image_tag(source, options = {}) if mouseover = options.delete(:mouseover) options[:onmouseover] = "this.src='#{image_path(mouseover)}'" - options[:onmouseout] = "this.src='#{image_path(options[:src])}'" + options[:onmouseout] = "this.src='#{src}'" end tag("img", options) end + # Returns an html video tag for the +sources+. If +sources+ is a string, + # a single video tag will be returned. If +sources+ is an array, a video + # tag with nested source tags for each source will be returned. The + # +sources+ can be full paths or files that exists in your public videos + # directory. + # + # ==== Options + # You can add HTML attributes using the +options+. The +options+ supports + # two additional keys for convenience and conformance: + # + # * :poster - Set an image (like a screenshot) to be shown + # before the video loads. The path is calculated like the +src+ of +image_tag+. + # * :size - Supplied as "{Width}x{Height}", so "30x45" becomes + # width="30" and height="45". :size will be ignored if the + # value is not in the correct format. + # + # ==== Examples + # video_tag("trailer") # => + #