aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
authorPratik Naik <pratiknaik@gmail.com>2009-11-17 22:47:23 +0000
committerPratik Naik <pratiknaik@gmail.com>2009-11-17 22:47:23 +0000
commit5446d5cb05b50a9a3f317ded774be438e0eff909 (patch)
tree6b0b87efe3e95783763208215a3159fb63217a6d /actionpack
parent9754debb9a72f9385950e5282f3642b995ab76d8 (diff)
parentf8877d4b2a2a6f68770b376f0b1391a6295f62f2 (diff)
downloadrails-5446d5cb05b50a9a3f317ded774be438e0eff909.tar.gz
rails-5446d5cb05b50a9a3f317ded774be438e0eff909.tar.bz2
rails-5446d5cb05b50a9a3f317ded774be438e0eff909.zip
Merge remote branch 'mainstream/master'
Conflicts: activesupport/lib/active_support/core_ext/hash/conversions.rb
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/Gemfile15
-rw-r--r--actionpack/Rakefile22
-rw-r--r--actionpack/actionpack.gemspec6
-rw-r--r--actionpack/lib/abstract_controller.rb1
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb10
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb81
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb53
-rw-r--r--actionpack/lib/abstract_controller/localized_cache.rb49
-rw-r--r--actionpack/lib/abstract_controller/logger.rb24
-rw-r--r--actionpack/lib/abstract_controller/rendering_controller.rb25
-rw-r--r--actionpack/lib/action_controller.rb14
-rw-r--r--actionpack/lib/action_controller/base.rb1
-rw-r--r--actionpack/lib/action_controller/caching.rb61
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb213
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb26
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb10
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb2
-rw-r--r--actionpack/lib/action_controller/deprecated.rb2
-rw-r--r--actionpack/lib/action_controller/dispatch/dispatcher.rb2
-rw-r--r--actionpack/lib/action_controller/legacy/layout.rb256
-rw-r--r--actionpack/lib/action_controller/metal.rb10
-rw-r--r--actionpack/lib/action_controller/metal/benchmarking.rb23
-rw-r--r--actionpack/lib/action_controller/metal/compatibility.rb8
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb41
-rw-r--r--actionpack/lib/action_controller/metal/configuration.rb28
-rw-r--r--actionpack/lib/action_controller/metal/cookies.rb47
-rw-r--r--actionpack/lib/action_controller/metal/filter_parameter_logging.rb65
-rw-r--r--actionpack/lib/action_controller/metal/flash.rb48
-rw-r--r--actionpack/lib/action_controller/metal/head.rb27
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb71
-rw-r--r--actionpack/lib/action_controller/metal/layouts.rb21
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb9
-rw-r--r--actionpack/lib/action_controller/metal/rack_convenience.rb2
-rw-r--r--actionpack/lib/action_controller/metal/redirector.rb2
-rw-r--r--actionpack/lib/action_controller/metal/rendering_controller.rb64
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb56
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb46
-rw-r--r--actionpack/lib/action_controller/metal/session_management.rb15
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/verification.rb4
-rw-r--r--actionpack/lib/action_controller/middleware.rb40
-rw-r--r--actionpack/lib/action_controller/notifications.rb10
-rw-r--r--actionpack/lib/action_controller/polymorphic_routes.rb (renamed from actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb)41
-rw-r--r--actionpack/lib/action_controller/routing/builder.rb199
-rw-r--r--actionpack/lib/action_controller/routing/optimisations.rb130
-rw-r--r--actionpack/lib/action_controller/routing/recognition_optimisation.rb167
-rw-r--r--actionpack/lib/action_controller/routing/resources.rb685
-rw-r--r--actionpack/lib/action_controller/routing/route.rb267
-rw-r--r--actionpack/lib/action_controller/routing/routing_ext.rb4
-rw-r--r--actionpack/lib/action_controller/routing/segments.rb343
-rw-r--r--actionpack/lib/action_controller/testing/process.rb2
-rw-r--r--actionpack/lib/action_controller/testing/test_case.rb9
-rw-r--r--actionpack/lib/action_controller/translation.rb4
-rw-r--r--actionpack/lib/action_controller/url_rewriter.rb (renamed from actionpack/lib/action_controller/routing/generation/url_rewriter.rb)0
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb1
-rwxr-xr-xactionpack/lib/action_dispatch/http/request.rb35
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing.rb (renamed from actionpack/lib/action_controller/routing.rb)30
-rw-r--r--actionpack/lib/action_dispatch/routing/deprecated_mapper.rb878
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb327
-rw-r--r--actionpack/lib/action_dispatch/routing/route.rb49
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb (renamed from actionpack/lib/action_controller/routing/route_set.rb)338
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions.rb19
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb10
-rw-r--r--actionpack/lib/action_view/base.rb36
-rw-r--r--actionpack/lib/action_view/erb/util.rb1
-rw-r--r--actionpack/lib/action_view/helpers.rb6
-rw-r--r--actionpack/lib/action_view/helpers/active_model_helper.rb28
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb28
-rw-r--r--actionpack/lib/action_view/helpers/benchmark_helper.rb54
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb13
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb4
-rw-r--r--actionpack/lib/action_view/locale/en.yml4
-rw-r--r--actionpack/lib/action_view/render/rendering.rb9
-rw-r--r--actionpack/lib/action_view/safe_buffer.rb2
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb11
-rw-r--r--actionpack/lib/action_view/template/resolver.rb2
-rw-r--r--actionpack/lib/action_view/template/template.rb4
-rw-r--r--actionpack/lib/action_view/template/text.rb8
-rw-r--r--actionpack/lib/action_view/test_case.rb1
-rw-r--r--actionpack/test/abstract/helper_test.rb65
-rw-r--r--actionpack/test/abstract/layouts_test.rb55
-rw-r--r--actionpack/test/abstract/localized_cache_test.rb57
-rw-r--r--actionpack/test/abstract/render_test.rb88
-rw-r--r--actionpack/test/abstract_unit.rb46
-rw-r--r--actionpack/test/activerecord/polymorphic_routes_test.rb14
-rw-r--r--actionpack/test/controller/base_test.rb24
-rw-r--r--actionpack/test/controller/caching_test.rb33
-rw-r--r--actionpack/test/controller/cookie_test.rb16
-rw-r--r--actionpack/test/controller/filter_params_test.rb18
-rw-r--r--actionpack/test/controller/filters_test.rb1
-rw-r--r--actionpack/test/controller/helper_test.rb46
-rw-r--r--actionpack/test/controller/integration_test.rb20
-rw-r--r--actionpack/test/controller/mime_responds_test.rb28
-rw-r--r--actionpack/test/controller/new_base/content_negotiation_test.rb2
-rw-r--r--actionpack/test/controller/new_base/metal_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_action_test.rb2
-rw-r--r--actionpack/test/controller/render_test.rb12
-rw-r--r--actionpack/test/controller/rescue_test.rb8
-rw-r--r--actionpack/test/controller/resources_test.rb22
-rw-r--r--actionpack/test/controller/routing_test.rb737
-rw-r--r--actionpack/test/controller/test_test.rb17
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb18
-rw-r--r--actionpack/test/controller/verification_test.rb4
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/request/xml_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request_test.rb4
-rw-r--r--actionpack/test/dispatch/routing_test.rb391
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb2
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb2
-rw-r--r--actionpack/test/dispatch/session/test_session_test.rb4
-rw-r--r--actionpack/test/dispatch/test_request_test.rb4
-rw-r--r--actionpack/test/fixtures/layouts/block_with_layout.erb2
-rw-r--r--actionpack/test/lib/controller/fake_controllers.rb7
-rw-r--r--actionpack/test/template/active_model_helper_i18n_test.rb42
-rw-r--r--actionpack/test/template/active_model_helper_test.rb (renamed from actionpack/test/template/active_record_helper_test.rb)2
-rw-r--r--actionpack/test/template/active_record_helper_i18n_test.rb51
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb10
-rw-r--r--actionpack/test/template/benchmark_helper_test.rb86
-rw-r--r--actionpack/test/template/form_helper_test.rb66
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb3
-rw-r--r--actionpack/test/template/number_helper_test.rb2
-rw-r--r--actionpack/test/template/safe_buffer_test.rb (renamed from actionpack/test/view/safe_buffer_test.rb)2
-rw-r--r--actionpack/test/template/test_case_test.rb13
-rw-r--r--actionpack/test/template/test_test.rb3
-rw-r--r--actionpack/test/template/url_helper_test.rb39
-rw-r--r--actionpack/test/ts_isolated.rb4
133 files changed, 3686 insertions, 3683 deletions
diff --git a/actionpack/Gemfile b/actionpack/Gemfile
deleted file mode 100644
index 60d24104d2..0000000000
--- a/actionpack/Gemfile
+++ /dev/null
@@ -1,15 +0,0 @@
-rails_root = Pathname.new(File.dirname(__FILE__)).join("..")
-
-gem "rack", "~> 1.0.0"
-gem "rack-test", "~> 0.5.0"
-gem "activesupport", "3.0.pre", :vendored_at => rails_root.join("activesupport")
-gem "activemodel", "3.0.pre", :vendored_at => rails_root.join("activemodel")
-gem "erubis", "~> 2.6.0"
-
-only :test do
- gem "mocha"
- gem "sqlite3-ruby"
- gem "RedCloth"
-end
-
-disable_system_gems
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index e186037aeb..99bdcc95fa 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -19,16 +19,6 @@ RUBY_FORGE_USER = "webster132"
desc "Default Task"
task :default => :test
-task :bundle do
- puts "Checking if the bundled testing requirements are up to date..."
- result = system "gem bundle"
- unless result
- puts "The gem bundler is not installed. Installing."
- system "gem install bundler"
- system "gem bundle"
- end
-end
-
# Run the unit tests
desc "Run all unit tests"
@@ -66,7 +56,7 @@ Rake::RDocTask.new { |rdoc|
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.options << '--charset' << 'utf-8'
rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
- if ENV['DOC_FILES']
+ if ENV['DOC_FILES']
rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
else
rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
@@ -80,8 +70,6 @@ spec = eval(File.read('actionpack.gemspec'))
Rake::GemPackageTask.new(spec) do |p|
p.gem_spec = spec
- p.need_tar = true
- p.need_zip = true
end
task :lines do
@@ -98,10 +86,10 @@ task :lines do
codelines += 1
end
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
-
+
total_lines += lines
total_codelines += codelines
-
+
lines, codelines = 0, 0
end
@@ -122,14 +110,14 @@ task :update_js => [ :update_scriptaculous ]
# Publishing ------------------------------------------------------
desc "Publish the API documentation"
-task :pgem => [:package] do
+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
+task :pdoc => [:rdoc] do
require 'rake/contrib/sshpublisher'
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload
end
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 1930416358..2c534642ab 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -16,8 +16,10 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', '= 3.0.pre')
s.add_dependency('activemodel', '= 3.0.pre')
- s.add_dependency('rack', '~> 1.0.0')
- s.add_dependency('rack-test', '~> 0.5.0')
+ s.add_dependency('rack', '~> 1.0.1')
+ s.add_dependency('rack-test', '~> 0.5.0')
+ s.add_dependency('rack-mount', '~> 0.0.1')
+ s.add_dependency('erubis', '~> 2.6.5')
s.require_path = 'lib'
s.autorequire = 'action_controller'
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb
index 76c5845f5b..1a6c4278c9 100644
--- a/actionpack/lib/abstract_controller.rb
+++ b/actionpack/lib/abstract_controller.rb
@@ -6,6 +6,7 @@ module AbstractController
autoload :Callbacks, "abstract_controller/callbacks"
autoload :Helpers, "abstract_controller/helpers"
autoload :Layouts, "abstract_controller/layouts"
+ autoload :LocalizedCache, "abstract_controller/localized_cache"
autoload :Logger, "abstract_controller/logger"
autoload :RenderingController, "abstract_controller/rendering_controller"
# === Exceptions
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 379eaf6d8e..ee496dadc5 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -1,13 +1,11 @@
-require "active_support/new_callbacks"
-
module AbstractController
module Callbacks
extend ActiveSupport::Concern
- # Uses ActiveSupport::NewCallbacks as the base functionality. For
+ # Uses ActiveSupport::Callbacks as the base functionality. For
# more details on the whole callback system, read the documentation
- # for ActiveSupport::NewCallbacks.
- include ActiveSupport::NewCallbacks
+ # for ActiveSupport::Callbacks.
+ include ActiveSupport::Callbacks
included do
define_callbacks :process_action, :terminator => "response_body"
@@ -16,7 +14,7 @@ module AbstractController
# 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
+ run_callbacks(:process_action, method_name) do
super
end
end
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index f3072fad74..d3b492ad09 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -1,3 +1,5 @@
+require 'active_support/dependencies'
+
module AbstractController
module Helpers
extend ActiveSupport::Concern
@@ -57,23 +59,48 @@ module AbstractController
end
end
- # Make a number of helper modules part of this class' default
- # helpers.
+ # The +helper+ class method can take a series of helper module names, a block, or both.
#
# ==== Parameters
- # *args<Array[Module]>:: Modules to be included
- # block<Block>:: Evalulate the block in the context
- # of the helper module. Any methods defined in the block
- # will be helpers.
+ # *args<Array[Module, Symbol, String, :all]>
+ # block<Block>:: A block defining helper methods
+ #
+ # ==== Examples
+ # When the argument is a module it will be included directly in the template class.
+ # helper FooHelper # => includes FooHelper
+ #
+ # 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
+ #
+ # 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)
self._helper_serial = AbstractController::Helpers.next_serial + 1
- args.flatten.each do |arg|
- case arg
- when Module
- add_template_helper(arg)
- end
+ _modules_for_helpers(args).each do |mod|
+ add_template_helper(mod)
end
+
_helpers.module_eval(&block) if block_given?
end
@@ -87,6 +114,38 @@ module AbstractController
def add_template_helper(mod)
_helpers.module_eval { include mod }
end
+
+ # 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.
+ #
+ # Module:: No further processing
+ #
+ # After loading the appropriate files, the corresponding modules
+ # are returned.
+ #
+ # ==== Parameters
+ # args<Array[String, Symbol, Module]>:: 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 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
end
end
end
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 796ef40584..c71cef42b2 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -89,7 +89,7 @@ module AbstractController
# ==== Returns
# String:: A template name
def _implied_layout_name
- name.underscore
+ name && name.underscore
end
# Takes the specified layout and creates a _layout method to be called
@@ -100,7 +100,7 @@ module AbstractController
# name, return that string. Otherwise, use the superclass'
# layout (which might also be implied)
def _write_layout_method
- case @_layout
+ case defined?(@_layout) ? @_layout : nil
when String
self.class_eval %{def _layout(details) #{@_layout.inspect} end}
when Symbol
@@ -119,17 +119,19 @@ module AbstractController
when true
raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
when nil
- self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def _layout(details)
- self.class.cache_layout(details) do
- if template_exists?("#{_implied_layout_name}", details, :_prefix => "layouts")
- "#{_implied_layout_name}"
- else
- super
+ if name
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def _layout(details)
+ self.class.cache_layout(details) do
+ if template_exists?("#{_implied_layout_name}", details, :_prefix => "layouts")
+ "#{_implied_layout_name}"
+ else
+ super
+ end
end
end
- end
- RUBY
+ RUBY
+ end
end
self.class_eval { private :_layout }
end
@@ -157,6 +159,7 @@ module AbstractController
end
private
+
# This will be overwritten by _write_layout_method
def _layout(details) end
@@ -171,6 +174,34 @@ module AbstractController
name && _find_layout(name, details)
end
+ # Determine the layout for a given name and details, taking into account
+ # the name type.
+ #
+ # ==== Parameters
+ # name<String|TrueClass|FalseClass|Symbol>:: The name of the template
+ # details<Hash{Symbol => Object}>:: A list of details to restrict
+ # the lookup to. By default, layout lookup is limited to the
+ # formats specified for the current request.
+ def _layout_for_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
+
+ def _determine_template(options)
+ super
+
+ return unless (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
+ layout = options.key?(:layout) ? options[:layout] : :default
+ options[:_layout] = _layout_for_option(layout, options[:_template].details)
+ end
+
# Take in the name and details and find a Template.
#
# ==== Parameters
diff --git a/actionpack/lib/abstract_controller/localized_cache.rb b/actionpack/lib/abstract_controller/localized_cache.rb
new file mode 100644
index 0000000000..ee7b43cb9f
--- /dev/null
+++ b/actionpack/lib/abstract_controller/localized_cache.rb
@@ -0,0 +1,49 @@
+module AbstractController
+ class HashKey
+ @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } }
+
+ def self.get(klass, formats, locale)
+ @hash_keys[klass][formats][locale] ||= new(klass, formats, locale)
+ end
+
+ attr_accessor :hash
+ def initialize(klass, formats, locale)
+ @formats, @locale = formats, locale
+ @hash = [formats, locale].hash
+ end
+
+ alias_method :eql?, :equal?
+
+ def inspect
+ "#<HashKey -- formats: #{@formats.inspect} locale: #{@locale.inspect}>"
+ end
+ end
+
+ module LocalizedCache
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def clear_template_caches!
+ ActionView::Partials::PartialRenderer::TEMPLATES.clear
+ template_cache.clear
+ super
+ end
+
+ def template_cache
+ @template_cache ||= Hash.new {|h,k| h[k] = {} }
+ end
+ end
+
+ def render(options)
+ Thread.current[:format_locale_key] = HashKey.get(self.class, formats, I18n.locale)
+ super
+ end
+
+ private
+
+ def with_template_cache(name)
+ self.class.template_cache[Thread.current[:format_locale_key]][name] ||= super
+ end
+
+ end
+end
diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb
index f4d017b8e5..27ba5be45f 100644
--- a/actionpack/lib/abstract_controller/logger.rb
+++ b/actionpack/lib/abstract_controller/logger.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/logger'
+require 'active_support/benchmarkable'
module AbstractController
module Logger
@@ -6,22 +7,7 @@ module AbstractController
included do
cattr_accessor :logger
- end
-
- module ClassMethods #:nodoc:
- # Logs a message appending the value measured.
- def log_with_time(message, time, log_level=::Logger::DEBUG)
- return unless logger && logger.level >= log_level
- logger.add(log_level, "#{message} (%.1fms)" % time)
- end
-
- # Silences the logger for the duration of the block.
- def silence
- old_logger_level, logger.level = logger.level, ::Logger::ERROR if logge
- yield
- ensure
- logger.level = old_logger_level if logger
- end
+ extend ActiveSupport::Benchmarkable
end
# A class that allows you to defer expensive processing
@@ -47,7 +33,7 @@ module AbstractController
# Override process_action in the AbstractController::Base
# to log details about the method.
def process_action(action)
- event = ActiveSupport::Orchestra.instrument(:process_action,
+ result = ActiveSupport::Notifications.instrument(:process_action,
:controller => self, :action => action) do
super
end
@@ -56,13 +42,13 @@ module AbstractController
log = DelayedLog.new do
"\n\nProcessing #{self.class.name}\##{action_name} " \
"to #{request.formats} (for #{request_origin}) " \
- "(%.1fms) [#{request.method.to_s.upcase}]" % event.duration
+ "[#{request.method.to_s.upcase}]"
end
logger.info(log)
end
- event.result
+ result
end
private
diff --git a/actionpack/lib/abstract_controller/rendering_controller.rb b/actionpack/lib/abstract_controller/rendering_controller.rb
index bbf941aa32..7054b9cf26 100644
--- a/actionpack/lib/abstract_controller/rendering_controller.rb
+++ b/actionpack/lib/abstract_controller/rendering_controller.rb
@@ -8,12 +8,16 @@ module AbstractController
included do
attr_internal :formats
-
extlib_inheritable_accessor :_view_paths
-
self._view_paths ||= ActionView::PathSet.new
end
+ # Initialize controller with nil formats.
+ def initialize(*) #:nodoc:
+ @_formats = nil
+ super
+ end
+
# An instance of a view class. The default view class is ActionView::Base
#
# The view class must have the following methods:
@@ -99,6 +103,7 @@ module AbstractController
end
private
+
# Take in a set of options and determine the template to render
#
# ==== Options
@@ -109,6 +114,18 @@ module AbstractController
# to a directory.
# _partial<TrueClass, FalseClass>:: Whether or not the file to look up is a partial
def _determine_template(options)
+ if options.key?(:text)
+ options[:_template] = ActionView::TextTemplate.new(options[:text], format_for_text)
+ elsif options.key?(:inline)
+ handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb")
+ template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {})
+ options[:_template] = template
+ elsif options.key?(:template)
+ options[:_template_name] = options[:template]
+ elsif options.key?(:file)
+ options[:_template_name] = options[:file]
+ end
+
name = (options[:_template_name] || action_name).to_s
options[:_template] ||= with_template_cache(name) do
@@ -128,6 +145,10 @@ module AbstractController
yield
end
+ def format_for_text
+ Mime[:text]
+ end
+
module ClassMethods
def clear_template_caches!
end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 6702cb47f8..03a40e4fce 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -2,6 +2,8 @@ module ActionController
autoload :Base, "action_controller/base"
autoload :Benchmarking, "action_controller/metal/benchmarking"
autoload :ConditionalGet, "action_controller/metal/conditional_get"
+ autoload :Configuration, "action_controller/metal/configuration"
+ autoload :Head, "action_controller/metal/head"
autoload :Helpers, "action_controller/metal/helpers"
autoload :HideActions, "action_controller/metal/hide_actions"
autoload :Layouts, "action_controller/metal/layouts"
@@ -18,22 +20,20 @@ module ActionController
autoload :Testing, "action_controller/metal/testing"
autoload :UrlFor, "action_controller/metal/url_for"
- # Ported modules
- # require 'action_controller/routing'
autoload :Caching, 'action_controller/caching'
autoload :Dispatcher, 'action_controller/dispatch/dispatcher'
autoload :Integration, 'action_controller/deprecated/integration_test'
autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
autoload :MimeResponds, 'action_controller/metal/mime_responds'
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
- autoload :PolymorphicRoutes, 'action_controller/routing/generation/polymorphic_routes'
+ autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
autoload :RecordIdentifier, 'action_controller/record_identifier'
- autoload :Resources, 'action_controller/routing/resources'
+ autoload :Routing, 'action_controller/deprecated'
autoload :SessionManagement, 'action_controller/metal/session_management'
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'
+ autoload :UrlRewriter, 'action_controller/url_rewriter'
+ autoload :UrlWriter, 'action_controller/url_rewriter'
autoload :Verification, 'action_controller/metal/verification'
autoload :Flash, 'action_controller/metal/flash'
@@ -54,8 +54,6 @@ module ActionController
autoload :RenderError, 'action_controller/metal/exceptions'
autoload :SessionOverflowError, 'action_controller/metal/exceptions'
autoload :UnknownHttpMethod, 'action_controller/metal/exceptions'
-
- autoload :Routing, 'action_controller/routing'
end
autoload :HTML, 'action_controller/vendor/html-scanner'
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 5338a70104..4c026fe5f7 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -15,6 +15,7 @@ module ActionController
include ActionController::ConditionalGet
include ActionController::RackConvenience
include ActionController::Benchmarking
+ include ActionController::Configuration
# Legacy modules
include SessionManagement
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 38cf1da6a8..3caf759032 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -3,26 +3,31 @@ require 'uri'
require 'set'
module ActionController #:nodoc:
- # Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
- # around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment.
+ # Caching is a cheap way of speeding up slow applications by keeping the result of
+ # calculations, renderings, and database calls around for subsequent requests.
+ # Action Controller affords you three approaches in varying levels of granularity:
+ # Page, Action, Fragment.
#
- # You can read more about each approach and the sweeping assistance by clicking the modules below.
- #
- # Note: To turn off all caching and sweeping, set Base.perform_caching = false.
+ # You can read more about each approach and the sweeping assistance by clicking the
+ # modules below.
#
+ # Note: To turn off all caching and sweeping, set
+ # config.action_controller.perform_caching = false.
#
# == Caching stores
#
- # All the caching stores from ActiveSupport::Cache are available to be used as backends for Action Controller caching. This setting only
- # affects action and fragment caching as page caching is always written to disk.
+ # All the caching stores from ActiveSupport::Cache are available to be used as backends
+ # for Action Controller caching. This setting only affects action and fragment caching
+ # as page caching is always written to disk.
#
# Configuration examples (MemoryStore is the default):
#
- # ActionController::Base.cache_store = :memory_store
- # ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
- # ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"
- # ActionController::Base.cache_store = :mem_cache_store, "localhost"
- # ActionController::Base.cache_store = MyOwnStore.new("parameter")
+ # config.action_controller.cache_store = :memory_store
+ # config.action_controller.cache_store = :file_store, "/path/to/cache/directory"
+ # config.action_controller.cache_store = :drb_store, "druby://localhost:9192"
+ # config.action_controller.cache_store = :mem_cache_store, "localhost"
+ # config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new("localhost:11211")
+ # config.action_controller.cache_store = MyOwnStore.new("parameter")
module Caching
extend ActiveSupport::Concern
@@ -46,25 +51,31 @@ module ActionController #:nodoc:
@@perform_caching = true
cattr_accessor :perform_caching
+ end
- def self.cache_configured?
+ module ClassMethods
+ def cache_configured?
perform_caching && cache_store
end
end
- protected
- # Convenience accessor
- def cache(key, options = {}, &block)
- if cache_configured?
- cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
- else
- yield
- end
- end
+ def caching_allowed?
+ request.get? && response.status == 200
+ end
- private
- def cache_configured?
- self.class.cache_configured?
+ protected
+ # Convenience accessor
+ def cache(key, options = {}, &block)
+ if cache_configured?
+ cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
+ else
+ yield
end
+ end
+
+ private
+ def cache_configured?
+ self.class.cache_configured?
+ end
end
end
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index cb0c3a1384..35111a4b92 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -2,9 +2,12 @@ require 'set'
module ActionController #:nodoc:
module Caching
- # Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching,
- # every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which
- # allows for authentication and other restrictions on whether someone is allowed to see the cache. Example:
+ # Action caching is similar to page caching by the fact that the entire
+ # output of the response is cached, but unlike page caching, every
+ # request still goes through the Action Pack. The key benefit
+ # of this is that filters are run before the cache is served, which
+ # allows for authentication and other restrictions on whether someone
+ # is allowed to see the cache. Example:
#
# class ListsController < ApplicationController
# before_filter :authenticate, :except => :public
@@ -12,45 +15,64 @@ module ActionController #:nodoc:
# caches_action :index, :show, :feed
# end
#
- # In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the
- # show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches.
+ # In this example, the public action doesn't require authentication,
+ # so it's possible to use the faster page caching method. But both
+ # the show and feed action are to be shielded behind the authenticate
+ # filter, so we need to implement those as action caches.
#
- # Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both
- # the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named
- # "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and
- # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern.
+ # Action caching internally uses the fragment caching and an around
+ # filter to do the job. The fragment cache is named according to both
+ # the current host and the path. So a page that is accessed at
+ # http://david.somewhere.com/lists/show/1 will result in a fragment named
+ # "david.somewhere.com/lists/show/1". This allows the cacher to
+ # differentiate between "david.somewhere.com/lists/" and
+ # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting
+ # the subdomain-as-account-key pattern.
#
- # Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt>
- # are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same
- # as <tt>:action => 'list', :format => :xml</tt>.
+ # Different representations of the same resource, e.g.
+ # <tt>http://david.somewhere.com/lists</tt> and
+ # <tt>http://david.somewhere.com/lists.xml</tt>
+ # are treated like separate requests and so are cached separately.
+ # Keep in mind when expiring an action cache that
+ # <tt>:action => 'lists'</tt> is not the same as
+ # <tt>:action => 'list', :format => :xml</tt>.
#
- # You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy
- # for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
+ # You can set modify the default action cache path by passing a
+ # :cache_path option. This will be passed directly to
+ # ActionCachePath.path_for. This is handy for actions with multiple
+ # possible routes that should be cached differently. If a block is
+ # given, it is called with the current controller instance.
#
- # And you can also use :if (or :unless) to pass a Proc that specifies when the action should be cached.
+ # And you can also use :if (or :unless) to pass a Proc that
+ # specifies when the action should be cached.
#
# Finally, if you are using memcached, you can also pass :expires_in.
#
# class ListsController < ApplicationController
# before_filter :authenticate, :except => :public
# caches_page :public
- # caches_action :index, :if => Proc.new { |c| !c.request.format.json? } # cache if is not a JSON request
- # caches_action :show, :cache_path => { :project => 1 }, :expires_in => 1.hour
- # caches_action :feed, :cache_path => Proc.new { |controller|
- # controller.params[:user_id] ?
- # controller.send(:user_list_url, controller.params[:user_id], controller.params[:id]) :
- # controller.send(:list_url, controller.params[:id]) }
+ # caches_action :index, :if => proc do |c|
+ # !c.request.format.json? # cache if is not a JSON request
+ # end
+ #
+ # caches_action :show, :cache_path => { :project => 1 },
+ # :expires_in => 1.hour
+ #
+ # caches_action :feed, :cache_path => proc do |controller|
+ # if controller.params[:user_id]
+ # controller.send(:user_list_url,
+ # controller.params[:user_id], controller.params[:id])
+ # else
+ # controller.send(:list_url, controller.params[:id])
+ # end
+ # end
# end
#
- # If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information.
+ # If you pass :layout => false, it will only cache your action
+ # content. It is useful when your layout has dynamic information.
#
module Actions
- def self.included(base) #:nodoc:
- base.extend(ClassMethods)
- base.class_eval do
- attr_accessor :rendered_action_cache, :action_cache_path
- end
- end
+ extend ActiveSupport::Concern
module ClassMethods
# Declares that +actions+ should be cached.
@@ -58,120 +80,83 @@ module ActionController #:nodoc:
def caches_action(*actions)
return unless cache_configured?
options = actions.extract_options!
- filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) }
-
- cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options)
+ filter_options = options.extract!(:if, :unless).merge(:only => actions)
+ cache_options = options.extract!(:layout, :cache_path).merge(:store_options => options)
- around_filter cache_filter, filter_options
+ around_filter ActionCacheFilter.new(cache_options), filter_options
end
end
- protected
- def expire_action(options = {})
- return unless cache_configured?
+ def _render_cache_fragment(cache, extension, layout)
+ render :text => cache, :layout => layout, :content_type => Mime[extension || :html]
+ end
- if options[:action].is_a?(Array)
- options[:action].dup.each do |action|
- expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action }), false))
- end
- else
- expire_fragment(ActionCachePath.path_for(self, options, false))
- end
+ def _save_fragment(name, layout, options)
+ return unless caching_allowed?
+
+ content = layout ? view_context.content_for(:layout) : response_body
+ write_fragment(name, content, options)
+ end
+
+ protected
+ def expire_action(options = {})
+ return unless cache_configured?
+
+ actions = options[:action]
+ if actions.is_a?(Array)
+ actions.each {|action| expire_action(options.merge(:action => action)) }
+ else
+ expire_fragment(ActionCachePath.new(self, options, false).path)
end
+ end
class ActionCacheFilter #:nodoc:
def initialize(options, &block)
- @options = options
+ @cache_path, @store_options, @layout =
+ options.values_at(:cache_path, :store_options, :layout)
end
def filter(controller)
- should_continue = before(controller)
- yield if should_continue
- after(controller)
- end
-
- def before(controller)
- cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path)))
-
- if cache = controller.read_fragment(cache_path.path, @options[:store_options])
- controller.rendered_action_cache = true
- set_content_type!(controller, cache_path.extension)
- options = { :text => cache }
- options.merge!(:layout => true) if cache_layout?
- controller.__send__(:render, options)
- false
+ path_options = if @cache_path.respond_to?(:call)
+ controller.instance_exec(controller, &@cache_path)
else
- controller.action_cache_path = cache_path
- end
- end
-
- def after(controller)
- return if controller.rendered_action_cache || !caching_allowed(controller)
- action_content = cache_layout? ? content_for_layout(controller) : controller.response.body
- controller.write_fragment(controller.action_cache_path.path, action_content, @options[:store_options])
- end
-
- private
- def set_content_type!(controller, extension)
- controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension
+ @cache_path
end
- def path_options_for(controller, options)
- ((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {}
- end
-
- def caching_allowed(controller)
- controller.request.get? && controller.response.status.to_i == 200
- end
-
- def cache_layout?
- @options[:layout] == false
- end
+ cache_path = ActionCachePath.new(controller, path_options || {})
- def content_for_layout(controller)
- template = controller.view_context
- template.layout && template.instance_variable_get('@cached_content_for_layout')
+ if cache = controller.read_fragment(cache_path.path, @store_options)
+ controller._render_cache_fragment(cache, cache_path.extension, @layout == false)
+ else
+ yield
+ controller._save_fragment(cache_path.path, @layout == false, @store_options)
end
+ end
end
class ActionCachePath
attr_reader :path, :extension
- class << self
- def path_for(controller, options, infer_extension = true)
- new(controller, options, infer_extension).path
- end
- end
-
- # If +infer_extension+ is true, the cache path extension is looked up from the request's path & format.
- # This is desirable when reading and writing the cache, but not when expiring the cache -
- # expire_action should expire the same files regardless of the request format.
+ # If +infer_extension+ is true, the cache path extension is looked up from the request's
+ # path & format. This is desirable when reading and writing the cache, but not when
+ # expiring the cache - expire_action should expire the same files regardless of the
+ # request format.
def initialize(controller, options = {}, infer_extension = true)
if infer_extension
- extract_extension(controller.request)
- options = options.reverse_merge(:format => @extension) if options.is_a?(Hash)
+ @extension = controller.params[:format]
+ options.reverse_merge!(:format => @extension) if options.is_a?(Hash)
end
- path = controller.url_for(options).split('://').last
- normalize!(path)
- add_extension!(path, @extension)
- @path = URI.unescape(path)
+ path = controller.url_for(options).split(%r{://}).last
+ @path = normalize!(path)
end
- private
- def normalize!(path)
- path << 'index' if path[-1] == ?/
- end
-
- def add_extension!(path, extension)
- path << ".#{extension}" if extension and !path.ends_with?(extension)
- end
-
- def extract_extension(request)
- # Don't want just what comes after the last '.' to accommodate multi part extensions
- # such as tar.gz.
- @extension = request.path[/^[^.]+\.(.+)$/, 1] || request.cache_format
- end
+ private
+ def normalize!(path)
+ path << 'index' if path[-1] == ?/
+ path << ".#{extension}" if extension and !path.ends_with?(extension)
+ URI.unescape(path)
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index 59e24619e3..8c1167d526 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -51,40 +51,32 @@ module ActionController #:nodoc:
# Writes <tt>content</tt> to the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
def write_fragment(key, content, options = nil)
return content unless cache_configured?
-
key = fragment_cache_key(key)
- event = ActiveSupport::Orchestra.instrument(:write_fragment, :key => key) do
+
+ ActiveSupport::Notifications.instrument(:write_fragment, :key => key) do
cache_store.write(key, content, options)
end
-
- self.class.log_with_time("Cached fragment miss: #{key}", event.duration)
content
end
# Reads a cached fragment from the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
def read_fragment(key, options = nil)
return unless cache_configured?
-
key = fragment_cache_key(key)
- event = ActiveSupport::Orchestra.instrument(:read_fragment, :key => key) do
+
+ ActiveSupport::Notifications.instrument(:read_fragment, :key => key) do
cache_store.read(key, options)
end
-
- self.class.log_with_time("Cached fragment hit: #{key}", event.duration)
- event.result
end
# Check if a cached fragment from the location signified by <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats)
def fragment_exist?(key, options = nil)
return unless cache_configured?
-
key = fragment_cache_key(key)
- event = ActiveSupport::Orchestra.instrument(:fragment_exist?, :key => key) do
+
+ ActiveSupport::Notifications.instrument(:fragment_exist?, :key => key) do
cache_store.exist?(key, options)
end
-
- self.class.log_with_time("Cached fragment exists?: #{key}", event.duration)
- event.result
end
# Removes fragments from the cache.
@@ -106,11 +98,10 @@ module ActionController #:nodoc:
# method (or <tt>delete_matched</tt>, for Regexp keys.)
def expire_fragment(key, options = nil)
return unless cache_configured?
-
key = fragment_cache_key(key) unless key.is_a?(Regexp)
message = nil
- event = ActiveSupport::Orchestra.instrument(:expire_fragment, :key => key) do
+ ActiveSupport::Notifications.instrument(:expire_fragment, :key => key) do
if key.is_a?(Regexp)
message = "Expired fragments matching: #{key.source}"
cache_store.delete_matched(key, options)
@@ -119,9 +110,6 @@ module ActionController #:nodoc:
cache_store.delete(key, options)
end
end
-
- self.class.log_with_time(message, event.duration)
- event.result
end
end
end
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
index 4fb154470f..d46f528c7e 100644
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ b/actionpack/lib/action_controller/caching/pages.rb
@@ -64,12 +64,9 @@ module ActionController #:nodoc:
return unless perform_caching
path = page_cache_path(path)
- event = ActiveSupport::Orchestra.instrument(:expire_page, :path => path) do
+ ActiveSupport::Notifications.instrument(:expire_page, :path => path) do
File.delete(path) if File.exist?(path)
end
-
- log_with_time("Expired page: #{path}", event.duration)
- event.result
end
# Manually cache the +content+ in the key determined by +path+. Example:
@@ -78,13 +75,10 @@ module ActionController #:nodoc:
return unless perform_caching
path = page_cache_path(path)
- event = ActiveSupport::Orchestra.instrument(:cache_page, :path => path) do
+ ActiveSupport::Notifications.instrument(:cache_page, :path => path) do
FileUtils.makedirs(File.dirname(path))
File.open(path, "wb+") { |f| f.write(content) }
end
-
- log_with_time("Cached page: #{path}", event.duration)
- event.result
end
# Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
index c1be264ffb..871f41bfdd 100644
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ b/actionpack/lib/action_controller/caching/sweeping.rb
@@ -70,7 +70,7 @@ module ActionController #:nodoc:
protected
# gets the action cache path for the given options.
def action_path_for(options)
- ActionController::Caching::Actions::ActionCachePath.path_for(controller, options)
+ Actions::ActionCachePath.new(controller, options).path
end
# Retrieve instance variables set in the controller.
diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb
index d98e9ac7bd..589061e77c 100644
--- a/actionpack/lib/action_controller/deprecated.rb
+++ b/actionpack/lib/action_controller/deprecated.rb
@@ -1,2 +1,4 @@
ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
+ActionController::Routing = ActionDispatch::Routing
+ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new
diff --git a/actionpack/lib/action_controller/dispatch/dispatcher.rb b/actionpack/lib/action_controller/dispatch/dispatcher.rb
index 008fb54715..e04da42637 100644
--- a/actionpack/lib/action_controller/dispatch/dispatcher.rb
+++ b/actionpack/lib/action_controller/dispatch/dispatcher.rb
@@ -50,7 +50,7 @@ module ActionController
def new
# DEPRECATE Rails application fallback
- Rails.application.new
+ Rails.application
end
end
end
diff --git a/actionpack/lib/action_controller/legacy/layout.rb b/actionpack/lib/action_controller/legacy/layout.rb
deleted file mode 100644
index 53762158fc..0000000000
--- a/actionpack/lib/action_controller/legacy/layout.rb
+++ /dev/null
@@ -1,256 +0,0 @@
-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)
- base.class_inheritable_accessor :layout_name, :layout_conditions
- end
-
- # 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:
- #
- # <h1><%= @page_title %></h1>
- # <%= 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:
- #
- # <h1>Welcome</h1>
- # Off-world colonies offers you a chance to start a new life
- #
- # == Automatic layout assignment
- #
- # If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically
- # set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named
- # <tt>app/views/layouts/weblog.erb</tt> or <tt>app/views/layouts/weblog.builder</tt> exists then it will be automatically set as
- # the layout for your WeblogController. You can create a layout with the name <tt>application.erb</tt> or <tt>application.builder</tt>
- # and this will be set as the default controller if there is no layout with the same name as the current controller and there is
- # no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout.
- # assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.erb</tt>.
- # Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set.
- # Explicitly setting the layout in a parent class, though, will not override the child class's layout assignment if the child
- # class has a layout with the same name.
- #
- # == Inheritance for layouts
- #
- # Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples:
- #
- # class BankController < ActionController::Base
- # layout "bank_standard"
- #
- # class InformationController < BankController
- #
- # 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.
- #
- # == 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 <tt>app/views/layouts/</tt>.
- # 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
- # <tt>:only</tt> and <tt>:except</tt> 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 <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
- # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
- #
- # == 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 <tt>:layout</tt> option to the <tt>render</tt> 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 ClassMethods
- extend ActiveSupport::Memoizable
-
- # If a layout is specified, all rendered actions will have their result rendered
- # when the layout <tt>yield</tt>s. This layout can itself depend on instance variables assigned during action
- # performance and have access to them as any normal template would.
- def layout(template_name, conditions = {}, auto = false)
- add_layout_conditions(conditions)
- self.layout_name = template_name
- end
-
- def memoized_default_layout(formats) #:nodoc:
- self.layout_name || begin
- layout = default_layout_name
- layout.is_a?(String) ? find_layout(layout, formats) : layout
- rescue ActionView::MissingTemplate
- end
- end
-
- def default_layout(*args)
- memoized_default_layout(*args)
- @_memoized_default_layout ||= ::ActiveSupport::ConcurrentHash.new
- @_memoized_default_layout[args] ||= memoized_default_layout(*args)
- end
-
- def memoized_find_layout(layout, formats) #:nodoc:
- return layout if layout.nil? || layout.respond_to?(:render)
- prefix = layout.to_s =~ /layouts\// ? nil : "layouts"
- find_template(layout.to_s, {:formats => formats}, :_prefix => prefix)
- end
-
- def find_layout(*args)
- @_memoized_find_layout ||= ::ActiveSupport::ConcurrentHash.new
- @_memoized_find_layout[args] ||= memoized_find_layout(*args)
- end
-
- def layout_list #:nodoc:
- Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] }
- end
- memoize :layout_list
-
- def default_layout_name
- layout_match = name.underscore.sub(/_controller$/, '')
- if layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty?
- superclass.default_layout_name if superclass.respond_to?(:default_layout_name)
- else
- layout_match
- end
- end
- memoize :default_layout_name
-
- private
- def add_layout_conditions(conditions)
- # :except => :foo == :except => [:foo] == :except => "foo" == :except => ["foo"]
- conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
- write_inheritable_hash(:layout_conditions, conditions)
- end
- end
-
- def active_layout(name)
- name = self.class.default_layout(formats) if name == true
-
- layout_name = case name
- when Symbol then __send__(name)
- when Proc then name.call(self)
- else name
- end
-
- self.class.find_layout(layout_name, formats)
- end
-
- def _pick_layout(layout_name = nil, implicit = false)
- return unless layout_name || implicit
- layout_name = true if layout_name.nil?
- active_layout(layout_name) if action_has_layout? && layout_name
- end
-
- private
- def action_has_layout?
- if conditions = self.class.layout_conditions
- if only = conditions[:only]
- return only.include?(action_name)
- elsif except = conditions[:except]
- return !except.include?(action_name)
- end
- end
- true
- end
-
- end
-end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index e9007d3631..60b3f9a89b 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -49,7 +49,7 @@ module ActionController
# and response object available. You might wish to control the
# environment and response manually for performance reasons.
- attr_internal :status, :headers, :content_type, :app, :response
+ attr_internal :status, :headers, :content_type, :response
def initialize(*)
@_headers = {}
@@ -69,7 +69,7 @@ module ActionController
end
# :api: private
- def call(name, env)
+ def dispatch(name, env)
@_env = env
process(name)
to_a
@@ -95,7 +95,7 @@ module ActionController
end
def call(env)
- controller = @controller.new.call(@action, env)
+ @controller.new.dispatch(@action, env)
end
end
@@ -109,6 +109,10 @@ module ActionController
middleware_stack
end
+ def self.call(env)
+ action(env['action_dispatch.request.path_parameters'][:action]).call(env)
+ 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.
diff --git a/actionpack/lib/action_controller/metal/benchmarking.rb b/actionpack/lib/action_controller/metal/benchmarking.rb
index d4cb1e122d..e58df69172 100644
--- a/actionpack/lib/action_controller/metal/benchmarking.rb
+++ b/actionpack/lib/action_controller/metal/benchmarking.rb
@@ -1,4 +1,4 @@
-require 'benchmark'
+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
@@ -6,25 +6,6 @@ module ActionController #:nodoc:
module Benchmarking #:nodoc:
extend ActiveSupport::Concern
- module ClassMethods
- # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it
- # (unless <tt>use_silence</tt> is set to false).
- #
- # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, 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
- end
-
protected
def render(*args, &block)
if logger
@@ -45,7 +26,7 @@ module ActionController #:nodoc:
else
super
end
- end
+ end
private
def process_action(*args)
diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb
index 22f9ab219c..c251d79f4e 100644
--- a/actionpack/lib/action_controller/metal/compatibility.rb
+++ b/actionpack/lib/action_controller/metal/compatibility.rb
@@ -25,9 +25,11 @@ module ActionController
# cattr_reader :protected_instance_variables
cattr_accessor :protected_instance_variables
- self.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)
+ self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render
+ @variables_added @request_origin @url
+ @parent_controller @action_name
+ @before_filter_chain_aborted @_headers @_params
+ @_flash @_response)
# Indicates whether or not optimise the generated named
# route helper methods
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 8575d30335..5156fbc1d5 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -3,6 +3,7 @@ module ActionController
extend ActiveSupport::Concern
include RackConvenience
+ include Head
# Sets the etag, last_modified, or both on the response and renders a
# "304 Not Modified" response if the request is already fresh.
@@ -27,43 +28,9 @@ module ActionController
response.etag = options[:etag] if options[:etag]
response.last_modified = options[:last_modified] if options[:last_modified]
+ response.cache_control[:public] = true if options[:public]
- if options[:public]
- response.cache_control[:public] = true
- end
-
- if request.fresh?(response)
- head :not_modified
- end
- 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 = args.shift || options.delete(:status) || :ok
- location = options.delete(:location)
-
- 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, :location => location
+ head :not_modified if request.fresh?(response)
end
# Sets the etag and/or last_modified on the response and checks it against
@@ -113,7 +80,7 @@ module ActionController
# 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"
+ response.cache_control.replace(:no_cache => true)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/configuration.rb b/actionpack/lib/action_controller/metal/configuration.rb
new file mode 100644
index 0000000000..5c829853b7
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/configuration.rb
@@ -0,0 +1,28 @@
+module ActionController
+ module Configuration
+ extend ActiveSupport::Concern
+
+ def config
+ @config ||= self.class.config
+ end
+
+ def config=(config)
+ @config = config
+ end
+
+ module ClassMethods
+ def default_config
+ @default_config ||= {}
+ end
+
+ def config
+ self.config ||= default_config
+ end
+
+ def config=(config)
+ @config = ActiveSupport::OrderedHash.new
+ @config.merge!(config)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb
index d4806623c3..6855ca1478 100644
--- a/actionpack/lib/action_controller/metal/cookies.rb
+++ b/actionpack/lib/action_controller/metal/cookies.rb
@@ -44,24 +44,31 @@ module ActionController #:nodoc:
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
# only HTTP. Defaults to +false+.
module Cookies
- def self.included(base)
- base.helper_method :cookies
+ extend ActiveSupport::Concern
+
+ include RackConvenience
+
+ included do
+ helper_method :cookies
end
- protected
- # Returns the cookie container, which operates as described above.
- def cookies
- @cookies ||= CookieJar.new(self)
- end
+ protected
+ # Returns the cookie container, which operates as described above.
+ def cookies
+ @cookies ||= CookieJar.build(request, response)
+ end
end
class CookieJar < Hash #:nodoc:
- def initialize(controller)
- @controller, @cookies = controller, controller.request.cookies
- super()
- update(@cookies)
+ def self.build(request, response)
+ new.tap do |hash|
+ hash.update(request.cookies)
+ hash.response = response
+ end
end
+ attr_accessor :response
+
# Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
def [](name)
super(name.to_s)
@@ -72,13 +79,16 @@ module ActionController #:nodoc:
def []=(key, options)
if options.is_a?(Hash)
options.symbolize_keys!
+ value = options[:value]
else
- options = { :value => options }
+ value = options
+ options = { :value => value }
end
- options[:path] = "/" unless options.has_key?(:path)
- super(key.to_s, options[:value])
- @controller.response.set_cookie(key, options)
+ super(key.to_s, value)
+
+ options[:path] ||= "/"
+ response.set_cookie(key, options)
end
# Removes the cookie on the client machine by setting the value to an empty string
@@ -86,9 +96,10 @@ module ActionController #:nodoc:
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
def delete(key, options = {})
options.symbolize_keys!
- options[:path] = "/" unless options.has_key?(:path)
- super(key.to_s)
- @controller.response.delete_cookie(key, options)
+ options[:path] ||= "/"
+ value = super(key.to_s)
+ response.delete_cookie(key, options)
+ value
end
end
end
diff --git a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb
index 4259d9de19..a53c052075 100644
--- a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb
+++ b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb
@@ -4,10 +4,6 @@ module ActionController
include AbstractController::Logger
- included do
- include InstanceMethodsForNewBase
- end
-
module ClassMethods
# Replace sensitive parameter data from the request log.
# Filters parameters that have any of the arguments as a substring.
@@ -17,8 +13,6 @@ module ActionController
# can be replaced using String#replace or similar method.
#
# Examples:
- # filter_parameter_logging
- # => Does nothing, just slows the logging process down
#
# filter_parameter_logging :password
# => replaces the value to all keys matching /password/i with "[FILTERED]"
@@ -33,64 +27,51 @@ module ActionController
# => reverses the value to all keys matching /secret/i, and
# replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
def filter_parameter_logging(*filter_words, &block)
- parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0
+ raise "You must filter at least one word from logging" if filter_words.empty?
+
+ parameter_filter = Regexp.new(filter_words.join('|'), true)
- define_method(:filter_parameters) do |unfiltered_parameters|
- filtered_parameters = {}
+ define_method(:filter_parameters) do |original_params|
+ filtered_params = {}
- unfiltered_parameters.each do |key, value|
+ original_params.each do |key, value|
if key =~ parameter_filter
- filtered_parameters[key] = '[FILTERED]'
+ value = '[FILTERED]'
elsif value.is_a?(Hash)
- filtered_parameters[key] = filter_parameters(value)
+ value = filter_parameters(value)
elsif value.is_a?(Array)
- filtered_parameters[key] = value.collect do |item|
- filter_parameters(item)
- end
+ value = value.map { |item| filter_parameters(item) }
elsif block_given?
key = key.dup
value = value.dup if value.duplicable?
yield key, value
- filtered_parameters[key] = value
- else
- filtered_parameters[key] = value
end
+
+ filtered_params[key] = value
end
- filtered_parameters
+ filtered_params
end
protected :filter_parameters
end
end
- module InstanceMethodsForNewBase
- # TODO : Fix the order of information inside such that it's exactly same as the old base
- def process(*)
- ret = super
-
- if logger
- parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
- parameters = parameters.except!(:controller, :action, :format, :_method, :only_path)
+ INTERNAL_PARAMS = [:controller, :action, :format, :_method, :only_path]
- unless parameters.empty?
- # TODO : Move DelayedLog to AS
- log = AbstractController::Logger::DelayedLog.new { " Parameters: #{parameters.inspect}" }
- logger.info(log)
- end
- end
-
- ret
+ def process(*)
+ response = super
+ if logger
+ parameters = filter_parameters(params).except!(*INTERNAL_PARAMS)
+ logger.info { " Parameters: #{parameters.inspect}" } unless parameters.empty?
end
+ response
end
- private
+ protected
- # TODO : This method is not needed for the new base
- def log_processing_for_parameters
- parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
- parameters = parameters.except!(:controller, :action, :format, :_method)
-
- logger.info " Parameters: #{parameters.inspect}" unless parameters.empty?
+ def filter_parameters(params)
+ params.dup
end
+
end
end
diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index 8753253dc6..feb066a6f6 100644
--- a/actionpack/lib/action_controller/metal/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -49,7 +49,7 @@ module ActionController #:nodoc:
class FlashHash < Hash
def initialize #:nodoc:
super
- @used = {}
+ @used = Set.new
end
def []=(k, v) #:nodoc:
@@ -65,7 +65,7 @@ module ActionController #:nodoc:
alias :merge! :update
def replace(h) #:nodoc:
- @used = {}
+ @used = Set.new
super
end
@@ -104,8 +104,8 @@ module ActionController #:nodoc:
# This method is called automatically by filters, so you generally don't need to care about it.
def sweep #:nodoc:
keys.each do |k|
- unless @used[k]
- use(k)
+ unless @used.include?(k)
+ @used << k
else
delete(k)
@used.delete(k)
@@ -113,47 +113,45 @@ module ActionController #:nodoc:
end
# clean up after keys that could have been left over by calling reject! or shift on the flash
- (@used.keys - keys).each{ |k| @used.delete(k) }
+ (@used - keys).each{ |k| @used.delete(k) }
end
- def store(session, key = "flash")
+ def store(session)
return if self.empty?
- session[key] = self
+ session["flash"] = self
end
- private
- # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
- # use() # marks the entire flash as used
- # use('msg') # marks the "msg" entry as used
- # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
- # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
- # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
- # if no key is passed.
- def use(key = nil, used = true)
- Array(key || keys).each { |k| @used[k] = used }
- return key ? self[key] : self
- end
+ private
+ # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
+ # use() # marks the entire flash as used
+ # use('msg') # marks the "msg" entry as used
+ # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
+ # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
+ # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
+ # if no key is passed.
+ def use(key = nil, used = true)
+ Array(key || keys).each { |k| used ? @used << k : @used.delete(k) }
+ return key ? self[key] : self
+ end
end
protected
def process_action(method_name)
super
- if defined? @_flash
- @_flash.store(session)
- remove_instance_variable(:@_flash)
- end
+ @_flash.store(session) if @_flash
+ @_flash = nil
end
def reset_session
super
- remove_instance_variable(:@_flash) if defined?(@_flash)
+ @_flash = nil
end
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
# to put a new one.
def flash #:doc:
- if !defined?(@_flash)
+ unless @_flash
@_flash = session["flash"] || FlashHash.new
@_flash.sweep
end
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
new file mode 100644
index 0000000000..68fa0a0402
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -0,0 +1,27 @@
+module ActionController
+ module Head
+ # 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(status, options = {})
+ options, status = status, nil if status.is_a?(Hash)
+ status ||= options.delete(:status) || :ok
+ location = options.delete(:location)
+
+ 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, :location => location
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 7c52779064..b4325e24ad 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -1,5 +1,3 @@
-require 'active_support/dependencies'
-
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
@@ -54,7 +52,7 @@ module ActionController
included do
# Set the default directory for helpers
extlib_inheritable_accessor(:helpers_dir) do
- defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers"
+ defined?(Rails) ? "#{Rails.root}/app/helpers" : "app/helpers"
end
end
@@ -64,46 +62,6 @@ module ActionController
super
end
- # The +helper+ class method can take a series of helper module names, a block, or both.
- #
- # ==== Parameters
- # *args<Array[Module, Symbol, String, :all]>
- # block<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 <tt>:all</tt>, the controller will include all helpers beneath
- # <tt>ActionController::Base.helpers_dir</tt> (defaults to <tt>app/helpers/**/*.rb</tt> 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)
- super(*_modules_for_helpers(args), &block)
- end
-
# Declares helper accessors for controller attributes. For example, the
# following adds new +name+ and <tt>name=</tt> instance methods to a
# controller and makes them available to the view:
@@ -123,15 +81,8 @@ module ActionController
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.
+ # Overwrite _modules_for_helpers to accept :all as argument, which loads
+ # all helpers in helpers_dir.
#
# ==== Parameters
# args<Array[String, Symbol, Module, all]>:: A list of helpers
@@ -140,20 +91,8 @@ module ActionController
# 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
+ args += all_application_helpers if args.delete(:all)
+ super(args)
end
def default_helper_module!
diff --git a/actionpack/lib/action_controller/metal/layouts.rb b/actionpack/lib/action_controller/metal/layouts.rb
index cac529b1ae..cc7088248a 100644
--- a/actionpack/lib/action_controller/metal/layouts.rb
+++ b/actionpack/lib/action_controller/metal/layouts.rb
@@ -167,26 +167,5 @@ module ActionController
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/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 3026067868..468c5f4fae 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -3,7 +3,8 @@ module ActionController #:nodoc:
extend ActiveSupport::Concern
included do
- class_inheritable_reader :mimes_for_respond_to
+ extlib_inheritable_accessor :responder, :mimes_for_respond_to, :instance_writer => false
+ self.responder = ActionController::Responder
clear_respond_to
end
@@ -46,7 +47,7 @@ module ActionController #:nodoc:
# Clear all mimes in respond_to.
#
def clear_respond_to
- write_inheritable_attribute(:mimes_for_respond_to, ActiveSupport::OrderedHash.new)
+ self.mimes_for_respond_to = ActiveSupport::OrderedHash.new
end
end
@@ -221,10 +222,6 @@ module ActionController #:nodoc:
end
end
- def responder
- ActionController::Responder
- end
-
protected
# Collect mimes declared in the class method respond_to valid for the
diff --git a/actionpack/lib/action_controller/metal/rack_convenience.rb b/actionpack/lib/action_controller/metal/rack_convenience.rb
index a80569c530..131d20114d 100644
--- a/actionpack/lib/action_controller/metal/rack_convenience.rb
+++ b/actionpack/lib/action_controller/metal/rack_convenience.rb
@@ -8,7 +8,7 @@ module ActionController
attr_internal :request
end
- def call(name, env)
+ def dispatch(action, env)
@_request = ActionDispatch::Request.new(env)
@_response = ActionDispatch::Response.new
@_response.request = request
diff --git a/actionpack/lib/action_controller/metal/redirector.rb b/actionpack/lib/action_controller/metal/redirector.rb
index f79fd54acd..b55f5e7bfc 100644
--- a/actionpack/lib/action_controller/metal/redirector.rb
+++ b/actionpack/lib/action_controller/metal/redirector.rb
@@ -16,7 +16,7 @@ module ActionController
logger.info("Redirected to #{url}") if logger && logger.info?
self.status = status
self.location = url.gsub(/[\r\n]/, '')
- self.response_body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>"
+ self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(url)}\">redirected</a>.</body></html>"
end
end
end
diff --git a/actionpack/lib/action_controller/metal/rendering_controller.rb b/actionpack/lib/action_controller/metal/rendering_controller.rb
index 4da32ca1b3..237299cd30 100644
--- a/actionpack/lib/action_controller/metal/rendering_controller.rb
+++ b/actionpack/lib/action_controller/metal/rendering_controller.rb
@@ -1,54 +1,18 @@
module ActionController
- class HashKey
- @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } }
-
- def self.get(klass, formats, locale)
- @hash_keys[klass][formats][locale] ||= new(klass, formats, locale)
- end
-
- attr_accessor :hash
- def initialize(klass, formats, locale)
- @formats, @locale = formats, locale
- @hash = [formats, locale].hash
- end
-
- alias_method :eql?, :equal?
-
- def inspect
- "#<HashKey -- formats: #{@formats} locale: #{@locale}>"
- end
- end
-
module RenderingController
extend ActiveSupport::Concern
- include AbstractController::RenderingController
-
- module ClassMethods
- def clear_template_caches!
- ActionView::Partials::PartialRenderer::TEMPLATES.clear
- template_cache.clear
- super
- end
-
- def template_cache
- @template_cache ||= Hash.new {|h,k| h[k] = {} }
- end
+ included do
+ include AbstractController::RenderingController
+ include AbstractController::LocalizedCache
end
def process_action(*)
self.formats = request.formats.map {|x| x.to_sym}
-
- super
- end
-
- def _determine_template(*)
super
end
def render(options)
- Thread.current[:format_locale_key] = HashKey.get(self.class, formats, I18n.locale)
-
super
self.content_type ||= options[:_template].mime_type.to_s
response_body
@@ -70,29 +34,19 @@ module ActionController
controller_path
end
- def with_template_cache(name)
- self.class.template_cache[Thread.current[:format_locale_key]][name] ||= super
- end
-
def _determine_template(options)
- if options.key?(:text)
- options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first)
- elsif options.key?(:inline)
- handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb")
- template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {})
- options[:_template] = template
- elsif options.key?(:template)
- options[:_template_name] = options[:template]
- elsif options.key?(:file)
- options[:_template_name] = options[:file]
- elsif !options.key?(:partial)
- options[:_template_name] = (options[:action] || action_name).to_s
+ if (options.keys & [:partial, :file, :template, :text, :inline]).empty?
+ options[:_template_name] ||= options[:action]
options[:_prefix] = _prefix
end
super
end
+ def format_for_text
+ formats.first
+ end
+
def _process_options(options)
status, content_type, location = options.values_at(:status, :content_type, :location)
self.status = status if status
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index ad06657f86..113c20a758 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -5,7 +5,6 @@ module ActionController #:nodoc:
module RequestForgeryProtection
extend ActiveSupport::Concern
- # TODO : Remove the defined? check when new base is the main base
include AbstractController::Helpers, Session
included do
@@ -21,26 +20,26 @@ module ActionController #:nodoc:
helper_method :protect_against_forgery?
end
- # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a
- # forged link from another site, is done by embedding a token based on a random string stored in the session (which an attacker wouldn't know) in all
- # forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only
- # HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
- # scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway.
+ # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current
+ # web application, not a forged link from another site, is done by embedding a token based on a random
+ # string stored in the session (which an attacker wouldn't know) in all forms and Ajax requests generated
+ # by Rails and then verifying the authenticity of that token in the controller. Only HTML/JavaScript
+ # requests are checked, so this will not protect your XML API (presumably you'll have a different
+ # authentication scheme there anyway). Also, GET requests are not protected as these should be
+ # idempotent anyway.
#
# This is turned on with the <tt>protect_from_forgery</tt> method, which will check the token and raise an
- # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the error message in
- # production by editing public/422.html. A call to this method in ApplicationController is generated by default in post-Rails 2.0
- # applications.
+ # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the
+ # error message in production by editing public/422.html. A call to this method in ApplicationController is
+ # generated by default in post-Rails 2.0 applications.
#
- # The token parameter is named <tt>authenticity_token</tt> by default. If you are generating an HTML form manually (without the
- # use of Rails' <tt>form_for</tt>, <tt>form_tag</tt> or other helpers), you have to include a hidden field named like that and
- # set its value to what is returned by <tt>form_authenticity_token</tt>. Same applies to manually constructed Ajax requests. To
- # make the token available through a global variable to scripts on a certain page, you could add something like this to a view:
+ # The token parameter is named <tt>authenticity_token</tt> by default. If you are generating an HTML form
+ # manually (without the use of Rails' <tt>form_for</tt>, <tt>form_tag</tt> or other helpers), you have to
+ # include a hidden field named like that and set its value to what is returned by
+ # <tt>form_authenticity_token</tt>.
#
- # <%= javascript_tag "window._token = '#{form_authenticity_token}'" %>
- #
- # Request forgery protection is disabled by default in test environment. If you are upgrading from Rails 1.x, add this to
- # config/environments/test.rb:
+ # Request forgery protection is disabled by default in test environment. If you are upgrading from Rails
+ # 1.x, add this to config/environments/test.rb:
#
# # Disable request forgery protection in test environment
# config.action_controller.allow_forgery_protection = false
@@ -57,7 +56,8 @@ module ActionController #:nodoc:
# * Keep your GET requests safe and idempotent. More reading material:
# * http://www.xml.com/pub/a/2002/04/24/deviant.html
# * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
- # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look for "Expires: at end of session"
+ # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look
+ # for "Expires: at end of session"
#
module ClassMethods
# Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
@@ -76,10 +76,7 @@ module ActionController #:nodoc:
# * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
def protect_from_forgery(options = {})
self.request_forgery_protection_token ||= :authenticity_token
- before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except)
- if options[:secret] || options[:digest]
- ActiveSupport::Deprecation.warn("protect_from_forgery only takes :only and :except options now. :digest and :secret have no effect", caller)
- end
+ before_filter :verify_authenticity_token, options
end
end
@@ -88,31 +85,24 @@ module ActionController #:nodoc:
def verify_authenticity_token
verified_request? || raise(ActionController::InvalidAuthenticityToken)
end
-
+
# Returns true or false if a request is verified. Checks:
#
# * is the format restricted? By default, only HTML requests are checked.
# * is it a GET request? Gets should be safe and idempotent
# * Does the form_authenticity_token match the given token value from the params?
def verified_request?
- !protect_against_forgery? ||
- request.method == :get ||
- request.xhr? ||
- !verifiable_request_format? ||
+ !protect_against_forgery? || request.forgery_whitelisted? ||
form_authenticity_token == params[request_forgery_protection_token]
end
-
- def verifiable_request_format?
- !request.content_type.nil? && request.content_type.verify_request?
- end
-
+
# Sets the token value for the current session.
def form_authenticity_token
session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32)
end
def protect_against_forgery?
- allow_forgery_protection && request_forgery_protection_token
+ allow_forgery_protection
end
end
end
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index a16ed97131..e8e88e7479 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -14,12 +14,11 @@ module ActionController #:nodoc:
#
# When a request comes, for example with format :xml, three steps happen:
#
- # 1) respond_with searches for a template at people/index.xml;
+ # 1) responder searches for a template at people/index.xml;
#
- # 2) if the template is not available, it will create a responder, passing
- # the controller and the resource and invoke :to_xml on it;
+ # 2) if the template is not available, it will invoke :to_xml in the given resource;
#
- # 3) if the responder does not respond_to :to_xml, call to_format on it.
+ # 3) if the responder does not respond_to :to_xml, call :to_format on it.
#
# === Builtin HTTP verb semantics
#
@@ -88,14 +87,16 @@ module ActionController #:nodoc:
@resource = resources.is_a?(Array) ? resources.last : resources
@resources = resources
@options = options
+ @action = options.delete(:action)
@default_response = options.delete(:default_response)
end
delegate :head, :render, :redirect_to, :to => :controller
delegate :get?, :post?, :put?, :delete?, :to => :request
- # Undefine :to_json since it's defined on Object
- undef_method :to_json
+ # Undefine :to_json and :to_yaml since it's defined on Object
+ undef_method(:to_json) if method_defined?(:to_json)
+ undef_method(:to_yaml) if method_defined?(:to_yaml)
# Initializes a new responder an invoke the proper format. If the format is
# not defined, call to_format.
@@ -111,14 +112,8 @@ module ActionController #:nodoc:
#
def to_html
default_render
- rescue ActionView::MissingTemplate
- if get?
- raise
- elsif has_errors?
- render :action => default_action
- else
- redirect_to resource_location
- end
+ rescue ActionView::MissingTemplate => e
+ navigation_behavior(e)
end
# All others formats follow the procedure below. First we try to render a
@@ -127,9 +122,26 @@ module ActionController #:nodoc:
#
def to_format
default_render
- rescue ActionView::MissingTemplate
+ rescue ActionView::MissingTemplate => e
raise unless resourceful?
+ api_behavior(e)
+ end
+ protected
+
+ # This is the common behavior for "navigation" requests, like :html, :iphone and so forth.
+ def navigation_behavior(error)
+ if get?
+ raise error
+ elsif has_errors?
+ render :action => default_action
+ else
+ redirect_to resource_location
+ end
+ end
+
+ # This is the common behavior for "API" requests, like :xml and :json.
+ def api_behavior(error)
if get?
display resource
elsif has_errors?
@@ -141,8 +153,6 @@ module ActionController #:nodoc:
end
end
- protected
-
# Checks whether the resource responds to the current format or not.
#
def resourceful?
@@ -194,7 +204,7 @@ module ActionController #:nodoc:
# the verb is post.
#
def default_action
- request.post? ? :new : :edit
+ @action || (request.post? ? :new : :edit)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb
index 654aa08cd3..d70f40ce7a 100644
--- a/actionpack/lib/action_controller/metal/session_management.rb
+++ b/actionpack/lib/action_controller/metal/session_management.rb
@@ -1,10 +1,8 @@
module ActionController #:nodoc:
module SessionManagement #:nodoc:
- def self.included(base)
- base.class_eval do
- extend ClassMethods
- end
- end
+ extend ActiveSupport::Concern
+
+ include ActionController::Configuration
module ClassMethods
# Set the session store to be used for keeping the session data between requests.
@@ -35,13 +33,6 @@ module ActionController #:nodoc:
session_options.merge!(options)
end
- # Returns the hash used to configure the session. Example use:
- #
- # ActionController::Base.session_options[:secure] = true # session only available over HTTPS
- def session_options
- @session_options ||= {}
- end
-
def session(*args)
ActiveSupport::Deprecation.warn(
"Disabling sessions for a single controller has been deprecated. " +
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 4761763a26..43c661bef4 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/string/bytesize'
-
module ActionController #:nodoc:
# Methods for sending arbitrary data and for streaming files to the browser,
# instead of rendering.
diff --git a/actionpack/lib/action_controller/metal/verification.rb b/actionpack/lib/action_controller/metal/verification.rb
index d3d78e3749..500cced539 100644
--- a/actionpack/lib/action_controller/metal/verification.rb
+++ b/actionpack/lib/action_controller/metal/verification.rb
@@ -79,8 +79,8 @@ module ActionController #:nodoc:
# do not apply this verification to the actions specified in the associated
# array (may also be a single value).
def verify(options={})
- before_filter :only => options[:only], :except => options[:except] do |c|
- c.__send__ :verify_action, options
+ before_filter :only => options[:only], :except => options[:except] do
+ verify_action options
end
end
end
diff --git a/actionpack/lib/action_controller/middleware.rb b/actionpack/lib/action_controller/middleware.rb
index fac0ed2645..17275793b7 100644
--- a/actionpack/lib/action_controller/middleware.rb
+++ b/actionpack/lib/action_controller/middleware.rb
@@ -1,34 +1,34 @@
module ActionController
class Middleware < Metal
class ActionMiddleware
- def initialize(controller)
- @controller = controller
+ def initialize(controller, app)
+ @controller, @app = controller, app
end
def call(env)
- controller = @controller.allocate
- controller.send(:initialize)
- controller.app = @app
- controller._call(env)
+ @controller.build(@app).dispatch(:index, env)
end
+ end
+
+ class << self
+ alias build new
- def app=(app)
- @app = app
+ def new(app)
+ ActionMiddleware.new(self, app)
end
end
-
- def self.new(app)
- middleware = ActionMiddleware.new(self)
- middleware.app = app
- middleware
+
+ attr_internal :app
+
+ def process(action)
+ response = super
+ self.status, self.headers, self.response_body = response if response.is_a?(Array)
+ response
end
-
- def _call(env)
- @_env = env
- @_request = ActionDispatch::Request.new(env)
- @_response = ActionDispatch::Response.new
- @_response.request = @_request
- process(:index)
+
+ def initialize(app)
+ super()
+ @_app = app
end
def index
diff --git a/actionpack/lib/action_controller/notifications.rb b/actionpack/lib/action_controller/notifications.rb
new file mode 100644
index 0000000000..1a4f29e0e2
--- /dev/null
+++ b/actionpack/lib/action_controller/notifications.rb
@@ -0,0 +1,10 @@
+require 'active_support/notifications'
+
+ActiveSupport::Notifications.subscribe(/(read|write|cache|expire|exist)_(fragment|page)\??/) do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+
+ if logger = ActionController::Base.logger
+ human_name = event.name.to_s.humanize
+ logger.info("#{human_name} (%.1fms)" % event.duration)
+ end
+end
diff --git a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb
index 2adf3575a7..eaed00cfb7 100644
--- a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb
+++ b/actionpack/lib/action_controller/polymorphic_routes.rb
@@ -80,9 +80,8 @@ module ActionController
record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
end
- record = extract_record(record_or_hash_or_array)
- record = record.to_model if record.respond_to?(:to_model)
- namespace = extract_namespace(record_or_hash_or_array)
+ record = extract_record(record_or_hash_or_array)
+ record = record.to_model if record.respond_to?(:to_model)
args = case record_or_hash_or_array
when Hash; [ record_or_hash_or_array ]
@@ -105,8 +104,7 @@ module ActionController
end
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
-
- named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
+ named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
url_options = options.except(:action, :routing_type)
unless url_options.empty?
@@ -138,18 +136,6 @@ module ActionController
EOT
end
- def formatted_polymorphic_url(record_or_hash, options = {})
- ActiveSupport::Deprecation.warn("formatted_polymorphic_url has been deprecated. Please pass :format to the polymorphic_url method instead", caller)
- options[:format] = record_or_hash.pop if Array === record_or_hash
- polymorphic_url(record_or_hash, options)
- end
-
- def formatted_polymorphic_path(record_or_hash, options = {})
- ActiveSupport::Deprecation.warn("formatted_polymorphic_path has been deprecated. Please pass :format to the polymorphic_path method instead", caller)
- options[:format] = record_or_hash.pop if record_or_hash === Array
- polymorphic_url(record_or_hash, options.merge(:routing_type => :path))
- end
-
private
def action_prefix(options)
options[:action] ? "#{options[:action]}_" : ''
@@ -159,7 +145,7 @@ module ActionController
options[:routing_type] || :url
end
- def build_named_route_call(records, namespace, inflection, options = {})
+ def build_named_route_call(records, inflection, options = {})
unless records.is_a?(Array)
record = extract_record(records)
route = ''
@@ -169,7 +155,7 @@ module ActionController
if parent.is_a?(Symbol) || parent.is_a?(String)
string << "#{parent}_"
else
- string << "#{RecordIdentifier.__send__("plural_class_name", parent)}".singularize
+ string << RecordIdentifier.__send__("plural_class_name", parent).singularize
string << "_"
end
end
@@ -178,12 +164,12 @@ module ActionController
if record.is_a?(Symbol) || record.is_a?(String)
route << "#{record}_"
else
- route << "#{RecordIdentifier.__send__("plural_class_name", record)}"
+ route << RecordIdentifier.__send__("plural_class_name", record)
route = route.singularize if inflection == :singular
route << "_"
end
- action_prefix(options) + namespace + route + routing_type(options).to_s
+ action_prefix(options) + route + routing_type(options).to_s
end
def extract_record(record_or_hash_or_array)
@@ -193,18 +179,5 @@ module ActionController
else record_or_hash_or_array
end
end
-
- # Remove the first symbols from the array and return the url prefix
- # implied by those symbols.
- def extract_namespace(record_or_hash_or_array)
- return "" unless record_or_hash_or_array.is_a?(Array)
-
- namespace_keys = []
- while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol)
- namespace_keys << record_or_hash_or_array.shift
- end
-
- namespace_keys.map {|k| "#{k}_"}.join
- end
end
end
diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb
deleted file mode 100644
index 42ad12e1ea..0000000000
--- a/actionpack/lib/action_controller/routing/builder.rb
+++ /dev/null
@@ -1,199 +0,0 @@
-require 'active_support/core_ext/hash/except'
-
-module ActionController
- module Routing
- class RouteBuilder #:nodoc:
- attr_reader :separators, :optional_separators
- attr_reader :separator_regexp, :nonseparator_regexp, :interval_regexp
-
- def initialize
- @separators = Routing::SEPARATORS
- @optional_separators = %w( / )
-
- @separator_regexp = /[#{Regexp.escape(separators.join)}]/
- @nonseparator_regexp = /\A([^#{Regexp.escape(separators.join)}]+)/
- @interval_regexp = /(.*?)(#{separator_regexp}|$)/
- end
-
- # Accepts a "route path" (a string defining a route), and returns the array
- # of segments that corresponds to it. Note that the segment array is only
- # partially initialized--the defaults and requirements, for instance, need
- # to be set separately, via the +assign_route_options+ method, and the
- # <tt>optional?</tt> method for each segment will not be reliable until after
- # +assign_route_options+ is called, as well.
- def segments_for_route_path(path)
- rest, segments = path, []
-
- until rest.empty?
- segment, rest = segment_for(rest)
- segments << segment
- end
- segments
- end
-
- # A factory method that returns a new segment instance appropriate for the
- # format of the given string.
- def segment_for(string)
- segment =
- case string
- when /\A\.(:format)?\//
- OptionalFormatSegment.new
- when /\A:(\w+)/
- key = $1.to_sym
- key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key)
- when /\A\*(\w+)/
- PathSegment.new($1.to_sym, :optional => true)
- when /\A\?(.*?)\?/
- StaticSegment.new($1, :optional => true)
- when nonseparator_regexp
- StaticSegment.new($1)
- when separator_regexp
- DividerSegment.new($&, :optional => optional_separators.include?($&))
- end
- [segment, $~.post_match]
- end
-
- # Split the given hash of options into requirement and default hashes. The
- # segments are passed alongside in order to distinguish between default values
- # and requirements.
- def divide_route_options(segments, options)
- options = options.except(:path_prefix, :name_prefix)
-
- if options[:namespace]
- options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
- end
-
- requirements = (options.delete(:requirements) || {}).dup
- defaults = (options.delete(:defaults) || {}).dup
- conditions = (options.delete(:conditions) || {}).dup
-
- validate_route_conditions(conditions)
-
- path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
- options.each do |key, value|
- hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
- hash[key] = value
- end
-
- [defaults, requirements, conditions]
- end
-
- # Takes a hash of defaults and a hash of requirements, and assigns them to
- # the segments. Any unused requirements (which do not correspond to a segment)
- # are returned as a hash.
- def assign_route_options(segments, defaults, requirements)
- route_requirements = {} # Requirements that do not belong to a segment
-
- segment_named = Proc.new do |key|
- segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
- end
-
- requirements.each do |key, requirement|
- segment = segment_named[key]
- if segment
- raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp)
- if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
- end
- if requirement.multiline?
- raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
- end
- segment.regexp = requirement
- else
- route_requirements[key] = requirement
- end
- end
-
- defaults.each do |key, default|
- segment = segment_named[key]
- raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
- segment.is_optional = true
- segment.default = default.to_param if default
- end
-
- assign_default_route_options(segments)
- ensure_required_segments(segments)
- route_requirements
- end
-
- # Assign default options, such as 'index' as a default for <tt>:action</tt>. This
- # method must be run *after* user supplied requirements and defaults have
- # been applied to the segments.
- def assign_default_route_options(segments)
- segments.each do |segment|
- next unless segment.is_a? DynamicSegment
- case segment.key
- when :action
- if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index'
- segment.default ||= 'index'
- segment.is_optional = true
- end
- when :id
- if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ ''
- segment.is_optional = true
- end
- end
- end
- end
-
- # Makes sure that there are no optional segments that precede a required
- # segment. If any are found that precede a required segment, they are
- # made required.
- def ensure_required_segments(segments)
- allow_optional = true
- segments.reverse_each do |segment|
- allow_optional &&= segment.optional?
- if !allow_optional && segment.optional?
- unless segment.optionality_implied?
- warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
- end
- segment.is_optional = false
- elsif allow_optional && segment.respond_to?(:default) && segment.default
- # if a segment has a default, then it is optional
- segment.is_optional = true
- end
- end
- end
-
- # Construct and return a route with the given path and options.
- def build(path, options)
- # Wrap the path with slashes
- path = "/#{path}" unless path[0] == ?/
- path = "#{path}/" unless path[-1] == ?/
-
- prefix = options[:path_prefix].to_s.gsub(/^\//,'')
- path = "/#{prefix}#{path}" unless prefix.blank?
-
- segments = segments_for_route_path(path)
- defaults, requirements, conditions = divide_route_options(segments, options)
- requirements = assign_route_options(segments, defaults, requirements)
-
- # TODO: Segments should be frozen on initialize
- segments.each { |segment| segment.freeze }
-
- route = Route.new(segments, requirements, conditions)
-
- if !route.significant_keys.include?(:controller)
- raise ArgumentError, "Illegal route: the :controller must be specified!"
- end
-
- route.freeze
- end
-
- private
- def validate_route_conditions(conditions)
- if method = conditions[:method]
- [method].flatten.each do |m|
- if m == :head
- raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
- end
-
- unless HTTP_METHODS.include?(m.to_sym)
- raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
- end
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb
deleted file mode 100644
index 714cf97861..0000000000
--- a/actionpack/lib/action_controller/routing/optimisations.rb
+++ /dev/null
@@ -1,130 +0,0 @@
-module ActionController
- module Routing
- # Much of the slow performance from routes comes from the
- # complexity of expiry, <tt>:requirements</tt> matching, defaults providing
- # and figuring out which url pattern to use. With named routes
- # we can avoid the expense of finding the right route. So if
- # they've provided the right number of arguments, and have no
- # <tt>:requirements</tt>, we can just build up a string and return it.
- #
- # To support building optimisations for other common cases, the
- # generation code is separated into several classes
- module Optimisation
- def generate_optimisation_block(route, kind)
- return "" unless route.optimise?
- OPTIMISERS.inject("") do |memo, klazz|
- memo << klazz.new(route, kind).source_code
- memo
- end
- end
-
- class Optimiser
- attr_reader :route, :kind
- GLOBAL_GUARD_CONDITIONS = [
- "(!defined?(default_url_options) || default_url_options.blank?)",
- "(!defined?(controller.default_url_options) || controller.default_url_options.blank?)",
- "defined?(request)",
- "request"
- ]
-
- def initialize(route, kind)
- @route = route
- @kind = kind
- end
-
- def guard_conditions
- ["false"]
- end
-
- def generation_code
- 'nil'
- end
-
- def source_code
- if applicable?
- guard_condition = (GLOBAL_GUARD_CONDITIONS + guard_conditions).join(" && ")
- "return #{generation_code} if #{guard_condition}\n"
- else
- "\n"
- end
- end
-
- # Temporarily disabled <tt>:url</tt> optimisation pending proper solution to
- # Issues around request.host etc.
- def applicable?
- true
- end
- end
-
- # Given a route
- #
- # map.person '/people/:id'
- #
- # If the user calls <tt>person_url(@person)</tt>, we can simply
- # return a string like "/people/#{@person.to_param}"
- # rather than triggering the expensive logic in +url_for+.
- class PositionalArguments < Optimiser
- def guard_conditions
- number_of_arguments = route.required_segment_keys.size
- # if they're using foo_url(:id=>2) it's one
- # argument, but we don't want to generate /foos/id2
- if number_of_arguments == 1
- ["args.size == 1", "!args.first.is_a?(Hash)"]
- else
- ["args.size == #{number_of_arguments}"]
- end
- end
-
- def generation_code
- elements = []
- idx = 0
-
- if kind == :url
- elements << '#{request.protocol}'
- elements << '#{request.host_with_port}'
- end
-
- elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}'
-
- # The last entry in <tt>route.segments</tt> appears to *always* be a
- # 'divider segment' for '/' but we have assertions to ensure that
- # we don't include the trailing slashes, so skip them.
- (route.segments.size == 1 ? route.segments : route.segments[0..-2]).each do |segment|
- if segment.is_a?(DynamicSegment)
- elements << segment.interpolation_chunk("args[#{idx}].to_param")
- idx += 1
- else
- elements << segment.interpolation_chunk
- end
- end
- %("#{elements * ''}")
- end
- end
-
- # This case is mostly the same as the positional arguments case
- # above, but it supports additional query parameters as the last
- # argument
- class PositionalArgumentsWithAdditionalParams < PositionalArguments
- def guard_conditions
- ["args.size == #{route.segment_keys.size + 1}"] +
- UrlRewriter::RESERVED_OPTIONS.collect{ |key| "!args.last.has_key?(:#{key})" }
- end
-
- # This case uses almost the same code as positional arguments,
- # but add a question mark and args.last.to_query on the end,
- # unless the last arg is empty
- def generation_code
- super.insert(-2, '#{\'?\' + args.last.to_query unless args.last.empty?}')
- end
-
- # To avoid generating "http://localhost/?host=foo.example.com" we
- # can't use this optimisation on routes without any segments
- def applicable?
- super && route.segment_keys.size > 0
- end
- end
-
- OPTIMISERS = [PositionalArguments, PositionalArgumentsWithAdditionalParams]
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/actionpack/lib/action_controller/routing/recognition_optimisation.rb
deleted file mode 100644
index 9bfebff0c0..0000000000
--- a/actionpack/lib/action_controller/routing/recognition_optimisation.rb
+++ /dev/null
@@ -1,167 +0,0 @@
-module ActionController
- module Routing
- # BEFORE: 0.191446860631307 ms/url
- # AFTER: 0.029847304022858 ms/url
- # Speed up: 6.4 times
- #
- # Route recognition is slow due to one-by-one iterating over
- # a whole routeset (each map.resources generates at least 14 routes)
- # and matching weird regexps on each step.
- #
- # We optimize this by skipping all URI segments that 100% sure can't
- # be matched, moving deeper in a tree of routes (where node == segment)
- # until first possible match is accured. In such case, we start walking
- # a flat list of routes, matching them with accurate matcher.
- # So, first step: search a segment tree for the first relevant index.
- # Second step: iterate routes starting with that index.
- #
- # How tree is walked? We can do a recursive tests, but it's smarter:
- # We just create a tree of if-s and elsif-s matching segments.
- #
- # We have segments of 3 flavors:
- # 1) nil (no segment, route finished)
- # 2) const-dot-dynamic (like "/posts.:xml", "/preview.:size.jpg")
- # 3) const (like "/posts", "/comments")
- # 4) dynamic ("/:id", "file.:size.:extension")
- #
- # We split incoming string into segments and iterate over them.
- # When segment is nil, we drop immediately, on a current node index.
- # When segment is equal to some const, we step into branch.
- # If none constants matched, we step into 'dynamic' branch (it's a last).
- # If we can't match anything, we drop to last index on a level.
- #
- # Note: we maintain the original routes order, so we finish building
- # steps on a first dynamic segment.
- #
- #
- # Example. Given the routes:
- # 0 /posts/
- # 1 /posts/:id
- # 2 /posts/:id/comments
- # 3 /posts/blah
- # 4 /users/
- # 5 /users/:id
- # 6 /users/:id/profile
- #
- # request_uri = /users/123
- #
- # There will be only 4 iterations:
- # 1) segm test for /posts prefix, skip all /posts/* routes
- # 2) segm test for /users/
- # 3) segm test for /users/:id
- # (jump to list index = 5)
- # 4) full test for /users/:id => here we are!
- class RouteSet
- def recognize_path(path, environment={})
- result = recognize_optimized(path, environment) and return result
-
- # Route was not recognized. Try to find out why (maybe wrong verb).
- allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, environment.merge(:method => verb)) } }
-
- if environment[:method] && !HTTP_METHODS.include?(environment[:method])
- raise NotImplemented.new(*allows)
- elsif !allows.empty?
- raise MethodNotAllowed.new(*allows)
- else
- raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
- end
- end
-
- def segment_tree(routes)
- tree = [0]
-
- i = -1
- routes.each do |route|
- i += 1
- # not fast, but runs only once
- segments = to_plain_segments(route.segments.inject("") { |str,s| str << s.to_s })
-
- node = tree
- segments.each do |seg|
- seg = :dynamic if seg && seg[0] == ?:
- node << [seg, [i]] if node.empty? || node[node.size - 1][0] != seg
- node = node[node.size - 1][1]
- end
- end
- tree
- end
-
- def generate_code(list, padding=' ', level = 0)
- # a digit
- return padding + "#{list[0]}\n" if list.size == 1 && !(Array === list[0])
-
- body = padding + "(seg = segments[#{level}]; \n"
-
- i = 0
- was_nil = false
- list.each do |item|
- if Array === item
- i += 1
- start = (i == 1)
- tag, sub = item
- if tag == :dynamic
- body += padding + "#{start ? 'if' : 'elsif'} true\n"
- body += generate_code(sub, padding + " ", level + 1)
- break
- elsif tag == nil && !was_nil
- was_nil = true
- body += padding + "#{start ? 'if' : 'elsif'} seg.nil?\n"
- body += generate_code(sub, padding + " ", level + 1)
- else
- body += padding + "#{start ? 'if' : 'elsif'} seg == '#{tag}'\n"
- body += generate_code(sub, padding + " ", level + 1)
- end
- end
- end
- body += padding + "else\n"
- body += padding + " #{list[0]}\n"
- body += padding + "end)\n"
- body
- end
-
- # this must be really fast
- def to_plain_segments(str)
- str = str.dup
- str.sub!(/^\/+/,'')
- str.sub!(/\/+$/,'')
- segments = str.split(/\.[^\/]+\/+|\/+|\.[^\/]+\Z/) # cut off ".format" also
- segments << nil
- segments
- end
-
- private
- def write_recognize_optimized!
- tree = segment_tree(routes)
- body = generate_code(tree)
-
- remove_recognize_optimized!
-
- instance_eval %{
- def recognize_optimized(path, env)
- segments = to_plain_segments(path)
- index = #{body}
- return nil unless index
- while index < routes.size
- result = routes[index].recognize(path, env) and return result
- index += 1
- end
- nil
- end
- }, '(recognize_optimized)', 1
- end
-
- def clear_recognize_optimized!
- remove_recognize_optimized!
- write_recognize_optimized!
- end
-
- def remove_recognize_optimized!
- if respond_to?(:recognize_optimized)
- class << self
- remove_method :recognize_optimized
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/resources.rb b/actionpack/lib/action_controller/routing/resources.rb
deleted file mode 100644
index 06506435a2..0000000000
--- a/actionpack/lib/action_controller/routing/resources.rb
+++ /dev/null
@@ -1,685 +0,0 @@
-require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/object/try'
-
-module ActionController
- # == Overview
- #
- # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
- # is something that can be pointed at and it will respond with a representation of the data requested.
- # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
- # requests XML data.
- #
- # RESTful design is based on the assumption that there are four generic verbs that a user of an
- # application can request from a \resource (the noun).
- #
- # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
- # denotes the type of action that should take place.
- #
- # === The Different Methods and their Usage
- #
- # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
- # * POST - Creation of \resources.
- # * PUT - Editing of attributes on a \resource.
- # * DELETE - Deletion of a \resource.
- #
- # === Examples
- #
- # # A GET request on the Posts resource is asking for all Posts
- # GET /posts
- #
- # # A GET request on a single Post resource is asking for that particular Post
- # GET /posts/1
- #
- # # A POST request on the Posts resource is asking for a Post to be created with the supplied details
- # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
- #
- # # A PUT request on a single Post resource is asking for a Post to be updated
- # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
- #
- # # A DELETE request on a single Post resource is asking for it to be deleted
- # DELETE /posts # with => { :id => 1 }
- #
- # By using the REST convention, users of our application can assume certain things about how the data
- # is requested and how it is returned. Rails simplifies the routing part of RESTful design by
- # supplying you with methods to create them in your routes.rb file.
- #
- # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
- module Resources
- INHERITABLE_OPTIONS = :namespace, :shallow
-
- class Resource #:nodoc:
- DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
-
- attr_reader :collection_methods, :member_methods, :new_methods
- attr_reader :path_prefix, :name_prefix, :path_segment
- attr_reader :plural, :singular
- attr_reader :options
-
- def initialize(entities, options)
- @plural ||= entities
- @singular ||= options[:singular] || plural.to_s.singularize
- @path_segment = options.delete(:as) || @plural
-
- @options = options
-
- arrange_actions
- add_default_actions
- set_allowed_actions
- set_prefixes
- end
-
- def controller
- @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
- end
-
- def requirements(with_id = false)
- @requirements ||= @options[:requirements] || {}
- @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
-
- with_id ? @requirements.merge(@id_requirement) : @requirements
- end
-
- def conditions
- @conditions ||= @options[:conditions] || {}
- end
-
- def path
- @path ||= "#{path_prefix}/#{path_segment}"
- end
-
- def new_path
- new_action = self.options[:path_names][:new] if self.options[:path_names]
- new_action ||= Base.resources_path_names[:new]
- @new_path ||= "#{path}/#{new_action}"
- end
-
- def shallow_path_prefix
- @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
- end
-
- def member_path
- @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
- end
-
- def nesting_path_prefix
- @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
- end
-
- def shallow_name_prefix
- @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
- end
-
- def nesting_name_prefix
- "#{shallow_name_prefix}#{singular}_"
- end
-
- def action_separator
- @action_separator ||= Base.resource_action_separator
- end
-
- def uncountable?
- @singular.to_s == @plural.to_s
- end
-
- def has_action?(action)
- !DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
- end
-
- protected
- def arrange_actions
- @collection_methods = arrange_actions_by_methods(options.delete(:collection))
- @member_methods = arrange_actions_by_methods(options.delete(:member))
- @new_methods = arrange_actions_by_methods(options.delete(:new))
- end
-
- def add_default_actions
- add_default_action(member_methods, :get, :edit)
- add_default_action(new_methods, :get, :new)
- end
-
- def set_allowed_actions
- only, except = @options.values_at(:only, :except)
- @allowed_actions ||= {}
-
- if only == :all || except == :none
- only = nil
- except = []
- elsif only == :none || except == :all
- only = []
- except = nil
- end
-
- if only
- @allowed_actions[:only] = Array(only).map {|a| a.to_sym }
- elsif except
- @allowed_actions[:except] = Array(except).map {|a| a.to_sym }
- end
- end
-
- def action_allowed?(action)
- only, except = @allowed_actions.values_at(:only, :except)
- (!only || only.include?(action)) && (!except || !except.include?(action))
- end
-
- def set_prefixes
- @path_prefix = options.delete(:path_prefix)
- @name_prefix = options.delete(:name_prefix)
- end
-
- def arrange_actions_by_methods(actions)
- (actions || {}).inject({}) do |flipped_hash, (key, value)|
- (flipped_hash[value] ||= []) << key
- flipped_hash
- end
- end
-
- def add_default_action(collection, method, action)
- (collection[method] ||= []).unshift(action)
- end
- end
-
- class SingletonResource < Resource #:nodoc:
- def initialize(entity, options)
- @singular = @plural = entity
- options[:controller] ||= @singular.to_s.pluralize
- super
- end
-
- alias_method :shallow_path_prefix, :path_prefix
- alias_method :shallow_name_prefix, :name_prefix
- alias_method :member_path, :path
- alias_method :nesting_path_prefix, :path
- end
-
- # Creates named routes for implementing verb-oriented controllers
- # for a collection \resource.
- #
- # For example:
- #
- # map.resources :messages
- #
- # will map the following actions in the corresponding controller:
- #
- # class MessagesController < ActionController::Base
- # # GET messages_url
- # def index
- # # return all messages
- # end
- #
- # # GET new_message_url
- # def new
- # # return an HTML form for describing a new message
- # end
- #
- # # POST messages_url
- # def create
- # # create a new message
- # end
- #
- # # GET message_url(:id => 1)
- # def show
- # # find and return a specific message
- # end
- #
- # # GET edit_message_url(:id => 1)
- # def edit
- # # return an HTML form for editing a specific message
- # end
- #
- # # PUT message_url(:id => 1)
- # def update
- # # find and update a specific message
- # end
- #
- # # DELETE message_url(:id => 1)
- # def destroy
- # # delete a specific message
- # end
- # end
- #
- # Along with the routes themselves, +resources+ generates named routes for use in
- # controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers:
- #
- # Named Route Helpers
- # ============ =====================================================
- # messages messages_url, hash_for_messages_url,
- # messages_path, hash_for_messages_path
- #
- # message message_url(id), hash_for_message_url(id),
- # message_path(id), hash_for_message_path(id)
- #
- # new_message new_message_url, hash_for_new_message_url,
- # new_message_path, hash_for_new_message_path
- #
- # edit_message edit_message_url(id), hash_for_edit_message_url(id),
- # edit_message_path(id), hash_for_edit_message_path(id)
- #
- # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
- #
- # redirect_to :controller => 'messages', :action => 'index'
- # # and
- # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
- #
- # now become:
- #
- # redirect_to messages_url
- # # and
- # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
- #
- # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
- # form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object:
- #
- # <%= form_tag message_path(@message), :method => :put %>
- #
- # or
- #
- # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
- #
- # or
- #
- # <% form_for @message do |f| %>
- #
- # which takes into account whether <tt>@message</tt> is a new record or not and generates the
- # path and method accordingly.
- #
- # The +resources+ method accepts the following options to customize the resulting routes:
- # * <tt>:collection</tt> - Add named routes for other actions that operate on the collection.
- # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>,
- # an array of any of the previous, or <tt>:any</tt> if the method does not matter.
- # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
- # * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member.
- # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
- # * <tt>:controller</tt> - Specify the controller name for the routes.
- # * <tt>:singular</tt> - Specify the singular name used in the member routes.
- # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either
- # regular expressions (which must match for the route to match) or extra parameters. For example:
- #
- # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
- #
- # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
- # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
- # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
- # # products_path == '/productos'
- # map.resources :products, :as => 'productos' do |product|
- # # product_reviews_path(product) == '/productos/1234/comentarios'
- # product.resources :product_reviews, :as => 'comentarios'
- # end
- #
- # * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
- # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources.
- #
- # You may directly specify the routing association with +has_one+ and +has_many+ like:
- #
- # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
- #
- # This is the same as:
- #
- # map.resources :notes do |notes|
- # notes.resource :author
- # notes.resources :comments
- # notes.resources :attachments
- # end
- #
- # * <tt>:path_names</tt> - Specify different path names for the actions. For example:
- # # new_products_path == '/productos/nuevo'
- # # bids_product_path(1) == '/productos/1/licitacoes'
- # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
- #
- # You can also set default action names from an environment, like this:
- # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
- #
- # * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables.
- #
- # Weblog comments usually belong to a post, so you might use +resources+ like:
- #
- # map.resources :articles
- # map.resources :comments, :path_prefix => '/articles/:article_id'
- #
- # You can nest +resources+ calls to set this automatically:
- #
- # map.resources :articles do |article|
- # article.resources :comments
- # end
- #
- # The comment \resources work the same, but must now include a value for <tt>:article_id</tt>.
- #
- # article_comments_url(@article)
- # article_comment_url(@article, @comment)
- #
- # article_comments_url(:article_id => @article)
- # article_comment_url(:article_id => @article, :id => @comment)
- #
- # If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly:
- #
- # articles_comments_url(@comment.article_id, @comment)
- #
- # * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore.
- # Use this if you have named routes that may clash.
- #
- # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
- # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
- #
- # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource:
- #
- # map.resources :articles do |article|
- # article.resources :comments, :name_prefix => nil
- # end
- #
- # This will yield named \resources like so:
- #
- # comments_url(@article)
- # comment_url(@article, @comment)
- #
- # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member
- # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
- #
- # The <tt>:shallow</tt> option is inherited by any nested resource(s).
- #
- # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
- #
- # map.resources :users, :shallow => true do |user|
- # user.resources :posts do |post|
- # post.resources :comments
- # end
- # end
- # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
- # # also adds the usual named route called "user_posts"
- # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
- # # also adds the named route called "post"
- # # --> GET /posts/2/comments (maps to the CommentsController#index action)
- # # also adds the named route called "post_comments"
- # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
- # # also adds the named route called "comment"
- #
- # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like:
- #
- # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
- #
- # * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to.
- #
- # <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a
- # list of action names. By default, routes are generated for all seven actions.
- #
- # For example:
- #
- # map.resources :posts, :only => [:index, :show] do |post|
- # post.resources :comments, :except => [:update, :destroy]
- # end
- # # --> GET /posts (maps to the PostsController#index action)
- # # --> POST /posts (fails)
- # # --> GET /posts/1 (maps to the PostsController#show action)
- # # --> DELETE /posts/1 (fails)
- # # --> POST /posts/1/comments (maps to the CommentsController#create action)
- # # --> PUT /posts/1/comments/1 (fails)
- #
- # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
- #
- # Examples:
- #
- # map.resources :messages, :path_prefix => "/thread/:thread_id"
- # # --> GET /thread/7/messages/1
- #
- # map.resources :messages, :collection => { :rss => :get }
- # # --> GET /messages/rss (maps to the #rss action)
- # # also adds a named route called "rss_messages"
- #
- # map.resources :messages, :member => { :mark => :post }
- # # --> POST /messages/1/mark (maps to the #mark action)
- # # also adds a named route called "mark_message"
- #
- # map.resources :messages, :new => { :preview => :post }
- # # --> POST /messages/new/preview (maps to the #preview action)
- # # also adds a named route called "preview_new_message"
- #
- # map.resources :messages, :new => { :new => :any, :preview => :post }
- # # --> POST /messages/new/preview (maps to the #preview action)
- # # also adds a named route called "preview_new_message"
- # # --> /messages/new can be invoked via any request method
- #
- # map.resources :messages, :controller => "categories",
- # :path_prefix => "/category/:category_id",
- # :name_prefix => "category_"
- # # --> GET /categories/7/messages/1
- # # has named route "category_message"
- #
- # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
- # HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
- # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes.
- def resources(*entities, &block)
- options = entities.extract_options!
- entities.each { |entity| map_resource(entity, options.dup, &block) }
- end
-
- # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
- # A singleton \resource is global to its current context. For unnested singleton \resources,
- # the \resource is global to the current user visiting the application, such as a user's
- # <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent
- # \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>.
- # The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>:
- #
- # map.resources :projects do |project|
- # project.resource :project_manager
- # end
- #
- # See +resources+ for general conventions. These are the main differences:
- # * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name.
- # * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option.
- # * No default index route is created for the singleton \resource controller.
- # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
- #
- # For example:
- #
- # map.resource :account
- #
- # maps these actions in the Accounts controller:
- #
- # class AccountsController < ActionController::Base
- # # GET new_account_url
- # def new
- # # return an HTML form for describing the new account
- # end
- #
- # # POST account_url
- # def create
- # # create an account
- # end
- #
- # # GET account_url
- # def show
- # # find and return the account
- # end
- #
- # # GET edit_account_url
- # def edit
- # # return an HTML form for editing the account
- # end
- #
- # # PUT account_url
- # def update
- # # find and update the account
- # end
- #
- # # DELETE account_url
- # def destroy
- # # delete the account
- # end
- # end
- #
- # Along with the routes themselves, +resource+ generates named routes for
- # use in controllers and views. <tt>map.resource :account</tt> produces
- # these named routes and helpers:
- #
- # Named Route Helpers
- # ============ =============================================
- # account account_url, hash_for_account_url,
- # account_path, hash_for_account_path
- #
- # new_account new_account_url, hash_for_new_account_url,
- # new_account_path, hash_for_new_account_path
- #
- # edit_account edit_account_url, hash_for_edit_account_url,
- # edit_account_path, hash_for_edit_account_path
- def resource(*entities, &block)
- options = entities.extract_options!
- entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
- end
-
- private
- def map_resource(entities, options = {}, &block)
- resource = Resource.new(entities, options)
-
- with_options :controller => resource.controller do |map|
- map_associations(resource, options)
-
- if block_given?
- with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
- end
-
- map_collection_actions(map, resource)
- map_default_collection_actions(map, resource)
- map_new_actions(map, resource)
- map_member_actions(map, resource)
- end
- end
-
- def map_singleton_resource(entities, options = {}, &block)
- resource = SingletonResource.new(entities, options)
-
- with_options :controller => resource.controller do |map|
- map_associations(resource, options)
-
- if block_given?
- with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
- end
-
- map_collection_actions(map, resource)
- map_new_actions(map, resource)
- map_member_actions(map, resource)
- map_default_singleton_actions(map, resource)
- end
- end
-
- def map_associations(resource, options)
- map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
-
- path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
- name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
-
- Array(options[:has_one]).each do |association|
- resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
- end
- end
-
- def map_has_many_associations(resource, associations, options)
- case associations
- when Hash
- associations.each do |association,has_many|
- map_has_many_associations(resource, association, options.merge(:has_many => has_many))
- end
- when Array
- associations.each do |association|
- map_has_many_associations(resource, association, options)
- end
- when Symbol, String
- resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
- else
- end
- end
-
- def map_collection_actions(map, resource)
- resource.collection_methods.each do |method, actions|
- actions.each do |action|
- [method].flatten.each do |m|
- action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
- action_path ||= action
-
- map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
- end
- end
- end
- end
-
- def map_default_collection_actions(map, resource)
- index_route_name = "#{resource.name_prefix}#{resource.plural}"
-
- if resource.uncountable?
- index_route_name << "_index"
- end
-
- map_resource_routes(map, resource, :index, resource.path, index_route_name)
- map_resource_routes(map, resource, :create, resource.path, index_route_name)
- end
-
- def map_default_singleton_actions(map, resource)
- map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}")
- end
-
- def map_new_actions(map, resource)
- resource.new_methods.each do |method, actions|
- actions.each do |action|
- route_path = resource.new_path
- route_name = "new_#{resource.name_prefix}#{resource.singular}"
-
- unless action == :new
- route_path = "#{route_path}#{resource.action_separator}#{action}"
- route_name = "#{action}_#{route_name}"
- end
-
- map_resource_routes(map, resource, action, route_path, route_name, method)
- end
- end
- end
-
- def map_member_actions(map, resource)
- resource.member_methods.each do |method, actions|
- actions.each do |action|
- [method].flatten.each do |m|
- action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
- action_path ||= Base.resources_path_names[action] || action
-
- map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
- end
- end
- end
-
- route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
- map_resource_routes(map, resource, :show, resource.member_path, route_path)
- map_resource_routes(map, resource, :update, resource.member_path, route_path)
- map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
- end
-
- def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
- if resource.has_action?(action)
- action_options = action_options_for(action, resource, method, resource_options)
- formatted_route_path = "#{route_path}.:format"
-
- if route_name && @set.named_routes[route_name.to_sym].nil?
- map.named_route(route_name, formatted_route_path, action_options)
- else
- map.connect(formatted_route_path, action_options)
- end
- end
- end
-
- def add_conditions_for(conditions, method)
- returning({:conditions => conditions.dup}) do |options|
- options[:conditions][:method] = method unless method == :any
- end
- end
-
- def action_options_for(action, resource, method = nil, resource_options = {})
- default_options = { :action => action.to_s }
- require_id = !resource.kind_of?(SingletonResource)
- force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
-
- case default_options[:action]
- when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
- when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
- when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
- when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
- when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
- else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/route.rb b/actionpack/lib/action_controller/routing/route.rb
deleted file mode 100644
index eba05a3c5a..0000000000
--- a/actionpack/lib/action_controller/routing/route.rb
+++ /dev/null
@@ -1,267 +0,0 @@
-require 'active_support/core_ext/object/misc'
-
-module ActionController
- module Routing
- class Route #:nodoc:
- attr_accessor :segments, :requirements, :conditions, :optimise
-
- def initialize(segments = [], requirements = {}, conditions = {})
- @segments = segments
- @requirements = requirements
- @conditions = conditions
-
- if !significant_keys.include?(:action) && !requirements[:action]
- @requirements[:action] = "index"
- @significant_keys << :action
- end
-
- # Routes cannot use the current string interpolation method
- # if there are user-supplied <tt>:requirements</tt> as the interpolation
- # code won't raise RoutingErrors when generating
- has_requirements = @segments.detect { |segment| segment.respond_to?(:regexp) && segment.regexp }
- if has_requirements || @requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
- @optimise = false
- else
- @optimise = true
- end
- end
-
- # Indicates whether the routes should be optimised with the string interpolation
- # version of the named routes methods.
- def optimise?
- @optimise && ActionController::Base::optimise_named_routes
- end
-
- def segment_keys
- segments.collect do |segment|
- segment.key if segment.respond_to? :key
- end.compact
- end
-
- def required_segment_keys
- required_segments = segments.select {|seg| (!seg.optional? && !seg.is_a?(DividerSegment)) || seg.is_a?(PathSegment) }
- required_segments.collect { |seg| seg.key if seg.respond_to?(:key)}.compact
- end
-
- # Build a query string from the keys of the given hash. If +only_keys+
- # is given (as an array), only the keys indicated will be used to build
- # the query string. The query string will correctly build array parameter
- # values.
- def build_query_string(hash, only_keys = nil)
- elements = []
-
- (only_keys || hash.keys).each do |key|
- if value = hash[key]
- elements << value.to_query(key)
- end
- end
-
- elements.empty? ? '' : "?#{elements.sort * '&'}"
- end
-
- # A route's parameter shell contains parameter values that are not in the
- # route's path, but should be placed in the recognized hash.
- #
- # For example, +{:controller => 'pages', :action => 'show'} is the shell for the route:
- #
- # map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
- #
- def parameter_shell
- @parameter_shell ||= {}.tap do |shell|
- requirements.each do |key, requirement|
- shell[key] = requirement unless requirement.is_a? Regexp
- end
- end
- end
-
- # Return an array containing all the keys that are used in this route. This
- # includes keys that appear inside the path, and keys that have requirements
- # placed upon them.
- def significant_keys
- @significant_keys ||= [].tap do |sk|
- segments.each { |segment| sk << segment.key if segment.respond_to? :key }
- sk.concat requirements.keys
- sk.uniq!
- end
- end
-
- # Return a hash of key/value pairs representing the keys in the route that
- # have defaults, or which are specified by non-regexp requirements.
- def defaults
- @defaults ||= {}.tap do |hash|
- segments.each do |segment|
- next unless segment.respond_to? :default
- hash[segment.key] = segment.default unless segment.default.nil?
- end
- requirements.each do |key,req|
- next if Regexp === req || req.nil?
- hash[key] = req
- end
- end
- end
-
- def matches_controller_and_action?(controller, action)
- prepare_matching!
- (@controller_requirement.nil? || @controller_requirement === controller) &&
- (@action_requirement.nil? || @action_requirement === action)
- end
-
- def to_s
- @to_s ||= begin
- segs = segments.inject("") { |str,s| str << s.to_s }
- "%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect]
- end
- end
-
- # TODO: Route should be prepared and frozen on initialize
- def freeze
- unless frozen?
- write_generation!
- write_recognition!
- prepare_matching!
-
- parameter_shell
- significant_keys
- defaults
- to_s
- end
-
- super
- end
-
- def generate(options, hash, expire_on = {})
- path, hash = generate_raw(options, hash, expire_on)
- append_query_string(path, hash, extra_keys(options))
- end
-
- def generate_extras(options, hash, expire_on = {})
- path, hash = generate_raw(options, hash, expire_on)
- [path, extra_keys(options)]
- end
-
- private
- def requirement_for(key)
- return requirements[key] if requirements.key? key
- segments.each do |segment|
- return segment.regexp if segment.respond_to?(:key) && segment.key == key
- end
- nil
- end
-
- # Write and compile a +generate+ method for this Route.
- def write_generation!
- # Build the main body of the generation
- body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
-
- # If we have conditions that must be tested first, nest the body inside an if
- body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
- args = "options, hash, expire_on = {}"
-
- # Nest the body inside of a def block, and then compile it.
- raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
- instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
-
- # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
- # are the same as the keys that were recalled from the previous request. Thus,
- # we can use the expire_on.keys to determine which keys ought to be used to build
- # the query string. (Never use keys from the recalled request when building the
- # query string.)
-
- raw_method
- end
-
- # Build several lines of code that extract values from the options hash. If any
- # of the values are missing or rejected then a return will be executed.
- def generation_extraction
- segments.collect do |segment|
- segment.extraction_code
- end.compact * "\n"
- end
-
- # Produce a condition expression that will check the requirements of this route
- # upon generation.
- def generation_requirements
- requirement_conditions = requirements.collect do |key, req|
- if req.is_a? Regexp
- value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
- "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
- else
- "hash[:#{key}] == #{req.inspect}"
- end
- end
- requirement_conditions * ' && ' unless requirement_conditions.empty?
- end
-
- def generation_structure
- segments.last.string_structure segments[0..-2]
- end
-
- # Write and compile a +recognize+ method for this Route.
- def write_recognition!
- # Create an if structure to extract the params from a match if it occurs.
- body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
- body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
-
- # Build the method declaration and compile it
- method_decl = "def recognize(path, env = {})\n#{body}\nend"
- instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
- method_decl
- end
-
- # Plugins may override this method to add other conditions, like checks on
- # host, subdomain, and so forth. Note that changes here only affect route
- # recognition, not generation.
- def recognition_conditions
- result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
- result << "[conditions[:method]].flatten.include?(env[:method])" if conditions[:method]
- result
- end
-
- # Build the regular expression pattern that will match this route.
- def recognition_pattern(wrap = true)
- pattern = ''
- segments.reverse_each do |segment|
- pattern = segment.build_pattern pattern
- end
- wrap ? ("\\A" + pattern + "\\Z") : pattern
- end
-
- # Write the code to extract the parameters from a matched route.
- def recognition_extraction
- next_capture = 1
- extraction = segments.collect do |segment|
- x = segment.match_extraction(next_capture)
- next_capture += segment.number_of_captures
- x
- end
- extraction.compact
- end
-
- # Generate the query string with any extra keys in the hash and append
- # it to the given path, returning the new path.
- def append_query_string(path, hash, query_keys = nil)
- return nil unless path
- query_keys ||= extra_keys(hash)
- "#{path}#{build_query_string(hash, query_keys)}"
- end
-
- # Determine which keys in the given hash are "extra". Extra keys are
- # those that were not used to generate a particular route. The extra
- # keys also do not include those recalled from the prior request, nor
- # do they include any keys that were implied in the route (like a
- # <tt>:controller</tt> that is required, but not explicitly used in the
- # text of the route.)
- def extra_keys(hash, recall = {})
- (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
- end
-
- def prepare_matching!
- unless defined? @matching_prepared
- @controller_requirement = requirement_for(:controller)
- @action_requirement = requirement_for(:action)
- @matching_prepared = true
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/routing_ext.rb b/actionpack/lib/action_controller/routing/routing_ext.rb
deleted file mode 100644
index 5e5b22b6c2..0000000000
--- a/actionpack/lib/action_controller/routing/routing_ext.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-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/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb
deleted file mode 100644
index 2603855476..0000000000
--- a/actionpack/lib/action_controller/routing/segments.rb
+++ /dev/null
@@ -1,343 +0,0 @@
-module ActionController
- module Routing
- class Segment #:nodoc:
- RESERVED_PCHAR = ':@&=+$,;%'
- SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
- if RUBY_VERSION >= '1.9'
- UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze
- else
- UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
- end
-
- # TODO: Convert :is_optional accessor to read only
- attr_accessor :is_optional
- alias_method :optional?, :is_optional
-
- def initialize
- @is_optional = false
- end
-
- def number_of_captures
- Regexp.new(regexp_chunk).number_of_captures
- end
-
- def extraction_code
- nil
- end
-
- # Continue generating string for the prior segments.
- def continue_string_structure(prior_segments)
- if prior_segments.empty?
- interpolation_statement(prior_segments)
- else
- new_priors = prior_segments[0..-2]
- prior_segments.last.string_structure(new_priors)
- end
- end
-
- def interpolation_chunk
- URI.escape(value, UNSAFE_PCHAR)
- end
-
- # Return a string interpolation statement for this segment and those before it.
- def interpolation_statement(prior_segments)
- chunks = prior_segments.collect { |s| s.interpolation_chunk }
- chunks << interpolation_chunk
- "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}"
- end
-
- def string_structure(prior_segments)
- optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments)
- end
-
- # Return an if condition that is true if all the prior segments can be generated.
- # If there are no optional segments before this one, then nil is returned.
- def all_optionals_available_condition(prior_segments)
- optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact
- optional_locals.empty? ? nil : " if #{optional_locals * ' && '}"
- end
-
- # Recognition
-
- def match_extraction(next_capture)
- nil
- end
-
- # Warning
-
- # Returns true if this segment is optional? because of a default. If so, then
- # no warning will be emitted regarding this segment.
- def optionality_implied?
- false
- end
- end
-
- class StaticSegment < Segment #:nodoc:
- attr_reader :value, :raw
- alias_method :raw?, :raw
-
- def initialize(value = nil, options = {})
- super()
- @value = value
- @raw = options[:raw] if options.key?(:raw)
- @is_optional = options[:optional] if options.key?(:optional)
- end
-
- def interpolation_chunk
- raw? ? value : super
- end
-
- def regexp_chunk
- chunk = Regexp.escape(value)
- optional? ? Regexp.optionalize(chunk) : chunk
- end
-
- def number_of_captures
- 0
- end
-
- def build_pattern(pattern)
- escaped = Regexp.escape(value)
- if optional? && ! pattern.empty?
- "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})"
- elsif optional?
- Regexp.optionalize escaped
- else
- escaped + pattern
- end
- end
-
- def to_s
- value
- end
- end
-
- class DividerSegment < StaticSegment #:nodoc:
- def initialize(value = nil, options = {})
- super(value, {:raw => true, :optional => true}.merge(options))
- end
-
- def optionality_implied?
- true
- end
- end
-
- class DynamicSegment < Segment #:nodoc:
- attr_reader :key
-
- # TODO: Convert these accessors to read only
- attr_accessor :default, :regexp
-
- def initialize(key = nil, options = {})
- super()
- @key = key
- @default = options[:default] if options.key?(:default)
- @regexp = options[:regexp] if options.key?(:regexp)
- @is_optional = true if options[:optional] || options.key?(:default)
- end
-
- def to_s
- ":#{key}"
- end
-
- # The local variable name that the value of this segment will be extracted to.
- def local_name
- "#{key}_value"
- end
-
- def extract_value
- "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
- end
-
- def value_check
- if default # Then we know it won't be nil
- "#{value_regexp.inspect} =~ #{local_name}" if regexp
- elsif optional?
- # If we have a regexp check that the value is not given, or that it matches.
- # If we have no regexp, return nil since we do not require a condition.
- "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp
- else # Then it must be present, and if we have a regexp, it must match too.
- "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
- end
- end
-
- def expiry_statement
- "expired, hash = true, options if !expired && expire_on[:#{key}]"
- end
-
- def extraction_code
- s = extract_value
- vc = value_check
- s << "\nreturn [nil,nil] unless #{vc}" if vc
- s << "\n#{expiry_statement}"
- end
-
- def interpolation_chunk(value_code = local_name)
- "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
- end
-
- def string_structure(prior_segments)
- if optional? # We have a conditional to do...
- # If we should not appear in the url, just write the code for the prior
- # segments. This occurs if our value is the default value, or, if we are
- # optional, if we have nil as our value.
- "if #{local_name} == #{default.inspect}\n" +
- continue_string_structure(prior_segments) +
- "\nelse\n" + # Otherwise, write the code up to here
- "#{interpolation_statement(prior_segments)}\nend"
- else
- interpolation_statement(prior_segments)
- end
- end
-
- def value_regexp
- Regexp.new "\\A#{regexp.to_s}\\Z" if regexp
- end
-
- def regexp_chunk
- regexp ? regexp_string : default_regexp_chunk
- end
-
- def regexp_string
- regexp_has_modifiers? ? "(#{regexp.to_s})" : "(#{regexp.source})"
- end
-
- def default_regexp_chunk
- "([^#{Routing::SEPARATORS.join}]+)"
- end
-
- def number_of_captures
- regexp ? regexp.number_of_captures + 1 : 1
- end
-
- def build_pattern(pattern)
- pattern = "#{regexp_chunk}#{pattern}"
- optional? ? Regexp.optionalize(pattern) : pattern
- end
-
- def match_extraction(next_capture)
- # All non code-related keys (such as :id, :slug) are URI-unescaped as
- # path parameters.
- default_value = default ? default.inspect : nil
- %[
- value = if (m = match[#{next_capture}])
- URI.unescape(m)
- else
- #{default_value}
- end
- params[:#{key}] = value if value
- ]
- end
-
- def optionality_implied?
- [:action, :id].include? key
- end
-
- def regexp_has_modifiers?
- regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
- end
- end
-
- class ControllerSegment < DynamicSegment #:nodoc:
- def regexp_chunk
- possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name }
- "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
- end
-
- # Don't URI.escape the controller name since it may contain slashes.
- def interpolation_chunk(value_code = local_name)
- "\#{#{value_code}.to_s}"
- end
-
- # Make sure controller names like Admin/Content are correctly normalized to
- # admin/content
- def extract_value
- "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase"
- end
-
- def match_extraction(next_capture)
- if default
- "params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'"
- else
- "params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]"
- end
- end
- end
-
- class PathSegment < DynamicSegment #:nodoc:
- def interpolation_chunk(value_code = local_name)
- "\#{#{value_code}}"
- end
-
- def extract_value
- "#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
- end
-
- def default
- ''
- end
-
- def default=(path)
- raise RoutingError, "paths cannot have non-empty default values" unless path.blank?
- end
-
- def match_extraction(next_capture)
- "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
- end
-
- def default_regexp_chunk
- "(.*)"
- end
-
- def number_of_captures
- regexp ? regexp.number_of_captures : 1
- end
-
- def optionality_implied?
- true
- end
-
- class Result < ::Array #:nodoc:
- def to_s() join '/' end
- def self.new_escaped(strings)
- new strings.collect {|str| URI.unescape str}
- end
- end
- end
-
- # The OptionalFormatSegment allows for any resource route to have an optional
- # :format, which decreases the amount of routes created by 50%.
- class OptionalFormatSegment < DynamicSegment
-
- def initialize(key = nil, options = {})
- super(:format, {:optional => true}.merge(options))
- end
-
- def interpolation_chunk
- "." + super
- end
-
- def regexp_chunk
- '/|(\.[^/?\.]+)?'
- end
-
- def to_s
- '(.:format)?'
- end
-
- def extract_value
- "#{local_name} = options[:#{key}] && options[:#{key}].to_s.downcase"
- end
-
- #the value should not include the period (.)
- def match_extraction(next_capture)
- %[
- if (m = match[#{next_capture}])
- params[:#{key}] = URI.unescape(m.from(1))
- end
- ]
- end
- end
-
- end
-end
diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb
index bbc7f3c8f9..323cce6a2f 100644
--- a/actionpack/lib/action_controller/testing/process.rb
+++ b/actionpack/lib/action_controller/testing/process.rb
@@ -35,7 +35,7 @@ module ActionController #:nodoc:
end
def cookies
- @response.cookies
+ @request.cookies.merge(@response.cookies)
end
def redirect_to_url
diff --git a/actionpack/lib/action_controller/testing/test_case.rb b/actionpack/lib/action_controller/testing/test_case.rb
index 178e3477a6..01a55fe930 100644
--- a/actionpack/lib/action_controller/testing/test_case.rb
+++ b/actionpack/lib/action_controller/testing/test_case.rb
@@ -10,6 +10,13 @@ module ActionController
self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16))
end
+ class Result < ::Array #:nodoc:
+ def to_s() join '/' end
+ def self.new_escaped(strings)
+ new strings.collect {|str| URI.unescape str}
+ end
+ end
+
def assign_parameters(controller_path, action, parameters = {})
parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
extra_keys = ActionController::Routing::Routes.extra_keys(parameters)
@@ -18,7 +25,7 @@ module ActionController
if value.is_a? Fixnum
value = value.to_s
elsif value.is_a? Array
- value = ActionController::Routing::PathSegment::Result.new(value)
+ value = Result.new(value)
end
if extra_keys.include?(key.to_sym)
diff --git a/actionpack/lib/action_controller/translation.rb b/actionpack/lib/action_controller/translation.rb
index 9bb63cdb15..65e9eddb0a 100644
--- a/actionpack/lib/action_controller/translation.rb
+++ b/actionpack/lib/action_controller/translation.rb
@@ -1,12 +1,12 @@
module ActionController
module Translation
def translate(*args)
- I18n.translate *args
+ I18n.translate(*args)
end
alias :t :translate
def localize(*args)
- I18n.localize *args
+ I18n.localize(*args)
end
alias :l :localize
end
diff --git a/actionpack/lib/action_controller/routing/generation/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb
index 52b66c9303..52b66c9303 100644
--- a/actionpack/lib/action_controller/routing/generation/url_rewriter.rb
+++ b/actionpack/lib/action_controller/url_rewriter.rb
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 11cd812695..259814a322 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -41,6 +41,8 @@ module ActionDispatch
autoload :Static, 'action_dispatch/middleware/static'
autoload :StringCoercion, 'action_dispatch/middleware/string_coercion'
+ autoload :Routing, 'action_dispatch/routing'
+
autoload :Assertions, 'action_dispatch/testing/assertions'
autoload :Integration, 'action_dispatch/testing/integration'
autoload :IntegrationTest, 'action_dispatch/testing/integration'
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index e85823d8db..c30897b32a 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -24,6 +24,7 @@ module Mime
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
def self.[](type)
+ return type if type.is_a?(Type)
Type.lookup_by_extension(type.to_s)
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index bff030f0e4..6a52854961 100755
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -5,7 +5,7 @@ require 'strscan'
require 'active_support/memoizable'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/hash/indifferent_access'
-require 'active_support/core_ext/object/tap'
+require 'active_support/core_ext/string/access'
module ActionDispatch
class Request < Rack::Request
@@ -97,6 +97,10 @@ module ActionDispatch
end
end
+ def forgery_whitelisted?
+ method == :get || xhr? || content_type.nil? || !content_type.verify_request?
+ end
+
def media_type
content_type.to_s
end
@@ -136,19 +140,16 @@ module ActionDispatch
# If-Modified-Since and If-None-Match conditions. If both headers are
# supplied, both must match, or the request is not considered fresh.
def fresh?(response)
- case
- when if_modified_since && if_none_match
- not_modified?(response.last_modified) && etag_matches?(response.etag)
- when if_modified_since
- not_modified?(response.last_modified)
- when if_none_match
- etag_matches?(response.etag)
- else
- false
- end
- end
+ last_modified = if_modified_since
+ etag = if_none_match
- ONLY_ALL = [Mime::ALL].freeze
+ return false unless last_modified || etag
+
+ success = true
+ success &&= not_modified?(response.last_modified) if last_modified
+ success &&= etag_matches?(response.etag) if etag
+ success
+ end
# Returns the Mime type for the \format used in the request.
#
@@ -165,7 +166,7 @@ module ActionDispatch
@env["action_dispatch.request.formats"] ||=
if parameters[:format]
- [Mime[parameters[:format]]]
+ Array.wrap(Mime[parameters[:format]])
elsif xhr? || (accept && !accept.include?(?,))
accepts
else
@@ -204,10 +205,6 @@ module ActionDispatch
end
end
- def cache_format
- parameters[:format]
- end
-
# Returns true if the request's "X-Requested-With" header contains
# "XMLHttpRequest". (The Prototype Javascript library sends this header with
# every Ajax request.)
@@ -492,7 +489,7 @@ EOM
def self.extended(object)
object.class_eval do
attr_accessor :original_path, :content_type
- alias_method :local_path, :path
+ alias_method :local_path, :path if method_defined?(:path)
end
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 3e3b473178..b3ed7c9d1a 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -270,6 +270,8 @@ module ActionDispatch # :nodoc:
if control.empty?
headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
+ elsif @cache_control[:no_cache]
+ headers["Cache-Control"] = "no-cache"
else
extras = control[:extras]
max_age = control[:max_age]
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index 56d6da1706..49bc20f11f 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -1,6 +1,6 @@
module ActionDispatch
class Callbacks
- include ActiveSupport::NewCallbacks
+ include ActiveSupport::Callbacks
define_callbacks :call, :terminator => "result == false", :rescuable => true
define_callbacks :prepare, :scope => :name
@@ -37,12 +37,12 @@ module ActionDispatch
def initialize(app, prepare_each_request = false)
@app, @prepare_each_request = app, prepare_each_request
- _run_prepare_callbacks
+ run_callbacks(:prepare)
end
def call(env)
- _run_call_callbacks do
- _run_prepare_callbacks if @prepare_each_request
+ run_callbacks(:call) do
+ run_callbacks(:prepare) if @prepare_each_request
@app.call(env)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 4f71ea6165..3b27309f58 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -1,3 +1,5 @@
+require "active_support/inflector/methods"
+
module ActionDispatch
class MiddlewareStack < Array
class Middleware
@@ -32,7 +34,7 @@ module ActionDispatch
elsif @klass.respond_to?(:call)
@klass.call
else
- @klass.to_s.constantize
+ ActiveSupport::Inflector.constantize(@klass.to_s)
end
end
@@ -53,7 +55,7 @@ module ActionDispatch
when Class
klass == middleware
else
- klass == middleware.to_s.constantize
+ klass == ActiveSupport::Inflector.constantize(middleware.to_s)
end
end
diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 5b9ded83dd..68ed1e3340 100644
--- a/actionpack/lib/action_controller/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -1,18 +1,7 @@
-require 'cgi'
-require 'uri'
-require 'set'
+require 'active_support/core_ext/object/to_param'
+require 'active_support/core_ext/regexp'
-require 'active_support/core_ext/module/aliasing'
-require 'active_support/core_ext/module/attribute_accessors'
-require 'action_controller/routing/optimisations'
-require 'action_controller/routing/routing_ext'
-require 'action_controller/routing/route'
-require 'action_controller/routing/segments'
-require 'action_controller/routing/builder'
-require 'action_controller/routing/route_set'
-require 'action_controller/routing/recognition_optimisation'
-
-module ActionController
+module ActionDispatch
# == Routing
#
# The routing module provides URL rewriting in native Ruby. It's a way to
@@ -197,7 +186,7 @@ module ActionController
#
# map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
#
- # will glob all remaining parts of the route that were not recognized earlier.
+ # will glob all remaining parts of the route that were not recognized earlier.
# The globbed values are in <tt>params[:path]</tt> as an array of path segments.
#
# == Route conditions
@@ -269,6 +258,11 @@ module ActionController
# Run <tt>rake routes</tt>.
#
module Routing
+ autoload :DeprecatedMapper, 'action_dispatch/routing/deprecated_mapper'
+ autoload :Mapper, 'action_dispatch/routing/mapper'
+ autoload :Route, 'action_dispatch/routing/route'
+ autoload :RouteSet, 'action_dispatch/routing/route_set'
+
SEPARATORS = %w( / . ? )
HTTP_METHODS = [:get, :head, :post, :put, :delete, :options]
@@ -281,7 +275,7 @@ module ActionController
# A helper module to hold URL related helpers.
module Helpers
- include PolymorphicRoutes
+ include ActionController::PolymorphicRoutes
end
class << self
@@ -373,13 +367,11 @@ module ActionController
end
end
- Routes = RouteSet.new
-
ActiveSupport::Inflector.module_eval do
# Ensures that routes are reloaded when Rails inflections are updated.
def inflections_with_route_reloading(&block)
returning(inflections_without_route_reloading(&block)) {
- ActionController::Routing::Routes.reload! if block_given?
+ ActionDispatch::Routing::Routes.reload! if block_given?
}
end
diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
new file mode 100644
index 0000000000..0564ba9797
--- /dev/null
+++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
@@ -0,0 +1,878 @@
+module ActionDispatch
+ module Routing
+ # Mapper instances are used to build routes. The object passed to the draw
+ # block in config/routes.rb is a Mapper instance.
+ #
+ # Mapper instances have relatively few instance methods, in order to avoid
+ # clashes with named routes.
+ #
+ # == Overview
+ #
+ # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
+ # is something that can be pointed at and it will respond with a representation of the data requested.
+ # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
+ # requests XML data.
+ #
+ # RESTful design is based on the assumption that there are four generic verbs that a user of an
+ # application can request from a \resource (the noun).
+ #
+ # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
+ # denotes the type of action that should take place.
+ #
+ # === The Different Methods and their Usage
+ #
+ # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
+ # * POST - Creation of \resources.
+ # * PUT - Editing of attributes on a \resource.
+ # * DELETE - Deletion of a \resource.
+ #
+ # === Examples
+ #
+ # # A GET request on the Posts resource is asking for all Posts
+ # GET /posts
+ #
+ # # A GET request on a single Post resource is asking for that particular Post
+ # GET /posts/1
+ #
+ # # A POST request on the Posts resource is asking for a Post to be created with the supplied details
+ # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
+ #
+ # # A PUT request on a single Post resource is asking for a Post to be updated
+ # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
+ #
+ # # A DELETE request on a single Post resource is asking for it to be deleted
+ # DELETE /posts # with => { :id => 1 }
+ #
+ # By using the REST convention, users of our application can assume certain things about how the data
+ # is requested and how it is returned. Rails simplifies the routing part of RESTful design by
+ # supplying you with methods to create them in your routes.rb file.
+ #
+ # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
+ class DeprecatedMapper #:doc:
+ def initialize(set) #:nodoc:
+ @set = set
+ end
+
+ # Create an unnamed route with the provided +path+ and +options+. See
+ # ActionDispatch::Routing for an introduction to routes.
+ def connect(path, options = {})
+ options = options.dup
+
+ if conditions = options.delete(:conditions)
+ conditions = conditions.dup
+ method = [conditions.delete(:method)].flatten.compact
+ method.map! { |m|
+ m = m.to_s.upcase
+
+ if m == "HEAD"
+ raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
+ end
+
+ unless HTTP_METHODS.include?(m.downcase.to_sym)
+ raise ArgumentError, "Invalid HTTP method specified in route conditions"
+ end
+
+ m
+ }
+
+ if method.length > 1
+ method = Regexp.union(*method)
+ elsif method.length == 1
+ method = method.first
+ else
+ method = nil
+ end
+ end
+
+ path_prefix = options.delete(:path_prefix)
+ name_prefix = options.delete(:name_prefix)
+ namespace = options.delete(:namespace)
+
+ name = options.delete(:_name)
+ name = "#{name_prefix}#{name}" if name_prefix
+
+ requirements = options.delete(:requirements) || {}
+ defaults = options.delete(:defaults) || {}
+ options.each do |k, v|
+ if v.is_a?(Regexp)
+ if value = options.delete(k)
+ requirements[k.to_sym] = value
+ end
+ else
+ value = options.delete(k)
+ defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param
+ end
+ end
+
+ requirements.each do |_, requirement|
+ if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
+ end
+ if requirement.multiline?
+ raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
+ end
+ end
+
+ possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) }
+ requirements[:controller] ||= Regexp.union(*possible_names)
+
+ if defaults[:controller]
+ defaults[:action] ||= 'index'
+ defaults[:controller] = defaults[:controller].to_s
+ defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace
+ end
+
+ if defaults[:action]
+ defaults[:action] = defaults[:action].to_s
+ end
+
+ if path.is_a?(String)
+ path = "#{path_prefix}/#{path}" if path_prefix
+ path = path.gsub('.:format', '(.:format)')
+ path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
+ glob = $1.to_sym if path =~ /\/\*(\w+)$/
+ path = ::Rack::Mount::Utils.normalize_path(path)
+
+ if glob && !defaults[glob].blank?
+ raise ActionController::RoutingError, "paths cannot have non-empty default values"
+ end
+ end
+
+ app = Routing::RouteSet::Dispatcher.new(:defaults => defaults, :glob => glob)
+
+ conditions = {}
+ conditions[:request_method] = method if method
+ conditions[:path_info] = path if path
+
+ @set.add_route(app, conditions, requirements, defaults, name)
+ end
+
+ def optionalize_trailing_dynamic_segments(path, requirements, defaults) #:nodoc:
+ path = (path =~ /^\//) ? path.dup : "/#{path}"
+ optional, segments = true, []
+
+ required_segments = requirements.keys
+ required_segments -= defaults.keys.compact
+
+ old_segments = path.split('/')
+ old_segments.shift
+ length = old_segments.length
+
+ old_segments.reverse.each_with_index do |segment, index|
+ required_segments.each do |required|
+ if segment =~ /#{required}/
+ optional = false
+ break
+ end
+ end
+
+ if optional
+ if segment == ":id" && segments.include?(":action")
+ optional = false
+ elsif segment == ":controller" || segment == ":action" || segment == ":id"
+ # Ignore
+ elsif !(segment =~ /^:\w+$/) &&
+ !(segment =~ /^:\w+\(\.:format\)$/)
+ optional = false
+ elsif segment =~ /^:(\w+)$/
+ if defaults.has_key?($1.to_sym)
+ defaults.delete($1.to_sym)
+ else
+ optional = false
+ end
+ end
+ end
+
+ if optional && index < length - 1
+ segments.unshift('(/', segment)
+ segments.push(')')
+ elsif optional
+ segments.unshift('/(', segment)
+ segments.push(')')
+ else
+ segments.unshift('/', segment)
+ end
+ end
+
+ segments.join
+ end
+ private :optionalize_trailing_dynamic_segments
+
+ # Creates a named route called "root" for matching the root level request.
+ def root(options = {})
+ if options.is_a?(Symbol)
+ if source_route = @set.named_routes.routes[options]
+ options = source_route.defaults.merge({ :conditions => source_route.conditions })
+ end
+ end
+ named_route("root", '', options)
+ end
+
+ def named_route(name, path, options = {}) #:nodoc:
+ options[:_name] = name
+ connect(path, options)
+ end
+
+ # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
+ # Example:
+ #
+ # map.namespace(:admin) do |admin|
+ # admin.resources :products,
+ # :has_many => [ :tags, :images, :variants ]
+ # end
+ #
+ # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
+ # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
+ # Admin::TagsController.
+ def namespace(name, options = {}, &block)
+ if options[:namespace]
+ with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
+ else
+ with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
+ end
+ end
+
+ def method_missing(route_name, *args, &proc) #:nodoc:
+ super unless args.length >= 1 && proc.nil?
+ named_route(route_name, *args)
+ end
+
+ INHERITABLE_OPTIONS = :namespace, :shallow
+
+ class Resource #:nodoc:
+ DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
+
+ attr_reader :collection_methods, :member_methods, :new_methods
+ attr_reader :path_prefix, :name_prefix, :path_segment
+ attr_reader :plural, :singular
+ attr_reader :options
+
+ def initialize(entities, options)
+ @plural ||= entities
+ @singular ||= options[:singular] || plural.to_s.singularize
+ @path_segment = options.delete(:as) || @plural
+
+ @options = options
+
+ arrange_actions
+ add_default_actions
+ set_allowed_actions
+ set_prefixes
+ end
+
+ def controller
+ @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
+ end
+
+ def requirements(with_id = false)
+ @requirements ||= @options[:requirements] || {}
+ @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
+
+ with_id ? @requirements.merge(@id_requirement) : @requirements
+ end
+
+ def conditions
+ @conditions ||= @options[:conditions] || {}
+ end
+
+ def path
+ @path ||= "#{path_prefix}/#{path_segment}"
+ end
+
+ def new_path
+ new_action = self.options[:path_names][:new] if self.options[:path_names]
+ new_action ||= ActionController::Base.resources_path_names[:new]
+ @new_path ||= "#{path}/#{new_action}"
+ end
+
+ def shallow_path_prefix
+ @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
+ end
+
+ def member_path
+ @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
+ end
+
+ def nesting_path_prefix
+ @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
+ end
+
+ def shallow_name_prefix
+ @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
+ end
+
+ def nesting_name_prefix
+ "#{shallow_name_prefix}#{singular}_"
+ end
+
+ def action_separator
+ @action_separator ||= ActionController::Base.resource_action_separator
+ end
+
+ def uncountable?
+ @singular.to_s == @plural.to_s
+ end
+
+ def has_action?(action)
+ !DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
+ end
+
+ protected
+ def arrange_actions
+ @collection_methods = arrange_actions_by_methods(options.delete(:collection))
+ @member_methods = arrange_actions_by_methods(options.delete(:member))
+ @new_methods = arrange_actions_by_methods(options.delete(:new))
+ end
+
+ def add_default_actions
+ add_default_action(member_methods, :get, :edit)
+ add_default_action(new_methods, :get, :new)
+ end
+
+ def set_allowed_actions
+ only, except = @options.values_at(:only, :except)
+ @allowed_actions ||= {}
+
+ if only == :all || except == :none
+ only = nil
+ except = []
+ elsif only == :none || except == :all
+ only = []
+ except = nil
+ end
+
+ if only
+ @allowed_actions[:only] = Array(only).map {|a| a.to_sym }
+ elsif except
+ @allowed_actions[:except] = Array(except).map {|a| a.to_sym }
+ end
+ end
+
+ def action_allowed?(action)
+ only, except = @allowed_actions.values_at(:only, :except)
+ (!only || only.include?(action)) && (!except || !except.include?(action))
+ end
+
+ def set_prefixes
+ @path_prefix = options.delete(:path_prefix)
+ @name_prefix = options.delete(:name_prefix)
+ end
+
+ def arrange_actions_by_methods(actions)
+ (actions || {}).inject({}) do |flipped_hash, (key, value)|
+ (flipped_hash[value] ||= []) << key
+ flipped_hash
+ end
+ end
+
+ def add_default_action(collection, method, action)
+ (collection[method] ||= []).unshift(action)
+ end
+ end
+
+ class SingletonResource < Resource #:nodoc:
+ def initialize(entity, options)
+ @singular = @plural = entity
+ options[:controller] ||= @singular.to_s.pluralize
+ super
+ end
+
+ alias_method :shallow_path_prefix, :path_prefix
+ alias_method :shallow_name_prefix, :name_prefix
+ alias_method :member_path, :path
+ alias_method :nesting_path_prefix, :path
+ end
+
+ # Creates named routes for implementing verb-oriented controllers
+ # for a collection \resource.
+ #
+ # For example:
+ #
+ # map.resources :messages
+ #
+ # will map the following actions in the corresponding controller:
+ #
+ # class MessagesController < ActionController::Base
+ # # GET messages_url
+ # def index
+ # # return all messages
+ # end
+ #
+ # # GET new_message_url
+ # def new
+ # # return an HTML form for describing a new message
+ # end
+ #
+ # # POST messages_url
+ # def create
+ # # create a new message
+ # end
+ #
+ # # GET message_url(:id => 1)
+ # def show
+ # # find and return a specific message
+ # end
+ #
+ # # GET edit_message_url(:id => 1)
+ # def edit
+ # # return an HTML form for editing a specific message
+ # end
+ #
+ # # PUT message_url(:id => 1)
+ # def update
+ # # find and update a specific message
+ # end
+ #
+ # # DELETE message_url(:id => 1)
+ # def destroy
+ # # delete a specific message
+ # end
+ # end
+ #
+ # Along with the routes themselves, +resources+ generates named routes for use in
+ # controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers:
+ #
+ # Named Route Helpers
+ # ============ =====================================================
+ # messages messages_url, hash_for_messages_url,
+ # messages_path, hash_for_messages_path
+ #
+ # message message_url(id), hash_for_message_url(id),
+ # message_path(id), hash_for_message_path(id)
+ #
+ # new_message new_message_url, hash_for_new_message_url,
+ # new_message_path, hash_for_new_message_path
+ #
+ # edit_message edit_message_url(id), hash_for_edit_message_url(id),
+ # edit_message_path(id), hash_for_edit_message_path(id)
+ #
+ # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
+ #
+ # redirect_to :controller => 'messages', :action => 'index'
+ # # and
+ # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
+ #
+ # now become:
+ #
+ # redirect_to messages_url
+ # # and
+ # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
+ #
+ # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
+ # form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object:
+ #
+ # <%= form_tag message_path(@message), :method => :put %>
+ #
+ # or
+ #
+ # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
+ #
+ # or
+ #
+ # <% form_for @message do |f| %>
+ #
+ # which takes into account whether <tt>@message</tt> is a new record or not and generates the
+ # path and method accordingly.
+ #
+ # The +resources+ method accepts the following options to customize the resulting routes:
+ # * <tt>:collection</tt> - Add named routes for other actions that operate on the collection.
+ # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>,
+ # an array of any of the previous, or <tt>:any</tt> if the method does not matter.
+ # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
+ # * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member.
+ # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
+ # * <tt>:controller</tt> - Specify the controller name for the routes.
+ # * <tt>:singular</tt> - Specify the singular name used in the member routes.
+ # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either
+ # regular expressions (which must match for the route to match) or extra parameters. For example:
+ #
+ # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
+ #
+ # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
+ # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
+ # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
+ # # products_path == '/productos'
+ # map.resources :products, :as => 'productos' do |product|
+ # # product_reviews_path(product) == '/productos/1234/comentarios'
+ # product.resources :product_reviews, :as => 'comentarios'
+ # end
+ #
+ # * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
+ # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources.
+ #
+ # You may directly specify the routing association with +has_one+ and +has_many+ like:
+ #
+ # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
+ #
+ # This is the same as:
+ #
+ # map.resources :notes do |notes|
+ # notes.resource :author
+ # notes.resources :comments
+ # notes.resources :attachments
+ # end
+ #
+ # * <tt>:path_names</tt> - Specify different path names for the actions. For example:
+ # # new_products_path == '/productos/nuevo'
+ # # bids_product_path(1) == '/productos/1/licitacoes'
+ # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
+ #
+ # You can also set default action names from an environment, like this:
+ # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
+ #
+ # * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables.
+ #
+ # Weblog comments usually belong to a post, so you might use +resources+ like:
+ #
+ # map.resources :articles
+ # map.resources :comments, :path_prefix => '/articles/:article_id'
+ #
+ # You can nest +resources+ calls to set this automatically:
+ #
+ # map.resources :articles do |article|
+ # article.resources :comments
+ # end
+ #
+ # The comment \resources work the same, but must now include a value for <tt>:article_id</tt>.
+ #
+ # article_comments_url(@article)
+ # article_comment_url(@article, @comment)
+ #
+ # article_comments_url(:article_id => @article)
+ # article_comment_url(:article_id => @article, :id => @comment)
+ #
+ # If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly:
+ #
+ # articles_comments_url(@comment.article_id, @comment)
+ #
+ # * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore.
+ # Use this if you have named routes that may clash.
+ #
+ # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
+ # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
+ #
+ # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource:
+ #
+ # map.resources :articles do |article|
+ # article.resources :comments, :name_prefix => nil
+ # end
+ #
+ # This will yield named \resources like so:
+ #
+ # comments_url(@article)
+ # comment_url(@article, @comment)
+ #
+ # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member
+ # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
+ #
+ # The <tt>:shallow</tt> option is inherited by any nested resource(s).
+ #
+ # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
+ #
+ # map.resources :users, :shallow => true do |user|
+ # user.resources :posts do |post|
+ # post.resources :comments
+ # end
+ # end
+ # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
+ # # also adds the usual named route called "user_posts"
+ # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
+ # # also adds the named route called "post"
+ # # --> GET /posts/2/comments (maps to the CommentsController#index action)
+ # # also adds the named route called "post_comments"
+ # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
+ # # also adds the named route called "comment"
+ #
+ # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like:
+ #
+ # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
+ #
+ # * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to.
+ #
+ # <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a
+ # list of action names. By default, routes are generated for all seven actions.
+ #
+ # For example:
+ #
+ # map.resources :posts, :only => [:index, :show] do |post|
+ # post.resources :comments, :except => [:update, :destroy]
+ # end
+ # # --> GET /posts (maps to the PostsController#index action)
+ # # --> POST /posts (fails)
+ # # --> GET /posts/1 (maps to the PostsController#show action)
+ # # --> DELETE /posts/1 (fails)
+ # # --> POST /posts/1/comments (maps to the CommentsController#create action)
+ # # --> PUT /posts/1/comments/1 (fails)
+ #
+ # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
+ #
+ # Examples:
+ #
+ # map.resources :messages, :path_prefix => "/thread/:thread_id"
+ # # --> GET /thread/7/messages/1
+ #
+ # map.resources :messages, :collection => { :rss => :get }
+ # # --> GET /messages/rss (maps to the #rss action)
+ # # also adds a named route called "rss_messages"
+ #
+ # map.resources :messages, :member => { :mark => :post }
+ # # --> POST /messages/1/mark (maps to the #mark action)
+ # # also adds a named route called "mark_message"
+ #
+ # map.resources :messages, :new => { :preview => :post }
+ # # --> POST /messages/new/preview (maps to the #preview action)
+ # # also adds a named route called "preview_new_message"
+ #
+ # map.resources :messages, :new => { :new => :any, :preview => :post }
+ # # --> POST /messages/new/preview (maps to the #preview action)
+ # # also adds a named route called "preview_new_message"
+ # # --> /messages/new can be invoked via any request method
+ #
+ # map.resources :messages, :controller => "categories",
+ # :path_prefix => "/category/:category_id",
+ # :name_prefix => "category_"
+ # # --> GET /categories/7/messages/1
+ # # has named route "category_message"
+ #
+ # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
+ # HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
+ # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes.
+ def resources(*entities, &block)
+ options = entities.extract_options!
+ entities.each { |entity| map_resource(entity, options.dup, &block) }
+ end
+
+ # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
+ # A singleton \resource is global to its current context. For unnested singleton \resources,
+ # the \resource is global to the current user visiting the application, such as a user's
+ # <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent
+ # \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>.
+ # The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>:
+ #
+ # map.resources :projects do |project|
+ # project.resource :project_manager
+ # end
+ #
+ # See +resources+ for general conventions. These are the main differences:
+ # * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name.
+ # * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option.
+ # * No default index route is created for the singleton \resource controller.
+ # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
+ #
+ # For example:
+ #
+ # map.resource :account
+ #
+ # maps these actions in the Accounts controller:
+ #
+ # class AccountsController < ActionController::Base
+ # # GET new_account_url
+ # def new
+ # # return an HTML form for describing the new account
+ # end
+ #
+ # # POST account_url
+ # def create
+ # # create an account
+ # end
+ #
+ # # GET account_url
+ # def show
+ # # find and return the account
+ # end
+ #
+ # # GET edit_account_url
+ # def edit
+ # # return an HTML form for editing the account
+ # end
+ #
+ # # PUT account_url
+ # def update
+ # # find and update the account
+ # end
+ #
+ # # DELETE account_url
+ # def destroy
+ # # delete the account
+ # end
+ # end
+ #
+ # Along with the routes themselves, +resource+ generates named routes for
+ # use in controllers and views. <tt>map.resource :account</tt> produces
+ # these named routes and helpers:
+ #
+ # Named Route Helpers
+ # ============ =============================================
+ # account account_url, hash_for_account_url,
+ # account_path, hash_for_account_path
+ #
+ # new_account new_account_url, hash_for_new_account_url,
+ # new_account_path, hash_for_new_account_path
+ #
+ # edit_account edit_account_url, hash_for_edit_account_url,
+ # edit_account_path, hash_for_edit_account_path
+ def resource(*entities, &block)
+ options = entities.extract_options!
+ entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
+ end
+
+ private
+ def map_resource(entities, options = {}, &block)
+ resource = Resource.new(entities, options)
+
+ with_options :controller => resource.controller do |map|
+ map_associations(resource, options)
+
+ if block_given?
+ with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
+ end
+
+ map_collection_actions(map, resource)
+ map_default_collection_actions(map, resource)
+ map_new_actions(map, resource)
+ map_member_actions(map, resource)
+ end
+ end
+
+ def map_singleton_resource(entities, options = {}, &block)
+ resource = SingletonResource.new(entities, options)
+
+ with_options :controller => resource.controller do |map|
+ map_associations(resource, options)
+
+ if block_given?
+ with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
+ end
+
+ map_collection_actions(map, resource)
+ map_new_actions(map, resource)
+ map_member_actions(map, resource)
+ map_default_singleton_actions(map, resource)
+ end
+ end
+
+ def map_associations(resource, options)
+ map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
+
+ path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
+ name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
+
+ Array(options[:has_one]).each do |association|
+ resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
+ end
+ end
+
+ def map_has_many_associations(resource, associations, options)
+ case associations
+ when Hash
+ associations.each do |association,has_many|
+ map_has_many_associations(resource, association, options.merge(:has_many => has_many))
+ end
+ when Array
+ associations.each do |association|
+ map_has_many_associations(resource, association, options)
+ end
+ when Symbol, String
+ resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
+ else
+ end
+ end
+
+ def map_collection_actions(map, resource)
+ resource.collection_methods.each do |method, actions|
+ actions.each do |action|
+ [method].flatten.each do |m|
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
+ action_path ||= action
+
+ map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
+ end
+ end
+ end
+ end
+
+ def map_default_collection_actions(map, resource)
+ index_route_name = "#{resource.name_prefix}#{resource.plural}"
+
+ if resource.uncountable?
+ index_route_name << "_index"
+ end
+
+ map_resource_routes(map, resource, :index, resource.path, index_route_name)
+ map_resource_routes(map, resource, :create, resource.path, index_route_name)
+ end
+
+ def map_default_singleton_actions(map, resource)
+ map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}")
+ end
+
+ def map_new_actions(map, resource)
+ resource.new_methods.each do |method, actions|
+ actions.each do |action|
+ route_path = resource.new_path
+ route_name = "new_#{resource.name_prefix}#{resource.singular}"
+
+ unless action == :new
+ route_path = "#{route_path}#{resource.action_separator}#{action}"
+ route_name = "#{action}_#{route_name}"
+ end
+
+ map_resource_routes(map, resource, action, route_path, route_name, method)
+ end
+ end
+ end
+
+ def map_member_actions(map, resource)
+ resource.member_methods.each do |method, actions|
+ actions.each do |action|
+ [method].flatten.each do |m|
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
+ action_path ||= ActionController::Base.resources_path_names[action] || action
+
+ map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
+ end
+ end
+ end
+
+ route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
+ map_resource_routes(map, resource, :show, resource.member_path, route_path)
+ map_resource_routes(map, resource, :update, resource.member_path, route_path)
+ map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
+ end
+
+ def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
+ if resource.has_action?(action)
+ action_options = action_options_for(action, resource, method, resource_options)
+ formatted_route_path = "#{route_path}.:format"
+
+ if route_name && @set.named_routes[route_name.to_sym].nil?
+ map.named_route(route_name, formatted_route_path, action_options)
+ else
+ map.connect(formatted_route_path, action_options)
+ end
+ end
+ end
+
+ def add_conditions_for(conditions, method)
+ returning({:conditions => conditions.dup}) do |options|
+ options[:conditions][:method] = method unless method == :any
+ end
+ end
+
+ def action_options_for(action, resource, method = nil, resource_options = {})
+ default_options = { :action => action.to_s }
+ require_id = !resource.kind_of?(SingletonResource)
+ force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
+
+ case default_options[:action]
+ when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
+ when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
+ when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
+ when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
+ when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
+ else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
new file mode 100644
index 0000000000..7d770dedd0
--- /dev/null
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -0,0 +1,327 @@
+module ActionDispatch
+ module Routing
+ class Mapper
+ module Resources
+ def resource(*resources, &block)
+ options = resources.last.is_a?(Hash) ? resources.pop : {}
+
+ if resources.length > 1
+ raise ArgumentError if block_given?
+ resources.each { |r| resource(r, options) }
+ return self
+ end
+
+ resource = resources.pop
+
+ if @scope[:scope_level] == :resources
+ member do
+ resource(resource, options, &block)
+ end
+ return self
+ end
+
+ singular = resource.to_s
+ plural = singular.pluralize
+
+ controller(plural) do
+ namespace(resource) do
+ with_scope_level(:resource) do
+ yield if block_given?
+
+ get "", :to => :show, :as => "#{singular}"
+ post "", :to => :create
+ put "", :to => :update
+ delete "", :to => :destroy
+ get "new", :to => :new, :as => "new_#{singular}"
+ get "edit", :to => :edit, :as => "edit_#{singular}"
+ end
+ end
+ end
+
+ self
+ end
+
+ def resources(*resources, &block)
+ options = resources.last.is_a?(Hash) ? resources.pop : {}
+
+ if resources.length > 1
+ raise ArgumentError if block_given?
+ resources.each { |r| resources(r, options) }
+ return self
+ end
+
+ resource = resources.pop
+
+ if @scope[:scope_level] == :resources
+ member do
+ resources(resource, options, &block)
+ end
+ return self
+ end
+
+ plural = resource.to_s
+ singular = plural.singularize
+
+ controller(resource) do
+ namespace(resource) do
+ with_scope_level(:resources) do
+ yield if block_given?
+
+ member do
+ get "", :to => :show, :as => "#{singular}"
+ put "", :to => :update
+ delete "", :to => :destroy
+ get "edit", :to => :edit, :as => "edit_#{singular}"
+ end
+
+ collection do
+ get "", :to => :index, :as => "#{plural}"
+ post "", :to => :create
+ get "new", :to => :new, :as => "new_#{singular}"
+ end
+ end
+ end
+ end
+
+ self
+ end
+
+ def collection
+ unless @scope[:scope_level] == :resources
+ raise ArgumentError, "can't use collection outside resources scope"
+ end
+
+ with_scope_level(:collection) do
+ yield
+ end
+ end
+
+ def member
+ unless @scope[:scope_level] == :resources
+ raise ArgumentError, "can't use member outside resources scope"
+ end
+
+ with_scope_level(:member) do
+ scope(":id") do
+ yield
+ end
+ end
+ end
+
+ def match(*args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ args.push(options)
+
+ case options.delete(:on)
+ when :collection
+ return collection { match(*args) }
+ when :member
+ return member { match(*args) }
+ end
+
+ if @scope[:scope_level] == :resources
+ raise ArgumentError, "can't define route directly in resources scope"
+ end
+
+ super
+ end
+
+ private
+ def with_scope_level(kind)
+ old, @scope[:scope_level] = @scope[:scope_level], kind
+ yield
+ ensure
+ @scope[:scope_level] = old
+ end
+ end
+
+ module Scoping
+ def scope(*args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+
+ constraints = options.delete(:constraints) || {}
+ unless constraints.is_a?(Hash)
+ block, constraints = constraints, {}
+ end
+ constraints, @scope[:constraints] = @scope[:constraints], (@scope[:constraints] || {}).merge(constraints)
+ blocks, @scope[:blocks] = @scope[:blocks], (@scope[:blocks] || []) + [block]
+
+ options, @scope[:options] = @scope[:options], (@scope[:options] || {}).merge(options)
+
+ path_set = controller_set = false
+
+ case args.first
+ when String
+ path_set = true
+ path = args.first
+ path, @scope[:path] = @scope[:path], "#{@scope[:path]}#{Rack::Mount::Utils.normalize_path(path)}"
+ when Symbol
+ controller_set = true
+ controller = args.first
+ controller, @scope[:controller] = @scope[:controller], controller
+ end
+
+ yield
+
+ self
+ ensure
+ @scope[:path] = path if path_set
+ @scope[:controller] = controller if controller_set
+ @scope[:options] = options
+ @scope[:blocks] = blocks
+ @scope[:constraints] = constraints
+ end
+
+ def controller(controller)
+ scope(controller.to_sym) { yield }
+ end
+
+ def namespace(path)
+ scope(path.to_s) { yield }
+ end
+
+ def constraints(constraints = {})
+ scope(:constraints => constraints) { yield }
+ end
+ end
+
+ class Constraints
+ def initialize(app, constraints = [])
+ @app, @constraints = app, constraints
+ end
+
+ def call(env)
+ req = Rack::Request.new(env)
+
+ @constraints.each { |constraint|
+ if constraint.respond_to?(:matches?) && !constraint.matches?(req)
+ return [417, {}, []]
+ elsif constraint.respond_to?(:call) && !constraint.call(req)
+ return [417, {}, []]
+ end
+ }
+
+ @app.call(env)
+ end
+ end
+
+ def initialize(set)
+ @set = set
+ @scope = {}
+
+ extend Scoping
+ extend Resources
+ end
+
+ def get(*args, &block)
+ map_method(:get, *args, &block)
+ end
+
+ def post(*args, &block)
+ map_method(:post, *args, &block)
+ end
+
+ def put(*args, &block)
+ map_method(:put, *args, &block)
+ end
+
+ def delete(*args, &block)
+ map_method(:delete, *args, &block)
+ end
+
+ def root(options = {})
+ match '/', options.merge(:as => :root)
+ end
+
+ def match(*args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+
+ if args.length > 1
+ args.each { |path| match(path, options) }
+ return self
+ end
+
+ if args.first.is_a?(Symbol)
+ return match(args.first.to_s, options.merge(:to => args.first.to_sym))
+ end
+
+ path = args.first
+
+ options = (@scope[:options] || {}).merge(options)
+ conditions, defaults = {}, {}
+
+ path = nil if path == ""
+ path = Rack::Mount::Utils.normalize_path(path) if path
+ path = "#{@scope[:path]}#{path}" if @scope[:path]
+
+ raise ArgumentError, "path is required" unless path
+
+ constraints = options[:constraints] || {}
+ unless constraints.is_a?(Hash)
+ block, constraints = constraints, {}
+ end
+ blocks = ((@scope[:blocks] || []) + [block]).compact
+ constraints = (@scope[:constraints] || {}).merge(constraints)
+ options.each { |k, v| constraints[k] = v if v.is_a?(Regexp) }
+
+ conditions[:path_info] = path
+ requirements = constraints.dup
+
+ path_regexp = Rack::Mount::Strexp.compile(path, constraints, SEPARATORS)
+ segment_keys = Rack::Mount::RegexpWithNamedGroups.new(path_regexp).names
+ constraints.reject! { |k, v| segment_keys.include?(k.to_s) }
+ conditions.merge!(constraints)
+
+ if via = options[:via]
+ via = Array(via).map { |m| m.to_s.upcase }
+ conditions[:request_method] = Regexp.union(*via)
+ end
+
+ defaults[:controller] = @scope[:controller].to_s if @scope[:controller]
+
+ if options[:to].respond_to?(:call)
+ app = options[:to]
+ defaults.delete(:controller)
+ defaults.delete(:action)
+ elsif options[:to].is_a?(String)
+ defaults[:controller], defaults[:action] = options[:to].split('#')
+ elsif options[:to].is_a?(Symbol)
+ defaults[:action] = options[:to].to_s
+ end
+ app ||= Routing::RouteSet::Dispatcher.new(:defaults => defaults)
+
+ if app.is_a?(Routing::RouteSet::Dispatcher)
+ unless defaults.include?(:controller) || segment_keys.include?("controller")
+ raise ArgumentError, "missing :controller"
+ end
+ unless defaults.include?(:action) || segment_keys.include?("action")
+ raise ArgumentError, "missing :action"
+ end
+ end
+
+ app = Constraints.new(app, blocks) if blocks.any?
+ @set.add_route(app, conditions, requirements, defaults, options[:as])
+
+ self
+ end
+
+ def redirect(path, options = {})
+ status = options[:status] || 301
+ lambda { |env|
+ req = Rack::Request.new(env)
+ url = req.scheme + '://' + req.host + path
+ [status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently']]
+ }
+ end
+
+ private
+ def map_method(method, *args, &block)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ options[:via] = method
+ args.push(options)
+ match(*args, &block)
+ self
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb
new file mode 100644
index 0000000000..f1431e7a37
--- /dev/null
+++ b/actionpack/lib/action_dispatch/routing/route.rb
@@ -0,0 +1,49 @@
+module ActionDispatch
+ module Routing
+ class Route #:nodoc:
+ attr_reader :app, :conditions, :defaults, :name
+ attr_reader :path, :requirements
+
+ def initialize(app, conditions = {}, requirements = {}, defaults = {}, name = nil)
+ @app = app
+ @defaults = defaults
+ @name = name
+
+ @requirements = requirements.merge(defaults)
+ @requirements.delete(:controller) if @requirements[:controller].is_a?(Regexp)
+ @requirements.delete_if { |k, v|
+ v == Regexp.compile("[^#{SEPARATORS.join}]+")
+ }
+
+ if path = conditions[:path_info]
+ @path = path
+ conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS)
+ end
+
+ @conditions = conditions.inject({}) { |h, (k, v)|
+ h[k] = Rack::Mount::RegexpWithNamedGroups.new(v)
+ h
+ }
+ end
+
+ def verb
+ if method = conditions[:request_method]
+ case method
+ when Regexp
+ method.source.upcase
+ else
+ method.to_s.upcase
+ end
+ end
+ end
+
+ def segment_keys
+ @segment_keys ||= conditions[:path_info].names.compact.map { |key| key.to_sym }
+ end
+
+ def to_a
+ [@app, @conditions, @defaults, @name]
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 25fdbf480e..c28df76f3f 100644
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,69 +1,63 @@
-module ActionController
+require 'rack/mount'
+require 'forwardable'
+
+module ActionDispatch
module Routing
class RouteSet #:nodoc:
- # Mapper instances are used to build routes. The object passed to the draw
- # block in config/routes.rb is a Mapper instance.
- #
- # Mapper instances have relatively few instance methods, in order to avoid
- # clashes with named routes.
- class Mapper #:doc:
- include ActionController::Resources
-
- def initialize(set) #:nodoc:
- @set = set
- end
+ NotFound = lambda { |env|
+ raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect} with #{env.inspect}"
+ }
+
+ PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
- # Create an unnamed route with the provided +path+ and +options+. See
- # ActionController::Routing for an introduction to routes.
- def connect(path, options = {})
- @set.add_route(path, options)
+ class Dispatcher
+ def initialize(options = {})
+ defaults = options[:defaults]
+ @glob_param = options.delete(:glob)
end
- # Creates a named route called "root" for matching the root level request.
- def root(options = {})
- if options.is_a?(Symbol)
- if source_route = @set.named_routes.routes[options]
- options = source_route.defaults.merge({ :conditions => source_route.conditions })
+ def call(env)
+ params = env[PARAMETERS_KEY]
+ merge_default_action!(params)
+ split_glob_param!(params) if @glob_param
+ params.each do |key, value|
+ if value.is_a?(String)
+ value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding)
+ params[key] = URI.unescape(value)
end
end
- named_route("root", '', options)
- end
-
- def named_route(name, path, options = {}) #:nodoc:
- @set.add_named_route(name, path, options)
- end
- # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
- # Example:
- #
- # map.namespace(:admin) do |admin|
- # admin.resources :products,
- # :has_many => [ :tags, :images, :variants ]
- # end
- #
- # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
- # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
- # Admin::TagsController.
- def namespace(name, options = {}, &block)
- if options[:namespace]
- with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
+ if env['action_controller.recognize']
+ [200, {}, params]
else
- with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
+ controller = controller(params)
+ controller.action(params[:action]).call(env)
end
end
- def method_missing(route_name, *args, &proc) #:nodoc:
- super unless args.length >= 1 && proc.nil?
- @set.add_named_route(route_name, *args)
- end
+ private
+ def controller(params)
+ if params && params.has_key?(:controller)
+ controller = "#{params[:controller].camelize}Controller"
+ ActiveSupport::Inflector.constantize(controller)
+ end
+ end
+
+ def merge_default_action!(params)
+ params[:action] ||= 'index'
+ end
+
+ def split_glob_param!(params)
+ params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) }
+ end
end
+
# A NamedRouteCollection instance is a collection of named routes, and also
# maintains an anonymous module that can be used to install helpers for the
# named routes.
class NamedRouteCollection #:nodoc:
include Enumerable
- include ActionController::Routing::Optimisation
attr_reader :routes, :helpers
def initialize
@@ -175,8 +169,6 @@ module ActionController
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
def #{selector}(*args) # def users_url(*args)
#
- #{generate_optimisation_block(route, kind)} # #{generate_optimisation_block(route, kind)}
- #
opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
args.first || {} # args.first || {}
else # else
@@ -216,28 +208,18 @@ module ActionController
clear!
end
- # Subclasses and plugins may override this method to specify a different
- # RouteBuilder instance, so that other route DSL's can be created.
- def builder
- @builder ||= RouteBuilder.new
- end
-
- def draw
+ def draw(&block)
clear!
- yield Mapper.new(self)
+ Mapper.new(self).instance_exec(DeprecatedMapper.new(self), &block)
+ @set.add_route(NotFound)
install_helpers
+ @set.freeze
end
def clear!
routes.clear
named_routes.clear
-
- @combined_regexp = nil
- @routes_by_controller = nil
-
- # This will force routing/recognition_optimization.rb
- # to refresh optimisations.
- clear_recognize_optimized!
+ @set = ::Rack::Mount::RouteSet.new(:parameters_key => PARAMETERS_KEY)
end
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
@@ -257,7 +239,7 @@ module ActionController
def configuration_file=(path)
add_configuration_file(path)
end
-
+
# Deprecated accessor
def configuration_file
configuration_files
@@ -296,31 +278,26 @@ module ActionController
def routes_changed_at
routes_changed_at = nil
-
+
configuration_files.each do |config|
config_changed_at = File.stat(config).mtime
if routes_changed_at.nil? || config_changed_at > routes_changed_at
- routes_changed_at = config_changed_at
+ routes_changed_at = config_changed_at
end
end
-
+
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)
+ def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil)
+ route = Route.new(app, conditions, requirements, defaults, name)
+ @set.add_route(*route)
+ named_routes[name] = route if name
routes << route
route
end
- def add_named_route(name, path, options = {})
- # TODO - is options EVER used?
- name = options[:name_prefix] + name.to_s if options[:name_prefix]
- named_routes[name.to_sym] = add_route(path, options)
- end
-
def options_as_params(options)
# If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like
@@ -356,24 +333,29 @@ module ActionController
generate(options, recall, :generate_extras)
end
- def generate(options, recall = {}, method=:generate)
- named_route_name = options.delete(:use_route)
- generate_all = options.delete(:generate_all)
- if named_route_name
- named_route = named_routes[named_route_name]
- options = named_route.parameter_shell.merge(options)
- end
+ def generate(options, recall = {}, method = :generate)
+ options, recall = options.dup, recall.dup
+ named_route = options.delete(:use_route)
options = options_as_params(options)
expire_on = build_expiry(options, recall)
- if options[:controller]
- options[:controller] = options[:controller].to_s
+ recall[:action] ||= 'index' if options[:controller] || recall[:controller]
+
+ if recall[:controller] && (!options.has_key?(:controller) || options[:controller] == recall[:controller])
+ options[:controller] = recall.delete(:controller)
+
+ if recall[:action] && (!options.has_key?(:action) || options[:action] == recall[:action])
+ options[:action] = recall.delete(:action)
+
+ if recall[:id] && (!options.has_key?(:id) || options[:id] == recall[:id])
+ options[:id] = recall.delete(:id)
+ end
+ end
end
- # if the controller has changed, make sure it changes relative to the
- # current controller module, if any. In other words, if we're currently
- # on admin/get, and the new controller is 'set', the new controller
- # should really be admin/set.
+
+ options[:controller] = options[:controller].to_s if options[:controller]
+
if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
old_parts = recall[:controller].split('/')
new_parts = options[:controller].split('/')
@@ -381,63 +363,54 @@ module ActionController
options[:controller] = parts.join('/')
end
- # drop the leading '/' on the controller name
options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
- merged = recall.merge(options)
-
- if named_route
- path = named_route.generate(options, merged, expire_on)
- if path.nil?
- raise_named_route_error(options, named_route, named_route_name)
- else
- return path
- end
- else
- merged[:action] ||= 'index'
- options[:action] ||= 'index'
-
- controller = merged[:controller]
- action = merged[:action]
- raise RoutingError, "Need controller and action!" unless controller && action
-
- if generate_all
- # Used by caching to expire all paths for a resource
- return routes.collect do |route|
- route.__send__(method, options, merged, expire_on)
- end.compact
- end
-
- # don't use the recalled keys when determining which routes to check
- routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }]
-
- routes.each_with_index do |route, index|
- results = route.__send__(method, options, merged, expire_on)
- if results && (!results.is_a?(Array) || results.first)
- return results
- end
- end
+ merged = options.merge(recall)
+ if options.has_key?(:action) && options[:action].nil?
+ options.delete(:action)
+ recall[:action] = 'index'
end
-
- raise RoutingError, "No route matches #{options.inspect}"
- end
-
- # try to give a helpful error message when named route generation fails
- def raise_named_route_error(options, named_route, named_route_name)
- diff = named_route.requirements.diff(options)
- unless diff.empty?
- raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
+ recall[:action] = options.delete(:action) if options[:action] == 'index'
+
+ path = _uri(named_route, options, recall)
+ if path && method == :generate_extras
+ uri = URI(path)
+ extras = uri.query ?
+ Rack::Utils.parse_nested_query(uri.query).keys.map { |k| k.to_sym } :
+ []
+ [uri.path, extras]
+ elsif path
+ path
else
- required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
- required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
- raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?"
+ raise ActionController::RoutingError, "No route matches #{options.inspect}"
end
+ rescue Rack::Mount::RoutingError
+ raise ActionController::RoutingError, "No route matches #{options.inspect}"
end
def call(env)
- request = ActionDispatch::Request.new(env)
- app = Routing::Routes.recognize(request)
- app.action(request.parameters[:action] || 'index').call(env)
+ @set.call(env)
+ rescue ActionController::RoutingError => e
+ raise e if env['action_controller.rescue_error'] == false
+
+ method, path = env['REQUEST_METHOD'].downcase.to_sym, env['PATH_INFO']
+
+ # Route was not recognized. Try to find out why (maybe wrong verb).
+ allows = HTTP_METHODS.select { |verb|
+ begin
+ recognize_path(path, {:method => verb}, false)
+ rescue ActionController::RoutingError
+ nil
+ end
+ }
+
+ if !HTTP_METHODS.include?(method)
+ raise ActionController::NotImplemented.new(*allows)
+ elsif !allows.empty?
+ raise ActionController::MethodNotAllowed.new(*allows)
+ else
+ raise e
+ end
end
def recognize(request)
@@ -446,33 +419,19 @@ module ActionController
"#{params[:controller].to_s.camelize}Controller".constantize
end
- def recognize_path(path, environment={})
- raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path."
- end
+ def recognize_path(path, environment = {}, rescue_error = true)
+ method = (environment[:method] || "GET").to_s.upcase
- def routes_by_controller
- @routes_by_controller ||= Hash.new do |controller_hash, controller|
- controller_hash[controller] = Hash.new do |action_hash, action|
- action_hash[action] = Hash.new do |key_hash, keys|
- key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
- end
- end
+ begin
+ env = Rack::MockRequest.env_for(path, {:method => method})
+ rescue URI::InvalidURIError => e
+ raise ActionController::RoutingError, e.message
end
- end
-
- def routes_for(options, merged, expire_on)
- raise "Need controller and action!" unless controller && action
- controller = merged[:controller]
- merged = options if expire_on[:controller]
- action = merged[:action] || 'index'
- routes_by_controller[controller][action][merged.keys][1]
- end
-
- def routes_for_controller_and_action_and_keys(controller, action, keys)
- routes.select do |route|
- route.matches_controller_and_action? controller, action
- end
+ env['action_controller.recognize'] = true
+ env['action_controller.rescue_error'] = rescue_error
+ status, headers, body = call(env)
+ body
end
# Subclasses and plugins may override this method to extract further attributes
@@ -480,6 +439,59 @@ module ActionController
def extract_request_environment(request)
{ :method => request.method }
end
+
+ private
+ def _uri(named_route, params, recall)
+ params = URISegment.wrap_values(params)
+ recall = URISegment.wrap_values(recall)
+
+ unless result = @set.generate(:path_info, named_route, params, recall)
+ return
+ end
+
+ uri, params = result
+ params.each do |k, v|
+ if v._value
+ params[k] = v._value
+ else
+ params.delete(k)
+ end
+ end
+
+ uri << "?#{Rack::Mount::Utils.build_nested_query(params)}" if uri && params.any?
+ uri
+ end
+
+ class URISegment < Struct.new(:_value, :_escape)
+ EXCLUDED = [:controller]
+
+ def self.wrap_values(hash)
+ hash.inject({}) { |h, (k, v)|
+ h[k] = new(v, !EXCLUDED.include?(k.to_sym))
+ h
+ }
+ end
+
+ extend Forwardable
+ def_delegators :_value, :==, :eql?, :hash
+
+ def to_param
+ @to_param ||= begin
+ if _value.is_a?(Array)
+ _value.map { |v| _escaped(v) }.join('/')
+ else
+ _escaped(_value)
+ end
+ end
+ end
+ alias_method :to_s, :to_param
+
+ private
+ def _escaped(value)
+ v = value.respond_to?(:to_param) ? value.to_param : value
+ _escape ? Rack::Mount::Utils.escape_uri(v) : v.to_s
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb
index 96f08f2355..0e4a92048f 100644
--- a/actionpack/lib/action_dispatch/testing/assertions.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions.rb
@@ -1,8 +1,21 @@
module ActionDispatch
module Assertions
- %w(response selector tag dom routing model).each do |kind|
- require "action_dispatch/testing/assertions/#{kind}"
- include const_get("#{kind.camelize}Assertions")
+ autoload :DomAssertions, 'action_dispatch/testing/assertions/dom'
+ autoload :ModelAssertions, 'action_dispatch/testing/assertions/model'
+ autoload :ResponseAssertions, 'action_dispatch/testing/assertions/response'
+ autoload :RoutingAssertions, 'action_dispatch/testing/assertions/routing'
+ autoload :SelectorAssertions, 'action_dispatch/testing/assertions/selector'
+ autoload :TagAssertions, 'action_dispatch/testing/assertions/tag'
+
+ extend ActiveSupport::Concern
+
+ included do
+ include DomAssertions
+ include ModelAssertions
+ include ResponseAssertions
+ include RoutingAssertions
+ include SelectorAssertions
+ include TagAssertions
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 2c4a3a356d..40d6f97b2a 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -396,8 +396,12 @@ module ActionDispatch
# Delegate unhandled messages to the current session instance.
def method_missing(sym, *args, &block)
reset! unless @integration_session
- returning @integration_session.__send__(sym, *args, &block) do
- copy_session_variables!
+ if @integration_session.respond_to?(sym)
+ returning @integration_session.__send__(sym, *args, &block) do
+ copy_session_variables!
+ end
+ else
+ super
end
end
end
@@ -486,7 +490,7 @@ module ActionDispatch
def self.app
# DEPRECATE Rails application fallback
# This should be set by the initializer
- @@app || (defined?(Rails.application) && Rails.application.new) || nil
+ @@app || (defined?(Rails.application) && Rails.application) || nil
end
def self.app=(app)
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 31e9c5ef9d..c33695770f 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -169,6 +169,15 @@ module ActionView #:nodoc:
include Helpers, Rendering, Partials, ::ERB::Util
+ def config
+ self.config = DEFAULT_CONFIG unless @config
+ @config
+ end
+
+ def config=(config)
+ @config = ActiveSupport::OrderedOptions.new.merge(config)
+ end
+
extend ActiveSupport::Memoizable
attr_accessor :base_path, :assigns, :template_extension, :formats
@@ -178,11 +187,11 @@ module ActionView #:nodoc:
def reset_formats(formats)
@formats = formats
- if defined?(ActionController)
+ if defined?(AbstractController::HashKey)
# This is expensive, but we need to reset this when the format is updated,
# which currently only happens
Thread.current[:format_locale_key] =
- ActionController::HashKey.get(self.class, formats, I18n.locale)
+ AbstractController::HashKey.get(self.class, formats, I18n.locale)
end
end
@@ -203,6 +212,11 @@ module ActionView #:nodoc:
@@cache_template_loading = nil
cattr_accessor :cache_template_loading
+ # :nodoc:
+ def self.xss_safe?
+ true
+ end
+
def self.cache_template_loading?
ActionController::Base.allow_concurrency || (cache_template_loading.nil? ? !ActiveSupport::Dependencies.load? : cache_template_loading)
end
@@ -236,16 +250,16 @@ module ActionView #:nodoc:
# they are in AC.
if controller.class.respond_to?(:_helper_serial)
klass = @views[controller.class._helper_serial] ||= Class.new(self) do
- const_set(:CONTROLLER_CLASS, controller.class)
-
# Try to make stack traces clearer
- def self.name
- "ActionView for #{CONTROLLER_CLASS}"
- end
-
- def inspect
- "#<#{self.class.name}>"
- end
+ class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
+ def self.name
+ "ActionView for #{controller.class}"
+ end
+
+ def inspect
+ "#<#{self.class.name}>"
+ end
+ ruby_eval
if controller.respond_to?(:_helpers)
include controller._helpers
diff --git a/actionpack/lib/action_view/erb/util.rb b/actionpack/lib/action_view/erb/util.rb
index f767a5e27e..aef859b3ac 100644
--- a/actionpack/lib/action_view/erb/util.rb
+++ b/actionpack/lib/action_view/erb/util.rb
@@ -23,6 +23,7 @@ class ERB
end
end
+ undef :h
alias h html_escape
module_function :html_escape
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index d63e8602f1..3d088678fc 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -1,10 +1,11 @@
+require 'active_support/benchmarkable'
+
module ActionView #:nodoc:
module Helpers #:nodoc:
autoload :ActiveModelHelper, 'action_view/helpers/active_model_helper'
autoload :AjaxHelper, 'action_view/helpers/ajax_helper'
autoload :AssetTagHelper, 'action_view/helpers/asset_tag_helper'
autoload :AtomFeedHelper, 'action_view/helpers/atom_feed_helper'
- autoload :BenchmarkHelper, 'action_view/helpers/benchmark_helper'
autoload :CacheHelper, 'action_view/helpers/cache_helper'
autoload :CaptureHelper, 'action_view/helpers/capture_helper'
autoload :DateHelper, 'action_view/helpers/date_helper'
@@ -33,10 +34,11 @@ module ActionView #:nodoc:
include SanitizeHelper::ClassMethods
end
+ include ActiveSupport::Benchmarkable
+
include ActiveModelHelper
include AssetTagHelper
include AtomFeedHelper
- include BenchmarkHelper
include CacheHelper
include CaptureHelper
include DateHelper
diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
index 7cc1e48572..c70f29f098 100644
--- a/actionpack/lib/action_view/helpers/active_model_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -191,19 +191,19 @@ module ActionView
options = params.extract_options!.symbolize_keys
objects = Array.wrap(options.delete(:object) || params).map do |object|
- unless object.respond_to?(:to_model)
- object = instance_variable_get("@#{object}")
- object = convert_to_model(object)
- else
- object = object.to_model
- options[:object_name] ||= object.class.model_name.human
+ object = instance_variable_get("@#{object}") unless object.respond_to?(:to_model)
+ object = convert_to_model(object)
+
+ if object.class.respond_to?(:model_name)
+ options[:object_name] ||= object.class.model_name.human.downcase
end
+
object
end
objects.compact!
-
count = objects.inject(0) {|sum, object| sum + object.errors.count }
+
unless count.zero?
html = {}
[:id, :class].each do |key|
@@ -216,16 +216,20 @@ module ActionView
end
options[:object_name] ||= params.first
- I18n.with_options :locale => options[:locale], :scope => [:activerecord, :errors, :template] do |locale|
+ I18n.with_options :locale => options[:locale], :scope => [:activemodel, :errors, :template] do |locale|
header_message = if options.include?(:header_message)
options[:header_message]
else
- object_name = options[:object_name].to_s.gsub('_', ' ')
- object_name = I18n.t(options[:object_name].to_s, :default => object_name, :scope => [:activerecord, :models], :count => 1)
- locale.t :header, :count => count, :model => object_name
+ locale.t :header, :count => count, :model => options[:object_name].to_s.gsub('_', ' ')
end
+
message = options.include?(:message) ? options[:message] : locale.t(:body)
- error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, ERB::Util.html_escape(msg)) } }.join
+
+ error_messages = objects.sum do |object|
+ object.errors.full_messages.map do |msg|
+ content_tag(:li, ERB::Util.html_escape(msg))
+ end
+ end.join
contents = ''
contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index faa7f2e2e9..15b70ecff5 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -133,9 +133,13 @@ module ActionView
# change. You can use something like Live HTTP Headers for Firefox to verify
# that the cache is indeed working.
module AssetTagHelper
- ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public"
- JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts"
- STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets"
+ assets_dir = defined?(Rails.public_path) ? Rails.public_path : "public"
+ ActionView::DEFAULT_CONFIG = {
+ :assets_dir => assets_dir,
+ :javascripts_dir => "#{assets_dir}/javascripts",
+ :stylesheets_dir => "#{assets_dir}/stylesheets",
+ }
+
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
# Returns a link tag that browsers and news readers can use to auto-detect
@@ -280,7 +284,7 @@ module ActionView
if concat || (ActionController::Base.perform_caching && cache)
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
- joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? ASSETS_DIR : JAVASCRIPTS_DIR, joined_javascript_name)
+ joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.javascripts_dir, joined_javascript_name)
unless ActionController::Base.perform_caching && File.exists?(joined_javascript_path)
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive))
@@ -431,7 +435,7 @@ module ActionView
if concat || (ActionController::Base.perform_caching && cache)
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
- joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? ASSETS_DIR : STYLESHEETS_DIR, joined_stylesheet_name)
+ joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.stylesheets_dir, joined_stylesheet_name)
unless ActionController::Base.perform_caching && File.exists?(joined_stylesheet_path)
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive))
@@ -630,11 +634,11 @@ module ActionView
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
# roots. Rewrite the asset path for cache-busting asset ids. Include
# asset host, if configured, with the correct request protocol.
- def compute_public_path(source, dir, ext = nil, include_host = true)
+ def compute_public_path(source, dir, ext = nil, include_host = true)
has_request = @controller.respond_to?(:request)
source_ext = File.extname(source)[1..-1]
- if ext && !is_uri?(source) && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}"))))
+ if ext && !is_uri?(source) && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}"))))
source += ".#{ext}"
end
@@ -700,7 +704,7 @@ module ActionView
if @@cache_asset_timestamps && (asset_id = @@asset_timestamps_cache[source])
asset_id
else
- path = File.join(ASSETS_DIR, source)
+ path = File.join(config.assets_dir, source)
asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
if @@cache_asset_timestamps
@@ -743,20 +747,20 @@ module ActionView
def expand_javascript_sources(sources, recursive = false)
if sources.include?(:all)
- all_javascript_files = collect_asset_files(JAVASCRIPTS_DIR, ('**' if recursive), '*.js')
+ all_javascript_files = collect_asset_files(config.javascripts_dir, ('**' if recursive), '*.js')
((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
else
expanded_sources = sources.collect do |source|
determine_source(source, @@javascript_expansions)
end.flatten
- expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
+ expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(config.javascripts_dir, "application.js"))
expanded_sources
end
end
def expand_stylesheet_sources(sources, recursive)
if sources.first == :all
- collect_asset_files(STYLESHEETS_DIR, ('**' if recursive), '*.css')
+ collect_asset_files(config.stylesheets_dir, ('**' if recursive), '*.css')
else
sources.collect do |source|
determine_source(source, @@stylesheet_expansions)
@@ -803,7 +807,7 @@ module ActionView
end
def asset_file_path(path)
- File.join(ASSETS_DIR, path.split('?').first)
+ File.join(config.assets_dir, path.split('?').first)
end
def asset_file_path!(path)
diff --git a/actionpack/lib/action_view/helpers/benchmark_helper.rb b/actionpack/lib/action_view/helpers/benchmark_helper.rb
deleted file mode 100644
index c13a069a35..0000000000
--- a/actionpack/lib/action_view/helpers/benchmark_helper.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'active_support/core_ext/benchmark'
-
-module ActionView
- module Helpers
- # This helper offers a method to measure the execution time of a block
- # in a template.
- module BenchmarkHelper
- # Allows you to measure the execution time of a block
- # in a template and records the result to the log. Wrap this block around
- # expensive operations or possible bottlenecks to get a time reading
- # for the operation. For example, let's say you thought your file
- # processing method was taking too long; you could wrap it in a benchmark block.
- #
- # <% benchmark "Process data files" do %>
- # <%= expensive_files_operation %>
- # <% end %>
- #
- # That would add something like "Process data files (345.2ms)" to the log,
- # which you can then use to compare timings when optimizing your code.
- #
- # You may give an optional logger level as the :level option.
- # (:debug, :info, :warn, :error); the default value is :info.
- #
- # <% benchmark "Low-level files", :level => :debug do %>
- # <%= lowlevel_files_operation %>
- # <% end %>
- #
- # Finally, you can pass true as the third argument to silence all log activity
- # inside the block. This is great for boiling down a noisy block to just a single statement:
- #
- # <% benchmark "Process data files", :level => :info, :silence => true do %>
- # <%= expensive_and_chatty_files_operation %>
- # <% end %>
- def benchmark(message = "Benchmarking", options = {})
- if controller.logger
- if options.is_a?(Symbol)
- ActiveSupport::Deprecation.warn("use benchmark('#{message}', :level => :#{options}) instead", caller)
- options = { :level => options, :silence => false }
- else
- options.assert_valid_keys(:level, :silence)
- options[:level] ||= :info
- end
-
- result = nil
- ms = Benchmark.ms { result = options[:silence] ? controller.logger.silence { yield } : yield }
- controller.logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
- result
- else
- yield
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index c46b39fc23..d0c66eda60 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -963,7 +963,7 @@ module ActionView
end
end
- (field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
+ (field_helpers - %w(label check_box radio_button fields_for hidden_field)).each do |selector|
src = <<-end_src
def #{selector}(method, options = {}) # def text_field(method, options = {})
@template.send( # @template.send(
@@ -1022,6 +1022,11 @@ module ActionView
def radio_button(method, tag_value, options = {})
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
end
+
+ def hidden_field(method, options = {})
+ @emitted_hidden_id = true if method == :id
+ @template.hidden_field(@object_name, method, objectify_options(options))
+ end
def error_message_on(method, *args)
@template.error_message_on(@object, method, *args)
@@ -1035,6 +1040,10 @@ module ActionView
@template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
end
+ def emitted_hidden_id?
+ @emitted_hidden_id
+ end
+
private
def objectify_options(options)
@default_options.merge(options.merge(:object => @object))
@@ -1069,8 +1078,8 @@ module ActionView
@template.fields_for(name, object, *args, &block)
else
@template.fields_for(name, object, *args) do |builder|
- @template.concat builder.hidden_field(:id)
block.call(builder)
+ @template.concat builder.hidden_field(:id) unless builder.emitted_hidden_id?
end
end
end
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index 4aed10f640..564f12c955 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -21,7 +21,7 @@ module ActionView
# Delegates to I18n.localize with no additional functionality.
def localize(*args)
- I18n.localize *args
+ I18n.localize(*args)
end
alias :l :localize
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index e651bc17a9..5b136d4f54 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -83,7 +83,7 @@ module ActionView
options
when Hash
options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
- escape = options.key?(:escape) ? options.delete(:escape) : true
+ escape = options.key?(:escape) ? options.delete(:escape) : false
@controller.send(:url_for, options)
when :back
escape = false
@@ -93,7 +93,7 @@ module ActionView
polymorphic_path(options)
end
- (escape ? escape_once(url) : url).html_safe!
+ escape ? escape_once(url).html_safe! : url
end
# Creates a link tag of the given +name+ using a URL created by the set
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index 84d94fd700..5e2a92b89a 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -102,7 +102,7 @@
minute: "Minute"
second: "Seconds"
- activerecord:
+ activemodel:
errors:
template:
header:
@@ -114,4 +114,4 @@
support:
select:
# default value for :prompt => true in FormOptionsHelper
- prompt: "Please select" \ No newline at end of file
+ prompt: "Please select"
diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb
index b6f5b9b6d1..7006a5b968 100644
--- a/actionpack/lib/action_view/render/rendering.rb
+++ b/actionpack/lib/action_view/render/rendering.rb
@@ -71,12 +71,10 @@ module ActionView
# In this case, the layout would receive the block passed into <tt>render :layout</tt>,
# and the Struct specified in the layout would be passed into the block. The result
# would be <html>Hello David</html>.
- def _layout_for(name = nil)
+ def _layout_for(name = nil, &block)
return @_content_for[name || :layout] if !block_given? || name
- with_output_buffer do
- return yield
- end
+ capture(&block)
end
def _render_inline(inline, layout, options)
@@ -123,7 +121,6 @@ module ActionView
template.render(self, locals)
end
- @cached_content_for_layout = content
@_content_for[:layout] = content
if layout
@@ -134,4 +131,4 @@ module ActionView
content
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/safe_buffer.rb b/actionpack/lib/action_view/safe_buffer.rb
index 8ba9cd80d6..09f44ab26f 100644
--- a/actionpack/lib/action_view/safe_buffer.rb
+++ b/actionpack/lib/action_view/safe_buffer.rb
@@ -5,7 +5,7 @@ module ActionView #:nodoc:
if value.html_safe?
super(value)
else
- super(CGI.escapeHTML(value))
+ super(ERB::Util.h(value))
end
end
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index a780ab8d85..88aeb4b053 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -37,11 +37,16 @@ module ActionView
self.erb_trim_mode = '-'
self.default_format = Mime::HTML
+
+ cattr_accessor :erubis_implementation
+ self.erubis_implementation = Erubis
def compile(template)
- magic = $1 if template.source =~ /\A(<%#.*coding[:=]\s*(\S+)\s*-?%>)/
- erb = "#{magic}<% __in_erb_template=true %>#{template.source}"
- Erubis.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src
+ source = template.source.gsub(/\A(<%(#.*coding[:=]\s*(\S+)\s*)-?%>)\s*\n?/, '')
+ erb = "<% __in_erb_template=true %>#{source}"
+ result = self.class.erubis_implementation.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src
+ result = "#{$2}\n#{result}" if $2
+ result
end
end
end
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index f5591ead09..7336114e1b 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -121,7 +121,7 @@ module ActionView
# # :api: plugin
def path_to_details(path)
# [:erb, :format => :html, :locale => :en, :partial => true/false]
- if m = path.match(%r'(?:^|/)(_)?[\w-]+(\.[\w-]+)*\.(\w+)$')
+ if m = path.match(%r'(?:^|/)(_)?[\w-]+((?:\.[\w-]+)*)\.(\w+)$')
partial = m[1] == '_'
details = (m[2]||"").split('.').reject { |e| e.empty? }
handler = Template.handler_class_for_extension(m[3])
diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb
index 0f64c23649..d1970ca3c7 100644
--- a/actionpack/lib/action_view/template/template.rb
+++ b/actionpack/lib/action_view/template/template.rb
@@ -27,10 +27,10 @@ module ActionView
end
def render(view, locals, &block)
- ActiveSupport::Orchestra.instrument(:render_template, :identifier => identifier) do
+ ActiveSupport::Notifications.instrument(:render_template, :identifier => identifier) do
method_name = compile(locals, view)
view.send(method_name, locals, &block)
- end.result
+ end
rescue Exception => e
if e.is_a?(TemplateError)
e.sub_template_of(self)
diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb
index f7d0df5ba0..f6e011a5ab 100644
--- a/actionpack/lib/action_view/template/text.rb
+++ b/actionpack/lib/action_view/template/text.rb
@@ -1,6 +1,8 @@
module ActionView #:nodoc:
class TextTemplate < String #:nodoc:
- def initialize(string, content_type = Mime[:html])
+ HTML = Mime[:html]
+
+ def initialize(string, content_type = HTML)
super(string.to_s)
@content_type = Mime[content_type] || content_type
end
@@ -14,11 +16,11 @@ module ActionView #:nodoc:
end
def inspect
- 'inline template'
+ 'text template'
end
def render(*args)
- self
+ to_s
end
def mime_type
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 8beda24aba..86bbad822d 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -120,6 +120,7 @@ module ActionView
def _view
view = ActionView::Base.new(ActionController::Base.view_paths, _assigns, @controller)
view.class.send :include, _helpers
+ view.output_buffer = self.output_buffer
view
end
diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb
index 5a363c9aa5..efcd68e5c8 100644
--- a/actionpack/test/abstract/helper_test.rb
+++ b/actionpack/test/abstract/helper_test.rb
@@ -1,17 +1,17 @@
require 'abstract_unit'
+ActionController::Base.helpers_dir = File.dirname(__FILE__) + '/../fixtures/helpers'
+
module AbstractController
module Testing
class ControllerWithHelpers < AbstractController::Base
include AbstractController::RenderingController
include Helpers
-
- def render(string)
- super(:_template_name => string)
+
+ def with_module
+ render :inline => "Module <%= included_method %>"
end
-
- append_view_path File.expand_path(File.join(File.dirname(__FILE__), "views"))
end
module HelperyTest
@@ -20,24 +20,61 @@ module AbstractController
end
end
- class MyHelpers1 < ControllerWithHelpers
+ class AbstractHelpers < ControllerWithHelpers
helper(HelperyTest) do
def helpery_test
"World"
end
end
-
- def index
- render "helper_test.erb"
+
+ helper :abc
+
+ def with_block
+ render :inline => "Hello <%= helpery_test %>"
+ end
+
+ def with_symbol
+ render :inline => "I respond to bare_a: <%= respond_to?(:bare_a) %>"
+ end
+ end
+
+ class AbstractHelpersBlock < ControllerWithHelpers
+ helper do
+ include ::AbstractController::Testing::HelperyTest
end
end
-
+
class TestHelpers < ActiveSupport::TestCase
- def test_helpers
- controller = MyHelpers1.new
- controller.process(:index)
- assert_equal "Hello World : Included", controller.response_body
+
+ def setup
+ @controller = AbstractHelpers.new
end
+
+ def test_helpers_with_block
+ @controller.process(:with_block)
+ assert_equal "Hello World", @controller.response_body
+ end
+
+ def test_helpers_with_module
+ @controller.process(:with_module)
+ assert_equal "Module Included", @controller.response_body
+ end
+
+ def test_helpers_with_symbol
+ @controller.process(:with_symbol)
+ assert_equal "I respond to bare_a: true", @controller.response_body
+ end
+
+ def test_declare_missing_helper
+ assert_raise(MissingSourceFile) { AbstractHelpers.helper :missing }
+ end
+
+ def test_helpers_with_module_through_block
+ @controller = AbstractHelpersBlock.new
+ @controller.process(:with_module)
+ assert_equal "Module Included", @controller.response_body
+ end
+
end
end
diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb
index 453d31826e..ae2f1bf1f2 100644
--- a/actionpack/test/abstract/layouts_test.rb
+++ b/actionpack/test/abstract/layouts_test.rb
@@ -17,17 +17,6 @@ module AbstractControllerTests
"layouts/omg.erb" => "OMGHI2U <%= yield %>",
"layouts/with_false_layout.erb" => "False Layout <%= yield %>"
)]
-
- def self.controller_path
- @controller_path ||= self.name.sub(/Controller$/, '').underscore
- end
-
- def controller_path() self.class.controller_path end
-
- def render_to_body(options)
- options[:_layout] = _default_layout({})
- super
- end
end
class Blank < Base
@@ -44,6 +33,22 @@ module AbstractControllerTests
def index
render :_template => ActionView::TextTemplate.new("Hello string!")
end
+
+ def overwrite_default
+ render :_template => ActionView::TextTemplate.new("Hello string!"), :layout => :default
+ end
+
+ def overwrite_false
+ render :_template => ActionView::TextTemplate.new("Hello string!"), :layout => false
+ end
+
+ def overwrite_string
+ render :_template => ActionView::TextTemplate.new("Hello string!"), :layout => "omg"
+ end
+
+ def overwrite_skip
+ render :text => "Hello text!"
+ end
end
class WithStringChild < WithString
@@ -153,7 +158,31 @@ module AbstractControllerTests
controller.process(:index)
assert_equal "With String Hello string!", controller.response_body
end
-
+
+ test "when layout is overwriten by :default in render, render default layout" do
+ controller = WithString.new
+ controller.process(:overwrite_default)
+ assert_equal "With String Hello string!", controller.response_body
+ end
+
+ test "when layout is overwriten by string in render, render new layout" do
+ controller = WithString.new
+ controller.process(:overwrite_string)
+ assert_equal "OMGHI2U Hello string!", controller.response_body
+ end
+
+ test "when layout is overwriten by false in render, render no layout" do
+ controller = WithString.new
+ controller.process(:overwrite_false)
+ assert_equal "Hello string!", controller.response_body
+ end
+
+ test "when text is rendered, render no layout" do
+ controller = WithString.new
+ controller.process(:overwrite_skip)
+ assert_equal "Hello text!", controller.response_body
+ end
+
test "when layout is specified as a string, but the layout is missing, raise an exception" do
assert_raises(ActionView::MissingTemplate) { WithMissingLayout.new.process(:index) }
end
@@ -183,7 +212,7 @@ module AbstractControllerTests
end
test "when the layout is specified as a symbol and the method doesn't exist, raise an exception" do
- assert_raises(NoMethodError, /:nilz/) { WithSymbolAndNoMethod.new.process(:index) }
+ assert_raises(NoMethodError) { WithSymbolAndNoMethod.new.process(:index) }
end
test "when the layout is specified as a symbol and the method returns something besides a string/false/nil, raise an exception" do
diff --git a/actionpack/test/abstract/localized_cache_test.rb b/actionpack/test/abstract/localized_cache_test.rb
new file mode 100644
index 0000000000..6f9bb693f7
--- /dev/null
+++ b/actionpack/test/abstract/localized_cache_test.rb
@@ -0,0 +1,57 @@
+require 'abstract_unit'
+
+module AbstractController
+ module Testing
+
+ class CachedController < AbstractController::Base
+ include AbstractController::RenderingController
+ include AbstractController::LocalizedCache
+
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "default.erb" => "With Default",
+ "template.erb" => "With Template",
+ "some/file.erb" => "With File",
+ "template_name.erb" => "With Template Name"
+ )]
+ end
+
+ class TestLocalizedCache < ActiveSupport::TestCase
+
+ def setup
+ @controller = CachedController.new
+ CachedController.clear_template_caches!
+ end
+
+ def test_templates_are_cached
+ @controller.render :template => "default.erb"
+ assert_equal "With Default", @controller.response_body
+
+ cached = @controller.class.template_cache
+ assert_equal 1, cached.size
+ assert_kind_of ActionView::Template, cached.values.first["default.erb"]
+ end
+
+ def test_cache_is_used
+ CachedController.new.render :template => "default.erb"
+
+ @controller.expects(:find_template).never
+ @controller.render :template => "default.erb"
+
+ assert_equal 1, @controller.class.template_cache.size
+ end
+
+ def test_cache_changes_with_locale
+ CachedController.new.render :template => "default.erb"
+
+ I18n.locale = :es
+ @controller.render :template => "default.erb"
+
+ assert_equal 2, @controller.class.template_cache.size
+ ensure
+ I18n.locale = :en
+ end
+
+ end
+
+ end
+end
diff --git a/actionpack/test/abstract/render_test.rb b/actionpack/test/abstract/render_test.rb
new file mode 100644
index 0000000000..45a4763fe4
--- /dev/null
+++ b/actionpack/test/abstract/render_test.rb
@@ -0,0 +1,88 @@
+require 'abstract_unit'
+
+module AbstractController
+ module Testing
+
+ class ControllerRenderer < AbstractController::Base
+ include AbstractController::RenderingController
+
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "default.erb" => "With Default",
+ "template.erb" => "With Template",
+ "some/file.erb" => "With File",
+ "template_name.erb" => "With Template Name"
+ )]
+
+ def template
+ render :template => "template"
+ end
+
+ def file
+ render :file => "some/file"
+ end
+
+ def inline
+ render :inline => "With <%= :Inline %>"
+ end
+
+ def text
+ render :text => "With Text"
+ end
+
+ def default
+ render
+ end
+
+ def template_name
+ render :_template_name => :template_name
+ end
+
+ def object
+ render :_template => ActionView::TextTemplate.new("With Object")
+ end
+ end
+
+ class TestRenderer < ActiveSupport::TestCase
+
+ def setup
+ @controller = ControllerRenderer.new
+ end
+
+ def test_render_template
+ @controller.process(:template)
+ assert_equal "With Template", @controller.response_body
+ end
+
+ def test_render_file
+ @controller.process(:file)
+ assert_equal "With File", @controller.response_body
+ end
+
+ def test_render_inline
+ @controller.process(:inline)
+ assert_equal "With Inline", @controller.response_body
+ end
+
+ def test_render_text
+ @controller.process(:text)
+ assert_equal "With Text", @controller.response_body
+ end
+
+ def test_render_default
+ @controller.process(:default)
+ assert_equal "With Default", @controller.response_body
+ end
+
+ def test_render_template_name
+ @controller.process(:template_name)
+ assert_equal "With Template Name", @controller.response_body
+ end
+
+ def test_render_object
+ @controller.process(:object)
+ assert_equal "With Object", @controller.response_body
+ end
+
+ end
+ end
+end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 4820f00aa1..775cfc82bf 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -1,20 +1,18 @@
-$:.unshift(File.dirname(__FILE__) + '/../lib')
-$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib')
-$:.unshift(File.dirname(__FILE__) + '/../../activemodel/lib')
+root = File.expand_path('../../..', __FILE__)
+begin
+ require "#{root}/vendor/gems/environment"
+rescue LoadError
+ $:.unshift "#{root}/activesupport/lib"
+ $:.unshift "#{root}/activemodel/lib"
+end
+
+lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
+$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
$:.unshift(File.dirname(__FILE__) + '/lib')
$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
$:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers')
-bundler = File.join(File.dirname(__FILE__), '..', 'vendor', 'gems', 'environment')
-require bundler if File.exist?("#{bundler}.rb")
-
-begin
- %w( rack rack/test sqlite3 ).each { |lib| require lib }
-rescue LoadError => e
- abort e.message
-end
-
ENV['TMPDIR'] = File.join(File.dirname(__FILE__), 'tmp')
require 'test/unit'
@@ -103,6 +101,26 @@ class ActionController::IntegrationTest < ActiveSupport::TestCase
self.app = build_app
+ class StubDispatcher
+ def self.new(*args)
+ lambda { |env|
+ params = env['action_dispatch.request.path_parameters']
+ controller, action = params[:controller], params[:action]
+ [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]]
+ }
+ end
+ end
+
+ def self.stub_controllers
+ old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher
+ ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
+ ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher }
+ yield ActionDispatch::Routing::RouteSet.new
+ ensure
+ ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
+ ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher }
+ end
+
def with_routing(&block)
real_routes = ActionController::Routing::Routes
ActionController::Routing.module_eval { remove_const :Routes }
@@ -173,8 +191,8 @@ class ::ApplicationController < ActionController::Base
end
module ActionController
- class << Routing
- def possible_controllers
+ module Routing
+ def self.possible_controllers
@@possible_controllers ||= []
end
end
diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb
index 37f1f6dff8..ad744421db 100644
--- a/actionpack/test/activerecord/polymorphic_routes_test.rb
+++ b/actionpack/test/activerecord/polymorphic_routes_test.rb
@@ -98,14 +98,6 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
- def test_formatted_url_helper_is_deprecated
- with_test_routes do
- assert_deprecated do
- formatted_polymorphic_url([@project, :pdf])
- end
- end
- end
-
def test_format_option
with_test_routes do
@project.save
@@ -251,6 +243,12 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
+ def test_with_array_containing_symbols
+ with_test_routes do
+ assert_equal "http://example.com/series/new", polymorphic_url([:new, :series])
+ end
+ end
+
def test_with_hash
with_test_routes do
@project.save
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index b97ceb4594..b57550a69a 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -10,12 +10,12 @@ module Submodule
def public_action
render :nothing => true
end
-
+
hide_action :hidden_action
def hidden_action
raise "Noooo!"
end
-
+
def another_hidden_action
end
hide_action :another_hidden_action
@@ -30,25 +30,25 @@ class NonEmptyController < ActionController::Base
def public_action
render :nothing => true
end
-
+
hide_action :hidden_action
def hidden_action
end
end
class MethodMissingController < ActionController::Base
-
+
hide_action :shouldnt_be_called
def shouldnt_be_called
raise "NO WAY!"
end
-
+
protected
-
+
def method_missing(selector)
render :text => selector.to_s
end
-
+
end
class DefaultUrlOptionsController < ActionController::Base
@@ -79,7 +79,7 @@ class ControllerInstanceTests < Test::Unit::TestCase
@empty = EmptyController.new
@contained = Submodule::ContainedEmptyController.new
@empty_controllers = [@empty, @contained, Submodule::SubclassedController.new]
-
+
@non_empty_controllers = [NonEmptyController.new,
Submodule::ContainedNonEmptyController.new]
end
@@ -135,24 +135,24 @@ class PerformActionTest < ActionController::TestCase
rescue_action_in_public!
end
-
+
def test_get_on_priv_should_show_selector
use_controller MethodMissingController
get :shouldnt_be_called
assert_response :success
assert_equal 'shouldnt_be_called', @response.body
end
-
+
def test_method_missing_is_not_an_action_name
use_controller MethodMissingController
assert ! @controller.__send__(:action_method?, 'method_missing')
-
+
get :method_missing
assert_response :success
assert_equal 'method_missing', @response.body
end
-
+
def test_get_on_hidden_should_fail
use_controller NonEmptyController
assert_raise(ActionController::UnknownAction) { get :hidden_action }
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 69b0eb5e3e..3ce90b6ccf 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -149,7 +149,9 @@ end
class ActionCachingTestController < ActionController::Base
rescue_from(Exception) { head 500 }
- rescue_from(ActiveRecord::RecordNotFound) { head :not_found }
+ if defined? ActiveRecord
+ rescue_from(ActiveRecord::RecordNotFound) { head :not_found }
+ end
caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour
caches_action :show, :cache_path => 'http://test.host/custom/show'
@@ -226,12 +228,16 @@ class ActionCachingMockController
@mock_url_for
end
+ def params
+ request.parameters
+ end
+
def request
mocked_path = @mock_path
Object.new.instance_eval(<<-EVAL)
def path; '#{@mock_path}' end
def format; 'all' end
- def cache_format; nil end
+ def parameters; {:format => nil}; end
self
EVAL
end
@@ -464,7 +470,7 @@ class ActionCacheTest < ActionController::TestCase
@mock_controller.mock_url_for = 'http://example.org/'
@mock_controller.mock_path = '/'
- assert_equal 'example.org/index', @path_class.path_for(@mock_controller, {})
+ assert_equal 'example.org/index', @path_class.new(@mock_controller, {}).path
end
def test_file_extensions
@@ -474,11 +480,13 @@ class ActionCacheTest < ActionController::TestCase
assert_response :success
end
- def test_record_not_found_returns_404_for_multiple_requests
- get :record_not_found
- assert_response 404
- get :record_not_found
- assert_response 404
+ if defined? ActiveRecord
+ def test_record_not_found_returns_404_for_multiple_requests
+ get :record_not_found
+ assert_response 404
+ get :record_not_found
+ assert_response 404
+ end
end
def test_four_oh_four_returns_404_for_multiple_requests
@@ -624,20 +632,13 @@ class FragmentCachingTest < ActionController::TestCase
def test_fragment_for_logging
fragment_computed = false
-
- listener = []
- ActiveSupport::Orchestra.register listener
+ ActiveSupport::Notifications.queue.expects(:publish).times(2)
buffer = 'generated till now -> '
@controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
- assert_equal 1, listener.count { |e| e.name == :fragment_exist? }
- assert_equal 1, listener.count { |e| e.name == :write_fragment }
-
assert fragment_computed
assert_equal 'generated till now -> ', buffer
- ensure
- ActiveSupport::Orchestra.unregister listener
end
end
diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb
index 7199da3441..53d4364576 100644
--- a/actionpack/test/controller/cookie_test.rb
+++ b/actionpack/test/controller/cookie_test.rb
@@ -106,7 +106,7 @@ class CookieTest < ActionController::TestCase
def test_cookiejar_accessor
@request.cookies["user_name"] = "david"
@controller.request = @request
- jar = ActionController::CookieJar.new(@controller)
+ jar = ActionController::CookieJar.build(@controller.request, @controller.response)
assert_equal "david", jar["user_name"]
assert_equal nil, jar["something_else"]
end
@@ -114,19 +114,25 @@ class CookieTest < ActionController::TestCase
def test_cookiejar_accessor_with_array_value
@request.cookies["pages"] = %w{1 2 3}
@controller.request = @request
- jar = ActionController::CookieJar.new(@controller)
+ jar = ActionController::CookieJar.build(@controller.request, @controller.response)
assert_equal %w{1 2 3}, jar["pages"]
end
+ def test_cookiejar_delete_removes_item_and_returns_its_value
+ @request.cookies["user_name"] = "david"
+ @controller.response = @response
+ jar = ActionController::CookieJar.build(@controller.request, @controller.response)
+ assert_equal "david", jar.delete("user_name")
+ end
+
def test_delete_cookie_with_path
get :delete_cookie_with_path
assert_cookie_header "user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT"
end
def test_cookies_persist_throughout_request
- get :authenticate
- cookies = @controller.send(:cookies)
- assert_equal 'david', cookies['user_name']
+ response = get :authenticate
+ assert response.headers["Set-Cookie"] =~ /user_name=david/
end
private
diff --git a/actionpack/test/controller/filter_params_test.rb b/actionpack/test/controller/filter_params_test.rb
index 19232c6bc9..43bef34885 100644
--- a/actionpack/test/controller/filter_params_test.rb
+++ b/actionpack/test/controller/filter_params_test.rb
@@ -19,23 +19,23 @@ class FilterParamTest < ActionController::TestCase
def method_missing(method, *args)
@logged ||= []
- @logged << args.first
+ @logged << args.first unless block_given?
+ @logged << yield if block_given?
end
end
setup :set_logger
+ def test_filter_parameters_must_have_one_word
+ assert_raises RuntimeError do
+ FilterParamController.filter_parameter_logging
+ end
+ end
+
def test_filter_parameters
assert FilterParamController.respond_to?(:filter_parameter_logging)
- assert !@controller.respond_to?(:filter_parameters)
-
- FilterParamController.filter_parameter_logging
- assert @controller.respond_to?(:filter_parameters)
- test_hashes = [[{},{},[]],
- [{'foo'=>nil},{'foo'=>nil},[]],
- [{'foo'=>'bar'},{'foo'=>'bar'},[]],
- [{'foo'=>1},{'foo'=>1},[]],
+ test_hashes = [
[{'foo'=>'bar'},{'foo'=>'bar'},%w'food'],
[{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'],
[{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'],
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index 2da97a9d86..8445428e8f 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'active_support/core_ext/symbol'
class ActionController::Base
class << self
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 23149fee27..b9be163904 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -3,12 +3,6 @@ require 'active_support/core_ext/kernel/reporting'
ActionController::Base.helpers_dir = File.dirname(__FILE__) + '/../fixtures/helpers'
-class TestController < ActionController::Base
- attr_accessor :delegate_attr
- def delegate_method() end
- def rescue_action(e) raise end
-end
-
module Fun
class GamesController < ActionController::Base
def render_hello_world
@@ -38,6 +32,12 @@ module LocalAbcHelper
end
class HelperTest < Test::Unit::TestCase
+ class TestController < ActionController::Base
+ attr_accessor :delegate_attr
+ def delegate_method() end
+ def rescue_action(e) raise end
+ end
+
def setup
# Increment symbol counter.
@symbol = (@@counter ||= 'A0').succ!.dup
@@ -57,40 +57,6 @@ class HelperTest < Test::Unit::TestCase
assert_equal [], missing_methods
end
- def test_declare_helper
- require 'abc_helper'
- self.test_helper = AbcHelper
- assert_equal expected_helper_methods, missing_methods
- assert_nothing_raised { @controller_class.helper :abc }
- assert_equal [], missing_methods
- end
-
- def test_declare_missing_helper
- assert_equal expected_helper_methods, missing_methods
- assert_raise(MissingSourceFile) { @controller_class.helper :missing }
- end
-
- def test_declare_missing_file_from_helper
- require 'broken_helper'
- rescue LoadError => e
- assert_nil(/\bbroken_helper\b/.match(e.to_s)[1])
- end
-
- def test_helper_block
- assert_nothing_raised {
- @controller_class.helper { def block_helper_method; end }
- }
- assert master_helper_methods.include?('block_helper_method')
- end
-
- def test_helper_block_include
- assert_equal expected_helper_methods, missing_methods
- assert_nothing_raised {
- @controller_class.helper { include HelperTest::TestHelper }
- }
- assert [], missing_methods
- end
-
def test_helper_method
assert_nothing_raised { @controller_class.helper_method :delegate_method }
assert master_helper_methods.include?('delegate_method')
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 508364d0b5..624b14e69b 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -199,6 +199,24 @@ class IntegrationTestTest < Test::Unit::TestCase
assert_equal ::ActionController::Integration::Session, session2.class
assert_not_equal session1, session2
end
+
+ # RSpec mixes Matchers (which has a #method_missing) into
+ # IntegrationTest's superclass. Make sure IntegrationTest does not
+ # try to delegate these methods to the session object.
+ def test_does_not_prevent_method_missing_passing_up_to_ancestors
+ mixin = Module.new do
+ def method_missing(name, *args)
+ name.to_s == 'foo' ? 'pass' : super
+ end
+ end
+ @test.class.superclass.__send__(:include, mixin)
+ begin
+ assert_equal 'pass', @test.foo
+ ensure
+ # leave other tests as unaffected as possible
+ mixin.__send__(:remove_method, :method_missing)
+ end
+ end
end
# Tests that integration tests don't call Controller test methods for processing.
@@ -372,7 +390,7 @@ class IntegrationProcessTest < ActionController::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do |map|
- map.connect "/:action", :controller => "integration_process_test/integration"
+ match ':action', :to => ::IntegrationProcessTest::IntegrationController
end
yield
end
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index a79648396c..fee9cf46f9 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -501,6 +501,12 @@ class RespondWithController < ActionController::Base
respond_with(Customer.new("david", 13), :responder => responder)
end
+ def using_resource_with_action
+ respond_with(Customer.new("david", 13), :action => :foo) do |format|
+ format.html { raise ActionView::MissingTemplate.new([], "method") }
+ end
+ end
+
protected
def _render_js(js, options)
@@ -715,6 +721,20 @@ class RespondWithControllerTest < ActionController::TestCase
assert_match /<name>jamis<\/name>/, @response.body
end
+ def test_using_resource_with_action
+ @controller.instance_eval do
+ def render(params={})
+ self.response_body = "#{params[:action]} - #{formats}"
+ end
+ end
+
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+
+ post :using_resource_with_action
+ assert_equal "foo - #{[:html].to_s}", @controller.response_body
+ end
+
def test_clear_respond_to
@controller = InheritedRespondWithController.new
@request.accept = "text/html"
@@ -760,6 +780,14 @@ class RespondWithControllerTest < ActionController::TestCase
assert_equal "Resource name is david", @response.body
end
+ def test_using_resource_with_set_responder
+ RespondWithController.responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" }
+ get :using_resource
+ assert_equal "Resource name is david", @response.body
+ ensure
+ RespondWithController.responder = ActionController::Responder
+ end
+
def test_not_acceptable
@request.accept = "application/xml"
get :using_defaults
diff --git a/actionpack/test/controller/new_base/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb
index 7b38a82f51..b98a22dfcc 100644
--- a/actionpack/test/controller/new_base/content_negotiation_test.rb
+++ b/actionpack/test/controller/new_base/content_negotiation_test.rb
@@ -5,7 +5,7 @@ module ContentNegotiation
# This has no layout and it works
class BasicController < ActionController::Base
self.view_paths = [ActionView::FixtureResolver.new(
- "content_negotiation/basic/hello.html.erb" => "Hello world <%= request.formats %>!"
+ "content_negotiation/basic/hello.html.erb" => "Hello world <%= request.formats.first.to_s %>!"
)]
end
diff --git a/actionpack/test/controller/new_base/metal_test.rb b/actionpack/test/controller/new_base/metal_test.rb
index e1d46b906e..ab61fd98ee 100644
--- a/actionpack/test/controller/new_base/metal_test.rb
+++ b/actionpack/test/controller/new_base/metal_test.rb
@@ -20,8 +20,8 @@ module MetalTest
class TestMiddleware < ActiveSupport::TestCase
def setup
@app = Rack::Builder.new do
- use MetalMiddleware
- run Endpoint.new
+ use MetalTest::MetalMiddleware
+ run MetalTest::Endpoint.new
end.to_app
end
diff --git a/actionpack/test/controller/new_base/render_action_test.rb b/actionpack/test/controller/new_base/render_action_test.rb
index ecd29c4530..239f68659c 100644
--- a/actionpack/test/controller/new_base/render_action_test.rb
+++ b/actionpack/test/controller/new_base/render_action_test.rb
@@ -86,7 +86,7 @@ module RenderAction
describe "Both <controller_path>.html.erb and application.html.erb are missing"
test "rendering with layout => true" do
- assert_raise(ArgumentError, /no default layout for RenderAction::BasicController in/) do
+ assert_raise(ArgumentError) do
get "/render_action/basic/hello_world_with_layout", {}, "action_dispatch.show_exceptions" => false
end
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 2db524ca4b..b32325fa20 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -73,6 +73,11 @@ class TestController < ActionController::Base
render :action => 'hello_world'
end
+ def conditional_hello_with_expires_now
+ expires_now
+ render :action => 'hello_world'
+ end
+
def conditional_hello_with_bangs
render :action => 'hello_world'
end
@@ -855,7 +860,7 @@ class RenderTest < ActionController::TestCase
# :ported:
def test_access_to_controller_name_in_view
get :accessing_controller_name_in_template
- assert_equal "test", @response.body # name is explicitly set to 'test' inside the controller.
+ assert_equal "test", @response.body # name is explicitly set in the controller.
end
# :ported:
@@ -1321,6 +1326,11 @@ class ExpiresInRenderTest < ActionController::TestCase
get :conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"]
end
+
+ def test_expires_now
+ get :conditional_hello_with_expires_now
+ assert_equal "no-cache", @response.headers["Cache-Control"]
+ end
end
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index 689359166f..37367eaafc 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -327,7 +327,7 @@ class RescueTest < ActionController::IntegrationTest
test 'rescue routing exceptions' do
@app = ActionDispatch::Rescue.new(ActionController::Routing::Routes) do
- rescue_from ActionController::RoutingError, lambda { |env| [200, {"Content-Type" => "text/html"}, "Gotcha!"] }
+ rescue_from ActionController::RoutingError, lambda { |env| [200, {"Content-Type" => "text/html"}, ["Gotcha!"]] }
end
get '/b00m'
@@ -343,9 +343,9 @@ class RescueTest < ActionController::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do |map|
- map.connect 'foo', :controller => "rescue_test/test", :action => 'foo'
- map.connect 'invalid', :controller => "rescue_test/test", :action => 'invalid'
- map.connect 'b00m', :controller => "rescue_test/test", :action => 'b00m'
+ match 'foo', :to => ::RescueTest::TestController.action(:foo)
+ match 'invalid', :to => ::RescueTest::TestController.action(:invalid)
+ match 'b00m', :to => ::RescueTest::TestController.action(:b00m)
end
yield
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 5b47de19ae..04e9acf855 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -41,7 +41,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_should_arrange_actions
- resource = ActionController::Resources::Resource.new(:messages,
+ resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages,
:collection => { :rss => :get, :reorder => :post, :csv => :post },
:member => { :rss => :get, :atom => :get, :upload => :post, :fix => :post },
:new => { :preview => :get, :draft => :get })
@@ -54,18 +54,18 @@ class ResourcesTest < ActionController::TestCase
end
def test_should_resource_controller_name_equal_resource_name_by_default
- resource = ActionController::Resources::Resource.new(:messages, {})
+ resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, {})
assert_equal 'messages', resource.controller
end
def test_should_resource_controller_name_equal_controller_option
- resource = ActionController::Resources::Resource.new(:messages, :controller => 'posts')
+ resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, :controller => 'posts')
assert_equal 'posts', resource.controller
end
def test_should_all_singleton_paths_be_the_same
[ :path, :nesting_path_prefix, :member_path ].each do |method|
- resource = ActionController::Resources::SingletonResource.new(:messages, :path_prefix => 'admin')
+ resource = ActionDispatch::Routing::DeprecatedMapper::SingletonResource.new(:messages, :path_prefix => 'admin')
assert_equal 'admin/messages', resource.send(method)
end
end
@@ -121,7 +121,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_override_paths_for_default_restful_actions
- resource = ActionController::Resources::Resource.new(:messages,
+ resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages,
:path_names => {:new => 'nuevo', :edit => 'editar'})
assert_equal resource.new_path, "#{resource.path}/nuevo"
end
@@ -135,7 +135,7 @@ class ResourcesTest < ActionController::TestCase
def test_with_custom_conditions
with_restful_routing :messages, :conditions => { :subdomain => 'app' } do
- assert ActionController::Routing::Routes.recognize_path("/messages", :method => :get, :subdomain => 'app')
+ assert ActionDispatch::Routing::Routes.recognize_path("/messages", :method => :get, :subdomain => 'app')
end
end
@@ -281,7 +281,7 @@ class ResourcesTest < ActionController::TestCase
def test_with_member_action_and_requirement
expected_options = {:controller => 'messages', :action => 'mark', :id => '1.1.1'}
-
+
with_restful_routing(:messages, :requirements => {:id => /[0-9]\.[0-9]\.[0-9]/}, :member => { :mark => :get }) do
assert_recognizes(expected_options, :path => 'messages/1.1.1/mark', :method => :get)
end
@@ -701,8 +701,8 @@ class ResourcesTest < ActionController::TestCase
def test_should_not_allow_invalid_head_method_for_member_routes
with_routing do |set|
- set.draw do |map|
- assert_raise(ArgumentError) do
+ assert_raise(ArgumentError) do
+ set.draw do |map|
map.resources :messages, :member => {:something => :head}
end
end
@@ -711,8 +711,8 @@ class ResourcesTest < ActionController::TestCase
def test_should_not_allow_invalid_http_methods_for_member_routes
with_routing do |set|
- set.draw do |map|
- assert_raise(ArgumentError) do
+ assert_raise(ArgumentError) do
+ set.draw do |map|
map.resources :messages, :member => {:something => :invalid}
end
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index edf243337f..3971aacadb 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -11,24 +11,15 @@ end
ROUTING = ActionController::Routing
-class ROUTING::RouteBuilder
- attr_reader :warn_output
-
- def warn(msg)
- (@warn_output ||= []) << msg
- end
-end
-
# See RFC 3986, section 3.3 for allowed path characters.
class UriReservedCharactersRoutingTest < Test::Unit::TestCase
def setup
- ActionController::Routing.use_controllers! ['controller']
@set = ActionController::Routing::RouteSet.new
@set.draw do |map|
map.connect ':controller/:action/:variable/*additional'
end
- safe, unsafe = %w(: @ & = + $ , ;), %w(^ / ? # [ ])
+ safe, unsafe = %w(: @ & = + $ , ;), %w(^ ? # [ ])
hex = unsafe.map { |char| '%' + char.unpack('H2').first.upcase }
@segment = "#{safe.join}#{unsafe.join}".freeze
@@ -36,8 +27,9 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase
end
def test_route_generation_escapes_unsafe_path_characters
- assert_equal "/contr#{@segment}oller/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2",
- @set.generate(:controller => "contr#{@segment}oller",
+ @set.generate(:controller => "content", :action => "act#{@segment}ion", :variable => "variable", :additional => "foo")
+ assert_equal "/content/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2",
+ @set.generate(:controller => "content",
:action => "act#{@segment}ion",
:variable => "var#{@segment}iable",
:additional => ["add#{@segment}itional-1", "add#{@segment}itional-2"])
@@ -52,7 +44,7 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase
end
def test_route_generation_allows_passing_non_string_values_to_generated_helper
- assert_equal "/controller/action/variable/1/2", @set.generate(:controller => "controller",
+ assert_equal "/content/action/variable/1/2", @set.generate(:controller => "content",
:action => "action",
:variable => "variable",
:additional => [1, 2])
@@ -118,8 +110,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
ActionController::Base.optimise_named_routes = true
@rs = ::ActionController::Routing::RouteSet.new
-
- ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed)
end
def teardown
@@ -240,18 +230,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
x.send(:home_url))
end
- def test_basic_named_route_with_relative_url_root
- rs.draw do |map|
- map.home '', :controller => 'content', :action => 'list'
- end
- x = setup_for_named_route
- ActionController::Base.relative_url_root = "/foo"
- assert_equal("http://test.host/foo/",
- x.send(:home_url))
- assert_equal "/foo/", x.send(:home_path)
- ActionController::Base.relative_url_root = nil
- end
-
def test_named_route_with_option
rs.draw do |map|
map.page 'page/:title', :controller => 'content', :action => 'show_page'
@@ -307,19 +285,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
x.send(:users_url))
end
- def test_optimised_named_route_call_never_uses_url_for
- rs.draw do |map|
- map.users 'admin/user', :controller => '/admin/user', :action => 'index'
- map.user 'admin/user/:id', :controller=>'/admin/user', :action=>'show'
- end
- x = setup_for_named_route
- x.expects(:url_for).never
- x.send(:users_url)
- x.send(:users_path)
- x.send(:user_url, 2, :foo=>"bar")
- x.send(:user_path, 3, :bar=>"foo")
- end
-
def test_optimised_named_route_with_host
rs.draw do |map|
map.pages 'pages', :controller => 'content', :action => 'show_page', :host => 'foo.com'
@@ -391,10 +356,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
results = rs.recognize_path "/file/hello%20world/how%20are%20you%3F"
assert results, "Recognition should have succeeded"
assert_equal ['hello world', 'how are you?'], results[:path]
-
- results = rs.recognize_path "/file"
- assert results, "Recognition should have succeeded"
- assert_equal [], results[:path]
end
def test_paths_slashes_unescaped_with_ordered_parameters
@@ -404,7 +365,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
# No / to %2F in URI, only for query params.
x = setup_for_named_route
- assert_equal("/file/hello/world", x.send(:path_path, 'hello/world'))
+ assert_equal("/file/hello/world", x.send(:path_path, ['hello', 'world']))
end
def test_non_controllers_cannot_be_matched
@@ -432,35 +393,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
rs.draw do |map|
map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/}
end
- exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post") }
- assert_match /^post_url failed to generate/, exception.message
- from_match = exception.message.match(/from \{[^\}]+\}/).to_s
- assert_match /:bad_param=>"foo"/, from_match
- assert_match /:action=>"show"/, from_match
- assert_match /:controller=>"post"/, from_match
-
- expected_match = exception.message.match(/expected: \{[^\}]+\}/).to_s
- assert_no_match /:bad_param=>"foo"/, expected_match
- assert_match /:action=>"show"/, expected_match
- assert_match /:controller=>"post"/, expected_match
-
- diff_match = exception.message.match(/diff: \{[^\}]+\}/).to_s
- assert_match /:bad_param=>"foo"/, diff_match
- assert_no_match /:action=>"show"/, diff_match
- assert_no_match /:controller=>"post"/, diff_match
- end
-
- # this specifies the case where your formerly would get a very confusing error message with an empty diff
- def test_should_have_better_error_message_when_options_diff_is_empty
- rs.draw do |map|
- map.content '/content/:query', :controller => 'content', :action => 'show'
- end
-
- exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'content', :action => 'show', :use_route => "content") }
- assert_match %r[:action=>"show"], exception.message
- assert_match %r[:controller=>"content"], exception.message
- assert_match %r[you may have ambiguous routes, or you may need to supply additional parameters for this route], exception.message
- assert_match %r[content_url has the following required parameters: \["content", :query\] - are they all satisfied?], exception.message
+ assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post") }
end
def test_dynamic_path_allowed
@@ -517,6 +450,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
assert_equal({:controller => "content", :action => 'show_page', :id => 'foo'}, rs.recognize_path("/page/foo"))
token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in russian
+ token.force_encoding(Encoding::BINARY) if token.respond_to?(:force_encoding)
escaped_token = CGI::escape(token)
assert_equal '/page/' + escaped_token, rs.generate(:controller => 'content', :action => 'show_page', :id => token)
@@ -528,17 +462,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
assert_equal '/content', rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
end
- def test_recognition_with_uppercase_controller_name
- @rs.draw {|m| m.connect ':controller/:action/:id' }
- assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/Content"))
- assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/ConTent/list"))
- assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/CONTENT/show/10"))
-
- # these used to work, before the routes rewrite, but support for this was pulled in the new version...
- #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/NewsFeed"))
- #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/News_Feed"))
- end
-
def test_requirement_should_prevent_optional_id
rs.draw do |map|
map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/}
@@ -785,12 +708,9 @@ class RouteSetTest < ActiveSupport::TestCase
def default_route_set
@default_route_set ||= begin
- set = nil
- ActionController::Routing.with_controllers(['accounts']) do
- set = ROUTING::RouteSet.new
- set.draw do |map|
- map.connect '/:controller/:action/:id/'
- end
+ set = ROUTING::RouteSet.new
+ set.draw do |map|
+ map.connect '/:controller/:action/:id/'
end
set
end
@@ -978,47 +898,38 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_draw_default_route
- ActionController::Routing.with_controllers(['users']) do
- set.draw do |map|
- map.connect '/:controller/:action/:id'
- end
-
- assert_equal 1, set.routes.size
- route = set.routes.first
+ set.draw do |map|
+ map.connect '/:controller/:action/:id'
+ end
- assert route.segments.last.optional?
+ assert_equal 1, set.routes.size
- assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10)
- assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10)
+ assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10)
+ assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10)
- assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10'))
- assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/'))
- end
+ assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10'))
+ assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/'))
end
def test_draw_default_route_with_default_controller
- ActionController::Routing.with_controllers(['users']) do
- set.draw do |map|
- map.connect '/:controller/:action/:id', :controller => 'users'
- end
- assert_equal({:controller => 'users', :action => 'index'}, set.recognize_path('/'))
+ set.draw do |map|
+ map.connect '/:controller/:action/:id', :controller => 'users'
end
+ assert_equal({:controller => 'users', :action => 'index'}, set.recognize_path('/'))
end
def test_route_with_parameter_shell
- ActionController::Routing.with_controllers(['users', 'pages']) do
- set.draw do |map|
- map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
- map.connect '/:controller/:action/:id'
- end
+ set.draw do |map|
+ map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
+ map.connect '/:controller/:action/:id'
+ end
- assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages'))
- assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index'))
- assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list'))
+ assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages'))
+ assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index'))
+ assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list'))
- assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10'))
- assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10'))
- end
+ assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10'))
+ assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10'))
end
def test_route_requirements_with_anchor_chars_are_invalid
@@ -1047,14 +958,6 @@ class RouteSetTest < ActiveSupport::TestCase
map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\z/
end
end
- assert_nothing_raised do
- set.draw do |map|
- map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/, :name => /^(david|jamis)/
- end
- assert_raise ActionController::RoutingError do
- set.generate :controller => 'pages', :action => 'show', :id => 10
- end
- end
end
def test_route_requirements_with_invalid_http_method_is_invalid
@@ -1081,19 +984,6 @@ class RouteSetTest < ActiveSupport::TestCase
end
end
- def test_non_path_route_requirements_match_all
- set.draw do |map|
- map.connect 'page/37s', :controller => 'pages', :action => 'show', :name => /(jamis|david)/
- end
- assert_equal '/page/37s', set.generate(:controller => 'pages', :action => 'show', :name => 'jamis')
- assert_raise ActionController::RoutingError do
- set.generate(:controller => 'pages', :action => 'show', :name => 'not_jamis')
- end
- assert_raise ActionController::RoutingError do
- set.generate(:controller => 'pages', :action => 'show', :name => 'nor_jamis_and_david')
- end
- end
-
def test_recognize_with_encoded_id_and_regex
set.draw do |map|
map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /[a-zA-Z0-9\+]+/
@@ -1331,16 +1221,16 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal "/foo/bar/baz/7", url
end
- def test_id_is_not_impossibly_sticky
- set.draw do |map|
- map.connect 'foo/:number', :controller => "people", :action => "index"
- map.connect ':controller/:action/:id'
- end
-
- url = set.generate({:controller => "people", :action => "index", :number => 3},
- {:controller => "people", :action => "index", :id => "21"})
- assert_equal "/foo/3", url
- end
+ # def test_id_is_not_impossibly_sticky
+ # set.draw do |map|
+ # map.connect 'foo/:number', :controller => "people", :action => "index"
+ # map.connect ':controller/:action/:id'
+ # end
+ #
+ # url = set.generate({:controller => "people", :action => "index", :number => 3},
+ # {:controller => "people", :action => "index", :id => "21"})
+ # assert_equal "/foo/3", url
+ # end
def test_id_is_sticky_when_it_ought_to_be
set.draw do |map|
@@ -1403,20 +1293,20 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do |map|
map.connect ':controller/:action/:id'
end
- assert_equal '/post', set.generate(
- {:controller => 'post', :action => 'index'},
- {:controller => 'post', :action => 'show', :id => '10'}
+ assert_equal '/books', set.generate(
+ {:controller => 'books', :action => 'index'},
+ {:controller => 'books', :action => 'show', :id => '10'}
)
end
def test_query_params_will_be_shown_when_recalled
set.draw do |map|
- map.connect 'show_post/:parameter', :controller => 'post', :action => 'show'
+ map.connect 'show_weblog/:parameter', :controller => 'weblog', :action => 'show'
map.connect ':controller/:action/:id'
end
- assert_equal '/post/edit?parameter=1', set.generate(
+ assert_equal '/weblog/edit?parameter=1', set.generate(
{:action => 'edit', :parameter => 1},
- {:controller => 'post', :action => 'show', :parameter => 1}
+ {:controller => 'weblog', :action => 'show', :parameter => 1}
)
end
@@ -1438,23 +1328,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_expiry_determination_should_consider_values_with_to_param
set.draw { |map| map.connect 'projects/:project_id/:controller/:action' }
- assert_equal '/projects/1/post/show', set.generate(
+ assert_equal '/projects/1/weblog/show', set.generate(
{:action => 'show', :project_id => 1},
- {:controller => 'post', :action => 'show', :project_id => '1'})
- end
-
- def test_generate_all
- set.draw do |map|
- map.connect 'show_post/:id', :controller => 'post', :action => 'show'
- map.connect ':controller/:action/:id'
- end
- all = set.generate(
- {:action => 'show', :id => 10, :generate_all => true},
- {:controller => 'post', :action => 'show'}
- )
- assert_equal 2, all.length
- assert_equal '/show_post/10', all.first
- assert_equal '/post/show/10', all.last
+ {:controller => 'weblog', :action => 'show', :project_id => '1'})
end
def test_named_route_in_nested_resource
@@ -1631,101 +1507,53 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/named'))
end
-
- def test_interpolation_chunk_should_respect_raw
- ActionController::Routing.with_controllers(['hello']) do
- set.draw do |map|
- map.connect '/Hello World', :controller => 'hello'
- end
-
- assert_equal '/Hello%20World', set.generate(:controller => 'hello')
- assert_equal({:controller => "hello", :action => "index"}, set.recognize_path('/Hello World'))
- assert_raise(ActionController::RoutingError) { set.recognize_path('/Hello%20World') }
- end
- end
-
- def test_value_should_not_be_double_unescaped
- ActionController::Routing.with_controllers(['foo']) do
- set.draw do |map|
- map.connect '/Карта', :controller => 'foo'
- end
-
- assert_equal '/%D0%9A%D0%B0%D1%80%D1%82%D0%B0', set.generate(:controller => 'foo')
- assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Карта'))
- assert_raise(ActionController::RoutingError) { set.recognize_path('/%D0%9A%D0%B0%D1%80%D1%82%D0%B0') }
- end
- end
-
- def test_regexp_chunk_should_escape_specials
- ActionController::Routing.with_controllers(['foo', 'bar']) do
- set.draw do |map|
- map.connect '/Hello*World', :controller => 'foo'
- map.connect '/HelloWorld', :controller => 'bar'
- end
-
- assert_equal '/Hello*World', set.generate(:controller => 'foo')
- assert_equal '/HelloWorld', set.generate(:controller => 'bar')
-
- assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Hello*World'))
- assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/HelloWorld'))
- end
- end
-
def test_regexp_chunk_should_add_question_mark_for_optionals
- ActionController::Routing.with_controllers(['foo', 'bar']) do
- set.draw do |map|
- map.connect '/', :controller => 'foo'
- map.connect '/hello', :controller => 'bar'
- end
+ set.draw do |map|
+ map.connect '/', :controller => 'foo'
+ map.connect '/hello', :controller => 'bar'
+ end
- assert_equal '/', set.generate(:controller => 'foo')
- assert_equal '/hello', set.generate(:controller => 'bar')
+ assert_equal '/', set.generate(:controller => 'foo')
+ assert_equal '/hello', set.generate(:controller => 'bar')
- assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/'))
- assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/hello'))
- end
+ assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/'))
+ assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/hello'))
end
def test_assign_route_options_with_anchor_chars
- ActionController::Routing.with_controllers(['cars']) do
- set.draw do |map|
- map.connect '/cars/:action/:person/:car/', :controller => 'cars'
- end
+ set.draw do |map|
+ map.connect '/cars/:action/:person/:car/', :controller => 'cars'
+ end
- assert_equal '/cars/buy/1/2', set.generate(:controller => 'cars', :action => 'buy', :person => '1', :car => '2')
+ assert_equal '/cars/buy/1/2', set.generate(:controller => 'cars', :action => 'buy', :person => '1', :car => '2')
- assert_equal({:controller => "cars", :action => "buy", :person => "1", :car => "2"}, set.recognize_path('/cars/buy/1/2'))
- end
+ assert_equal({:controller => "cars", :action => "buy", :person => "1", :car => "2"}, set.recognize_path('/cars/buy/1/2'))
end
def test_segmentation_of_dot_path
- ActionController::Routing.with_controllers(['books']) do
- set.draw do |map|
- map.connect '/books/:action.rss', :controller => 'books'
- end
+ set.draw do |map|
+ map.connect '/books/:action.rss', :controller => 'books'
+ end
- assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list')
+ assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list')
- assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list.rss'))
- end
+ assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list.rss'))
end
def test_segmentation_of_dynamic_dot_path
- ActionController::Routing.with_controllers(['books']) do
- set.draw do |map|
- map.connect '/books/:action.:format', :controller => 'books'
- end
+ set.draw do |map|
+ map.connect '/books/:action.:format', :controller => 'books'
+ end
- assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list', :format => 'rss')
- assert_equal '/books/list.xml', set.generate(:controller => 'books', :action => 'list', :format => 'xml')
- assert_equal '/books/list', set.generate(:controller => 'books', :action => 'list')
- assert_equal '/books', set.generate(:controller => 'books', :action => 'index')
+ assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list', :format => 'rss')
+ assert_equal '/books/list.xml', set.generate(:controller => 'books', :action => 'list', :format => 'xml')
+ assert_equal '/books/list', set.generate(:controller => 'books', :action => 'list')
+ assert_equal '/books', set.generate(:controller => 'books', :action => 'index')
- assert_equal({:controller => "books", :action => "list", :format => "rss"}, set.recognize_path('/books/list.rss'))
- assert_equal({:controller => "books", :action => "list", :format => "xml"}, set.recognize_path('/books/list.xml'))
- assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list'))
- assert_equal({:controller => "books", :action => "index"}, set.recognize_path('/books'))
- end
+ assert_equal({:controller => "books", :action => "list", :format => "rss"}, set.recognize_path('/books/list.rss'))
+ assert_equal({:controller => "books", :action => "list", :format => "xml"}, set.recognize_path('/books/list.xml'))
+ assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list'))
+ assert_equal({:controller => "books", :action => "index"}, set.recognize_path('/books'))
end
def test_slashes_are_implied
@@ -1780,7 +1608,6 @@ class RouteSetTest < ActiveSupport::TestCase
def test_default_route_should_uri_escape_pluses
expected = { :controller => 'pages', :action => 'show', :id => 'hello world' }
- assert_equal expected, default_route_set.recognize_path('/pages/show/hello world')
assert_equal expected, default_route_set.recognize_path('/pages/show/hello%20world')
assert_equal '/pages/show/hello%20world', default_route_set.generate(expected, expected)
@@ -1790,52 +1617,96 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal '/pages/show/hello+world', default_route_set.generate(expected, expected)
end
- def test_parameter_shell
- page_url = ROUTING::Route.new
- page_url.requirements = {:controller => 'pages', :action => 'show', :id => /\d+/}
- assert_equal({:controller => 'pages', :action => 'show'}, page_url.parameter_shell)
- end
-
- def test_defaults
- route = ROUTING::RouteBuilder.new.build '/users/:id.:format', :controller => "users", :action => "show", :format => "html"
- assert_equal(
- { :controller => "users", :action => "show", :format => "html" },
- route.defaults)
- end
-
- def test_builder_complains_without_controller
- assert_raise(ArgumentError) do
- ROUTING::RouteBuilder.new.build '/contact', :contoller => "contact", :action => "index"
- end
- end
-
def test_build_empty_query_string
- assert_equal '/foo', default_route_set.generate({:controller => 'foo'})
+ assert_uri_equal '/foo', default_route_set.generate({:controller => 'foo'})
end
def test_build_query_string_with_nil_value
- assert_equal '/foo', default_route_set.generate({:controller => 'foo', :x => nil})
+ assert_uri_equal '/foo', default_route_set.generate({:controller => 'foo', :x => nil})
end
def test_simple_build_query_string
- assert_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => '1', :y => '2'})
+ assert_uri_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => '1', :y => '2'})
end
def test_convert_ints_build_query_string
- assert_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => 1, :y => 2})
+ assert_uri_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => 1, :y => 2})
end
def test_escape_spaces_build_query_string
- assert_equal '/foo?x=hello+world&y=goodbye+world', default_route_set.generate({:controller => 'foo', :x => 'hello world', :y => 'goodbye world'})
+ assert_uri_equal '/foo?x=hello+world&y=goodbye+world', default_route_set.generate({:controller => 'foo', :x => 'hello world', :y => 'goodbye world'})
end
def test_expand_array_build_query_string
- assert_equal '/foo?x%5B%5D=1&x%5B%5D=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]})
+ assert_uri_equal '/foo?x%5B%5D=1&x%5B%5D=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]})
end
def test_escape_spaces_build_query_string_selected_keys
- assert_equal '/foo?x=hello+world', default_route_set.generate({:controller => 'foo', :x => 'hello world'})
+ assert_uri_equal '/foo?x=hello+world', default_route_set.generate({:controller => 'foo', :x => 'hello world'})
+ end
+
+ def test_generate_with_default_params
+ set.draw do |map|
+ map.connect 'dummy/page/:page', :controller => 'dummy'
+ map.connect 'dummy/dots/page.:page', :controller => 'dummy', :action => 'dots'
+ map.connect 'ibocorp/:page', :controller => 'ibocorp',
+ :requirements => { :page => /\d+/ },
+ :defaults => { :page => 1 }
+
+ map.connect ':controller/:action/:id'
+ end
+
+ pending do
+ assert_equal '/ibocorp', set.generate({:controller => 'ibocorp', :page => 1})
+ end
+ end
+
+ def test_generate_with_optional_params_recalls_last_request
+ set.draw do |map|
+ map.connect "blog/", :controller => "blog", :action => "index"
+
+ map.connect "blog/:year/:month/:day",
+ :controller => "blog",
+ :action => "show_date",
+ :requirements => { :year => /(19|20)\d\d/, :month => /[01]?\d/, :day => /[0-3]?\d/ },
+ :day => nil, :month => nil
+
+ map.connect "blog/show/:id", :controller => "blog", :action => "show", :id => /\d+/
+ map.connect "blog/:controller/:action/:id"
+ map.connect "*anything", :controller => "blog", :action => "unknown_request"
+ end
+
+ assert_equal({:controller => "blog", :action => "index"}, set.recognize_path("/blog"))
+ assert_equal({:controller => "blog", :action => "show", :id => "123"}, set.recognize_path("/blog/show/123"))
+ assert_equal({:controller => "blog", :action => "show_date", :year => "2004"}, set.recognize_path("/blog/2004"))
+ assert_equal({:controller => "blog", :action => "show_date", :year => "2004", :month => "12"}, set.recognize_path("/blog/2004/12"))
+ assert_equal({:controller => "blog", :action => "show_date", :year => "2004", :month => "12", :day => "25"}, set.recognize_path("/blog/2004/12/25"))
+ assert_equal({:controller => "articles", :action => "edit", :id => "123"}, set.recognize_path("/blog/articles/edit/123"))
+ assert_equal({:controller => "articles", :action => "show_stats"}, set.recognize_path("/blog/articles/show_stats"))
+ assert_equal({:controller => "blog", :action => "unknown_request", :anything => ["blog", "wibble"]}, set.recognize_path("/blog/wibble"))
+ assert_equal({:controller => "blog", :action => "unknown_request", :anything => ["junk"]}, set.recognize_path("/junk"))
+
+ last_request = set.recognize_path("/blog/2006/07/28").freeze
+ assert_equal({:controller => "blog", :action => "show_date", :year => "2006", :month => "07", :day => "28"}, last_request)
+ assert_equal("/blog/2006/07/25", set.generate({:day => 25}, last_request))
+ assert_equal("/blog/2005", set.generate({:year => 2005}, last_request))
+ assert_equal("/blog/show/123", set.generate({:action => "show" , :id => 123}, last_request))
+ pending do
+ assert_equal("/blog/2006/07/28", set.generate({:year => 2006}, last_request))
+ end
+ assert_equal("/blog/2006", set.generate({:year => 2006, :month => nil}, last_request))
end
+
+ private
+ def assert_uri_equal(expected, actual)
+ assert_equal(sort_query_string_params(expected), sort_query_string_params(actual))
+ end
+
+ def sort_query_string_params(uri)
+ path, qs = uri.split('?')
+ qs = qs.split('&').sort.join('&') if qs
+ qs ? "#{path}?#{qs}" : path
+ end
end
class RouteLoadingTest < Test::Unit::TestCase
@@ -1916,3 +1787,315 @@ class RouteLoadingTest < Test::Unit::TestCase
ActionController::Routing::Routes
end
end
+
+class RackMountIntegrationTests < ActiveSupport::TestCase
+ Model = Struct.new(:to_param)
+
+ Mapping = lambda { |map|
+ map.namespace :admin do |admin|
+ admin.resources :users
+ end
+
+ map.namespace 'api' do |api|
+ api.root :controller => 'users'
+ end
+
+ map.connect 'blog/:year/:month/:day',
+ :controller => 'posts',
+ :action => 'show_date',
+ :requirements => { :year => /(19|20)\d\d/, :month => /[01]?\d/, :day => /[0-3]?\d/},
+ :day => nil,
+ :month => nil
+
+ map.blog('archive/:year', :controller => 'archive', :action => 'index',
+ :defaults => { :year => nil },
+ :requirements => { :year => /\d{4}/ }
+ )
+
+ map.resources :people
+ map.connect 'legacy/people', :controller => 'people', :action => 'index', :legacy => 'true'
+
+ map.connect 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol
+ map.connect 'id_default/:id', :controller => 'foo', :action => 'id_default', :id => 1
+ map.connect 'get_or_post', :controller => 'foo', :action => 'get_or_post', :conditions => { :method => [:get, :post] }
+ map.connect 'optional/:optional', :controller => 'posts', :action => 'index'
+ map.project 'projects/:project_id', :controller => 'project'
+ map.connect 'clients', :controller => 'projects', :action => 'index'
+
+ map.connect 'ignorecase/geocode/:postalcode', :controller => 'geocode',
+ :action => 'show', :postalcode => /hx\d\d-\d[a-z]{2}/i
+ map.geocode 'extended/geocode/:postalcode', :controller => 'geocode',
+ :action => 'show',:requirements => {
+ :postalcode => /# Postcode format
+ \d{5} #Prefix
+ (-\d{4})? #Suffix
+ /x
+ }
+
+ map.connect '', :controller => 'news', :format => nil
+ map.connect 'news.:format', :controller => 'news'
+
+ map.connect 'comment/:id/:action', :controller => 'comments', :action => 'show'
+ map.connect 'ws/:controller/:action/:id', :ws => true
+ map.connect 'account/:action', :controller => :account, :action => :subscription
+ map.connect 'pages/:page_id/:controller/:action/:id'
+ map.connect ':controller/ping', :action => 'ping'
+ map.connect ':controller/:action/:id'
+ }
+
+ def setup
+ @routes = ActionController::Routing::RouteSet.new
+ @routes.draw(&Mapping)
+ end
+
+ def test_add_route
+ @routes.clear!
+
+ assert_raise(ActionController::RoutingError) do
+ @routes.draw do |map|
+ map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default)
+ map.connect ':controller/:action/:id'
+ end
+ end
+ end
+
+ def test_recognize_path
+ assert_equal({:controller => 'admin/users', :action => 'index'}, @routes.recognize_path('/admin/users', :method => :get))
+ assert_equal({:controller => 'admin/users', :action => 'create'}, @routes.recognize_path('/admin/users', :method => :post))
+ assert_equal({:controller => 'admin/users', :action => 'new'}, @routes.recognize_path('/admin/users/new', :method => :get))
+ assert_equal({:controller => 'admin/users', :action => 'show', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :get))
+ assert_equal({:controller => 'admin/users', :action => 'update', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :put))
+ assert_equal({:controller => 'admin/users', :action => 'destroy', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :delete))
+ assert_equal({:controller => 'admin/users', :action => 'edit', :id => '1'}, @routes.recognize_path('/admin/users/1/edit', :method => :get))
+
+ assert_equal({:controller => 'admin/posts', :action => 'index'}, @routes.recognize_path('/admin/posts', :method => :get))
+ assert_equal({:controller => 'admin/posts', :action => 'new'}, @routes.recognize_path('/admin/posts/new', :method => :get))
+
+ assert_equal({:controller => 'api/users', :action => 'index'}, @routes.recognize_path('/api', :method => :get))
+ assert_equal({:controller => 'api/users', :action => 'index'}, @routes.recognize_path('/api/', :method => :get))
+
+ assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009'}, @routes.recognize_path('/blog/2009', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009', :month => '01'}, @routes.recognize_path('/blog/2009/01', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009', :month => '01', :day => '01'}, @routes.recognize_path('/blog/2009/01/01', :method => :get))
+ assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/blog/123456789', :method => :get) }
+
+ assert_equal({:controller => 'archive', :action => 'index', :year => '2010'}, @routes.recognize_path('/archive/2010'))
+ assert_equal({:controller => 'archive', :action => 'index'}, @routes.recognize_path('/archive'))
+ assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/archive/january') }
+
+ assert_equal({:controller => 'people', :action => 'index'}, @routes.recognize_path('/people', :method => :get))
+ assert_equal({:controller => 'people', :action => 'index', :format => 'xml'}, @routes.recognize_path('/people.xml', :method => :get))
+ assert_equal({:controller => 'people', :action => 'create'}, @routes.recognize_path('/people', :method => :post))
+ assert_equal({:controller => 'people', :action => 'new'}, @routes.recognize_path('/people/new', :method => :get))
+ assert_equal({:controller => 'people', :action => 'show', :id => '1'}, @routes.recognize_path('/people/1', :method => :get))
+ assert_equal({:controller => 'people', :action => 'show', :id => '1', :format => 'xml'}, @routes.recognize_path('/people/1.xml', :method => :get))
+ assert_equal({:controller => 'people', :action => 'update', :id => '1'}, @routes.recognize_path('/people/1', :method => :put))
+ assert_equal({:controller => 'people', :action => 'destroy', :id => '1'}, @routes.recognize_path('/people/1', :method => :delete))
+ assert_equal({:controller => 'people', :action => 'edit', :id => '1'}, @routes.recognize_path('/people/1/edit', :method => :get))
+ assert_equal({:controller => 'people', :action => 'edit', :id => '1', :format => 'xml'}, @routes.recognize_path('/people/1/edit.xml', :method => :get))
+
+ assert_equal({:controller => 'symbols', :action => 'show', :name => :as_symbol}, @routes.recognize_path('/symbols'))
+ assert_equal({:controller => 'foo', :action => 'id_default', :id => '1'}, @routes.recognize_path('/id_default/1'))
+ assert_equal({:controller => 'foo', :action => 'id_default', :id => '2'}, @routes.recognize_path('/id_default/2'))
+ assert_equal({:controller => 'foo', :action => 'id_default', :id => '1'}, @routes.recognize_path('/id_default'))
+ assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :get))
+ assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :post))
+ assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/get_or_post', :method => :put) }
+ assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/get_or_post', :method => :delete) }
+
+ assert_equal({:controller => 'posts', :action => 'index', :optional => 'bar'}, @routes.recognize_path('/optional/bar'))
+ assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/optional') }
+
+ assert_equal({:controller => 'posts', :action => 'show', :id => '1', :ws => true}, @routes.recognize_path('/ws/posts/show/1', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'list', :ws => true}, @routes.recognize_path('/ws/posts/list', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'index', :ws => true}, @routes.recognize_path('/ws/posts', :method => :get))
+
+ assert_equal({:controller => 'account', :action => 'subscription'}, @routes.recognize_path('/account', :method => :get))
+ assert_equal({:controller => 'account', :action => 'subscription'}, @routes.recognize_path('/account/subscription', :method => :get))
+ assert_equal({:controller => 'account', :action => 'billing'}, @routes.recognize_path('/account/billing', :method => :get))
+
+ assert_equal({:page_id => '1', :controller => 'notes', :action => 'index'}, @routes.recognize_path('/pages/1/notes', :method => :get))
+ assert_equal({:page_id => '1', :controller => 'notes', :action => 'list'}, @routes.recognize_path('/pages/1/notes/list', :method => :get))
+ assert_equal({:page_id => '1', :controller => 'notes', :action => 'show', :id => '2'}, @routes.recognize_path('/pages/1/notes/show/2', :method => :get))
+
+ assert_equal({:controller => 'posts', :action => 'ping'}, @routes.recognize_path('/posts/ping', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'index'}, @routes.recognize_path('/posts', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'index'}, @routes.recognize_path('/posts/index', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'show'}, @routes.recognize_path('/posts/show', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'show', :id => '1'}, @routes.recognize_path('/posts/show/1', :method => :get))
+ assert_equal({:controller => 'posts', :action => 'create'}, @routes.recognize_path('/posts/create', :method => :post))
+
+ assert_equal({:controller => 'geocode', :action => 'show', :postalcode => 'hx12-1az'}, @routes.recognize_path('/ignorecase/geocode/hx12-1az'))
+ assert_equal({:controller => 'geocode', :action => 'show', :postalcode => 'hx12-1AZ'}, @routes.recognize_path('/ignorecase/geocode/hx12-1AZ'))
+ assert_equal({:controller => 'geocode', :action => 'show', :postalcode => '12345-1234'}, @routes.recognize_path('/extended/geocode/12345-1234'))
+ assert_equal({:controller => 'geocode', :action => 'show', :postalcode => '12345'}, @routes.recognize_path('/extended/geocode/12345'))
+
+ assert_equal({:controller => 'news', :action => 'index', :format => nil}, @routes.recognize_path('/', :method => :get))
+ assert_equal({:controller => 'news', :action => 'index', :format => 'rss'}, @routes.recognize_path('/news.rss', :method => :get))
+
+ assert_raise(ActionController::RoutingError) { @routes.recognize_path('/none', :method => :get) }
+ end
+
+ def test_generate
+ assert_equal '/admin/users', @routes.generate(:use_route => 'admin_users')
+ assert_equal '/admin/users', @routes.generate(:controller => 'admin/users')
+ assert_equal '/admin/users', @routes.generate(:controller => 'admin/users', :action => 'index')
+ assert_equal '/admin/users', @routes.generate({:action => 'index'}, {:controller => 'admin/users'})
+ assert_equal '/admin/users', @routes.generate({:controller => 'users', :action => 'index'}, {:controller => 'admin/accounts'})
+ assert_equal '/people', @routes.generate({:controller => '/people', :action => 'index'}, {:controller => 'admin/accounts'})
+
+ assert_equal '/admin/posts', @routes.generate({:controller => 'admin/posts'})
+ assert_equal '/admin/posts/new', @routes.generate({:controller => 'admin/posts', :action => 'new'})
+
+ assert_equal '/blog/2009', @routes.generate(:controller => 'posts', :action => 'show_date', :year => 2009)
+ assert_equal '/blog/2009/1', @routes.generate(:controller => 'posts', :action => 'show_date', :year => 2009, :month => 1)
+ assert_equal '/blog/2009/1/1', @routes.generate(:controller => 'posts', :action => 'show_date', :year => 2009, :month => 1, :day => 1)
+
+ assert_equal '/archive/2010', @routes.generate(:controller => 'archive', :action => 'index', :year => '2010')
+ assert_equal '/archive', @routes.generate(:controller => 'archive', :action => 'index')
+ assert_equal '/archive?year=january', @routes.generate(:controller => 'archive', :action => 'index', :year => 'january')
+
+ assert_equal '/people', @routes.generate(:use_route => 'people')
+ assert_equal '/people', @routes.generate(:use_route => 'people', :controller => 'people', :action => 'index')
+ assert_equal '/people.xml', @routes.generate(:use_route => 'people', :controller => 'people', :action => 'index', :format => 'xml')
+ assert_equal '/people', @routes.generate({:use_route => 'people', :controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'index'})
+ assert_equal '/people', @routes.generate(:controller => 'people')
+ assert_equal '/people', @routes.generate(:controller => 'people', :action => 'index')
+ assert_equal '/people', @routes.generate({:action => 'index'}, {:controller => 'people'})
+ assert_equal '/people', @routes.generate({:action => 'index'}, {:controller => 'people', :action => 'show', :id => '1'})
+ assert_equal '/people', @routes.generate({:controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'show', :id => '1'})
+ assert_equal '/people', @routes.generate({}, {:controller => 'people', :action => 'index'})
+ assert_equal '/people/1', @routes.generate({:controller => 'people', :action => 'show'}, {:controller => 'people', :action => 'show', :id => '1'})
+ assert_equal '/people/new', @routes.generate(:use_route => 'new_person')
+ assert_equal '/people/new', @routes.generate(:controller => 'people', :action => 'new')
+ assert_equal '/people/1', @routes.generate(:use_route => 'person', :id => '1')
+ assert_equal '/people/1', @routes.generate(:controller => 'people', :action => 'show', :id => '1')
+ assert_equal '/people/1.xml', @routes.generate(:controller => 'people', :action => 'show', :id => '1', :format => 'xml')
+ assert_equal '/people/1', @routes.generate(:controller => 'people', :action => 'show', :id => 1)
+ assert_equal '/people/1', @routes.generate(:controller => 'people', :action => 'show', :id => Model.new('1'))
+ assert_equal '/people/1', @routes.generate({:action => 'show', :id => '1'}, {:controller => 'people', :action => 'index'})
+ assert_equal '/people/1', @routes.generate({:action => 'show', :id => 1}, {:controller => 'people', :action => 'show', :id => '1'})
+ # assert_equal '/people', @routes.generate({:controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'index', :id => '1'})
+ assert_equal '/people', @routes.generate({:controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'show', :id => '1'})
+ assert_equal '/people/1', @routes.generate({}, {:controller => 'people', :action => 'show', :id => '1'})
+ assert_equal '/people/1', @routes.generate({:controller => 'people', :action => 'show'}, {:controller => 'people', :action => 'index', :id => '1'})
+ assert_equal '/people/1/edit', @routes.generate(:controller => 'people', :action => 'edit', :id => '1')
+ assert_equal '/people/1/edit.xml', @routes.generate(:controller => 'people', :action => 'edit', :id => '1', :format => 'xml')
+ assert_equal '/people/1/edit', @routes.generate(:use_route => 'edit_person', :id => '1')
+ assert_equal '/people/1?legacy=true', @routes.generate(:controller => 'people', :action => 'show', :id => '1', :legacy => 'true')
+ assert_equal '/people?legacy=true', @routes.generate(:controller => 'people', :action => 'index', :legacy => 'true')
+
+ assert_equal '/id_default/2', @routes.generate(:controller => 'foo', :action => 'id_default', :id => '2')
+ assert_equal '/id_default', @routes.generate(:controller => 'foo', :action => 'id_default', :id => '1')
+ assert_equal '/id_default', @routes.generate(:controller => 'foo', :action => 'id_default', :id => 1)
+ assert_equal '/id_default', @routes.generate(:controller => 'foo', :action => 'id_default')
+ assert_equal '/optional/bar', @routes.generate(:controller => 'posts', :action => 'index', :optional => 'bar')
+ assert_equal '/posts', @routes.generate(:controller => 'posts', :action => 'index')
+
+ assert_equal '/project', @routes.generate({:controller => 'project', :action => 'index'})
+ assert_equal '/projects/1', @routes.generate({:controller => 'project', :action => 'index', :project_id => '1'})
+ assert_equal '/projects/1', @routes.generate({:controller => 'project', :action => 'index'}, {:project_id => '1'})
+ assert_raise(ActionController::RoutingError) { @routes.generate({:use_route => 'project', :controller => 'project', :action => 'index'}) }
+ assert_equal '/projects/1', @routes.generate({:use_route => 'project', :controller => 'project', :action => 'index', :project_id => '1'})
+ assert_equal '/projects/1', @routes.generate({:use_route => 'project', :controller => 'project', :action => 'index'}, {:project_id => '1'})
+
+ assert_equal '/clients', @routes.generate(:controller => 'projects', :action => 'index')
+ assert_equal '/clients?project_id=1', @routes.generate(:controller => 'projects', :action => 'index', :project_id => '1')
+ assert_equal '/clients', @routes.generate({:controller => 'projects', :action => 'index'}, {:project_id => '1'})
+ assert_equal '/clients', @routes.generate({:action => 'index'}, {:controller => 'projects', :action => 'index', :project_id => '1'})
+
+ assert_equal '/comment/20', @routes.generate({:id => 20}, {:controller => 'comments', :action => 'show'})
+ assert_equal '/comment/20', @routes.generate(:controller => 'comments', :id => 20, :action => 'show')
+ assert_equal '/comments/boo', @routes.generate(:controller => 'comments', :action => 'boo')
+
+ assert_equal '/ws/posts/show/1', @routes.generate(:controller => 'posts', :action => 'show', :id => '1', :ws => true)
+ assert_equal '/ws/posts', @routes.generate(:controller => 'posts', :action => 'index', :ws => true)
+
+ assert_equal '/account', @routes.generate(:controller => 'account', :action => 'subscription')
+ assert_equal '/account/billing', @routes.generate(:controller => 'account', :action => 'billing')
+
+ assert_equal '/pages/1/notes/show/1', @routes.generate(:page_id => '1', :controller => 'notes', :action => 'show', :id => '1')
+ assert_equal '/pages/1/notes/list', @routes.generate(:page_id => '1', :controller => 'notes', :action => 'list')
+ assert_equal '/pages/1/notes', @routes.generate(:page_id => '1', :controller => 'notes', :action => 'index')
+ assert_equal '/pages/1/notes', @routes.generate(:page_id => '1', :controller => 'notes')
+ assert_equal '/notes', @routes.generate(:page_id => nil, :controller => 'notes')
+ assert_equal '/notes', @routes.generate(:controller => 'notes')
+ assert_equal '/notes/print', @routes.generate(:controller => 'notes', :action => 'print')
+ assert_equal '/notes/print', @routes.generate({}, {:controller => 'notes', :action => 'print'})
+
+ assert_equal '/notes/index/1', @routes.generate({:controller => 'notes'}, {:controller => 'notes', :id => '1'})
+ assert_equal '/notes/index/1', @routes.generate({:controller => 'notes'}, {:controller => 'notes', :id => '1', :foo => 'bar'})
+ assert_equal '/notes/index/1', @routes.generate({:controller => 'notes'}, {:controller => 'notes', :id => '1'})
+ assert_equal '/notes/index/1', @routes.generate({:action => 'index'}, {:controller => 'notes', :id => '1'})
+ assert_equal '/notes/index/1', @routes.generate({}, {:controller => 'notes', :id => '1'})
+ assert_equal '/notes/show/1', @routes.generate({}, {:controller => 'notes', :action => 'show', :id => '1'})
+ assert_equal '/notes/index/1', @routes.generate({:controller => 'notes', :id => '1'}, {:foo => 'bar'})
+ assert_equal '/posts', @routes.generate({:controller => 'posts'}, {:controller => 'notes', :action => 'show', :id => '1'})
+ assert_equal '/notes/list', @routes.generate({:action => 'list'}, {:controller => 'notes', :action => 'show', :id => '1'})
+
+ assert_equal '/posts/ping', @routes.generate(:controller => 'posts', :action => 'ping')
+ assert_equal '/posts/show/1', @routes.generate(:controller => 'posts', :action => 'show', :id => '1')
+ assert_equal '/posts', @routes.generate(:controller => 'posts')
+ assert_equal '/posts', @routes.generate(:controller => 'posts', :action => 'index')
+ assert_equal '/posts', @routes.generate({:controller => 'posts'}, {:controller => 'posts', :action => 'index'})
+ assert_equal '/posts/create', @routes.generate({:action => 'create'}, {:controller => 'posts'})
+ assert_equal '/posts?foo=bar', @routes.generate(:controller => 'posts', :foo => 'bar')
+ assert_equal '/posts?foo%5B%5D=bar&foo%5B%5D=baz', @routes.generate(:controller => 'posts', :foo => ['bar', 'baz'])
+ assert_equal '/posts?page=2', @routes.generate(:controller => 'posts', :page => 2)
+ assert_equal '/posts?q%5Bfoo%5D%5Ba%5D=b', @routes.generate(:controller => 'posts', :q => { :foo => { :a => 'b'}})
+
+ assert_equal '/', @routes.generate(:controller => 'news', :action => 'index')
+ assert_equal '/', @routes.generate(:controller => 'news', :action => 'index', :format => nil)
+ assert_equal '/news.rss', @routes.generate(:controller => 'news', :action => 'index', :format => 'rss')
+
+
+ assert_raise(ActionController::RoutingError) { @routes.generate({:action => 'index'}) }
+ end
+
+ def test_generate_extras
+ assert_equal ['/people', []], @routes.generate_extras(:controller => 'people')
+ assert_equal ['/people', [:foo]], @routes.generate_extras(:controller => 'people', :foo => 'bar')
+ assert_equal ['/people', []], @routes.generate_extras(:controller => 'people', :action => 'index')
+ assert_equal ['/people', [:foo]], @routes.generate_extras(:controller => 'people', :action => 'index', :foo => 'bar')
+ assert_equal ['/people/new', []], @routes.generate_extras(:controller => 'people', :action => 'new')
+ assert_equal ['/people/new', [:foo]], @routes.generate_extras(:controller => 'people', :action => 'new', :foo => 'bar')
+ assert_equal ['/people/1', []], @routes.generate_extras(:controller => 'people', :action => 'show', :id => '1')
+ assert_equal ['/people/1', [:bar, :foo]], sort_extras!(@routes.generate_extras(:controller => 'people', :action => 'show', :id => '1', :foo => '2', :bar => '3'))
+ assert_equal ['/people', [:person]], @routes.generate_extras(:controller => 'people', :action => 'create', :person => { :first_name => 'Josh', :last_name => 'Peek' })
+ assert_equal ['/people', [:people]], @routes.generate_extras(:controller => 'people', :action => 'create', :people => ['Josh', 'Dave'])
+
+ assert_equal ['/posts/show/1', []], @routes.generate_extras(:controller => 'posts', :action => 'show', :id => '1')
+ assert_equal ['/posts/show/1', [:bar, :foo]], sort_extras!(@routes.generate_extras(:controller => 'posts', :action => 'show', :id => '1', :foo => '2', :bar => '3'))
+ assert_equal ['/posts', []], @routes.generate_extras(:controller => 'posts', :action => 'index')
+ assert_equal ['/posts', [:foo]], @routes.generate_extras(:controller => 'posts', :action => 'index', :foo => 'bar')
+ end
+
+ def test_extras
+ params = {:controller => 'people'}
+ assert_equal [], @routes.extra_keys(params)
+ assert_equal({:controller => 'people'}, params)
+
+ params = {:controller => 'people', :foo => 'bar'}
+ assert_equal [:foo], @routes.extra_keys(params)
+ assert_equal({:controller => 'people', :foo => 'bar'}, params)
+
+ params = {:controller => 'people', :action => 'create', :person => { :name => 'Josh'}}
+ assert_equal [:person], @routes.extra_keys(params)
+ assert_equal({:controller => 'people', :action => 'create', :person => { :name => 'Josh'}}, params)
+ end
+
+ private
+ def sort_extras!(extras)
+ if extras.length == 2
+ extras[1].sort! { |a, b| a.to_s <=> b.to_s }
+ end
+ extras
+ end
+
+ def assert_raise(e)
+ result = yield
+ flunk "Did not raise #{e}, but returned #{result.inspect}"
+ rescue e
+ assert true
+ end
+end
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index 73870a56bb..375878b755 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -108,6 +108,11 @@ XML
head :created, :location => 'created resource'
end
+ def delete_cookie
+ cookies.delete("foo")
+ render :nothing => true
+ end
+
private
def rescue_action(e)
raise e
@@ -512,6 +517,18 @@ XML
assert @request.params[:foo].blank?
end
+ def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set
+ @request.cookies['foo'] = 'bar'
+ get :no_op
+ assert_equal 'bar', cookies['foo']
+ end
+
+ def test_should_detect_if_cookie_is_deleted
+ @request.cookies['foo'] = 'bar'
+ get :delete_cookie
+ assert_nil cookies['foo']
+ end
+
%w(controller response request).each do |variable|
%w(get post put delete head process).each do |method|
define_method("test_#{variable}_missing_for_#{method}_raises_error") do
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index 4c4bf9ade4..3b14cbb2d8 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -224,9 +224,8 @@ class UrlWriterTests < ActionController::TestCase
def test_named_routes
with_routing do |set|
set.draw do |map|
- map.no_args '/this/is/verbose', :controller => 'home', :action => 'index'
- map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
- map.connect ':controller/:action/:id'
+ match 'this/is/verbose', :to => 'home#index', :as => :no_args
+ match 'home/sweet/home/:user', :to => 'home#index', :as => :home
end
# We need to create a new class in order to install the new named route.
@@ -264,7 +263,7 @@ class UrlWriterTests < ActionController::TestCase
def test_only_path
with_routing do |set|
set.draw do |map|
- map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
+ match 'home/sweet/home/:user', :to => 'home#index', :as => :home
map.connect ':controller/:action/:id'
end
@@ -321,8 +320,8 @@ class UrlWriterTests < ActionController::TestCase
params = extract_params(url)
assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
- assert_equal params[2], { 'query[person][position][]' => 'prof' }.to_query
- assert_equal params[3], { 'query[person][position][]' => 'art director' }.to_query
+ assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query
+ assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query
end
def test_path_generation_for_symbol_parameter_keys
@@ -334,7 +333,6 @@ class UrlWriterTests < ActionController::TestCase
set.draw do |map|
map.main '', :controller => 'posts', :format => nil
map.resources :posts
- map.connect ':controller/:action/:id'
end
# We need to create a new class in order to install the new named route.
@@ -359,10 +357,10 @@ class UrlWriterTests < ActionController::TestCase
controller = kls.new
params = {:id => 1, :format => :xml}
assert_deprecated do
- assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params))
+ assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params))
end
assert_deprecated do
- assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml))
+ assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml))
end
end
end
@@ -382,6 +380,6 @@ class UrlWriterTests < ActionController::TestCase
private
def extract_params(url)
- url.split('?', 2).last.split('&')
+ url.split('?', 2).last.split('&').sort
end
end
diff --git a/actionpack/test/controller/verification_test.rb b/actionpack/test/controller/verification_test.rb
index 1a9eb65f29..11d0d10897 100644
--- a/actionpack/test/controller/verification_test.rb
+++ b/actionpack/test/controller/verification_test.rb
@@ -125,8 +125,8 @@ class VerificationTest < ActionController::TestCase
assert_not_deprecated do
with_routing do |set|
set.draw do |map|
- map.foo '/foo', :controller => 'test', :action => 'foo'
- map.connect ":controller/:action/:id"
+ match 'foo', :to => 'test#foo', :as => :foo
+ match 'verification_test/:action', :to => ::VerificationTest::TestController
end
get :guarded_one_for_named_route_test, :two => "not one"
assert_redirected_to '/foo'
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index db6cf7b330..d3308f73cc 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -57,7 +57,7 @@ class JsonParamsParsingTest < ActionController::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do |map|
- map.connect ':action', :controller => "json_params_parsing_test/test"
+ match ':action', :to => ::JsonParamsParsingTest::TestController
end
yield
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index a31e326ddf..071d80c5b0 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -109,12 +109,12 @@ class QueryStringParsingTest < ActionController::IntegrationTest
def assert_parses(expected, actual)
with_routing do |set|
set.draw do |map|
- map.connect ':action', :controller => "query_string_parsing_test/test"
+ match ':action', :to => ::QueryStringParsingTest::TestController
end
get "/parse", actual
assert_response :ok
- assert_equal(expected, TestController.last_query_parameters)
+ assert_equal(expected, ::QueryStringParsingTest::TestController.last_query_parameters)
end
end
end
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index 7167cdafac..69dbd7f528 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -130,7 +130,7 @@ class UrlEncodedParamsParsingTest < ActionController::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do |map|
- map.connect ':action', :controller => "url_encoded_params_parsing_test/test"
+ match ':action', :to => ::UrlEncodedParamsParsingTest::TestController
end
yield
end
diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
index 521002b519..96189e4ca2 100644
--- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
@@ -84,7 +84,7 @@ class XmlParamsParsingTest < ActionController::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do |map|
- map.connect ':action', :controller => "xml_params_parsing_test/test"
+ match ':action', :to => ::XmlParamsParsingTest::TestController
end
yield
end
@@ -100,4 +100,4 @@ class LegacyXmlParamsParsingTest < XmlParamsParsingTest
def default_headers
{'HTTP_X_POST_DATA_FORMAT' => 'xml'}
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 239fda98e0..b62df9a6b2 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -432,6 +432,10 @@ class RequestTest < ActiveSupport::TestCase
request = stub_request
request.expects(:parameters).at_least_once.returns({ :format => :txt })
assert_equal with_set(Mime::TEXT), request.formats
+
+ request = stub_request
+ request.expects(:parameters).at_least_once.returns({ :format => :unknown })
+ assert request.formats.empty?
end
test "negotiate_mime" do
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
new file mode 100644
index 0000000000..ca07bc7a28
--- /dev/null
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -0,0 +1,391 @@
+require 'abstract_unit'
+require 'controller/fake_controllers'
+
+class TestRoutingMapper < ActionDispatch::IntegrationTest
+ SprocketsApp = lambda { |env|
+ [200, {"Content-Type" => "text/html"}, ["javascripts"]]
+ }
+
+ class IpRestrictor
+ def self.matches?(request)
+ request.ip =~ /192\.168\.1\.1\d\d/
+ end
+ end
+
+ stub_controllers do |routes|
+ Routes = routes
+ Routes.draw do |map|
+ controller :sessions do
+ get 'login', :to => :new, :as => :login
+ post 'login', :to => :create
+
+ delete 'logout', :to => :destroy, :as => :logout
+ end
+
+ match 'account/login', :to => redirect("/login")
+
+ match 'openid/login', :via => [:get, :post], :to => "openid#login"
+
+ controller(:global) do
+ match 'global/:action'
+ match 'global/export', :to => :export, :as => :export_request
+ match 'global/hide_notice', :to => :hide_notice, :as => :hide_notice
+ match '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ }
+ end
+
+ constraints(:ip => /192\.168\.1\.\d\d\d/) do
+ get 'admin', :to => "queenbee#index"
+ end
+
+ constraints ::TestRoutingMapper::IpRestrictor do
+ get 'admin/accounts', :to => "queenbee#accounts"
+ end
+
+ resources :projects, :controller => :project do
+ resources :involvements, :attachments
+
+ resources :participants do
+ put :update_all, :on => :collection
+ end
+
+ resources :companies do
+ resources :people
+ resource :avatar
+ end
+
+ resources :images do
+ post :revise, :on => :member
+ end
+
+ resources :people do
+ namespace ":access_token" do
+ resource :avatar
+ end
+
+ member do
+ put :accessible_projects
+ post :resend, :generate_new_password
+ end
+ end
+
+ resources :posts do
+ get :archive, :toggle_view, :on => :collection
+ post :preview, :on => :member
+
+ resource :subscription
+
+ resources :comments do
+ post :preview, :on => :collection
+ end
+ end
+ end
+
+ match 'sprockets.js', :to => ::TestRoutingMapper::SprocketsApp
+
+ match 'people/:id/update', :to => 'people#update', :as => :update_person
+ match '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person
+
+ # misc
+ match 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article
+
+ namespace :account do
+ resource :subscription, :credit, :credit_card
+ end
+
+ controller :articles do
+ scope 'articles' do
+ scope ':title', :title => /[a-z]+/, :as => :with_title do
+ match ':id', :to => :with_id
+ end
+ end
+ end
+
+ scope ':access_token', :constraints => { :access_token => /\w{5,5}/ } do
+ resources :rooms
+ end
+ end
+ end
+
+ def app
+ Routes
+ end
+
+ def test_logout
+ with_test_routes do
+ delete '/logout'
+ assert_equal 'sessions#destroy', @response.body
+
+ assert_equal '/logout', logout_path
+ assert_equal '/logout', url_for(:controller => 'sessions', :action => 'destroy', :only_path => true)
+ end
+ end
+
+ def test_login
+ with_test_routes do
+ get '/login'
+ assert_equal 'sessions#new', @response.body
+ assert_equal '/login', login_path
+
+ post '/login'
+ assert_equal 'sessions#create', @response.body
+
+ assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true)
+ assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true)
+ end
+ end
+
+ def test_login_redirect
+ with_test_routes do
+ get '/account/login'
+ assert_equal 301, @response.status
+ assert_equal 'http://www.example.com/login', @response.headers['Location']
+ assert_equal 'Moved Permanently', @response.body
+ end
+ end
+
+ def test_openid
+ with_test_routes do
+ get '/openid/login'
+ assert_equal 'openid#login', @response.body
+
+ post '/openid/login'
+ assert_equal 'openid#login', @response.body
+ end
+ end
+
+ # TODO: rackmount is broken
+ # def test_admin
+ # with_test_routes do
+ # get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ # assert_equal 'queenbee#index', @response.body
+ #
+ # assert_raise(ActionController::RoutingError) { get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'} }
+ #
+ # get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ # assert_equal 'queenbee#accounts', @response.body
+ #
+ # assert_raise(ActionController::RoutingError) { get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} }
+ # end
+ # end
+
+ def test_global
+ with_test_routes do
+ get '/global/dashboard'
+ assert_equal 'global#dashboard', @response.body
+
+ get '/global/export'
+ assert_equal 'global#export', @response.body
+
+ get '/global/hide_notice'
+ assert_equal 'global#hide_notice', @response.body
+
+ get '/export/123/foo.txt'
+ assert_equal 'global#export', @response.body
+
+ assert_equal '/global/export', export_request_path
+ assert_equal '/global/hide_notice', hide_notice_path
+ assert_equal '/export/123/foo.txt', export_download_path(:id => 123, :file => 'foo.txt')
+ end
+ end
+
+ def test_projects
+ with_test_routes do
+ get '/projects'
+ assert_equal 'projects#index', @response.body
+ assert_equal '/projects', projects_path
+
+ get '/projects/new'
+ assert_equal 'projects#new', @response.body
+ assert_equal '/projects/new', new_project_path
+
+ get '/projects/1'
+ assert_equal 'projects#show', @response.body
+ assert_equal '/projects/1', project_path(:id => '1')
+
+ get '/projects/1/edit'
+ assert_equal 'projects#edit', @response.body
+ assert_equal '/projects/1/edit', edit_project_path(:id => '1')
+ end
+ end
+
+ def test_projects_involvements
+ with_test_routes do
+ get '/projects/1/involvements'
+ assert_equal 'involvements#index', @response.body
+
+ get '/projects/1/involvements/1'
+ assert_equal 'involvements#show', @response.body
+ end
+ end
+
+ def test_projects_attachments
+ with_test_routes do
+ get '/projects/1/attachments'
+ assert_equal 'attachments#index', @response.body
+ end
+ end
+
+ def test_projects_participants
+ with_test_routes do
+ get '/projects/1/participants'
+ assert_equal 'participants#index', @response.body
+
+ put '/projects/1/participants/update_all'
+ assert_equal 'participants#update_all', @response.body
+ end
+ end
+
+ def test_projects_companies
+ with_test_routes do
+ get '/projects/1/companies'
+ assert_equal 'companies#index', @response.body
+
+ get '/projects/1/companies/1/people'
+ assert_equal 'people#index', @response.body
+
+ get '/projects/1/companies/1/avatar'
+ assert_equal 'avatars#show', @response.body
+ end
+ end
+
+ def test_project_images
+ with_test_routes do
+ get '/projects/1/images'
+ assert_equal 'images#index', @response.body
+
+ post '/projects/1/images/1/revise'
+ assert_equal 'images#revise', @response.body
+ end
+ end
+
+ def test_projects_people
+ with_test_routes do
+ get '/projects/1/people'
+ assert_equal 'people#index', @response.body
+
+ get '/projects/1/people/1'
+ assert_equal 'people#show', @response.body
+
+ get '/projects/1/people/1/7a2dec8/avatar'
+ assert_equal 'avatars#show', @response.body
+
+ put '/projects/1/people/1/accessible_projects'
+ assert_equal 'people#accessible_projects', @response.body
+
+ post '/projects/1/people/1/resend'
+ assert_equal 'people#resend', @response.body
+
+ post '/projects/1/people/1/generate_new_password'
+ assert_equal 'people#generate_new_password', @response.body
+ end
+ end
+
+ def test_projects_posts
+ with_test_routes do
+ get '/projects/1/posts'
+ assert_equal 'posts#index', @response.body
+
+ get '/projects/1/posts/archive'
+ assert_equal 'posts#archive', @response.body
+
+ get '/projects/1/posts/toggle_view'
+ assert_equal 'posts#toggle_view', @response.body
+
+ post '/projects/1/posts/1/preview'
+ assert_equal 'posts#preview', @response.body
+
+ get '/projects/1/posts/1/subscription'
+ assert_equal 'subscriptions#show', @response.body
+
+ get '/projects/1/posts/1/comments'
+ assert_equal 'comments#index', @response.body
+
+ post '/projects/1/posts/1/comments/preview'
+ assert_equal 'comments#preview', @response.body
+ end
+ end
+
+ def test_sprockets
+ with_test_routes do
+ get '/sprockets.js'
+ assert_equal 'javascripts', @response.body
+ end
+ end
+
+ def test_update_person_route
+ with_test_routes do
+ get '/people/1/update'
+ assert_equal 'people#update', @response.body
+
+ assert_equal '/people/1/update', update_person_path(:id => 1)
+ end
+ end
+
+ def test_update_project_person
+ with_test_routes do
+ get '/projects/1/people/2/update'
+ assert_equal 'people#update', @response.body
+
+ assert_equal '/projects/1/people/2/update', update_project_person_path(:project_id => 1, :id => 2)
+ end
+ end
+
+ def test_articles_perma
+ with_test_routes do
+ get '/articles/2009/08/18/rails-3'
+ assert_equal 'articles#show', @response.body
+
+ assert_equal '/articles/2009/8/18/rails-3', article_path(:year => 2009, :month => 8, :day => 18, :title => 'rails-3')
+ end
+ end
+
+ def test_account_namespace
+ with_test_routes do
+ get '/account/subscription'
+ assert_equal 'subscriptions#show', @response.body
+
+ get '/account/credit'
+ assert_equal 'credits#show', @response.body
+
+ get '/account/credit_card'
+ assert_equal 'credit_cards#show', @response.body
+ end
+ end
+
+ def test_articles_with_id
+ with_test_routes do
+ get '/articles/rails/1'
+ assert_equal 'articles#with_id', @response.body
+
+ assert_raise(ActionController::RoutingError) { get '/articles/123/1' }
+
+ assert_equal '/articles/rails/1', with_title_path(:title => 'rails', :id => 1)
+ end
+ end
+
+ def test_access_token_rooms
+ with_test_routes do
+ get '/12345/rooms'
+ assert_equal 'rooms#index', @response.body
+
+ get '/12345/rooms/1'
+ assert_equal 'rooms#show', @response.body
+
+ get '/12345/rooms/1/edit'
+ assert_equal 'rooms#edit', @response.body
+ end
+ end
+
+ private
+ def with_test_routes
+ real_routes, temp_routes = ActionController::Routing::Routes, Routes
+
+ ActionController::Routing.module_eval { remove_const :Routes }
+ ActionController::Routing.module_eval { const_set :Routes, temp_routes }
+
+ yield
+ ensure
+ ActionController::Routing.module_eval { remove_const :Routes }
+ ActionController::Routing.const_set(:Routes, real_routes)
+ end
+end
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index ab5fabde65..ab7b9bc31b 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -219,7 +219,7 @@ class CookieStoreTest < ActionController::IntegrationTest
def with_test_route_set(options = {})
with_routing do |set|
set.draw do |map|
- map.connect "/:action", :controller => "cookie_store_test/test"
+ match ':action', :to => ::CookieStoreTest::TestController
end
options = {:key => SessionKey, :secret => SessionSecret}.merge(options)
@app = ActionDispatch::Session::CookieStore.new(set, options)
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index c7435bd06b..5a1dcb4dab 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -112,7 +112,7 @@ class MemCacheStoreTest < ActionController::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do |map|
- map.connect "/:action", :controller => "mem_cache_store_test/test"
+ match ':action', :to => ::MemCacheStoreTest::TestController
end
@app = ActionDispatch::Session::MemCacheStore.new(set, :key => '_session_id')
yield
diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb
index 0ff93f1c5d..c8dc4ab461 100644
--- a/actionpack/test/dispatch/session/test_session_test.rb
+++ b/actionpack/test/dispatch/session/test_session_test.rb
@@ -26,11 +26,11 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase
assert_equal('value', session[:key])
end
- def test_calling_delete_removes_item
+ def test_calling_delete_removes_item_and_returns_its_value
session = ActionController::TestSession.new
session[:key] = 'value'
assert_equal('value', session[:key])
- session.delete(:key)
+ assert_equal('value', session.delete(:key))
assert_nil(session[:key])
end
diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb
index b8e340e055..5da02b2ea6 100644
--- a/actionpack/test/dispatch/test_request_test.rb
+++ b/actionpack/test/dispatch/test_request_test.rb
@@ -5,7 +5,7 @@ class TestRequestTest < ActiveSupport::TestCase
env = ActionDispatch::TestRequest.new.env
assert_equal "GET", env.delete("REQUEST_METHOD")
- assert_equal nil, env.delete("HTTPS")
+ assert_equal "off", env.delete("HTTPS")
assert_equal "http", env.delete("rack.url_scheme")
assert_equal "example.org", env.delete("SERVER_NAME")
assert_equal "80", env.delete("SERVER_PORT")
@@ -18,7 +18,7 @@ class TestRequestTest < ActiveSupport::TestCase
assert_equal "0.0.0.0", env.delete("REMOTE_ADDR")
assert_equal "Rails Testing", env.delete("HTTP_USER_AGENT")
- assert_equal [0, 1], env.delete("rack.version")
+ assert_equal [1, 0], env.delete("rack.version")
assert_equal "", env.delete("rack.input").string
assert_kind_of StringIO, env.delete("rack.errors")
assert_equal true, env.delete("rack.multithread")
diff --git a/actionpack/test/fixtures/layouts/block_with_layout.erb b/actionpack/test/fixtures/layouts/block_with_layout.erb
index 6a8b41914b..f25b41271d 100644
--- a/actionpack/test/fixtures/layouts/block_with_layout.erb
+++ b/actionpack/test/fixtures/layouts/block_with_layout.erb
@@ -1,3 +1,3 @@
-<% render(:layout => "layout_for_partial", :locals => { :name => "Anthony" }) do %>Inside from first block in layout<% end %>
+<% render(:layout => "layout_for_partial", :locals => { :name => "Anthony" }) do %>Inside from first block in layout<% "Return value should be discarded" %><% end %>
<%= yield %>
<% render(:layout => "layout_for_partial", :locals => { :name => "Ramm" }) do %>Inside from second block in layout<% end %>
diff --git a/actionpack/test/lib/controller/fake_controllers.rb b/actionpack/test/lib/controller/fake_controllers.rb
index 9ec7f330b8..250327e6dc 100644
--- a/actionpack/test/lib/controller/fake_controllers.rb
+++ b/actionpack/test/lib/controller/fake_controllers.rb
@@ -5,8 +5,10 @@ class NotAController; end
module Admin
class << self; alias_method :const_available?, :const_defined?; end
- class UserController < ActionController::Base; end
class NewsFeedController < ActionController::Base; end
+ class PostsController < ActionController::Base; end
+ class StuffController < ActionController::Base; end
+ class UserController < ActionController::Base; end
end
module Api
@@ -24,8 +26,11 @@ class ElsewhereController < ActionController::Base; end
class FooController < ActionController::Base; end
class HiController < ActionController::Base; end
class ImageController < ActionController::Base; end
+class NotesController < ActionController::Base; end
class PeopleController < ActionController::Base; end
+class PostsController < ActionController::Base; end
class SessionsController < ActionController::Base; end
+class StuffController < ActionController::Base; end
class SubpathBooksController < ActionController::Base; end
class WeblogController < ActionController::Base; end
diff --git a/actionpack/test/template/active_model_helper_i18n_test.rb b/actionpack/test/template/active_model_helper_i18n_test.rb
new file mode 100644
index 0000000000..2465444fc5
--- /dev/null
+++ b/actionpack/test/template/active_model_helper_i18n_test.rb
@@ -0,0 +1,42 @@
+require 'abstract_unit'
+
+class ActiveModelHelperI18nTest < Test::Unit::TestCase
+ include ActionView::Context
+ include ActionView::Helpers::ActiveModelHelper
+
+ attr_reader :request
+
+ def setup
+ @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
+ @object.stubs :to_model => @object
+ @object.stubs :class => stub(:model_name => stub(:human => ""))
+
+ @object_name = 'book_seller'
+ @object_name_without_underscore = 'book seller'
+
+ stubs(:content_tag).returns 'content_tag'
+
+ I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved"
+ I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:'
+ end
+
+ def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message
+ I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').never
+ error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en')
+ end
+
+ def test_error_messages_for_given_no_header_option_it_translates_header_message
+ I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns 'header message'
+ error_messages_for(:object => @object, :locale => 'en')
+ end
+
+ def test_error_messages_for_given_a_message_option_it_does_not_translate_message
+ I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).never
+ error_messages_for(:object => @object, :message => 'message', :locale => 'en')
+ end
+
+ def test_error_messages_for_given_no_message_option_it_translates_message
+ I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:'
+ error_messages_for(:object => @object, :locale => 'en')
+ end
+end
diff --git a/actionpack/test/template/active_record_helper_test.rb b/actionpack/test/template/active_model_helper_test.rb
index c149070f2a..3e01ae78c3 100644
--- a/actionpack/test/template/active_record_helper_test.rb
+++ b/actionpack/test/template/active_model_helper_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class ActiveRecordHelperTest < ActionView::TestCase
+class ActiveModelHelperTest < ActionView::TestCase
tests ActionView::Helpers::ActiveModelHelper
silence_warnings do
diff --git a/actionpack/test/template/active_record_helper_i18n_test.rb b/actionpack/test/template/active_record_helper_i18n_test.rb
deleted file mode 100644
index 047f81be29..0000000000
--- a/actionpack/test/template/active_record_helper_i18n_test.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'abstract_unit'
-
-class ActiveRecordHelperI18nTest < Test::Unit::TestCase
- include ActionView::Context
- include ActionView::Helpers::ActiveModelHelper
-
- attr_reader :request
-
- def setup
- @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
- @object.stubs :to_model => @object
- @object.stubs :class => stub(:model_name => stub(:human => ""))
-
- @object_name = 'book_seller'
- @object_name_without_underscore = 'book seller'
-
- stubs(:content_tag).returns 'content_tag'
-
- I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved"
- I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:'
- end
-
- def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message
- I18n.expects(:translate).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').never
- error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en')
- end
-
- def test_error_messages_for_given_no_header_option_it_translates_header_message
- I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns 'header message'
- I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns ''
- error_messages_for(:object => @object, :locale => 'en')
- end
-
- def test_error_messages_for_given_a_message_option_it_does_not_translate_message
- I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).never
- I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns ''
- error_messages_for(:object => @object, :message => 'message', :locale => 'en')
- end
-
- def test_error_messages_for_given_no_message_option_it_translates_message
- I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:'
- I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns ''
- error_messages_for(:object => @object, :locale => 'en')
- end
-
- def test_error_messages_for_given_object_name_it_translates_object_name
- I18n.expects(:t).with(:header, :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => @object_name_without_underscore).returns "1 error prohibited this #{@object_name_without_underscore} from being saved"
- I18n.expects(:t).with(@object_name, :default => @object_name_without_underscore, :count => 1, :scope => [:activerecord, :models]).once.returns @object_name_without_underscore
- error_messages_for(:object => @object, :locale => 'en', :object_name => @object_name)
- end
-end
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index d94135b04b..57802ebf42 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -3,6 +3,13 @@ require 'abstract_unit'
class AssetTagHelperTest < ActionView::TestCase
tests ActionView::Helpers::AssetTagHelper
+ DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG.merge(
+ :assets_dir => File.dirname(__FILE__) + "/../fixtures/public",
+ :javascripts_dir => File.dirname(__FILE__) + "/../fixtures/public/javascripts",
+ :stylesheets_dir => File.dirname(__FILE__) + "/../fixtures/public/stylesheets")
+
+ include ActiveSupport::Configurable
+
def setup
super
silence_warnings do
@@ -872,6 +879,9 @@ end
class AssetTagHelperNonVhostTest < ActionView::TestCase
tests ActionView::Helpers::AssetTagHelper
+ DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG
+ include ActiveSupport::Configurable
+
def setup
super
ActionController::Base.relative_url_root = "/collaboration/hieraki"
diff --git a/actionpack/test/template/benchmark_helper_test.rb b/actionpack/test/template/benchmark_helper_test.rb
deleted file mode 100644
index ac31fc6503..0000000000
--- a/actionpack/test/template/benchmark_helper_test.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-require 'abstract_unit'
-require 'action_view/helpers/benchmark_helper'
-
-class BenchmarkHelperTest < ActionView::TestCase
- tests ActionView::Helpers::BenchmarkHelper
-
- def setup
- super
- controller.logger = ActiveSupport::BufferedLogger.new(StringIO.new)
- controller.logger.auto_flushing = false
- end
-
- def teardown
- controller.logger.send(:clear_buffer)
- end
-
- def test_without_block
- assert_raise(LocalJumpError) { benchmark }
- assert buffer.empty?
- end
-
- def test_defaults
- i_was_run = false
- benchmark { i_was_run = true }
- assert i_was_run
- assert_last_logged
- end
-
- def test_with_message
- i_was_run = false
- benchmark('test_run') { i_was_run = true }
- assert i_was_run
- assert_last_logged 'test_run'
- end
-
- def test_with_message_and_deprecated_level
- i_was_run = false
-
- assert_deprecated do
- benchmark('debug_run', :debug) { i_was_run = true }
- end
-
- assert i_was_run
- assert_last_logged 'debug_run'
- end
-
- def test_within_level
- controller.logger.level = ActiveSupport::BufferedLogger::DEBUG
- benchmark('included_debug_run', :level => :debug) { }
- assert_last_logged 'included_debug_run'
- end
-
- def test_outside_level
- controller.logger.level = ActiveSupport::BufferedLogger::ERROR
- benchmark('skipped_debug_run', :level => :debug) { }
- assert_no_match(/skipped_debug_run/, buffer.last)
- ensure
- controller.logger.level = ActiveSupport::BufferedLogger::DEBUG
- end
-
- def test_without_silencing
- benchmark('debug_run', :silence => false) do
- controller.logger.info "not silenced!"
- end
-
- assert_equal 2, buffer.size
- end
-
- def test_with_silencing
- benchmark('debug_run', :silence => true) do
- controller.logger.info "silenced!"
- end
-
- assert_equal 1, buffer.size
- end
-
-
- private
- def buffer
- controller.logger.send(:buffer)
- end
-
- def assert_last_logged(message = 'Benchmarking')
- assert_match(/^#{message} \(.*\)$/, buffer.last)
- end
-end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 04c635e770..44734abb18 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -613,6 +613,26 @@ class FormHelperTest < ActionView::TestCase
expected = '<form action="http://www.example.com" method="post">' +
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
+ '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
+ '</form>'
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_for_with_existing_records_on_a_nested_attributes_one_to_one_association_with_explicit_hidden_field_placement
+ @post.author = Author.new(321)
+
+ form_for(:post, @post) do |f|
+ concat f.text_field(:title)
+ f.fields_for(:author) do |af|
+ concat af.hidden_field(:id)
+ concat af.text_field(:name)
+ end
+ end
+
+ expected = '<form action="http://www.example.com" method="post">' +
+ '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
'<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
'</form>'
@@ -634,6 +654,30 @@ class FormHelperTest < ActionView::TestCase
expected = '<form action="http://www.example.com" method="post">' +
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
+ '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
+ '</form>'
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_for_with_existing_records_on_a_nested_attributes_collection_association_with_explicit_hidden_field_placement
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+
+ form_for(:post, @post) do |f|
+ concat f.text_field(:title)
+ @post.comments.each do |comment|
+ f.fields_for(:comments, comment) do |cf|
+ concat cf.hidden_field(:id)
+ concat cf.text_field(:name)
+ end
+ end
+ end
+
+ expected = '<form action="http://www.example.com" method="post">' +
+ '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
'<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
@@ -678,8 +722,8 @@ class FormHelperTest < ActionView::TestCase
expected = '<form action="http://www.example.com" method="post">' +
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
'<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' +
'</form>'
@@ -713,10 +757,10 @@ class FormHelperTest < ActionView::TestCase
expected = '<form action="http://www.example.com" method="post">' +
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
'<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
+ '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
'</form>'
assert_dom_equal expected, output_buffer
@@ -736,8 +780,8 @@ class FormHelperTest < ActionView::TestCase
expected = '<form action="http://www.example.com" method="post">' +
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
'<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' +
'</form>'
@@ -755,8 +799,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = '<form action="http://www.example.com" method="post">' +
- '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' +
'<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" size="30" type="text" value="comment #321" />' +
+ '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' +
'</form>'
assert_dom_equal expected, output_buffer
@@ -790,18 +834,18 @@ class FormHelperTest < ActionView::TestCase
end
expected = '<form action="http://www.example.com" method="post">' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
'<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
- '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' +
'<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="commentrelevance #314" />' +
- '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' +
+ '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' +
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
'<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" size="30" type="text" value="tag #123" />' +
- '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' +
'<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #3141" />' +
- '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />' +
+ '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' +
+ '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' +
'<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" size="30" type="text" value="tag #456" />' +
- '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' +
'<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #31415" />' +
+ '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' +
+ '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />' +
'</form>'
assert_dom_equal expected, output_buffer
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index d64b9492e2..47462b1237 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -3,6 +3,9 @@ require 'abstract_unit'
class FormTagHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormTagHelper
+ include ActiveSupport::Configurable
+ DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG
+
def setup
super
@controller = Class.new do
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 85a97d570c..0a2b82bd89 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -88,7 +88,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("111.00", number_with_precision(111, :precision => 2))
assert_equal("111.235", number_with_precision("111.2346"))
assert_equal("31.83", number_with_precision("31.825", :precision => 2))
- assert_equal("3268", number_with_precision((32.675 * 100.00), :precision => 0))
+ assert_equal("3268", number_with_precision((32.6751 * 100.00), :precision => 0))
assert_equal("112", number_with_precision(111.50, :precision => 0))
assert_equal("1234567892", number_with_precision(1234567891.50, :precision => 0))
diff --git a/actionpack/test/view/safe_buffer_test.rb b/actionpack/test/template/safe_buffer_test.rb
index 2236709627..6a18201d16 100644
--- a/actionpack/test/view/safe_buffer_test.rb
+++ b/actionpack/test/template/safe_buffer_test.rb
@@ -26,7 +26,7 @@ class SafeBufferTest < ActionView::TestCase
end
test "Should not mess with a previously escape test" do
- @buffer << CGI.escapeHTML("<script>")
+ @buffer << ERB::Util.html_escape("<script>")
assert_equal "&lt;script&gt;", @buffer
end
diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb
index ca72c13ffa..05a409d05a 100644
--- a/actionpack/test/template/test_case_test.rb
+++ b/actionpack/test/template/test_case_test.rb
@@ -24,7 +24,7 @@ module ActionView
test_case.class_eval do
test "helpers defined on ActionView::TestCase are available" do
assert test_case.ancestors.include?(ASharedTestHelper)
- assert 'Holla!', from_shared_helper
+ assert_equal 'Holla!', from_shared_helper
end
end
end
@@ -38,10 +38,15 @@ module ActionView
assert_equal 'Eloy', render('developers/developer', :developer => stub(:name => 'Eloy'))
end
+ test "can render a layout with block" do
+ assert_equal "Before (ChrisCruft)\n!\nAfter",
+ render(:layout => "test/layout_for_partial", :locals => {:name => "ChrisCruft"}) {"!"}
+ end
+
helper AnotherTestHelper
test "additional helper classes can be specified as in a controller" do
assert test_case.ancestors.include?(AnotherTestHelper)
- assert 'Howdy!', from_another_helper
+ assert_equal 'Howdy!', from_another_helper
end
end
@@ -58,14 +63,14 @@ module ActionView
helper AnotherTestHelper
test "additional helper classes can be specified as in a controller" do
assert test_case.ancestors.include?(AnotherTestHelper)
- assert 'Howdy!', from_another_helper
+ assert_equal 'Howdy!', from_another_helper
test_case.helper_class.module_eval do
def render_from_helper
from_another_helper
end
end
- assert 'Howdy!', render(:partial => 'test/from_helper')
+ assert_equal 'Howdy!', render(:partial => 'test/from_helper')
end
end
diff --git a/actionpack/test/template/test_test.rb b/actionpack/test/template/test_test.rb
index 05a14f3554..68e790cf46 100644
--- a/actionpack/test/template/test_test.rb
+++ b/actionpack/test/template/test_test.rb
@@ -48,8 +48,7 @@ class PeopleHelperTest < ActionView::TestCase
def with_test_route_set
with_routing do |set|
set.draw do |map|
- map.people 'people', :controller => 'people', :action => 'index'
- map.connect ':controller/:action/:id'
+ match 'people', :to => 'people#index', :as => :people
end
yield
end
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 7f6ebc56b7..bf0b4ad3a7 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -5,6 +5,9 @@ require 'controller/fake_controllers'
RequestMock = Struct.new("Request", :request_uri, :protocol, :host_with_port, :env)
class UrlHelperTest < ActionView::TestCase
+ include ActiveSupport::Configurable
+ DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG
+
def setup
super
@controller = Class.new do
@@ -19,11 +22,16 @@ class UrlHelperTest < ActionView::TestCase
def test_url_for_escapes_urls
@controller.url = "http://www.example.com?a=b&c=d"
- assert_equal "http://www.example.com?a=b&amp;c=d", url_for(:a => 'b', :c => 'd')
+ assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd')
assert_equal "http://www.example.com?a=b&amp;c=d", url_for(:a => 'b', :c => 'd', :escape => true)
assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => false)
end
+ def test_url_for_escaping_is_safety_aware
+ assert url_for(:a => 'b', :c => 'd', :escape => true).html_safe?, "escaped urls should be html_safe?"
+ assert !url_for(:a => 'b', :c => 'd', :escape => false).html_safe?, "non-escaped urls shouldn't be safe"
+ end
+
def test_url_for_escapes_url_once
@controller.url = "http://www.example.com?a=b&amp;c=d"
assert_equal "http://www.example.com?a=b&amp;c=d", url_for("http://www.example.com?a=b&amp;c=d")
@@ -39,6 +47,16 @@ class UrlHelperTest < ActionView::TestCase
assert_equal 'javascript:history.back()', url_for(:back)
end
+ def test_url_for_from_hash_doesnt_escape_ampersand
+ @controller = TestController.new
+ @view = ActionView::Base.new
+ @view.controller = @controller
+
+ path = @view.url_for(:controller => :cheeses, :foo => :bar, :baz => :quux)
+
+ assert_equal '/cheeses?baz=quux&foo=bar', sort_query_string_params(path)
+ end
+
# todo: missing test cases
def test_button_to_with_straight_url
assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com")
@@ -266,21 +284,21 @@ class UrlHelperTest < ActionView::TestCase
assert current_page?({ :action => "show", :controller => "weblog" })
assert current_page?("http://www.example.com/weblog/show")
end
-
+
def test_current_page_ignoring_params
@controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1")
@controller.url = "http://www.example.com/weblog/show?order=desc&page=1"
assert current_page?({ :action => "show", :controller => "weblog" })
assert current_page?("http://www.example.com/weblog/show")
end
-
+
def test_current_page_with_params_that_match
@controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1")
@controller.url = "http://www.example.com/weblog/show?order=desc&page=1"
assert current_page?({ :action => "show", :controller => "weblog", :order => "desc", :page => "1" })
assert current_page?("http://www.example.com/weblog/show?order=desc&amp;page=1")
end
-
+
def test_link_unless_current
@controller.request = RequestMock.new("http://www.example.com/weblog/show")
@controller.url = "http://www.example.com/weblog/show"
@@ -295,7 +313,7 @@ class UrlHelperTest < ActionView::TestCase
@controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1")
@controller.url = "http://www.example.com/weblog/show?order=desc&page=1"
assert_equal "Showing", link_to_unless_current("Showing", { :action => "show", :controller => "weblog", :order=>'desc', :page=>'1' })
- assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&amp;page=1")
+ assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=1")
assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=1")
@controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc")
@@ -305,7 +323,7 @@ class UrlHelperTest < ActionView::TestCase
@controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1")
@controller.url = "http://www.example.com/weblog/show?order=desc&page=2"
- assert_equal "<a href=\"http://www.example.com/weblog/show?order=desc&amp;page=2\">Showing</a>", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" })
+ assert_equal "<a href=\"http://www.example.com/weblog/show?order=desc&page=2\">Showing</a>", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" })
assert_equal "<a href=\"http://www.example.com/weblog/show?order=desc&amp;page=2\">Showing</a>", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=2")
@@ -360,10 +378,17 @@ class UrlHelperTest < ActionView::TestCase
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
end
-
+
def protect_against_forgery?
false
end
+
+ private
+ def sort_query_string_params(uri)
+ path, qs = uri.split('?')
+ qs = qs.split('&').sort.join('&') if qs
+ qs ? "#{path}?#{qs}" : path
+ end
end
class UrlHelperController < ActionController::Base
diff --git a/actionpack/test/ts_isolated.rb b/actionpack/test/ts_isolated.rb
index 21d62f6aa7..5670d93613 100644
--- a/actionpack/test/ts_isolated.rb
+++ b/actionpack/test/ts_isolated.rb
@@ -10,8 +10,8 @@ class TestIsolated < Test::Unit::TestCase
Dir["#{File.dirname(__FILE__)}/{abstract,controller,dispatch,template}/**/*_test.rb"].each do |file|
define_method("test #{file}") do
command = "#{ruby} -Ilib:test #{file}"
- silence_stderr { `#{command}` }
- assert_equal 0, $?.to_i, command
+ result = silence_stderr { `#{command}` }
+ assert_block("#{command}\n#{result}") { $?.to_i.zero? }
end
end
end