aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-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
81 files changed, 2211 insertions, 3027 deletions
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