aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
authorJon Leighton <j@jonathanleighton.com>2011-03-02 21:24:56 +0000
committerJon Leighton <j@jonathanleighton.com>2011-03-04 09:30:27 +0000
commit735844db712c511dd8abf36a5279318fbc0ff9d0 (patch)
tree5fbd5d224ef85d8c878bf221db98b422c9345466 /actionpack/lib
parent9a98c766e045aebc2ef6d5b716936b73407f095d (diff)
parentb171b9e73dcc6a89b1da652da61c5127fe605b51 (diff)
downloadrails-735844db712c511dd8abf36a5279318fbc0ff9d0.tar.gz
rails-735844db712c511dd8abf36a5279318fbc0ff9d0.tar.bz2
rails-735844db712c511dd8abf36a5279318fbc0ff9d0.zip
Merge branch 'master' into nested_has_many_through
Conflicts: activerecord/CHANGELOG activerecord/lib/active_record/association_preload.rb activerecord/lib/active_record/associations.rb activerecord/lib/active_record/associations/class_methods/join_dependency.rb activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb activerecord/lib/active_record/associations/has_many_association.rb activerecord/lib/active_record/associations/has_many_through_association.rb activerecord/lib/active_record/associations/has_one_association.rb activerecord/lib/active_record/associations/has_one_through_association.rb activerecord/lib/active_record/associations/through_association_scope.rb activerecord/lib/active_record/reflection.rb activerecord/test/cases/associations/has_many_through_associations_test.rb activerecord/test/cases/associations/has_one_through_associations_test.rb activerecord/test/cases/reflection_test.rb activerecord/test/cases/relations_test.rb activerecord/test/fixtures/memberships.yml activerecord/test/models/categorization.rb activerecord/test/models/category.rb activerecord/test/models/member.rb activerecord/test/models/reference.rb activerecord/test/models/tagging.rb
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb2
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb4
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb36
-rw-r--r--actionpack/lib/action_controller/base.rb2
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb8
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb58
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb6
-rw-r--r--actionpack/lib/action_controller/metal.rb63
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb4
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb12
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb37
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb21
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb6
-rw-r--r--actionpack/lib/action_controller/railties/paths.rb8
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb2
-rw-r--r--actionpack/lib/action_dispatch.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb35
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb24
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb76
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb24
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb58
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb8
-rw-r--r--actionpack/lib/action_dispatch/routing.rb12
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb331
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb13
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb6
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_view.rb2
-rw-r--r--actionpack/lib/action_view/base.rb14
-rw-r--r--actionpack/lib/action_view/helpers.rb4
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb22
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb23
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb7
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb26
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb22
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb49
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb81
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb5
-rw-r--r--actionpack/lib/action_view/helpers/output_safety_helper.rb38
-rw-r--r--actionpack/lib/action_view/helpers/raw_output_helper.rb18
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb9
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb27
-rw-r--r--actionpack/lib/action_view/lookup_context.rb40
-rw-r--r--actionpack/lib/action_view/partials.rb22
-rw-r--r--actionpack/lib/action_view/path_set.rb12
-rw-r--r--actionpack/lib/action_view/railtie.rb10
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb10
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb8
-rw-r--r--actionpack/lib/action_view/template.rb4
-rw-r--r--actionpack/lib/action_view/template/error.rb13
-rw-r--r--actionpack/lib/action_view/template/resolver.rb34
-rw-r--r--actionpack/lib/action_view/test_case.rb5
-rw-r--r--actionpack/lib/action_view/testing/resolvers.rb6
66 files changed, 1009 insertions, 414 deletions
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index f169ab7c3a..95992c2698 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -13,7 +13,7 @@ module AbstractController
# Override AbstractController::Base's process_action to run the
# process_action callbacks around the normal behavior.
- def process_action(method_name)
+ def process_action(method_name, *args)
run_callbacks(:process_action, method_name) do
super
end
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 606f7eedec..4ee54474cc 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -265,11 +265,11 @@ module AbstractController
raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
when nil
if name
- _prefix = "layouts" unless _implied_layout_name =~ /\blayouts/
+ _prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def _layout
- if template_exists?("#{_implied_layout_name}", #{_prefix.inspect})
+ if template_exists?("#{_implied_layout_name}", #{_prefixes.inspect})
"#{_implied_layout_name}"
else
super
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 91b75273fa..691310d5d2 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -13,14 +13,15 @@ module AbstractController
# This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
# it will trigger the lookup_context and consequently expire the cache.
class I18nProxy < ::I18n::Config #:nodoc:
- attr_reader :i18n_config, :lookup_context
+ attr_reader :original_config, :lookup_context
- def initialize(i18n_config, lookup_context)
- @i18n_config, @lookup_context = i18n_config, lookup_context
+ def initialize(original_config, lookup_context)
+ original_config = original_config.original_config if original_config.respond_to?(:original_config)
+ @original_config, @lookup_context = original_config, lookup_context
end
def locale
- @i18n_config.locale
+ @original_config.locale
end
def locale=(value)
@@ -60,6 +61,20 @@ module AbstractController
end
end
end
+
+ def parent_prefixes
+ @parent_prefixes ||= begin
+ parent_controller = superclass
+ prefixes = []
+
+ until parent_controller.abstract?
+ prefixes << parent_controller.controller_path
+ parent_controller = parent_controller.superclass
+ end
+
+ prefixes
+ end
+ end
end
attr_writer :view_context_class
@@ -98,7 +113,7 @@ module AbstractController
def render_to_string(*args, &block)
options = _normalize_args(*args, &block)
_normalize_options(options)
- render_to_body(options)
+ render_to_body(options).tap { self.response_body = nil }
end
# Raw rendering of a template to a Rack-compatible body.
@@ -114,9 +129,12 @@ module AbstractController
view_context.render(options)
end
- # The prefix used in render "foo" shortcuts.
- def _prefix
- controller_path
+ # The prefixes used in render "foo" shortcuts.
+ def _prefixes
+ @_prefixes ||= begin
+ parent_prefixes = self.class.parent_prefixes
+ parent_prefixes.dup.unshift(controller_path)
+ end
end
private
@@ -156,7 +174,7 @@ module AbstractController
end
if (options.keys & [:partial, :file, :template, :once]).empty?
- options[:prefix] ||= _prefix
+ options[:prefixes] ||= _prefixes
end
options[:template] ||= (options[:action] || action_name).to_s
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 48308cbb60..81c0698fb8 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -24,7 +24,7 @@ module ActionController
#
# Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action
# after executing code in the action. For example, the +index+ action of the PostsController would render the
- # template <tt>app/views/posts/index.erb</tt> by default after populating the <tt>@posts</tt> instance variable.
+ # template <tt>app/views/posts/index.html.erb</tt> by default after populating the <tt>@posts</tt> instance variable.
#
# Unlike index, the create action will not render a template. After performing its main purpose (creating a
# new post), it initiates a redirect instead. This redirect works by returning an external
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index a4bac3caed..a1c582560c 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -103,12 +103,14 @@ module ActionController #:nodoc:
end
def _save_fragment(name, options)
- return unless caching_allowed?
-
content = response_body
content = content.join if content.is_a?(Array)
- write_fragment(name, content, options)
+ if caching_allowed?
+ write_fragment(name, content, options)
+ else
+ content
+ end
end
protected
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index 37c155b9cd..0be04b70a1 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -1,52 +1,72 @@
module ActionController #:nodoc:
module Caching
- # Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when
- # certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple
- # parties. The caching is done using the cache helper available in the Action View. A template with caching might look something like:
+ # Fragment caching is used for caching various blocks within
+ # views without caching the entire action as a whole. This is
+ # useful when certain elements of an action change frequently or
+ # depend on complicated state while other parts rarely change or
+ # can be shared amongst multiple parties. The caching is done using
+ # the <tt>cache</tt> helper available in the Action View. A
+ # template with fragment caching might look like:
#
# <b>Hello <%= @name %></b>
+ #
# <% cache do %>
# All the topics in the system:
# <%= render :partial => "topic", :collection => Topic.find(:all) %>
# <% end %>
#
- # This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would
- # be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>.
+ # This cache will bind the name of the action that called it, so if
+ # this code was part of the view for the topics/list action, you
+ # would be able to invalidate it using:
+ #
+ # expire_fragment(:controller => "topics", :action => "list")
#
- # This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using
- # <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like:
+ # This default behavior is limited if you need to cache multiple
+ # fragments per action or if the action itself is cached using
+ # <tt>caches_action</tt>. To remedy this, there is an option to
+ # qualify the name of the cached fragment by using the
+ # <tt>:action_suffix</tt> option:
#
# <% cache(:action => "list", :action_suffix => "all_topics") do %>
#
- # That would result in a name such as <tt>/topics/list/all_topics</tt>, avoiding conflicts with the action cache and with any fragments that use a
- # different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique
- # cache names that we can refer to when we need to expire the cache.
+ # That would result in a name such as
+ # <tt>/topics/list/all_topics</tt>, avoiding conflicts with the
+ # action cache and with any fragments that use a different suffix.
+ # Note that the URL doesn't have to really exist or be callable
+ # - the url_for system is just used to generate unique cache names
+ # that we can refer to when we need to expire the cache.
#
# The expiration call for this example is:
#
- # expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
+ # expire_fragment(:controller => "topics",
+ # :action => "list",
+ # :action_suffix => "all_topics")
module Fragments
- # Given a key (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,
- # writing, or expiring a cached fragment. If the key is a hash, the generated key is the return
- # value of url_for on that hash (without the protocol). All keys are prefixed with <tt>views/</tt> and uses
+ # Given a key (as described in <tt>expire_fragment</tt>), returns
+ # a key suitable for use in reading, writing, or expiring a
+ # cached fragment. If the key is a hash, the generated key is the
+ # return value of url_for on that hash (without the protocol).
+ # All keys are prefixed with <tt>views/</tt> and uses
# ActiveSupport::Cache.expand_cache_key for the expansion.
def fragment_cache_key(key)
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
end
- # Writes <tt>content</tt> to the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
+ # 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)
instrument_fragment_cache :write_fragment, key do
- content = content.html_safe.to_str if content.respond_to?(:html_safe)
+ content = content.to_str
cache_store.write(key, content, options)
end
content
end
- # Reads a cached fragment from the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
+ # 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?
@@ -57,7 +77,8 @@ module ActionController #:nodoc:
end
end
- # Check if a cached fragment from the location signified by <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats)
+ # 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)
@@ -70,6 +91,7 @@ module ActionController #:nodoc:
# Removes fragments from the cache.
#
# +key+ can take one of three forms:
+ #
# * String - This would normally take the form of a path, like
# <tt>pages/45/notes</tt>.
# * Hash - Treated as an implicit call to +url_for+, like
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
index 3e57d2c236..8c583c7ce0 100644
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ b/actionpack/lib/action_controller/caching/pages.rb
@@ -106,7 +106,7 @@ module ActionController #:nodoc:
end
def page_cache_path(path, extension = nil)
- page_cache_directory + page_cache_file(path, extension)
+ page_cache_directory.to_s + page_cache_file(path, extension)
end
def instrument_page_cache(name, path)
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 3b19310a69..3fae697cc3 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -16,7 +16,11 @@ module ActionController
payload = event.payload
additions = ActionController::Base.log_process_action(payload)
- message = "Completed #{payload[:status]} #{Rack::Utils::HTTP_STATUS_CODES[payload[:status]]} in %.0fms" % event.duration
+ status = payload[:status]
+ if status.nil? && payload[:exception].present?
+ status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil
+ end
+ message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration
message << " (#{additions.join(" | ")})" unless additions.blank?
info(message)
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 329798e84f..e5db31061b 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -36,19 +36,68 @@ module ActionController
action = action.to_s
raise "MiddlewareStack#build requires an app" unless app
- reverse.inject(app) do |a, middleware|
+ middlewares.reverse.inject(app) do |a, middleware|
middleware.valid?(action) ?
middleware.build(a) : a
end
end
end
- # Provides a way to get a valid Rack application from a controller.
+ # <tt>ActionController::Metal</tt> is the simplest possible controller, providing a
+ # valid Rack interface without the additional niceties provided by
+ # <tt>ActionController::Base</tt>.
+ #
+ # A sample metal controller might look like this:
+ #
+ # class HelloController < ActionController::Metal
+ # def index
+ # self.response_body = "Hello World!"
+ # end
+ # end
+ #
+ # And then to route requests to your metal controller, you would add
+ # something like this to <tt>config/routes.rb</tt>:
+ #
+ # match 'hello', :to => HelloController.action(:index)
+ #
+ # The +action+ method returns a valid Rack application for the \Rails
+ # router to dispatch to.
+ #
+ # == Rendering Helpers
+ #
+ # <tt>ActionController::Metal</tt> by default provides no utilities for rendering
+ # views, partials, or other responses aside from explicitly calling of
+ # <tt>response_body=</tt>, <tt>content_type=</tt>, and <tt>status=</tt>. To
+ # add the render helpers you're used to having in a normal controller, you
+ # can do the following:
+ #
+ # class HelloController < ActionController::Metal
+ # include ActionController::Rendering
+ # append_view_path "#{Rails.root}/app/views"
+ #
+ # def index
+ # render "hello/index"
+ # end
+ # end
+ #
+ # == Redirection Helpers
+ #
+ # To add redirection helpers to your metal controller, do the following:
+ #
+ # class HelloController < ActionController::Metal
+ # include ActionController::Redirecting
+ # include Rails.application.routes.url_helpers
+ #
+ # def index
+ # redirect_to root_url
+ # end
+ # end
+ #
+ # == Other Helpers
+ #
+ # You can refer to the modules included in <tt>ActionController::Base</tt> to see
+ # other features you can bring into your metal controller.
#
- # In AbstractController, dispatching is triggered directly by calling #process on a new controller.
- # <tt>ActionController::Metal</tt> provides an <tt>action</tt> method that returns a valid Rack application for a
- # given action. Other rack builders, such as Rack::Builder, Rack::URLMap, and the \Rails router,
- # can dispatch directly to actions returned by controllers in your application.
class Metal < AbstractController::Base
abstract!
@@ -133,7 +182,7 @@ module ActionController
end
def response_body=(val)
- body = val.respond_to?(:each) ? val : [val]
+ body = val.nil? ? nil : (val.respond_to?(:each) ? val : [val])
super body
end
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index 282dcf66b3..cfa7004048 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -12,10 +12,10 @@ module ActionController
def method_for_action(action_name)
super || begin
- if template_exists?(action_name.to_s, _prefix)
+ if template_exists?(action_name.to_s, _prefixes)
"default_render"
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 9ba37134b8..a2e06fe0a6 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -63,13 +63,13 @@ module ActionController #:nodoc:
# might look something like this:
#
# def index
- # @people = Person.find(:all)
+ # @people = Person.all
# end
#
# Here's the same action, with web-service support baked in:
#
# def index
- # @people = Person.find(:all)
+ # @people = Person.all
#
# respond_to do |format|
# format.html
@@ -155,7 +155,7 @@ module ActionController #:nodoc:
# Respond to also allows you to specify a common block for different formats by using any:
#
# def index
- # @people = Person.find(:all)
+ # @people = Person.all
#
# respond_to do |format|
# format.html
@@ -178,7 +178,7 @@ module ActionController #:nodoc:
# respond_to :html, :xml, :json
#
# def index
- # @people = Person.find(:all)
+ # @people = Person.all
# respond_with(@person)
# end
# end
@@ -208,8 +208,8 @@ module ActionController #:nodoc:
# It also accepts a block to be given. It's used to overwrite a default
# response:
#
- # def destroy
- # @user = User.find(params[:id])
+ # def create
+ # @user = User.new(params[:user])
# flash[:notice] = "User was successfully created." if @user.save
#
# respond_with(@user) do |format|
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index d6f6ab1855..38711c8462 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/blank'
module ActionController
+ # See <tt>Renderers.add</tt>
def self.add_renderer(key, &block)
Renderers.add(key, &block)
end
@@ -39,7 +40,43 @@ module ActionController
nil
end
+ # Hash of available renderers, mapping a renderer name to its proc.
+ # Default keys are :json, :js, :xml and :update.
RENDERERS = {}
+
+ # Adds a new renderer to call within controller actions.
+ # A renderer is invoked by passing its name as an option to
+ # <tt>AbstractController::Rendering#render</tt>. To create a renderer
+ # pass it a name and a block. The block takes two arguments, the first
+ # is the value paired with its key and the second is the remaining
+ # hash of options passed to +render+.
+ #
+ # === Example
+ # Create a csv renderer:
+ #
+ # ActionController::Renderers.add :csv do |obj, options|
+ # filename = options[:filename] || 'data'
+ # str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
+ # send_data str, :type => Mime::CSV,
+ # :disposition => "attachment; filename=#{filename}.csv"
+ # end
+ #
+ # Note that we used Mime::CSV for the csv mime type as it comes with Rails.
+ # For a custom renderer, you'll need to register a mime type with
+ # <tt>Mime::Type.register</tt>.
+ #
+ # To use the csv renderer in a controller action:
+ #
+ # def show
+ # @csvable = Csvable.find(params[:id])
+ # respond_to do |format|
+ # format.html
+ # format.csv { render :csv => @csvable, :filename => @csvable.name }
+ # }
+ # end
+ # To use renderers and their mime types in more concise ways, see
+ # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and
+ # <tt>ActionController::MimeResponds#respond_with</tt>
def self.add(key, &block)
define_method("_render_option_#{key}", &block)
RENDERERS[key] = block
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 14cc547dd0..32d52c84c4 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -6,7 +6,7 @@ module ActionController
# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
- self.formats = request.formats.map { |x| x.to_sym }
+ self.formats = request.formats.map { |x| x.ref }
super
end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 148efbb081..1cd93a188c 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -66,30 +66,29 @@ 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, options
+ prepend_before_filter :verify_authenticity_token, options
end
end
protected
-
- def protect_from_forgery(options = {})
- self.request_forgery_protection_token ||= :authenticity_token
- before_filter :verify_authenticity_token, options
- end
-
# The actual before_filter that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
- verified_request? || raise(ActionController::InvalidAuthenticityToken)
+ verified_request? || handle_unverified_request
+ end
+
+ def handle_unverified_request
+ reset_session
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?
+ # * Does the X-CSRF-Token header match the form_authenticity_token
def verified_request?
- !protect_against_forgery? || request.forgery_whitelisted? ||
- form_authenticity_token == params[request_forgery_protection_token]
+ !protect_against_forgery? || request.get? ||
+ form_authenticity_token == params[request_forgery_protection_token] ||
+ form_authenticity_token == request.headers['X-CSRF-Token']
end
# Sets the token value for the current session.
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index 38d32211cc..4b45413cf8 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -77,8 +77,6 @@ module ActionController #:nodoc:
#
# respond_with(@project, :manager, @task)
#
- # Check <code>polymorphic_url</code> documentation for more examples.
- #
class Responder
attr_reader :controller, :request, :format, :resource, :resources, :options
@@ -115,7 +113,7 @@ module ActionController #:nodoc:
# Main entry point for responder responsible to dispatch to the proper format.
#
def respond
- method = :"to_#{format}"
+ method = "to_#{format}"
respond_to?(method) ? send(method) : to_format
end
@@ -171,7 +169,7 @@ module ActionController #:nodoc:
# Checks whether the resource responds to the current format or not.
#
def resourceful?
- resource.respond_to?(:"to_#{format}")
+ resource.respond_to?("to_#{format}")
end
# Returns the resource location by retrieving it from the options or
diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb
index 699c44c62c..dce3c2fe88 100644
--- a/actionpack/lib/action_controller/railties/paths.rb
+++ b/actionpack/lib/action_controller/railties/paths.rb
@@ -16,6 +16,14 @@ module ActionController
if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
klass.helper :all
end
+
+ if app.config.serve_static_assets && namespace
+ paths = namespace._railtie.config.paths
+
+ klass.config.assets_dir = paths["public"].first
+ klass.config.javascripts_dir = paths["public/javascripts"].first
+ klass.config.stylesheets_dir = paths["public/stylesheets"].first
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
index 3e5d23b5c1..09dd08898c 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
@@ -170,7 +170,7 @@ module HTML
def contains_bad_protocols?(attr_name, value)
uri_attributes.include?(attr_name) &&
- (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first))
+ (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase))
end
end
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 50faf666e6..49971fc9f8 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2010 David Heinemeier Hansson
+# Copyright (c) 2004-2011 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -53,6 +53,7 @@ module ActionDispatch
autoload :Flash
autoload :Head
autoload :ParamsParser
+ autoload :Reloader
autoload :RemoteIp
autoload :Rescue
autoload :ShowExceptions
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 1d2f7e4f19..4f4cb96a74 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -43,7 +43,7 @@ module ActionDispatch
alias :etag? :etag
def initialize(*)
- status, header, body = super
+ super
@cache_control = {}
@etag = self["ETag"]
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 5b87a80c1b..7c9ebe7c7b 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -216,7 +216,11 @@ module Mime
end
def to_sym
- @symbol || @string.to_sym
+ @symbol
+ end
+
+ def ref
+ to_sym || to_s
end
def ===(list)
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 08f30e068d..f07ac44f7a 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -2,6 +2,7 @@ require 'tempfile'
require 'stringio'
require 'strscan'
+require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/string/access'
require 'active_support/inflector'
@@ -133,8 +134,9 @@ module ActionDispatch
end
def forgery_whitelisted?
- get? || xhr? || content_mime_type.nil? || !content_mime_type.verify_request?
+ get?
end
+ deprecate :forgery_whitelisted? => "it is just an alias for 'get?' now, update your code"
def media_type
content_mime_type.to_s
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 796cd8c09b..535ff42b90 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -28,8 +28,11 @@ module ActionDispatch
rewritten_url = ""
unless options[:only_path]
- rewritten_url << (options[:protocol] || "http")
- rewritten_url << "://" unless rewritten_url.match("://")
+ unless options[:protocol] == false
+ rewritten_url << (options[:protocol] || "http")
+ rewritten_url << ":" unless rewritten_url.match(%r{:|//})
+ end
+ rewritten_url << "//" unless rewritten_url.match("//")
rewritten_url << rewrite_authentication(options)
rewritten_url << host_or_subdomain_and_domain(options)
rewritten_url << ":#{options.delete(:port)}" if options[:port]
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index 0bb950d1cc..1bb2ad7f67 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -1,32 +1,14 @@
+require 'active_support/core_ext/module/delegation'
+
module ActionDispatch
# Provide callbacks to be executed before and after the request dispatch.
- #
- # It also provides a to_prepare callback, which is performed in all requests
- # in development by only once in production and notification callback for async
- # operations.
- #
class Callbacks
include ActiveSupport::Callbacks
define_callbacks :call, :rescuable => true
- define_callbacks :prepare, :scope => :name
- # Add a preparation callback. Preparation callbacks are run before every
- # request in development mode, and before the first request in production mode.
- #
- # If a symbol with a block is given, the symbol is used as an identifier.
- # That allows to_prepare to be called again with the same identifier to
- # replace the existing callback. Passing an identifier is a suggested
- # practice if the code adding a preparation block may be reloaded.
- def self.to_prepare(*args, &block)
- first_arg = args.first
- if first_arg.is_a?(Symbol) && block_given?
- remove_method :"__#{first_arg}" if method_defined?(:"__#{first_arg}")
- define_method :"__#{first_arg}", &block
- set_callback(:prepare, :"__#{first_arg}")
- else
- set_callback(:prepare, *args, &block)
- end
+ class << self
+ delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader"
end
def self.before(*args, &block)
@@ -37,14 +19,13 @@ module ActionDispatch
set_callback(:call, :after, *args, &block)
end
- def initialize(app, prepare_each_request = false)
- @app, @prepare_each_request = app, prepare_each_request
- _run_prepare_callbacks
+ def initialize(app, unused = nil)
+ ActiveSupport::Deprecation.warn "Passing a second argument to ActionDispatch::Callbacks.new is deprecated." unless unused.nil?
+ @app = app
end
def call(env)
- _run_call_callbacks do
- _run_prepare_callbacks if @prepare_each_request
+ run_callbacks :call do
@app.call(env)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index b0a4e3d949..7ac608f0a8 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -90,17 +90,14 @@ module ActionDispatch
# **.**, ***.** style TLDs like co.uk or com.au
#
# www.example.co.uk gives:
- # $1 => example
- # $2 => co.uk
+ # $& => example.co.uk
#
# example.com gives:
- # $1 => example
- # $2 => com
+ # $& => example.com
#
# lots.of.subdomains.example.local gives:
- # $1 => example
- # $2 => local
- DOMAIN_REGEXP = /([^.]*)\.([^.]*|..\...|...\...)$/
+ # $& => example.local
+ DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
def self.build(request)
secret = request.env[TOKEN_KEY]
@@ -131,8 +128,17 @@ module ActionDispatch
options[:path] ||= "/"
if options[:domain] == :all
- @host =~ DOMAIN_REGEXP
- options[:domain] = ".#{$1}.#{$2}"
+ # if there is a provided tld length then we use it otherwise default domain regexp
+ domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
+
+ # if host is not ip and matches domain regexp
+ # (ip confirms to domain regexp so we explicitly check for ip)
+ options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp)
+ ".#{$&}"
+ end
+ elsif options[:domain].is_a? Array
+ # if host matches one of the supplied domains without a dot in front of it
+ options[:domain] = options[:domain].find {|domain| @host.include? domain[/^\.?(.*)$/, 1] }
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
new file mode 100644
index 0000000000..29289a76b4
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -0,0 +1,76 @@
+module ActionDispatch
+ # ActionDispatch::Reloader provides prepare and cleanup callbacks,
+ # intended to assist with code reloading during development.
+ #
+ # Prepare callbacks are run before each request, and cleanup callbacks
+ # after each request. In this respect they are analogs of ActionDispatch::Callback's
+ # before and after callbacks. However, cleanup callbacks are not called until the
+ # request is fully complete -- that is, after #close has been called on
+ # the response body. This is important for streaming responses such as the
+ # following:
+ #
+ # self.response_body = lambda { |response, output|
+ # # code here which refers to application models
+ # }
+ #
+ # Cleanup callbacks will not be called until after the response_body lambda
+ # is evaluated, ensuring that it can refer to application models and other
+ # classes before they are unloaded.
+ #
+ # By default, ActionDispatch::Reloader is included in the middleware stack
+ # only in the development environment; specifically, when config.cache_classes
+ # is false. Callbacks may be registered even when it is not included in the
+ # middleware stack, but are executed only when +ActionDispatch::Reloader.prepare!+
+ # or +ActionDispatch::Reloader.cleanup!+ are called manually.
+ #
+ class Reloader
+ include ActiveSupport::Callbacks
+
+ define_callbacks :prepare, :scope => :name
+ define_callbacks :cleanup, :scope => :name
+
+ # Add a prepare callback. Prepare callbacks are run before each request, prior
+ # to ActionDispatch::Callback's before callbacks.
+ def self.to_prepare(*args, &block)
+ set_callback(:prepare, *args, &block)
+ end
+
+ # Add a cleanup callback. Cleanup callbacks are run after each request is
+ # complete (after #close is called on the response body).
+ def self.to_cleanup(*args, &block)
+ set_callback(:cleanup, *args, &block)
+ end
+
+ # Execute all prepare callbacks.
+ def self.prepare!
+ new(nil).run_callbacks :prepare
+ end
+
+ # Execute all cleanup callbacks.
+ def self.cleanup!
+ new(nil).run_callbacks :cleanup
+ end
+
+ def initialize(app)
+ @app = app
+ end
+
+ module CleanupOnClose
+ def close
+ super if defined?(super)
+ ensure
+ ActionDispatch::Reloader.cleanup!
+ end
+ end
+
+ def call(env)
+ run_callbacks :prepare
+ response = @app.call(env)
+ response[2].extend(CleanupOnClose)
+ response
+ rescue Exception
+ run_callbacks :cleanup
+ raise
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 71e736ce9f..dbe3206808 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -43,20 +43,20 @@ module ActionDispatch
end
def call(env)
- status, headers, body = @app.call(env)
-
- # Only this middleware cares about RoutingError. So, let's just raise
- # it here.
- # TODO: refactor this middleware to handle the X-Cascade scenario without
- # having to raise an exception.
- if headers['X-Cascade'] == 'pass'
- raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}"
+ begin
+ status, headers, body = @app.call(env)
+ exception = nil
+
+ # Only this middleware cares about RoutingError. So, let's just raise
+ # it here.
+ if headers['X-Cascade'] == 'pass'
+ raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}"
+ end
+ rescue Exception => exception
+ raise exception if env['action_dispatch.show_exceptions'] == false
end
- [status, headers, body]
- rescue Exception => exception
- raise exception if env['action_dispatch.show_exceptions'] == false
- render_exception(env, exception)
+ exception ? render_exception(env, exception) : [status, headers, body]
end
private
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index e3cd779756..a4308f528c 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -2,17 +2,26 @@ require "active_support/inflector/methods"
require "active_support/dependencies"
module ActionDispatch
- class MiddlewareStack < Array
+ class MiddlewareStack
class Middleware
- attr_reader :args, :block
+ attr_reader :args, :block, :name, :classcache
def initialize(klass_or_name, *args, &block)
- @ref = ActiveSupport::Dependencies::Reference.new(klass_or_name)
+ @klass = nil
+
+ if klass_or_name.respond_to?(:name)
+ @klass = klass_or_name
+ @name = @klass.name
+ else
+ @name = klass_or_name.to_s
+ end
+
+ @classcache = ActiveSupport::Dependencies::Reference
@args, @block = args, block
end
def klass
- @ref.get
+ @klass || classcache[@name]
end
def ==(middleware)
@@ -22,7 +31,7 @@ module ActionDispatch
when Class
klass == middleware
else
- normalize(@ref.name) == normalize(middleware)
+ normalize(@name) == normalize(middleware)
end
end
@@ -41,18 +50,39 @@ module ActionDispatch
end
end
- # Use this instead of super to work around a warning.
- alias :array_initialize :initialize
+ include Enumerable
+
+ attr_accessor :middlewares
def initialize(*args)
- array_initialize(*args)
+ @middlewares = []
yield(self) if block_given?
end
+ def each
+ @middlewares.each { |x| yield x }
+ end
+
+ def size
+ middlewares.size
+ end
+
+ def last
+ middlewares.last
+ end
+
+ def [](i)
+ middlewares[i]
+ end
+
+ def initialize_copy(other)
+ self.middlewares = other.middlewares.dup
+ end
+
def insert(index, *args, &block)
index = assert_index(index, :before)
middleware = self.class::Middleware.new(*args, &block)
- super(index, middleware)
+ middlewares.insert(index, middleware)
end
alias_method :insert_before, :insert
@@ -67,21 +97,25 @@ module ActionDispatch
delete(target)
end
+ def delete(target)
+ middlewares.delete target
+ end
+
def use(*args, &block)
middleware = self.class::Middleware.new(*args, &block)
- push(middleware)
+ middlewares.push(middleware)
end
def build(app = nil, &block)
app ||= block
raise "MiddlewareStack#build requires an app" unless app
- reverse.inject(app) { |a, e| e.build(a) }
+ middlewares.reverse.inject(app) { |a, e| e.build(a) }
end
protected
def assert_index(index, where)
- i = index.is_a?(Integer) ? index : self.index(index)
+ i = index.is_a?(Integer) ? index : middlewares.index(index)
raise "No such middleware to insert #{where}: #{index.inspect}" unless i
i
end
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 913b899e20..c57f694c4d 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -3,10 +3,10 @@ require 'rack/utils'
module ActionDispatch
class FileHandler
def initialize(at, root)
- @at, @root = at.chomp('/'), root.chomp('/')
- @compiled_at = (Regexp.compile(/^#{Regexp.escape(at)}/) unless @at.blank?)
- @compiled_root = Regexp.compile(/^#{Regexp.escape(root)}/)
- @file_server = ::Rack::File.new(@root)
+ @at, @root = at.chomp('/'), root.chomp('/')
+ @compiled_at = @at.blank? ? nil : /^#{Regexp.escape(at)}/
+ @compiled_root = /^#{Regexp.escape(root)}/
+ @file_server = ::Rack::File.new(@root)
end
def match?(path)
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
index bd6ffbab5d..50d8ca9484 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
@@ -6,5 +6,5 @@
</h1>
<pre><%=h @exception.message %></pre>
-<%= render :file => "rescues/_trace.erb" %>
-<%= render :file => "rescues/_request_and_response.erb" %>
+<%= render :template => "rescues/_trace" %>
+<%= render :template => "rescues/_request_and_response" %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
index 02fa18211d..c658559be9 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
@@ -13,9 +13,5 @@
<p><%=h @exception.sub_template_message %></p>
-<% @real_exception = @exception
- @exception = @exception.original_exception || @exception %>
-<%= render :file => "rescues/_trace.erb" %>
-<% @exception = @real_exception %>
-
-<%= render :file => "rescues/_request_and_response.erb" %>
+<%= render :template => "rescues/_trace" %>
+<%= render :template => "rescues/_request_and_response" %>
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 8810227a59..43fd93adf6 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -56,6 +56,18 @@ module ActionDispatch
# resources :posts, :comments
# end
#
+ # Alternately, you can add prefixes to your path without using a separate
+ # directory by using +scope+. +scope+ takes additional options which
+ # apply to all enclosed routes.
+ #
+ # scope :path => "/cpanel", :as => 'admin' do
+ # resources :posts, :comments
+ # end
+ #
+ # For more, see <tt>Routing::Mapper::Resources#resources</tt>,
+ # <tt>Routing::Mapper::Scoping#namespace</tt>, and
+ # <tt>Routing::Mapper::Scoping#scope</tt>.
+ #
# == Named routes
#
# Routes can be named by passing an <tt>:as</tt> option,
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 430fcdbe07..589df218a8 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -22,18 +22,22 @@ module ActionDispatch
@app, @constraints, @request = app, constraints, request
end
- def call(env)
+ def matches?(env)
req = @request.new(env)
@constraints.each { |constraint|
if constraint.respond_to?(:matches?) && !constraint.matches?(req)
- return [ 404, {'X-Cascade' => 'pass'}, [] ]
+ return false
elsif constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req))
- return [ 404, {'X-Cascade' => 'pass'}, [] ]
+ return false
end
}
- @app.call(env)
+ return true
+ end
+
+ def call(env)
+ matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ]
end
private
@@ -247,7 +251,7 @@ module ActionDispatch
#
# root :to => 'pages#main'
#
- # For options, see the +match+ method's documentation, as +root+ uses it internally.
+ # For options, see +match+, as +root+ uses it internally.
#
# You should put the root route at the top of <tt>config/routes.rb</tt>,
# because this means it will be matched first. As this is the most popular route
@@ -256,15 +260,114 @@ module ActionDispatch
match '/', options.reverse_merge(:as => :root)
end
- # When you set up a regular route, you supply a series of symbols that
- # Rails maps to parts of an incoming HTTP request.
+ # Matches a url pattern to one or more routes. Any symbols in a pattern
+ # are interpreted as url query parameters and thus available as +params+
+ # in an action:
+ #
+ # # sets :controller, :action and :id in params
+ # match ':controller/:action/:id'
+ #
+ # Two of these symbols are special, +:controller+ maps to the controller
+ # and +:action+ to the controller's action. A pattern can also map
+ # wildcard segments (globs) to params:
+ #
+ # match 'songs/*category/:title' => 'songs#show'
+ #
+ # # 'songs/rock/classic/stairway-to-heaven' sets
+ # # params[:category] = 'rock/classic'
+ # # params[:title] = 'stairway-to-heaven'
+ #
+ # When a pattern points to an internal route, the route's +:action+ and
+ # +:controller+ should be set in options or hash shorthand. Examples:
+ #
+ # match 'photos/:id' => 'photos#show'
+ # match 'photos/:id', :to => 'photos#show'
+ # match 'photos/:id', :controller => 'photos', :action => 'show'
+ #
+ # A pattern can also point to a +Rack+ endpoint i.e. anything that
+ # responds to +call+:
+ #
+ # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon" }
+ # match 'photos/:id' => PhotoRackApp
+ # # Yes, controller actions are just rack endpoints
+ # match 'photos/:id' => PhotosController.action(:show)
+ #
+ # === Options
+ #
+ # Any options not seen here are passed on as params with the url.
+ #
+ # [:controller]
+ # The route's controller.
+ #
+ # [:action]
+ # The route's action.
+ #
+ # [:path]
+ # The path prefix for the routes.
+ #
+ # [:module]
+ # The namespace for :controller.
+ #
+ # match 'path' => 'c#a', :module => 'sekret', :controller => 'posts'
+ # #=> Sekret::PostsController
+ #
+ # See <tt>Scoping#namespace</tt> for its scope equivalent.
+ #
+ # [:as]
+ # The name used to generate routing helpers.
+ #
+ # [:via]
+ # Allowed HTTP verb(s) for route.
+ #
+ # match 'path' => 'c#a', :via => :get
+ # match 'path' => 'c#a', :via => [:get, :post]
+ #
+ # [:to]
+ # Points to a +Rack+ endpoint. Can be an object that responds to
+ # +call+ or a string representing a controller's action.
+ #
+ # match 'path', :to => 'controller#action'
+ # match 'path', :to => lambda { [200, {}, "Success!"] }
+ # match 'path', :to => RackApp
+ #
+ # [:on]
+ # Shorthand for wrapping routes in a specific RESTful context. Valid
+ # values are :member, :collection, and :new. Only use within
+ # <tt>resource(s)</tt> block. For example:
+ #
+ # resource :bar do
+ # match 'foo' => 'c#a', :on => :member, :via => [:get, :post]
+ # end
#
- # match ':controller/:action/:id/:user_id'
+ # Is equivalent to:
#
- # Two of these symbols are special: :controller maps to the name of a
- # controller in your application, and :action maps to the name of an
- # action within that controller. Anything other than :controller or
- # :action will be available to the action as part of params.
+ # resource :bar do
+ # member do
+ # match 'foo' => 'c#a', :via => [:get, :post]
+ # end
+ # end
+ #
+ # [:constraints]
+ # Constrains parameters with a hash of regular expressions or an
+ # object that responds to #matches?
+ #
+ # match 'path/:id', :constraints => { :id => /[A-Z]\d{5}/ }
+ #
+ # class Blacklist
+ # def matches?(request) request.remote_ip == '1.2.3.4' end
+ # end
+ # match 'path' => 'c#a', :constraints => Blacklist.new
+ #
+ # See <tt>Scoping#constraints</tt> for more examples with its scope
+ # equivalent.
+ #
+ # [:defaults]
+ # Sets defaults for parameters
+ #
+ # # Sets params[:format] to 'jpg' by default
+ # match 'path' => 'c#a', :defaults => { :format => 'jpg' }
+ #
+ # See <tt>Scoping#defaults</tt> for its scope equivalent.
def match(path, options=nil)
mapping = Mapping.new(@set, @scope, path, options || {}).to_route
@set.add_route(*mapping)
@@ -279,6 +382,8 @@ module ActionDispatch
#
# mount(SomeRackApp => "some_route")
#
+ # For options, see +match+, as +mount+ uses it internally.
+ #
# All mounted applications come with routing helpers to access them.
# These are named after the class specified, so for the above example
# the helper is either +some_rack_app_path+ or +some_rack_app_url+.
@@ -349,7 +454,7 @@ module ActionDispatch
module HttpHelpers
# Define a route that only recognizes HTTP GET.
- # For supported arguments, see +match+.
+ # For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
@@ -359,7 +464,7 @@ module ActionDispatch
end
# Define a route that only recognizes HTTP POST.
- # For supported arguments, see +match+.
+ # For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
@@ -369,7 +474,7 @@ module ActionDispatch
end
# Define a route that only recognizes HTTP PUT.
- # For supported arguments, see +match+.
+ # For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
@@ -379,7 +484,7 @@ module ActionDispatch
end
# Define a route that only recognizes HTTP PUT.
- # For supported arguments, see +match+.
+ # For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
@@ -458,51 +563,38 @@ module ActionDispatch
super
end
- # === Supported options
- # [:module]
- # If you want to route /posts (without the prefix /admin) to
- # Admin::PostsController, you could use
- #
- # scope :module => "admin" do
- # resources :posts
- # end
- #
- # [:path]
- # If you want to prefix the route, you could use
+ # Scopes a set of routes to the given default options.
#
- # scope :path => "/admin" do
- # resources :posts
- # end
+ # Take the following route definition as an example:
#
- # This will prefix all of the +posts+ resource's requests with '/admin'
- #
- # [:as]
- # Prefixes the routing helpers in this scope with the specified label.
+ # scope :path => ":account_id", :as => "account" do
+ # resources :projects
+ # end
#
- # scope :as => "sekret" do
- # resources :posts
- # end
+ # This generates helpers such as +account_projects_path+, just like +resources+ does.
+ # The difference here being that the routes generated are like /rails/projects/2,
+ # rather than /accounts/rails/projects/2.
#
- # Helpers such as +posts_path+ will now be +sekret_posts_path+
+ # === Options
#
- # [:shallow_path]
+ # Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
#
- # Prefixes nested shallow routes with the specified path.
+ # === Examples
#
- # scope :shallow_path => "sekret" do
- # resources :posts do
- # resources :comments, :shallow => true
- # end
+ # # route /posts (without the prefix /admin) to Admin::PostsController
+ # scope :module => "admin" do
+ # resources :posts
+ # end
#
- # The +comments+ resource here will have the following routes generated for it:
+ # # prefix the posts resource's requests with '/admin'
+ # scope :path => "/admin" do
+ # resources :posts
+ # end
#
- # post_comments GET /sekret/posts/:post_id/comments(.:format)
- # post_comments POST /sekret/posts/:post_id/comments(.:format)
- # new_post_comment GET /sekret/posts/:post_id/comments/new(.:format)
- # edit_comment GET /sekret/comments/:id/edit(.:format)
- # comment GET /sekret/comments/:id(.:format)
- # comment PUT /sekret/comments/:id(.:format)
- # comment DELETE /sekret/comments/:id(.:format)
+ # # prefix the routing helper name: sekret_posts_path instead of posts_path
+ # scope :as => "sekret" do
+ # resources :posts
+ # end
def scope(*args)
options = args.extract_options!
options = options.dup
@@ -558,50 +650,38 @@ module ActionDispatch
#
# This generates the following routes:
#
- # admin_posts GET /admin/posts(.:format) {:action=>"index", :controller=>"admin/posts"}
- # admin_posts POST /admin/posts(.:format) {:action=>"create", :controller=>"admin/posts"}
- # new_admin_post GET /admin/posts/new(.:format) {:action=>"new", :controller=>"admin/posts"}
- # edit_admin_post GET /admin/posts/:id/edit(.:format) {:action=>"edit", :controller=>"admin/posts"}
- # admin_post GET /admin/posts/:id(.:format) {:action=>"show", :controller=>"admin/posts"}
- # admin_post PUT /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"}
- # admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"}
- # === Supported options
+ # admin_posts GET /admin/posts(.:format) {:action=>"index", :controller=>"admin/posts"}
+ # admin_posts POST /admin/posts(.:format) {:action=>"create", :controller=>"admin/posts"}
+ # new_admin_post GET /admin/posts/new(.:format) {:action=>"new", :controller=>"admin/posts"}
+ # edit_admin_post GET /admin/posts/:id/edit(.:format) {:action=>"edit", :controller=>"admin/posts"}
+ # admin_post GET /admin/posts/:id(.:format) {:action=>"show", :controller=>"admin/posts"}
+ # admin_post PUT /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"}
+ # admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"}
#
- # The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+ options all default to the name of the namespace.
+ # === Options
#
- # [:path]
- # The path prefix for the routes.
+ # The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+
+ # options all default to the name of the namespace.
+ #
+ # For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see
+ # <tt>Resources#resources</tt>.
#
+ # === Examples
+ #
+ # # accessible through /sekret/posts rather than /admin/posts
# namespace :admin, :path => "sekret" do
# resources :posts
# end
#
- # All routes for the above +resources+ will be accessible through +/sekret/posts+, rather than +/admin/posts+
- #
- # [:module]
- # The namespace for the controllers.
- #
+ # # maps to Sekret::PostsController rather than Admin::PostsController
# namespace :admin, :module => "sekret" do
# resources :posts
# end
#
- # The +PostsController+ here should go in the +Sekret+ namespace and so it should be defined like this:
- #
- # class Sekret::PostsController < ApplicationController
- # # code go here
+ # # generates sekret_posts_path rather than admin_posts_path
+ # namespace :admin, :as => "sekret" do
+ # resources :posts
# end
- #
- # [:as]
- # Changes the name used in routing helpers for this namespace.
- #
- # namespace :admin, :as => "sekret" do
- # resources :posts
- # end
- #
- # Routing helpers such as +admin_posts_path+ will now be +sekret_posts_path+.
- #
- # [:shallow_path]
- # See the +scope+ method.
def namespace(path, options = {})
path = path.to_s
options = { :path => path, :as => path, :module => path,
@@ -668,9 +748,9 @@ module ActionDispatch
end
# Allows you to set default parameters for a route, such as this:
- # defaults :id => 'home' do
- # match 'scoped_pages/(:id)', :to => 'pages#show'
- # end
+ # defaults :id => 'home' do
+ # match 'scoped_pages/(:id)', :to => 'pages#show'
+ # end
# Using this, the +:id+ parameter here will default to 'home'.
def defaults(defaults = {})
scope(:defaults => defaults) { yield }
@@ -767,6 +847,14 @@ module ActionDispatch
# resources :posts, :comments
# end
#
+ # By default the :id parameter doesn't accept dots. If you need to
+ # use dots as part of the :id parameter add a constraint which
+ # overrides this restriction, e.g:
+ #
+ # resources :articles, :id => /[^\/]+/
+ #
+ # This allows any character other than a slash as part of your :id.
+ #
module Resources
# CANONICAL_ACTIONS holds all actions that does not need a prefix or
# a path appended since they fit properly in their scope level.
@@ -815,7 +903,8 @@ module ActionDispatch
alias :member_name :singular
- # Checks for uncountable plurals, and appends "_index" if they're.
+ # Checks for uncountable plurals, and appends "_index" if the plural
+ # and singular form are the same.
def collection_name
singular == plural ? "#{plural}_index" : plural
end
@@ -894,6 +983,9 @@ module ActionDispatch
# GET /geocoder/edit
# PUT /geocoder
# DELETE /geocoder
+ #
+ # === Options
+ # Takes same options as +resources+.
def resource(*resources, &block)
options = resources.extract_options!
@@ -955,7 +1047,9 @@ module ActionDispatch
# PUT /photos/:id/comments/:id
# DELETE /photos/:id/comments/:id
#
- # === Supported options
+ # === Options
+ # Takes same options as <tt>Base#match</tt> as well as:
+ #
# [:path_names]
# Allows you to change the paths of the seven default actions.
# Paths not specified are not changed.
@@ -964,20 +1058,59 @@ module ActionDispatch
#
# The above example will now change /posts/new to /posts/brand_new
#
- # [:module]
- # Set the module where the controller can be found. Defaults to nothing.
+ # [:only]
+ # Only generate routes for the given actions.
#
- # resources :posts, :module => "admin"
+ # resources :cows, :only => :show
+ # resources :cows, :only => [:show, :index]
#
- # All requests to the posts resources will now go to +Admin::PostsController+.
+ # [:except]
+ # Generate all routes except for the given actions.
#
- # [:path]
+ # resources :cows, :except => :show
+ # resources :cows, :except => [:show, :index]
+ #
+ # [:shallow]
+ # Generates shallow routes for nested resource(s). When placed on a parent resource,
+ # generates shallow routes for all nested resources.
+ #
+ # resources :posts, :shallow => true do
+ # resources :comments
+ # end
+ #
+ # Is the same as:
+ #
+ # resources :posts do
+ # resources :comments
+ # end
+ # resources :comments
+ #
+ # [:shallow_path]
+ # Prefixes nested shallow routes with the specified path.
+ #
+ # scope :shallow_path => "sekret" do
+ # resources :posts do
+ # resources :comments, :shallow => true
+ # end
+ # end
+ #
+ # The +comments+ resource here will have the following routes generated for it:
+ #
+ # post_comments GET /sekret/posts/:post_id/comments(.:format)
+ # post_comments POST /sekret/posts/:post_id/comments(.:format)
+ # new_post_comment GET /sekret/posts/:post_id/comments/new(.:format)
+ # edit_comment GET /sekret/comments/:id/edit(.:format)
+ # comment GET /sekret/comments/:id(.:format)
+ # comment PUT /sekret/comments/:id(.:format)
+ # comment DELETE /sekret/comments/:id(.:format)
#
- # Set a path prefix for this resource.
+ # === Examples
#
- # resources :posts, :path => "admin"
+ # # routes call Admin::PostsController
+ # resources :posts, :module => "admin"
#
- # All actions for this resource will now be at +/admin/posts+.
+ # # resource actions are at /admin/posts.
+ # resources :posts, :path => "admin"
def resources(*resources, &block)
options = resources.extract_options!
@@ -1099,7 +1232,7 @@ module ActionDispatch
end
def shallow
- scope(:shallow => true) do
+ scope(:shallow => true, :shallow_path => @scope[:path]) do
yield
end
end
@@ -1309,7 +1442,7 @@ module ActionDispatch
name = case @scope[:scope_level]
when :nested
- [member_name, prefix]
+ [name_prefix, prefix]
when :collection
[prefix, name_prefix, collection_name]
when :new
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 49e237f8db..82c4fadb50 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -99,11 +99,9 @@ module ActionDispatch
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 ]
- when Array; record_or_hash_or_array.dup
- else [ record_or_hash_or_array ]
- end
+ args = Array === record_or_hash_or_array ?
+ record_or_hash_or_array.dup :
+ [ record_or_hash_or_array ]
inflection = if options[:action] && options[:action].to_s == "new"
args.pop
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 03bfe178e5..fc86d52a3a 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -50,12 +50,13 @@ module ActionDispatch
private
def controller_reference(controller_param)
+ controller_name = "#{controller_param.camelize}Controller"
+
unless controller = @controllers[controller_param]
- controller_name = "#{controller_param.camelize}Controller"
controller = @controllers[controller_param] =
- ActiveSupport::Dependencies.ref(controller_name)
+ ActiveSupport::Dependencies.reference(controller_name)
end
- controller.get
+ controller.get(controller_name)
end
def dispatch(controller, action, env)
@@ -450,7 +451,7 @@ module ActionDispatch
end
def raise_routing_error
- raise ActionController::RoutingError.new("No route matches #{options.inspect}")
+ raise ActionController::RoutingError, "No route matches #{options.inspect}"
end
def different_controller?
@@ -540,7 +541,9 @@ module ActionDispatch
end
dispatcher = route.app
- dispatcher = dispatcher.app while dispatcher.is_a?(Mapper::Constraints)
+ while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
+ dispatcher = dispatcher.app
+ end
if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params, false)
dispatcher.prepare_params!(params)
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 1558c3ae05..77a15f3e97 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -20,7 +20,7 @@ module ActionDispatch
#
# You can also pass an explicit status number like <tt>assert_response(501)</tt>
# or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>.
- # See ActionDispatch::StatusCodes for a full list.
+ # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list.
#
# ==== Examples
#
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 1390b74a95..11e8c63fa0 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -37,9 +37,6 @@ module ActionDispatch
#
# # Test a custom route
# assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1')
- #
- # # Check a Simply RESTful generated route
- # assert_recognizes list_items_url, 'items/list'
def assert_recognizes(expected_options, path, extras={}, message=nil)
request = recognized_request_for(path)
@@ -124,7 +121,8 @@ module ActionDispatch
options[:controller] = "/#{controller}"
end
- assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message)
+ generate_options = options.dup.delete_if{ |k,v| defaults.key?(k) }
+ assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
end
# A helper to make it easier to test different route configurations.
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index 1a1497385a..914b13dbfb 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2010 David Heinemeier Hansson
+# Copyright (c) 2004-2011 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index dada64a86f..60665387b6 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2010 David Heinemeier Hansson
+# Copyright (c) 2004-2011 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 15944138f7..ab8c6259c5 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -18,7 +18,7 @@ module ActionView #:nodoc:
# following loop for names:
#
# <b>Names of all the people</b>
- # <% for person in @people %>
+ # <% @people.each do |person| %>
# Name: <%= person.name %><br/>
# <% end %>
#
@@ -172,6 +172,14 @@ module ActionView #:nodoc:
class << self
delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'
delegate :logger, :to => 'ActionController::Base', :allow_nil => true
+
+ def cache_template_loading
+ ActionView::Resolver.caching?
+ end
+
+ def cache_template_loading=(value)
+ ActionView::Resolver.caching = value
+ end
end
attr_accessor :_template
@@ -224,8 +232,8 @@ module ActionView #:nodoc:
@controller_path ||= controller && controller.controller_path
end
- def controller_prefix
- @controller_prefix ||= controller && controller._prefix
+ def controller_prefixes
+ @controller_prefixes ||= controller && controller._prefixes
end
ActiveSupport.run_load_hooks(:action_view, self)
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index 41013c800c..d338ce616a 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -18,7 +18,7 @@ module ActionView #:nodoc:
autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
autoload :NumberHelper
autoload :PrototypeHelper
- autoload :RawOutputHelper
+ autoload :OutputSafetyHelper
autoload :RecordTagHelper
autoload :SanitizeHelper
autoload :ScriptaculousHelper
@@ -48,7 +48,7 @@ module ActionView #:nodoc:
include JavaScriptHelper
include NumberHelper
include PrototypeHelper
- include RawOutputHelper
+ include OutputSafetyHelper
include RecordTagHelper
include SanitizeHelper
include ScriptaculousHelper
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
index e52797042f..52eb43a1cd 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
@@ -11,7 +11,9 @@ module ActionView
attr_reader :config, :asset_paths
class_attribute :expansions
- self.expansions = { }
+ def self.inherited(base)
+ base.expansions = { }
+ end
def initialize(config, asset_paths)
@config = config
@@ -69,9 +71,21 @@ module ActionView
if sources.first == :all
collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}")
else
- sources.collect do |source|
- determine_source(source, expansions)
- end.flatten
+ sources.inject([]) do |list, source|
+ determined_source = determine_source(source, expansions)
+ update_source_list(list, determined_source)
+ end
+ end
+ end
+
+ def update_source_list(list, source)
+ case source
+ when String
+ list.delete(source)
+ list << source
+ when Array
+ updated_sources = source - list
+ list.concat(updated_sources)
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
index b4e61f2034..014a03c54d 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
@@ -21,7 +21,7 @@ module ActionView
@controller = controller
end
- # Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
+ # Add the extension +ext+ if not present. Return full URLs otherwise untouched.
# 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.
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
index 6581e1d6f2..b9126af944 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
@@ -33,13 +33,21 @@ module ActionView
all_asset_files = (collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") - ['application']) << 'application'
((determine_source(:defaults, expansions).dup & all_asset_files) + all_asset_files).uniq
else
- expanded_sources = sources.collect do |source|
- determine_source(source, expansions)
- end.flatten
- expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(custom_dir, "application.#{extension}"))
+ expanded_sources = sources.inject([]) do |list, source|
+ determined_source = determine_source(source, expansions)
+ update_source_list(list, determined_source)
+ end
+ add_application_js(expanded_sources, sources)
expanded_sources
end
end
+
+ def add_application_js(expanded_sources, sources)
+ if sources.include?(:defaults) && File.exist?(File.join(custom_dir, "application.#{extension}"))
+ expanded_sources.delete('application')
+ expanded_sources << "application"
+ end
+ end
end
@@ -59,7 +67,10 @@ module ActionView
# <script type="text/javascript" src="/javascripts/body.js"></script>
# <script type="text/javascript" src="/javascripts/tail.js"></script>
def register_javascript_expansion(expansions)
- JavascriptIncludeTag.expansions.merge!(expansions)
+ js_expansions = JavascriptIncludeTag.expansions
+ expansions.each do |key, values|
+ js_expansions[key] = (js_expansions[key] || []) | Array(values) if values
+ end
end
end
@@ -170,4 +181,4 @@ module ActionView
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
index d02b28d7f6..f3e041de95 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
@@ -44,7 +44,10 @@ module ActionView
# <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
def register_stylesheet_expansion(expansions)
- StylesheetIncludeTag.expansions.merge!(expansions)
+ style_expansions = StylesheetIncludeTag.expansions
+ expansions.each do |key, values|
+ style_expansions[key] = (style_expansions[key] || []) | Array(values) if values
+ end
end
end
@@ -141,4 +144,4 @@ module ActionView
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index f544a9d147..385378ea29 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -2,31 +2,29 @@ module ActionView
# = Action View Cache Helper
module Helpers
module CacheHelper
- # This helper to exposes a method for caching of view fragments.
- # See ActionController::Caching::Fragments for usage instructions.
+ # This helper exposes a method for caching fragments of a view
+ # rather than an entire action or page. This technique is useful
+ # caching pieces like menus, lists of newstopics, static HTML
+ # fragments, and so on. This method takes a block that contains
+ # the content you wish to cache.
#
- # A method for caching fragments of a view rather than an entire
- # action or page. This technique is useful caching pieces like
- # menus, lists of news topics, static HTML fragments, and so on.
- # This method takes a block that contains the content you wish
- # to cache. See ActionController::Caching::Fragments for more
- # information.
+ # See ActionController::Caching::Fragments for usage instructions.
#
# ==== Examples
- # If you wanted to cache a navigation menu, you could do the
- # following.
+ # If you want to cache a navigation menu, you can do following:
#
# <% cache do %>
# <%= render :partial => "menu" %>
# <% end %>
#
- # You can also cache static content...
+ # You can also cache static content:
#
# <% cache do %>
# <p>Hello users! Welcome to our website!</p>
# <% end %>
#
- # ...and static content mixed with RHTML content.
+ # Static content with embedded ruby content can be cached as
+ # well:
#
# <% cache do %>
# Topics:
@@ -46,8 +44,8 @@ module ActionView
private
# TODO: Create an object that has caching read/write on it
def fragment_for(name = {}, options = nil, &block) #:nodoc:
- if controller.fragment_exist?(name, options)
- controller.read_fragment(name, options)
+ if fragment = controller.read_fragment(name, options)
+ fragment
else
# VIEW TODO: Make #capture usable outside of ERB
# This dance is needed because Builder can't use capture
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 875ec9b77b..dc8e4bc316 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -1,5 +1,6 @@
require 'date'
require 'action_view/helpers/tag_helper'
+require 'active_support/core_ext/date/conversions'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/with_options'
@@ -566,6 +567,27 @@ module ActionView
def select_year(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_year
end
+
+ # Returns an html time tag for the given date or time.
+ #
+ # ==== Examples
+ # time_tag Date.today # =>
+ # <time datetime="2010-11-04">November 04, 2010</time>
+ # time_tag Time.now # =>
+ # <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time>
+ # time_tag Date.yesterday, 'Yesterday' # =>
+ # <time datetime="2010-11-03">Yesterday</time>
+ # time_tag Date.today, :pubdate => true # =>
+ # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
+ #
+ def time_tag(date_or_time, *args)
+ options = args.extract_options!
+ format = options.delete(:format) || :long
+ content = args.first || I18n.l(date_or_time, :format => format)
+ datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.rfc3339
+
+ content_tag(:time, content, options.reverse_merge(:datetime => datetime))
+ end
end
class DateTimeSelector #:nodoc:
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index ef5bbd8ae3..befaa3e8d9 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -298,6 +298,23 @@ module ActionView
#
# If you don't need to attach a form to a model instance, then check out
# FormTagHelper#form_tag.
+ #
+ # === Form to external resources
+ #
+ # When you build forms to external resources sometimes you need to set an authenticity token or just render a form
+ # without it, for example when you submit data to a payment gateway number and types of fields could be limited.
+ #
+ # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
+ #
+ # <%= form_for @invoice, :url => external_url, :authenticity_token => 'external_token' do |f|
+ # ...
+ # <% end %>
+ #
+ # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
+ #
+ # <%= form_for @invoice, :url => external_url, :authenticity_token => false do |f|
+ # ...
+ # <% end %>
def form_for(record, options = {}, &proc)
raise ArgumentError, "Missing block" unless block_given?
@@ -314,6 +331,8 @@ module ActionView
end
options[:html][:remote] = options.delete(:remote)
+ options[:html][:authenticity_token] = options.delete(:authenticity_token)
+
builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &proc)
fields_for = fields_for(object_name, object, options, &proc)
default_options = builder.multipart? ? { :multipart => true } : {}
@@ -530,8 +549,11 @@ module ActionView
# <% end %>
# ...
# <% end %>
- def fields_for(record, record_object = nil, options = nil, &block)
- capture(instantiate_builder(record, record_object, options, &block), &block)
+ def fields_for(record, record_object = nil, options = {}, &block)
+ builder = instantiate_builder(record, record_object, options, &block)
+ output = capture(builder, &block)
+ output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
+ output
end
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
@@ -858,8 +880,7 @@ module ActionView
end
end
- module InstanceTagMethods #:nodoc:
- extend ActiveSupport::Concern
+ class InstanceTag
include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper
attr_reader :object, :method_name, :object_name
@@ -1025,7 +1046,7 @@ module ActionView
self.class.value_before_type_cast(object, @method_name)
end
- module ClassMethods
+ class << self
def value(object, method_name)
object.send method_name if object
end
@@ -1111,11 +1132,7 @@ module ActionView
end
end
- class InstanceTag
- include InstanceTagMethods
- end
-
- class FormBuilder #:nodoc:
+ class FormBuilder
# The methods which wrap a form helper call.
class_attribute :field_helpers
self.field_helpers = (FormHelper.instance_method_names - ['form_for'])
@@ -1248,7 +1265,7 @@ module ActionView
def submit(value=nil, options={})
value, options = nil, value if value.is_a?(Hash)
value ||= submit_default_value
- @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
+ @template.submit_tag(value, options)
end
def emitted_hidden_id?
@@ -1309,14 +1326,8 @@ module ActionView
def fields_for_nested_model(name, object, options, block)
object = convert_to_model(object)
- if object.persisted?
- @template.fields_for(name, object, options) do |builder|
- block.call(builder)
- @template.concat builder.hidden_field(:id) unless builder.emitted_hidden_id?
- end
- else
- @template.fields_for(name, object, options, &block)
- end
+ options[:hidden_field_id] = object.persisted?
+ @template.fields_for(name, object, options, &block)
end
def nested_child_index(name)
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 6ac8577785..7698602022 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -533,7 +533,7 @@ module ActionView
else
selected = Array.wrap(selected)
options = selected.extract_options!.symbolize_keys
- [ options[:selected] || selected , options[:disabled] ]
+ [ options.include?(:selected) ? options[:selected] : selected, options[:disabled] ]
end
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 50f065f03d..71f8534cbf 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -25,6 +25,9 @@ module ActionView
# * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
# If "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt>
# is added to simulate the verb over post.
+ # * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to
+ # pass custom authenticity token string, or to not add authenticity_token field at all
+ # (by passing <tt>false</tt>).
# * A list of parameters to feed to the URL the form will be posted to.
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
# submit behaviour. By default this behaviour is an ajax submit.
@@ -47,6 +50,12 @@ module ActionView
# <%= form_tag('/posts', :remote => true) %>
# # => <form action="/posts" method="post" data-remote="true">
#
+ # form_tag('http://far.away.com/form', :authenticity_token => false)
+ # # form without authenticity token
+ #
+ # form_tag('http://far.away.com/form', :authenticity_token => "cf50faa3fe97702ca1ae")
+ # # form with custom authenticity token
+ #
def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
html_options = html_options_for_form(url_for_options, options, *parameters_for_url)
if block_given?
@@ -68,7 +77,7 @@ module ActionView
# * Any other key creates standard HTML attributes for the tag.
#
# ==== Examples
- # select_tag "people", options_from_collection_for_select(@people, "name", "id")
+ # select_tag "people", options_from_collection_for_select(@people, "id", "name")
# # <select id="people" name="people"><option value="1">David</option></select>
#
# select_tag "people", "<option>David</option>"
@@ -112,6 +121,7 @@ module ActionView
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
# * <tt>:size</tt> - The number of visible characters that will fit in the input.
# * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
+ # * <tt>:placeholder</tt> - The text contained in the field by default which is removed when the field receives focus.
# * Any other key creates standard HTML attributes for the tag.
#
# ==== Examples
@@ -121,6 +131,9 @@ module ActionView
# text_field_tag 'query', 'Enter your search query here'
# # => <input id="query" name="query" type="text" value="Enter your search query here" />
#
+ # text_field_tag 'search', nil, :placeholder => 'Enter search term...'
+ # # => <input id="search" name="search" placeholder="Enter search term..." type="text" />
+ #
# text_field_tag 'request', nil, :class => 'special_input'
# # => <input class="special_input" id="request" name="request" type="text" />
#
@@ -397,6 +410,59 @@ module ActionView
tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
end
+ # Creates a button element that defines a <tt>submit</tt> button,
+ # <tt>reset</tt>button or a generic button which can be used in
+ # JavaScript, for example. You can use the button tag as a regular
+ # submit tag but it isn't supported in legacy browsers. However,
+ # the button tag allows richer labels such as images and emphasis,
+ # so this helper will also accept a block.
+ #
+ # ==== Options
+ # * <tt>:confirm => 'question?'</tt> - If present, the
+ # unobtrusive JavaScript drivers will provide a prompt with
+ # the question specified. If the user accepts, the form is
+ # processed normally, otherwise no action is taken.
+ # * <tt>:disabled</tt> - If true, the user will not be able to
+ # use this input.
+ # * <tt>:disable_with</tt> - Value of this parameter will be
+ # used as the value for a disabled version of the submit
+ # button when the form is submitted. This feature is provided
+ # by the unobtrusive JavaScript driver.
+ # * Any other key creates standard HTML options for the tag.
+ #
+ # ==== Examples
+ # button_tag
+ # # => <button name="button" type="submit">Button</button>
+ #
+ # button_tag(:type => 'button') do
+ # content_tag(:strong, 'Ask me!')
+ # end
+ # # => <button name="button" type="button">
+ # <strong>Ask me!</strong>
+ # </button>
+ #
+ # button_tag "Checkout", :disable_with => "Please wait..."
+ # # => <button data-disable-with="Please wait..." name="button"
+ # type="submit">Checkout</button>
+ #
+ def button_tag(content_or_options = nil, options = nil, &block)
+ options = content_or_options if block_given? && content_or_options.is_a?(Hash)
+ options ||= {}
+ options.stringify_keys!
+
+ if disable_with = options.delete("disable_with")
+ options["data-disable-with"] = disable_with
+ end
+
+ if confirm = options.delete("confirm")
+ options["data-confirm"] = confirm
+ end
+
+ options.reverse_merge! 'name' => 'button', 'type' => 'submit'
+
+ content_tag :button, content_or_options || 'Button', options, &block
+ end
+
# Displays an image which when clicked will submit the form.
#
# <tt>source</tt> is passed to AssetTagHelper#path_to_image
@@ -530,6 +596,7 @@ module ActionView
html_options["action"] = url_for(url_for_options, *parameters_for_url)
html_options["accept-charset"] = "UTF-8"
html_options["data-remote"] = true if html_options.delete("remote")
+ html_options["authenticity_token"] = html_options.delete("authenticity_token") if html_options.has_key?("authenticity_token")
end
end
@@ -537,6 +604,7 @@ module ActionView
snowman_tag = tag(:input, :type => "hidden",
:name => "utf8", :value => "&#x2713;".html_safe)
+ authenticity_token = html_options.delete("authenticity_token")
method = html_options.delete("method").to_s
method_tag = case method
@@ -545,10 +613,10 @@ module ActionView
''
when /^post$/i, "", nil
html_options["method"] = "post"
- token_tag
+ token_tag(authenticity_token)
else
html_options["method"] = "post"
- tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag
+ tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token)
end
tags = snowman_tag << method_tag
@@ -568,11 +636,12 @@ module ActionView
output.safe_concat("</form>")
end
- def token_tag
- unless protect_against_forgery?
+ def token_tag(token)
+ if token == false || !protect_against_forgery?
''
else
- tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
+ token = form_authenticity_token if token.nil?
+ tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
end
end
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index a9400c347f..26f8dce3c3 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -270,12 +270,13 @@ module ActionView
digits, rounded_number = 1, 0
else
digits = (Math.log10(number.abs) + 1).floor
- rounded_number = BigDecimal.new((number / 10 ** (digits - precision)).to_s).round.to_f * 10 ** (digits - precision)
+ rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision)
+ digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
end
precision -= digits
precision = precision > 0 ? precision : 0 #don't let it be negative
else
- rounded_number = BigDecimal.new((number * (10 ** precision)).to_s).round.to_f / 10 ** precision
+ rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
end
formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options)
if strip_insignificant_zeros
diff --git a/actionpack/lib/action_view/helpers/output_safety_helper.rb b/actionpack/lib/action_view/helpers/output_safety_helper.rb
new file mode 100644
index 0000000000..a035dd70ad
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/output_safety_helper.rb
@@ -0,0 +1,38 @@
+require 'active_support/core_ext/string/output_safety'
+
+module ActionView #:nodoc:
+ # = Action View Raw Output Helper
+ module Helpers #:nodoc:
+ module OutputSafetyHelper
+ # This method outputs without escaping a string. Since escaping tags is
+ # now default, this can be used when you don't want Rails to automatically
+ # escape tags. This is not recommended if the data is coming from the user's
+ # input.
+ #
+ # For example:
+ #
+ # <%=raw @user.name %>
+ def raw(stringish)
+ stringish.to_s.html_safe
+ end
+
+ # This method returns a html safe string similar to what <tt>Array#join</tt>
+ # would return. All items in the array, including the supplied separator, are
+ # html escaped unless they are html safe, and the returned string is marked
+ # as html safe.
+ #
+ # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />")
+ # # => "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;"
+ #
+ # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>".html_safe], "<br />".html_safe)
+ # # => "<p>foo</p><br /><p>bar</p>"
+ #
+ def safe_join(array, sep=$,)
+ sep ||= "".html_safe
+ sep = ERB::Util.html_escape(sep)
+
+ array.map { |i| ERB::Util.html_escape(i) }.join(sep).html_safe
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/raw_output_helper.rb b/actionpack/lib/action_view/helpers/raw_output_helper.rb
deleted file mode 100644
index 216683a2e0..0000000000
--- a/actionpack/lib/action_view/helpers/raw_output_helper.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-module ActionView #:nodoc:
- # = Action View Raw Output Helper
- module Helpers #:nodoc:
- module RawOutputHelper
- # This method outputs without escaping a string. Since escaping tags is
- # now default, this can be used when you don't want Rails to automatically
- # escape tags. This is not recommended if the data is coming from the user's
- # input.
- #
- # For example:
- #
- # <%=raw @user.name %>
- def raw(stringish)
- stringish.to_s.html_safe
- end
- end
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index ee51617a2b..786af5ca58 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -14,7 +14,7 @@ module ActionView
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer
autoplay controls loop selected hidden scoped async
defer reversed ismap seemless muted required
- autofocus novalidate formnovalidate open).to_set
+ autofocus novalidate formnovalidate open pubdate).to_set
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
# Returns an empty HTML tag of type +name+ which by default is XHTML
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 3d276000a1..2d3c5fe7e7 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -234,6 +234,10 @@ module ActionView
#
# You can pass any HTML attributes into <tt>html_options</tt>. These
# will be added to all created paragraphs.
+ #
+ # ==== Options
+ # * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
+ #
# ==== Examples
# my_text = "Here is some basic text...\n...with a line break."
#
@@ -247,6 +251,9 @@ module ActionView
#
# simple_format("Look ma! A class!", :class => 'description')
# # => "<p class='description'>Look ma! A class!</p>"
+ #
+ # simple_format("<span>I'm allowed!</span> It's true.", {}, :sanitize => false)
+ # # => "<p><span>I'm allowed!</span> It's true.</p>"
def simple_format(text, html_options={}, options={})
text = ''.html_safe if text.nil?
start_tag = tag('p', html_options, true)
@@ -459,7 +466,7 @@ module ActionView
end
AUTO_LINK_RE = %r{
- (?: ([\w+.:-]+:)// | www\. )
+ (?: ([0-9A-Za-z+.:-]+:)// | www\. )
[^\s<]+
}x
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index c23315b344..2cd2dca711 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -253,8 +253,9 @@ module ActionView
# using the +link_to+ method with the <tt>:method</tt> modifier as described in
# the +link_to+ documentation.
#
- # The generated form element has a class name of <tt>button_to</tt>
- # to allow styling of the form itself and its children. You can control
+ # By default, the generated form element has a class name of <tt>button_to</tt>
+ # to allow styling of the form itself and its children. This can be changed
+ # using the <tt>:form_class</tt> modifier within +html_options+. You can control
# the form submission and input element behavior using +html_options+.
# This method accepts the <tt>:method</tt> and <tt>:confirm</tt> modifiers
# described in the +link_to+ documentation. If no <tt>:method</tt> modifier
@@ -275,6 +276,8 @@ module ActionView
# processed normally, otherwise no action is taken.
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
# submit behaviour. By default this behaviour is an ajax submit.
+ # * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
+ # be placed
#
# ==== Examples
# <%= button_to "New", :action => "new" %>
@@ -283,6 +286,12 @@ module ActionView
# # </form>"
#
#
+ # <%= button_to "New", :action => "new", :form_class => "new-thing" %>
+ # # => "<form method="post" action="/controller/new" class="new-thing">
+ # # <div><input value="New" type="submit" /></div>
+ # # </form>"
+ #
+ #
# <%= button_to "Delete Image", { :action => "delete", :id => @image.id },
# :confirm => "Are you sure?", :method => :delete %>
# # => "<form method="post" action="/images/delete/1" class="button_to">
@@ -312,6 +321,7 @@ module ActionView
end
form_method = method.to_s == 'get' ? 'get' : 'post'
+ form_class = html_options.delete('form_class') || 'button_to'
remote = html_options.delete('remote')
@@ -327,7 +337,7 @@ module ActionView
html_options.merge!("type" => "submit", "value" => name)
- ("<form method=\"#{form_method}\" action=\"#{ERB::Util.html_escape(url)}\" #{"data-remote=\"true\"" if remote} class=\"button_to\"><div>" +
+ ("<form method=\"#{form_method}\" action=\"#{ERB::Util.html_escape(url)}\" #{"data-remote=\"true\"" if remote} class=\"#{ERB::Util.html_escape(form_class)}\"><div>" +
method_tag + tag("input", html_options) + request_token_tag + "</div></form>").html_safe
end
@@ -487,13 +497,14 @@ module ActionView
email_address_obfuscated = email_address.dup
email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.key?("replace_at")
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.key?("replace_dot")
-
case encode
when "javascript"
- string =
- "document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))}');".unpack('C*').map { |c|
- sprintf("%%%x", c)
- }.join
+ string = ''
+ html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))
+ html = escape_javascript(html)
+ "document.write('#{html}');".each_byte do |c|
+ string << sprintf("%%%x", c)
+ end
"<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe
when "hex"
email_address_encoded = email_address_obfuscated.unpack('C*').map {|c|
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index d524c68450..06c607931d 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -78,17 +78,17 @@ module ActionView
@view_paths = ActionView::Base.process_view_paths(paths)
end
- def find(name, prefix = nil, partial = false, keys = [])
- @view_paths.find(*args_for_lookup(name, prefix, partial, keys))
+ def find(name, prefixes = [], partial = false, keys = [])
+ @view_paths.find(*args_for_lookup(name, prefixes, partial, keys))
end
alias :find_template :find
- def find_all(name, prefix = nil, partial = false, keys = [])
- @view_paths.find_all(*args_for_lookup(name, prefix, partial, keys))
+ def find_all(name, prefixes = [], partial = false, keys = [])
+ @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys))
end
- def exists?(name, prefix = nil, partial = false, keys = [])
- @view_paths.exists?(*args_for_lookup(name, prefix, partial, keys))
+ def exists?(name, prefixes = [], partial = false, keys = [])
+ @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys))
end
alias :template_exists? :exists?
@@ -107,18 +107,26 @@ module ActionView
protected
- def args_for_lookup(name, prefix, partial, keys) #:nodoc:
- name, prefix = normalize_name(name, prefix)
- [name, prefix, partial || false, @details, details_key, keys]
+ def args_for_lookup(name, prefixes, partial, keys) #:nodoc:
+ name, prefixes = normalize_name(name, prefixes)
+ [name, prefixes, partial || false, @details, details_key, keys]
end
# Support legacy foo.erb names even though we now ignore .erb
# as well as incorrectly putting part of the path in the template
# name instead of the prefix.
- def normalize_name(name, prefix) #:nodoc:
+ def normalize_name(name, prefixes) #:nodoc:
name = name.to_s.gsub(handlers_regexp, '')
parts = name.split('/')
- return parts.pop, [prefix, *parts].compact.join("/")
+ name = parts.pop
+
+ prefixes = if prefixes.blank?
+ [parts.join('/')]
+ else
+ prefixes.map { |prefix| [prefix, *parts].compact.join('/') }
+ end
+
+ return name, prefixes
end
def default_handlers #:nodoc:
@@ -156,11 +164,11 @@ module ActionView
@frozen_formats = true
end
- # Overload formats= to reject [:"*/*"] values.
+ # Overload formats= to reject ["*/*"] values.
def formats=(values)
if values && values.size == 1
value = values.first
- values = nil if value == :"*/*"
+ values = nil if value == "*/*"
values << :html if value == :js
end
super(values)
@@ -178,11 +186,11 @@ module ActionView
end
# Overload locale= to also set the I18n.locale. If the current I18n.config object responds
- # to i18n_config, it means that it's has a copy of the original I18n configuration and it's
+ # to original_config, it means that it's has a copy of the original I18n configuration and it's
# acting as proxy, which we need to skip.
def locale=(value)
if value
- config = I18n.config.respond_to?(:i18n_config) ? I18n.config.i18n_config : I18n.config
+ config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config
config.locale = value
end
@@ -237,4 +245,4 @@ module ActionView
include Details
include ViewPaths
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb
index cd3f130dc4..c181689e62 100644
--- a/actionpack/lib/action_view/partials.rb
+++ b/actionpack/lib/action_view/partials.rb
@@ -12,19 +12,19 @@ module ActionView
#
# <%= render :partial => "account" %>
#
- # This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable
+ # This would render "advertiser/_account.html.erb" and pass the instance variable @account in as a local variable
# +account+ to the template for display.
#
# In another template for Advertiser#buy, we could have:
#
# <%= render :partial => "account", :locals => { :account => @buyer } %>
#
- # <% for ad in @advertisements %>
+ # <% @advertisements.each do |ad| %>
# <%= render :partial => "ad", :locals => { :ad => ad } %>
# <% end %>
#
- # This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then
- # render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display.
+ # This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then
+ # render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display.
#
# == The :as and :object options
#
@@ -40,16 +40,16 @@ module ActionView
# With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we
# wanted it to be +agreement+ instead of +contract+ we'd do:
#
- # <%= render :partial => "contract", :as => :agreement %>
+ # <%= render :partial => "contract", :as => 'agreement' %>
#
# The <tt>:object</tt> option can be used to directly specify which object is rendered into the partial;
# useful when the template's object is elsewhere, in a different ivar or in a local variable for instance.
- #
+ #
# Revisiting a previous example we could have written this code:
- #
+ #
# <%= render :partial => "account", :object => @buyer %>
#
- # <% for ad in @advertisements %>
+ # <% @advertisements.each do |ad| %>
# <%= render :partial => "ad", :object => ad %>
# <% end %>
#
@@ -64,12 +64,12 @@ module ActionView
#
# <%= render :partial => "ad", :collection => @advertisements %>
#
- # This will render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. An
+ # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An
# iteration counter will automatically be made available to the template with a name of the form
# +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+.
#
# The <tt>:as</tt> option may be used when rendering partials.
- #
+ #
# You can specify a partial to be rendered between elements via the <tt>:spacer_template</tt> option.
# The following example will render <tt>advertiser/_ad_divider.html.erb</tt> between each ad partial:
#
@@ -89,7 +89,7 @@ module ActionView
#
# <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>
#
- # This will render the partial "advertisement/_ad.erb" regardless of which controller this is being called from.
+ # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
#
# == Rendering objects with the RecordIdentifier
#
diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb
index fa35120a0d..e3de3e1eac 100644
--- a/actionpack/lib/action_view/path_set.rb
+++ b/actionpack/lib/action_view/path_set.rb
@@ -11,13 +11,15 @@ module ActionView #:nodoc:
end
def find(*args)
- find_all(*args).first || raise(MissingTemplate.new(self, "#{args[1]}/#{args[0]}", args[3], args[2]))
+ find_all(*args).first || raise(MissingTemplate.new(self, *args))
end
- def find_all(*args)
- each do |resolver|
- templates = resolver.find_all(*args)
- return templates unless templates.empty?
+ def find_all(path, prefixes = [], *args)
+ prefixes.each do |prefix|
+ each do |resolver|
+ templates = resolver.find_all(path, prefix, *args)
+ return templates unless templates.empty?
+ end
end
[]
end
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 71cd1a788a..501ec07b09 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -35,5 +35,13 @@ module ActionView
end
end
end
+
+ initializer "action_view.caching" do |app|
+ ActiveSupport.on_load(:action_view) do
+ if app.config.action_view.cache_template_loading.nil?
+ ActionView::Resolver.caching = app.config.cache_classes
+ end
+ end
+ end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index 317479ad4c..94c0a8a8fb 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -108,11 +108,11 @@ module ActionView
locals << @variable_counter if @collection
find_template(path, locals)
end
- end
+ end
def find_template(path=@path, locals=@locals.keys)
- prefix = @view.controller_prefix unless path.include?(?/)
- @lookup_context.find_template(path, prefix, true, locals)
+ prefixes = path.include?(?/) ? [] : @view.controller_prefixes
+ @lookup_context.find_template(path, prefixes, true, locals)
end
def collection_with_template
@@ -152,14 +152,14 @@ module ActionView
object = object.to_model if object.respond_to?(:to_model)
object.class.model_name.partial_path.dup.tap do |partial|
- path = @view.controller_prefix
+ path = @view.controller_prefixes.first
partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/)
end
end
end
def retrieve_variable(path)
- variable = @options[:as] || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym
+ variable = @options[:as].try(:to_sym) || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym
variable_counter = :"#{variable}_counter" if @collection
[variable, variable_counter]
end
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
index ece3f621b6..9ae1636131 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -22,14 +22,14 @@ module ActionView
def render_once(options)
paths, locals = options[:once], options[:locals] || {}
layout, keys = options[:layout], locals.keys
- prefix = options.fetch(:prefix, @view.controller_prefix)
+ prefixes = options.fetch(:prefixes, @view.controller_prefixes)
raise "render :once expects a String or an Array to be given" unless paths
render_with_layout(layout, locals) do
contents = []
Array.wrap(paths).each do |path|
- template = find_template(path, prefix, false, keys)
+ template = find_template(path, prefixes, false, keys)
contents << render_template(template, nil, locals) if @rendered.add?(template)
end
contents.join("\n")
@@ -43,13 +43,13 @@ module ActionView
if options.key?(:text)
Template::Text.new(options[:text], formats.try(:first))
elsif options.key?(:file)
- with_fallbacks { find_template(options[:file], options[:prefix], false, keys) }
+ with_fallbacks { find_template(options[:file], options[:prefixes], false, keys) }
elsif options.key?(:inline)
handler = Template.handler_for_extension(options[:type] || "erb")
Template.new(options[:inline], "inline template", handler, :locals => keys)
elsif options.key?(:template)
options[:template].respond_to?(:render) ?
- options[:template] : find_template(options[:template], options[:prefix], false, keys)
+ options[:template] : find_template(options[:template], options[:prefixes], false, keys)
end
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 0d8373ef14..96d506fac5 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -123,7 +123,7 @@ module ActionView
@locals = details[:locals] || []
@virtual_path = details[:virtual_path]
@updated_at = details[:updated_at] || Time.now
- @formats = Array.wrap(format).map(&:to_sym)
+ @formats = Array.wrap(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f }
end
# Render a template. If the template was not compiled yet, it is done
@@ -163,7 +163,7 @@ module ActionView
name = pieces.pop
partial = !!name.sub!(/^_/, "")
lookup.disable_cache do
- lookup.find_template(name, pieces.join('/'), partial, @locals)
+ lookup.find_template(name, [ pieces.join('/') ], partial, @locals)
end
end
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
index ff256738a9..e246646963 100644
--- a/actionpack/lib/action_view/template/error.rb
+++ b/actionpack/lib/action_view/template/error.rb
@@ -27,7 +27,7 @@ module ActionView
class MissingTemplate < ActionViewError #:nodoc:
attr_reader :path
- def initialize(paths, path, details, partial)
+ def initialize(paths, path, prefixes, partial, details, *)
@path = path
display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ")
template_type = if partial
@@ -38,7 +38,11 @@ module ActionView
'template'
end
- super("Missing #{template_type} #{path} with #{details.inspect} in view paths #{display_paths}")
+ searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
+
+ out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n"
+ out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
+ super out
end
end
@@ -52,6 +56,7 @@ module ActionView
attr_reader :original_exception, :backtrace
def initialize(template, assigns, original_exception)
+ super(original_exception.message)
@template, @assigns, @original_exception = template, assigns.dup, original_exception
@sub_templates = nil
@backtrace = original_exception.backtrace
@@ -61,10 +66,6 @@ module ActionView
@template.identifier
end
- def message
- ActiveSupport::Deprecation.silence { original_exception.message }
- end
-
def sub_template_message
if @sub_templates
"Trace of template inclusion: " +
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index a17454da28..4d999fb3b2 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -5,6 +5,13 @@ require "action_view/template"
module ActionView
# = Action View Resolver
class Resolver
+ cattr_accessor :caching
+ self.caching = true
+
+ class << self
+ alias :caching? :caching
+ end
+
def initialize
@cached = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2|
h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }
@@ -23,9 +30,7 @@ module ActionView
private
- def caching?
- @caching ||= !defined?(Rails.application) || Rails.application.config.cache_classes
- end
+ delegate :caching?, :to => "self.class"
# This is what child classes implement. No defaults are needed
# because Resolver guarantees that the arguments are present and
@@ -42,7 +47,7 @@ module ActionView
path
end
- # Hnadles templates caching. If a key is given and caching is on
+ # Handles templates caching. If a key is given and caching is on
# always check the cache before hitting the resolver. Otherwise,
# it always hits the resolver but check if the resolver is fresher
# before returning it.
@@ -104,18 +109,27 @@ module ActionView
def query(path, exts, formats)
query = File.join(@path, path)
- exts.each do |ext|
- query << '{' << ext.map {|e| e && ".#{e}" }.join(',') << ',}'
- end
+ query << exts.map { |ext|
+ "{#{ext.compact.map { |e| ".#{e}" }.join(',')},}"
+ }.join
- Dir[query].reject { |p| File.directory?(p) }.map do |p|
- handler, format = extract_handler_and_format(p, formats)
+ query.gsub!(/\{\.html,/, "{.html,.text.html,")
+ query.gsub!(/\{\.text,/, "{.text,.text.plain,")
+
+ templates = []
+ sanitizer = Hash.new { |h,k| h[k] = Dir["#{File.dirname(k)}/*"] }
+
+ Dir[query].each do |p|
+ next if File.directory?(p) || !sanitizer[p].include?(p)
+ handler, format = extract_handler_and_format(p, formats)
contents = File.open(p, "rb") {|io| io.read }
- Template.new(contents, File.expand_path(p), handler,
+ templates << Template.new(contents, File.expand_path(p), handler,
:virtual_path => path, :format => format, :updated_at => mtime(p))
end
+
+ templates
end
# Returns the file mtime from the filesystem.
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 4026f7a40e..2ce109ea99 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -156,10 +156,8 @@ module ActionView
# The instance of ActionView::Base that is used by +render+.
def view
@view ||= begin
- view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller)
+ view = @controller.view_context
view.singleton_class.send :include, _helpers
- view.singleton_class.send :include, @controller._routes.url_helpers
- view.singleton_class.send :delegate, :alert, :notice, :to => "request.flash"
view.extend(Locals)
view.locals = self.locals
view.output_buffer = self.output_buffer
@@ -171,6 +169,7 @@ module ActionView
INTERNAL_IVARS = %w{
@__name__
+ @__io__
@_assertion_wrapped
@_assertions
@_result
diff --git a/actionpack/lib/action_view/testing/resolvers.rb b/actionpack/lib/action_view/testing/resolvers.rb
index 55583096e0..5c5cab7c7d 100644
--- a/actionpack/lib/action_view/testing/resolvers.rb
+++ b/actionpack/lib/action_view/testing/resolvers.rb
@@ -13,7 +13,11 @@ module ActionView #:nodoc:
@hash = hash
end
- private
+ def to_s
+ @hash.keys.join(', ')
+ end
+
+ private
def query(path, exts, formats)
query = ""