aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/abstract_controller/asset_paths.rb2
-rw-r--r--actionpack/lib/abstract_controller/base.rb18
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb28
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb34
-rw-r--r--actionpack/lib/abstract_controller/logger.rb2
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb1
-rw-r--r--actionpack/lib/abstract_controller/url_for.rb12
-rw-r--r--actionpack/lib/action_controller.rb2
-rw-r--r--actionpack/lib/action_controller/base.rb22
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb21
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb2
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb7
-rw-r--r--actionpack/lib/action_controller/deprecated.rb6
-rw-r--r--actionpack/lib/action_controller/deprecated/integration_test.rb3
-rw-r--r--actionpack/lib/action_controller/deprecated/performance_test.rb2
-rw-r--r--actionpack/lib/action_controller/metal.rb16
-rw-r--r--actionpack/lib/action_controller/metal/compatibility.rb58
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb11
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb56
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb7
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb21
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb1
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb10
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb2
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb205
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb4
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb12
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb11
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb10
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb21
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb29
-rw-r--r--actionpack/lib/action_controller/metal/session_management.rb9
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb10
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb43
-rw-r--r--actionpack/lib/action_controller/railtie.rb33
-rw-r--r--actionpack/lib/action_controller/railties/helpers.rb22
-rw-r--r--actionpack/lib/action_controller/railties/paths.rb25
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb12
-rw-r--r--actionpack/lib/action_controller/test_case.rb66
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb13
-rw-r--r--actionpack/lib/action_dispatch.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb14
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb15
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb16
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/rack_cache.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb16
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb45
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb34
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb23
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb90
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb23
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb70
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb14
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb4
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing.rb19
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb235
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb14
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb30
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb177
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb14
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb37
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb7
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb11
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb32
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb1
-rw-r--r--actionpack/lib/action_view/asset_paths.rb2
-rw-r--r--actionpack/lib/action_view/base.rb12
-rw-r--r--actionpack/lib/action_view/flows.rb5
-rw-r--r--actionpack/lib/action_view/helpers/active_model_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb112
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb1
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb63
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb53
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb5
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/csrf_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb274
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb647
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb292
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb58
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb44
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb144
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb25
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb16
-rw-r--r--actionpack/lib/action_view/helpers/tags.rb33
-rw-r--r--actionpack/lib/action_view/helpers/tags/base.rb146
-rw-r--r--actionpack/lib/action_view/helpers/tags/check_box.rb62
-rw-r--r--actionpack/lib/action_view/helpers/tags/checkable.rb16
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb37
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_helpers.rb82
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb30
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_select.rb28
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_field.rb15
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_select.rb70
-rw-r--r--actionpack/lib/action_view/helpers/tags/datetime_select.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/email_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/file_field.rb12
-rw-r--r--actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb29
-rw-r--r--actionpack/lib/action_view/helpers/tags/hidden_field.rb12
-rw-r--r--actionpack/lib/action_view/helpers/tags/label.rb65
-rw-r--r--actionpack/lib/action_view/helpers/tags/number_field.rb19
-rw-r--r--actionpack/lib/action_view/helpers/tags/password_field.rb12
-rw-r--r--actionpack/lib/action_view/helpers/tags/radio_button.rb31
-rw-r--r--actionpack/lib/action_view/helpers/tags/range_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/search_field.rb24
-rw-r--r--actionpack/lib/action_view/helpers/tags/select.rb41
-rw-r--r--actionpack/lib/action_view/helpers/tags/tel_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/text_area.rb18
-rw-r--r--actionpack/lib/action_view/helpers/tags/text_field.rb29
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_select.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_zone_select.rb20
-rw-r--r--actionpack/lib/action_view/helpers/tags/url_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb39
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb16
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb94
-rw-r--r--actionpack/lib/action_view/locale/en.yml10
-rw-r--r--actionpack/lib/action_view/log_subscriber.rb3
-rw-r--r--actionpack/lib/action_view/lookup_context.rb15
-rw-r--r--actionpack/lib/action_view/railtie.rb12
-rw-r--r--actionpack/lib/action_view/renderer/abstract_renderer.rb2
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb80
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb8
-rw-r--r--actionpack/lib/action_view/template.rb5
-rw-r--r--actionpack/lib/action_view/template/error.rb4
-rw-r--r--actionpack/lib/action_view/template/handlers.rb14
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb4
-rw-r--r--actionpack/lib/action_view/template/resolver.rb8
-rw-r--r--actionpack/lib/action_view/test_case.rb6
-rw-r--r--actionpack/lib/sprockets/assets.rake101
-rw-r--r--actionpack/lib/sprockets/bootstrap.rb37
-rw-r--r--actionpack/lib/sprockets/compressors.rb83
-rw-r--r--actionpack/lib/sprockets/helpers.rb6
-rw-r--r--actionpack/lib/sprockets/helpers/isolated_helper.rb13
-rw-r--r--actionpack/lib/sprockets/helpers/rails_helper.rb167
-rw-r--r--actionpack/lib/sprockets/railtie.rb59
-rw-r--r--actionpack/lib/sprockets/static_compiler.rb62
151 files changed, 3158 insertions, 2232 deletions
diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb
index dd5f9a1942..822254b1a4 100644
--- a/actionpack/lib/abstract_controller/asset_paths.rb
+++ b/actionpack/lib/abstract_controller/asset_paths.rb
@@ -1,5 +1,5 @@
module AbstractController
- module AssetPaths
+ module AssetPaths #:nodoc:
extend ActiveSupport::Concern
included do
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index fd6a46fbec..97a9eec144 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,11 +1,15 @@
require 'erubis'
+require 'set'
require 'active_support/configurable'
require 'active_support/descendants_tracker'
require 'active_support/core_ext/module/anonymous'
module AbstractController
- class Error < StandardError; end
- class ActionNotFound < StandardError; end
+ class Error < StandardError #:nodoc:
+ end
+
+ class ActionNotFound < StandardError #:nodoc:
+ end
# <tt>AbstractController::Base</tt> is a low-level API. Nobody should be
# using it directly, and subclasses (like ActionController::Base) are
@@ -42,8 +46,8 @@ module AbstractController
controller.public_instance_methods(true)
end
- # The list of hidden actions to an empty array. Defaults to an
- # empty array. This can be modified by other modules or subclasses
+ # The list of hidden actions. Defaults to an empty array.
+ # This can be modified by other modules or subclasses
# to specify particular actions as hidden.
#
# ==== Returns
@@ -59,7 +63,7 @@ module AbstractController
# itself. Finally, #hidden_actions are removed.
#
# ==== Returns
- # * <tt>array</tt> - A list of all methods that should be considered actions.
+ # * <tt>set</tt> - A set of all methods that should be considered actions.
def action_methods
@action_methods ||= begin
# All public instance methods of this class, including ancestors
@@ -72,7 +76,7 @@ module AbstractController
hidden_actions.to_a
# Clear out AS callback method pollution
- methods.reject { |method| method =~ /_one_time_conditions/ }
+ Set.new(methods.reject { |method| method =~ /_one_time_conditions/ })
end
end
@@ -85,7 +89,7 @@ module AbstractController
# Returns the full controller name, underscored, without the ending Controller.
# For instance, MyApp::MyPostsController would return "my_app/my_posts" for
- # controller_name.
+ # controller_path.
#
# ==== Returns
# * <tt>string</tt>
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index fffe3edac2..c0fa28cae9 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -8,7 +8,7 @@ module AbstractController
include ActiveSupport::Callbacks
included do
- define_callbacks :process_action, :terminator => "response_body"
+ define_callbacks :process_action, :terminator => "response_body", :skip_after_callbacks_if_terminated => true
end
# Override AbstractController::Base's process_action to run the
@@ -21,11 +21,9 @@ module AbstractController
module ClassMethods
# If :only or :except are used, convert the options into the
- # primitive form (:per_key) used by ActiveSupport::Callbacks.
+ # :unless and :if options of ActiveSupport::Callbacks.
# The basic idea is that :only => :index gets converted to
- # :if => proc {|c| c.action_name == "index" }, but that the
- # proc is only evaluated once per action for the lifetime of
- # a Rails process.
+ # :if => proc {|c| c.action_name == "index" }.
#
# ==== Options
# * <tt>only</tt> - The callback should be run only for this action
@@ -33,11 +31,11 @@ module AbstractController
def _normalize_callback_options(options)
if only = options[:only]
only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ")
- options[:per_key] = {:if => only}
+ options[:if] = Array(options[:if]) << only
end
if except = options[:except]
except = Array(except).map {|e| "action_name == '#{e}'"}.join(" || ")
- options[:per_key] = {:unless => except}
+ options[:unless] = Array(options[:unless]) << except
end
end
@@ -48,7 +46,7 @@ module AbstractController
# callbacks. Note that skipping uses Ruby equality, so it's
# impossible to skip a callback defined using an anonymous proc
# using #skip_filter
- def skip_filter(*names, &blk)
+ def skip_filter(*names)
skip_before_filter(*names)
skip_after_filter(*names)
skip_around_filter(*names)
@@ -66,7 +64,7 @@ module AbstractController
# ==== Block Parameters
# * <tt>name</tt> - The callback to be added
# * <tt>options</tt> - A hash of options to be used when adding the callback
- def _insert_callbacks(callbacks, block)
+ def _insert_callbacks(callbacks, block = nil)
options = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
_normalize_callback_options(options)
callbacks.push(block) if block
@@ -92,7 +90,7 @@ module AbstractController
##
# :method: skip_before_filter
#
- # :call-seq: skip_before_filter(names, block)
+ # :call-seq: skip_before_filter(names)
#
# Skip a before filter. See _insert_callbacks for parameter details.
@@ -120,7 +118,7 @@ module AbstractController
##
# :method: skip_after_filter
#
- # :call-seq: skip_after_filter(names, block)
+ # :call-seq: skip_after_filter(names)
#
# Skip an after filter. See _insert_callbacks for parameter details.
@@ -148,7 +146,7 @@ module AbstractController
##
# :method: skip_around_filter
#
- # :call-seq: skip_around_filter(names, block)
+ # :call-seq: skip_around_filter(names)
#
# Skip an around filter. See _insert_callbacks for parameter details.
@@ -167,7 +165,6 @@ module AbstractController
# for details on the allowed parameters.
def #{filter}_filter(*names, &blk) # def before_filter(*names, &blk)
_insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- options[:if] = (Array(options[:if]) << "!halted") if #{filter == :after} # options[:if] = (Array(options[:if]) << "!halted") if false
set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options)
end # end
end # end
@@ -176,15 +173,14 @@ module AbstractController
# for details on the allowed parameters.
def prepend_#{filter}_filter(*names, &blk) # def prepend_before_filter(*names, &blk)
_insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- options[:if] = (Array(options[:if]) << "!halted") if #{filter == :after} # options[:if] = (Array(options[:if]) << "!halted") if false
set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
end # end
end # end
# Skip a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
- def skip_#{filter}_filter(*names, &blk) # def skip_before_filter(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ def skip_#{filter}_filter(*names) # def skip_before_filter(*names)
+ _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options|
skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :before, name, options)
end # end
end # end
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 6a6387632c..bc9f6fc3e8 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -89,7 +89,7 @@ module AbstractController
# class TillController < BankController
# layout false
#
- # In these examples, we have three implicit lookup scenrios:
+ # In these examples, we have three implicit lookup scenarios:
# * The BankController uses the "bank" layout.
# * The ExchangeController uses the "exchange" layout.
# * The CurrencyController inherits the layout from BankController.
@@ -120,6 +120,7 @@ module AbstractController
# def writers_and_readers
# logged_in? ? "writer_layout" : "reader_layout"
# end
+ # end
#
# Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
# is logged in or not.
@@ -127,7 +128,14 @@ module AbstractController
# If you want to use an inline method, such as a proc, do something like this:
#
# class WeblogController < ActionController::Base
- # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
+ # layout proc { |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
+ # end
+ #
+ # If an argument isn't given to the proc, it's evaluated in the context of
+ # the current controller anyway.
+ #
+ # class WeblogController < ActionController::Base
+ # layout proc { logged_in? ? "writer_layout" : "reader_layout" }
# end
#
# Of course, the most common way of specifying a layout is still just as a plain template name:
@@ -136,8 +144,8 @@ module AbstractController
# layout "weblog_standard"
# end
#
- # If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>.
- # Otherwise, it will be looked up relative to the template root.
+ # The template will be looked always in <tt>app/views/layouts/</tt> folder. But you can point
+ # <tt>layouts</tt> folder direct also. <tt>layout "layouts/demo"</tt> is the same as <tt>layout "demo"</tt>.
#
# Setting the layout to nil forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists.
# Setting it to nil is useful to re-enable template lookup overriding a previous configuration set in the parent:
@@ -238,10 +246,10 @@ module AbstractController
#
# If the specified layout is a:
# String:: the String is the template name
- # Symbol:: call the method specified by the symbol, which will return
- # the template name
+ # Symbol:: call the method specified by the symbol, which will return the template name
# false:: There is no layout
# true:: raise an ArgumentError
+ # nil:: Force default layout behavior with inheritance
#
# ==== Parameters
# * <tt>layout</tt> - The layout to use.
@@ -280,6 +288,10 @@ module AbstractController
<<-RUBY
lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first || super
RUBY
+ else
+ <<-RUBY
+ super
+ RUBY
end
layout_definition = case _layout
@@ -295,12 +307,12 @@ module AbstractController
end
RUBY
when Proc
- define_method :_layout_from_proc, &_layout
- "_layout_from_proc(self)"
+ define_method :_layout_from_proc, &_layout
+ _layout.arity == 0 ? "_layout_from_proc" : "_layout_from_proc(self)"
when false
nil
when true
- raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
+ raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil"
when nil
name_clause
end
@@ -322,7 +334,7 @@ module AbstractController
super
if _include_layout?(options)
- layout = options.key?(:layout) ? options.delete(:layout) : :default
+ layout = options.delete(:layout) { :default }
options[:layout] = _layout_for_option(layout)
end
end
@@ -360,7 +372,7 @@ module AbstractController
when false, nil then nil
else
raise ArgumentError,
- "String, true, or false, expected for `layout'; you passed #{name.inspect}"
+ "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}"
end
end
diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb
index a4e31cd2e5..c31ea6c5b5 100644
--- a/actionpack/lib/abstract_controller/logger.rb
+++ b/actionpack/lib/abstract_controller/logger.rb
@@ -1,7 +1,7 @@
require "active_support/benchmarkable"
module AbstractController
- module Logger
+ module Logger #:nodoc:
extend ActiveSupport::Concern
included do
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index ddc93464cd..7d73c6af8d 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -105,6 +105,7 @@ module AbstractController
# Find and renders a template based on the options given.
# :api: private
def _render_template(options) #:nodoc:
+ lookup_context.rendered_format = nil if options[:formats]
view_renderer.render(view_context, options)
end
diff --git a/actionpack/lib/abstract_controller/url_for.rb b/actionpack/lib/abstract_controller/url_for.rb
index e4261068d8..4a95e1f276 100644
--- a/actionpack/lib/abstract_controller/url_for.rb
+++ b/actionpack/lib/abstract_controller/url_for.rb
@@ -1,10 +1,10 @@
-# Includes +url_for+ into the host class (e.g. an abstract controller or mailer). The class
-# has to provide a +RouteSet+ by implementing the <tt>_routes</tt> methods. Otherwise, an
-# exception will be raised.
-#
-# Note that this module is completely decoupled from HTTP - the only requirement is a valid
-# <tt>_routes</tt> implementation.
module AbstractController
+ # Includes +url_for+ into the host class (e.g. an abstract controller or mailer). The class
+ # has to provide a +RouteSet+ by implementing the <tt>_routes</tt> methods. Otherwise, an
+ # exception will be raised.
+ #
+ # Note that this module is completely decoupled from HTTP - the only requirement is a valid
+ # <tt>_routes</tt> implementation.
module UrlFor
extend ActiveSupport::Concern
include ActionDispatch::Routing::UrlFor
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index f4eaa2fd1b..7c10fcbb8a 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -31,7 +31,6 @@ module ActionController
autoload :RequestForgeryProtection
autoload :Rescue
autoload :Responder
- autoload :SessionManagement
autoload :Streaming
autoload :Testing
autoload :UrlFor
@@ -40,7 +39,6 @@ module ActionController
autoload :Integration, 'action_controller/deprecated/integration_test'
autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
- autoload :UrlWriter, 'action_controller/deprecated'
autoload :Routing, 'action_controller/deprecated'
autoload :TestCase, 'action_controller/test_case'
autoload :TemplateAssertions, 'action_controller/test_case'
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index b6d441d544..71425cd542 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -116,7 +116,7 @@ module ActionController
#
# Title: <%= @post.title %>
#
- # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates
+ # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates
# will use the manual rendering methods:
#
# def search
@@ -133,7 +133,7 @@ module ActionController
# == Redirects
#
# Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to the
- # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're
+ # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're
# going to reuse (and redirect to) a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
#
# def create
@@ -171,6 +171,16 @@ module ActionController
class Base < Metal
abstract!
+ # Shortcut helper that returns all the ActionController::Base modules except the ones passed in the argument:
+ #
+ # class MetalController
+ # ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
+ # include left
+ # end
+ # end
+ #
+ # This gives better control over what you want to exclude and makes it easier
+ # to create a bare controller class, instead of listing the modules required manually.
def self.without_modules(*modules)
modules = modules.map do |m|
m.is_a?(Symbol) ? ActionController.const_get(m) : m
@@ -192,7 +202,6 @@ module ActionController
Renderers::All,
ConditionalGet,
RackDelegation,
- SessionManagement,
Caching,
MimeResponds,
ImplicitRender,
@@ -228,8 +237,11 @@ module ActionController
include mod
end
- # Rails 2.x compatibility
- include ActionController::Compatibility
+ # Define some internal variables that should not be propagated to the view.
+ self.protected_instance_variables = [
+ :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
+ :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout
+ ]
ActiveSupport.run_load_hooks(:action_controller, self)
end
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index e76a79f710..80901b8bf3 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -40,14 +40,15 @@ module ActionController #:nodoc:
#
# You can modify the default action cache path by passing a
# <tt>:cache_path</tt> option. This will be passed directly to
- # <tt>ActionCachePath.path_for</tt>. This is handy for actions with
+ # <tt>ActionCachePath.new</tt>. This is handy for actions with
# multiple possible routes that should be cached differently. If a
# block is given, it is called with the current controller instance.
#
# And you can also use <tt>:if</tt> (or <tt>:unless</tt>) to pass a
# proc that specifies when the action should be cached.
#
- # Finally, if you are using memcached, you can also pass <tt>:expires_in</tt>.
+ # As of Rails 3.0, you can also pass <tt>:expires_in</tt> with a time
+ # interval (in seconds) to schedule expiration of the cached item.
#
# The following example depicts some of the points made above:
#
@@ -102,8 +103,10 @@ module ActionController #:nodoc:
end
def _save_fragment(name, options)
- content = response_body
- content = content.join if content.is_a?(Array)
+ content = ""
+ response_body.each do |parts|
+ content << parts
+ end
if caching_allowed?
write_fragment(name, content, options)
@@ -130,6 +133,8 @@ module ActionController #:nodoc:
end
def filter(controller)
+ cache_layout = @cache_layout.respond_to?(:call) ? @cache_layout.call(controller) : @cache_layout
+
path_options = if @cache_path.respond_to?(:call)
controller.instance_exec(controller, &@cache_path)
else
@@ -141,13 +146,13 @@ module ActionController #:nodoc:
body = controller.read_fragment(cache_path.path, @store_options)
unless body
- controller.action_has_layout = false unless @cache_layout
+ controller.action_has_layout = false unless cache_layout
yield
controller.action_has_layout = true
body = controller._save_fragment(cache_path.path, @store_options)
end
- body = controller.render_to_string(:text => body, :layout => true) unless @cache_layout
+ body = controller.render_to_string(:text => body, :layout => true) unless cache_layout
controller.response_body = body
controller.content_type = Mime[cache_path.extension || :html]
@@ -167,14 +172,14 @@ module ActionController #:nodoc:
options.reverse_merge!(:format => @extension) if options.is_a?(Hash)
end
- path = controller.url_for(options).split(%r{://}).last
+ path = controller.url_for(options).split('://', 2).last
@path = normalize!(path)
end
private
def normalize!(path)
path << 'index' if path[-1] == ?/
- path << ".#{extension}" if extension and !path.split('?').first.ends_with?(".#{extension}")
+ path << ".#{extension}" if extension and !path.split('?', 2).first.ends_with?(".#{extension}")
URI.parser.unescape(path)
end
end
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
index 159f718029..307594d54a 100644
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ b/actionpack/lib/action_controller/caching/pages.rb
@@ -99,7 +99,7 @@ module ActionController #:nodoc:
# caches_page :index
#
# # cache the index action except for JSON requests
- # caches_page :index, :if => Proc.new { |c| !c.request.format.json? }
+ # caches_page :index, :if => Proc.new { !request.format.json? }
#
# # don't gzip images
# caches_page :image, :gzip => false
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
index 49cf70ec21..cc1fa23935 100644
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ b/actionpack/lib/action_controller/caching/sweeping.rb
@@ -54,6 +54,11 @@ module ActionController #:nodoc:
class Sweeper < ActiveRecord::Observer #:nodoc:
attr_accessor :controller
+ def initialize(*args)
+ super
+ @controller = nil
+ end
+
def before(controller)
self.controller = controller
callback(:before) if controller.perform_caching
@@ -88,7 +93,7 @@ module ActionController #:nodoc:
end
def method_missing(method, *arguments, &block)
- return unless @controller
+ return super unless @controller
@controller.__send__(method, *arguments, &block)
end
end
diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb
index aa0cfc9395..2405bebb97 100644
--- a/actionpack/lib/action_controller/deprecated.rb
+++ b/actionpack/lib/action_controller/deprecated.rb
@@ -1,3 +1,7 @@
ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
-ActionController::Routing = ActionDispatch::Routing \ No newline at end of file
+ActionController::Routing = ActionDispatch::Routing
+
+ActiveSupport::Deprecation.warn 'ActionController::AbstractRequest and ActionController::Request are deprecated and will be removed, use ActionDispatch::Request instead.'
+ActiveSupport::Deprecation.warn 'ActionController::AbstractResponse and ActionController::Response are deprecated and will be removed, use ActionDispatch::Response instead.'
+ActiveSupport::Deprecation.warn 'ActionController::Routing is deprecated and will be removed, use ActionDispatch::Routing instead.' \ No newline at end of file
diff --git a/actionpack/lib/action_controller/deprecated/integration_test.rb b/actionpack/lib/action_controller/deprecated/integration_test.rb
index 86336b6bc4..54eae48f47 100644
--- a/actionpack/lib/action_controller/deprecated/integration_test.rb
+++ b/actionpack/lib/action_controller/deprecated/integration_test.rb
@@ -1,2 +1,5 @@
ActionController::Integration = ActionDispatch::Integration
ActionController::IntegrationTest = ActionDispatch::IntegrationTest
+
+ActiveSupport::Deprecation.warn 'ActionController::Integration is deprecated and will be removed, use ActionDispatch::Integration instead.'
+ActiveSupport::Deprecation.warn 'ActionController::IntegrationTest is deprecated and will be removed, use ActionDispatch::IntegrationTest instead.'
diff --git a/actionpack/lib/action_controller/deprecated/performance_test.rb b/actionpack/lib/action_controller/deprecated/performance_test.rb
index fcf47d31a7..c7ba5a2fe7 100644
--- a/actionpack/lib/action_controller/deprecated/performance_test.rb
+++ b/actionpack/lib/action_controller/deprecated/performance_test.rb
@@ -1 +1,3 @@
ActionController::PerformanceTest = ActionDispatch::PerformanceTest
+
+ActiveSupport::Deprecation.warn 'ActionController::PerformanceTest is deprecated and will be removed, use ActionDispatch::PerformanceTest instead.'
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 125dbf6bb5..92433ab462 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -181,15 +181,13 @@ module ActionController
@_status = Rack::Utils.status_code(status)
end
- def response_body=(val)
- body = if val.is_a?(String)
- [val]
- elsif val.nil? || val.respond_to?(:each)
- val
- else
- [val]
- end
- super body
+ def response_body=(body)
+ body = [body] unless body.nil? || body.respond_to?(:each)
+ super
+ end
+
+ def performed?
+ !!response_body
end
def dispatch(name, request) #:nodoc:
diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb
deleted file mode 100644
index 43719d5808..0000000000
--- a/actionpack/lib/action_controller/metal/compatibility.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-module ActionController
- module Compatibility
- extend ActiveSupport::Concern
-
- class ::ActionController::ActionControllerError < StandardError #:nodoc:
- end
-
- # Temporary hax
- included do
- ::ActionController::UnknownAction = ::AbstractController::ActionNotFound
- ::ActionController::DoubleRenderError = ::AbstractController::DoubleRenderError
-
- # ROUTES TODO: This should be handled by a middleware and route generation
- # should be able to handle SCRIPT_NAME
- self.config.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT']
-
- class << self
- delegate :default_charset=, :to => "ActionDispatch::Response"
- end
-
- self.protected_instance_variables = [
- :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
- :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout
- ]
-
- def rescue_action(env)
- raise env["action_dispatch.rescue.exception"]
- end
- end
-
- # For old tests
- def initialize_template_class(*) end
- def assign_shortcuts(*) end
-
- def _normalize_options(options)
- options[:text] = nil if options.delete(:nothing) == true
- options[:text] = " " if options.key?(:text) && options[:text].nil?
- super
- end
-
- def render_to_body(options)
- options[:template].sub!(/^\//, '') if options.key?(:template)
- super || " "
- end
-
- def _handle_method_missing
- method_missing(@_action_name.to_sym)
- end
-
- def method_for_action(action_name)
- super || (respond_to?(:method_missing) && "_handle_method_missing")
- end
-
- def performed?
- response_body
- end
- end
-end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 1645400693..5b25a0d303 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -111,15 +111,22 @@ module ActionController
# Examples:
# expires_in 20.minutes
# expires_in 3.hours, :public => true
- # expires_in 3.hours, 'max-stale' => 5.hours, :public => true
+ # expires_in 3.hours, :public => true, :must_revalidate => true
#
# This method will overwrite an existing Cache-Control header.
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
+ #
+ # The method will also ensure a HTTP Date header for client compatibility.
def expires_in(seconds, options = {}) #:doc:
- response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public))
+ response.cache_control.merge!(
+ :max_age => seconds,
+ :public => options.delete(:public),
+ :must_revalidate => options.delete(:must_revalidate)
+ )
options.delete(:private)
response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
+ response.date = Time.now unless response.date?
end
# Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should occur by the browser or
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 30ddf6c16e..379ff97048 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -8,15 +8,13 @@ module ActionController #:nodoc:
include ActionController::Rendering
- DEFAULT_SEND_FILE_OPTIONS = {
- :type => 'application/octet-stream'.freeze,
- :disposition => 'attachment'.freeze,
- }.freeze
+ DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc:
+ DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc:
protected
# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
# via the Rack::Sendfile middleware. The header to use is set via
- # config.action_dispatch.x_sendfile_header.
+ # +config.action_dispatch.x_sendfile_header+.
# Your server can also configure this for you by setting the X-Sendfile-Type header.
#
# Be careful to sanitize the path parameter if it is coming from a web
@@ -74,7 +72,27 @@ module ActionController #:nodoc:
self.status = options[:status] || 200
self.content_type = options[:content_type] if options.key?(:content_type)
- self.response_body = File.open(path, "rb")
+ self.response_body = FileBody.new(path)
+ end
+
+ # Avoid having to pass an open file handle as the response body.
+ # Rack::Sendfile will usually intercepts the response and just uses
+ # the path directly, so no reason to open the file.
+ class FileBody #:nodoc:
+ attr_reader :to_path
+
+ def initialize(path)
+ @to_path = path
+ end
+
+ # Stream the file's contents if Rack::Sendfile isn't present.
+ def each
+ File.open(to_path, 'rb') do |file|
+ while chunk = file.read(16384)
+ yield chunk
+ end
+ end
+ end
end
# Sends the given binary data to the browser. This method is similar to
@@ -107,7 +125,7 @@ module ActionController #:nodoc:
#
# See +send_file+ for more information on HTTP Content-* headers and caching.
def send_data(data, options = {}) #:doc:
- send_file_headers! options.dup
+ send_file_headers! options
render options.slice(:status, :content_type).merge(:text => data)
end
@@ -115,15 +133,8 @@ module ActionController #:nodoc:
def send_file_headers!(options)
type_provided = options.has_key?(:type)
- options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
- [:type, :disposition].each do |arg|
- raise ArgumentError, ":#{arg} option required" if options[arg].nil?
- end
-
- disposition = options[:disposition]
- disposition += %(; filename="#{options[:filename]}") if options[:filename]
-
- content_type = options[:type]
+ content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE)
+ raise ArgumentError, ":type option required" if content_type.nil?
if content_type.is_a?(Symbol)
extension = Mime[content_type]
@@ -132,15 +143,18 @@ module ActionController #:nodoc:
else
if !type_provided && options[:filename]
# If type wasn't provided, try guessing from file extension.
- content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.tr('.','')) || content_type
+ content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type
end
self.content_type = content_type
end
- headers.merge!(
- 'Content-Disposition' => disposition,
- 'Content-Transfer-Encoding' => 'binary'
- )
+ disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
+ unless disposition.nil?
+ disposition += %(; filename="#{options[:filename]}") if options[:filename]
+ headers['Content-Disposition'] = disposition
+ end
+
+ headers['Content-Transfer-Encoding'] = 'binary'
response.sending_file = true
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index 07024d0a9a..9a9db0fe5f 100644
--- a/actionpack/lib/action_controller/metal/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -14,8 +14,6 @@ module ActionController
end
class MethodNotAllowed < ActionControllerError #:nodoc:
- attr_reader :allowed_methods
-
def initialize(*allowed_methods)
super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
end
@@ -30,9 +28,6 @@ module ActionController
class MissingFile < ActionControllerError #:nodoc:
end
- class RenderError < ActionControllerError #:nodoc:
- end
-
class SessionOverflowError < ActionControllerError #:nodoc:
DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
@@ -43,4 +38,4 @@ module ActionController
class UnknownHttpMethod < ActionControllerError #:nodoc:
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index b45f211e83..ac12cbb625 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -18,18 +18,33 @@ module ActionController
# Force the request to this particular controller or specified actions to be
# under HTTPS protocol.
#
- # Note that this method will not be effective on development environment.
+ # If you need to disable this for any reason (e.g. development) then you can use
+ # an +:if+ or +:unless+ condition.
+ #
+ # class AccountsController < ApplicationController
+ # force_ssl :if => :ssl_configured?
+ #
+ # def ssl_configured?
+ # !Rails.env.development?
+ # end
+ # end
#
# ==== Options
+ # * <tt>host</tt> - Redirect to a different host name
# * <tt>only</tt> - The callback should be run only for this action
# * <tt>except<tt> - The callback should be run for all actions except this action
+ # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
+ # will be called only when it returns a true value.
+ # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
+ # will be called only when it returns a false value.
def force_ssl(options = {})
host = options.delete(:host)
before_filter(options) do
- if !request.ssl? && !Rails.env.development?
+ unless request.ssl?
redirect_options = {:protocol => 'https://', :status => :moved_permanently}
redirect_options.merge!(:host => host) if host
- flash.keep
+ redirect_options.merge!(:params => request.query_parameters)
+ flash.keep if respond_to?(:flash)
redirect_to redirect_options
end
end
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index d070eaae5d..1a4bca12d2 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -52,6 +52,7 @@ module ActionController
module Helpers
extend ActiveSupport::Concern
+ class << self; attr_accessor :helpers_path; end
include AbstractController::Helpers
included do
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 4972c6bede..87225d74c1 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -67,7 +67,7 @@ module ActionController
# class PostsController < ApplicationController
# REALM = "SuperSecret"
# USERS = {"dhh" => "secret", #plain text password
- # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password
+ # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
#
# before_filter :authenticate, :except => [:index]
#
@@ -229,7 +229,7 @@ module ActionController
def decode_credentials(header)
Hash[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair|
key, value = pair.split('=', 2)
- [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')]
+ [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').delete('\'')]
end]
end
@@ -263,7 +263,7 @@ module ActionController
# The quality of the implementation depends on a good choice.
# A nonce might, for example, be constructed as the base 64 encoding of
#
- # => time-stamp H(time-stamp ":" ETag ":" private-key)
+ # time-stamp H(time-stamp ":" ETag ":" private-key)
#
# where time-stamp is a server-generated time or other non-repeating value,
# ETag is the value of the HTTP ETag header associated with the requested entity,
@@ -279,7 +279,7 @@ module ActionController
#
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
- # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
+ # POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
# of this document.
#
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
@@ -293,7 +293,7 @@ module ActionController
end
# Might want a shorter timeout depending on whether the request
- # is a PUT or POST, and if client is browser or web service.
+ # is a PATCH, PUT, or POST, and if client is browser or web service.
# Can be much shorter if the Stale directive is implemented. This would
# allow a user to use new nonce without prompting user again for their
# username and password.
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index e8e465d3ba..ae04b53825 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -2,7 +2,7 @@ module ActionController
module ImplicitRender
def send_action(method, *args)
ret = super
- default_render unless response_body
+ default_render unless performed?
ret
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index ca383be76b..f467b74256 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -6,8 +6,6 @@ module ActionController #:nodoc:
module MimeResponds
extend ActiveSupport::Concern
- include ActionController::ImplicitRender
-
included do
class_attribute :responder, :mimes_for_respond_to
self.responder = ActionController::Responder
@@ -58,7 +56,7 @@ module ActionController #:nodoc:
# Clear all mime types in <tt>respond_to</tt>.
#
def clear_respond_to
- self.mimes_for_respond_to = ActiveSupport::OrderedHash.new.freeze
+ self.mimes_for_respond_to = Hash.new.freeze
end
end
@@ -76,7 +74,7 @@ module ActionController #:nodoc:
#
# respond_to do |format|
# format.html
- # format.xml { render :xml => @people.to_xml }
+ # format.xml { render :xml => @people }
# end
# end
#
@@ -191,25 +189,112 @@ module ActionController #:nodoc:
def respond_to(*mimes, &block)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
- if response = retrieve_response_from_mimes(mimes, &block)
- response.call(nil)
+ if collector = retrieve_collector_from_mimes(mimes, &block)
+ response = collector.response
+ response ? response.call : render({})
end
end
- # respond_with wraps a resource around a responder for default representation.
- # First it invokes respond_to, if a response cannot be found (ie. no block
- # for the request was given and template was not available), it instantiates
- # an ActionController::Responder with the controller and resource.
+ # For a given controller action, respond_with generates an appropriate
+ # response based on the mime-type requested by the client.
#
- # ==== Example
+ # If the method is called with just a resource, as in this example -
#
- # def index
- # @users = User.all
- # respond_with(@users)
+ # class PeopleController < ApplicationController
+ # respond_to :html, :xml, :json
+ #
+ # def index
+ # @people = Person.all
+ # respond_with @people
+ # end
# end
#
- # It also accepts a block to be given. It's used to overwrite a default
- # response:
+ # then the mime-type of the response is typically selected based on the
+ # request's Accept header and the set of available formats declared
+ # by previous calls to the controller's class method +respond_to+. Alternatively
+ # the mime-type can be selected by explicitly setting <tt>request.format</tt> in
+ # the controller.
+ #
+ # If an acceptable format is not identified, the application returns a
+ # '406 - not acceptable' status. Otherwise, the default response is to render
+ # a template named after the current action and the selected format,
+ # e.g. <tt>index.html.erb</tt>. If no template is available, the behavior
+ # depends on the selected format:
+ #
+ # * for an html response - if the request method is +get+, an exception
+ # is raised but for other requests such as +post+ the response
+ # depends on whether the resource has any validation errors (i.e.
+ # assuming that an attempt has been made to save the resource,
+ # e.g. by a +create+ action) -
+ # 1. If there are no errors, i.e. the resource
+ # was saved successfully, the response +redirect+'s to the resource
+ # i.e. its +show+ action.
+ # 2. If there are validation errors, the response
+ # renders a default action, which is <tt>:new</tt> for a
+ # +post+ request or <tt>:edit</tt> for +put+.
+ # Thus an example like this -
+ #
+ # respond_to :html, :xml
+ #
+ # def create
+ # @user = User.new(params[:user])
+ # flash[:notice] = 'User was successfully created.' if @user.save
+ # respond_with(@user)
+ # end
+ #
+ # is equivalent, in the absence of <tt>create.html.erb</tt>, to -
+ #
+ # def create
+ # @user = User.new(params[:user])
+ # respond_to do |format|
+ # if @user.save
+ # flash[:notice] = 'User was successfully created.'
+ # format.html { redirect_to(@user) }
+ # format.xml { render :xml => @user }
+ # else
+ # format.html { render :action => "new" }
+ # format.xml { render :xml => @user }
+ # end
+ # end
+ # end
+ #
+ # * for a javascript request - if the template isn't found, an exception is
+ # raised.
+ # * for other requests - i.e. data formats such as xml, json, csv etc, if
+ # the resource passed to +respond_with+ responds to <code>to_<format></code>,
+ # the method attempts to render the resource in the requested format
+ # directly, e.g. for an xml request, the response is equivalent to calling
+ # <code>render :xml => resource</code>.
+ #
+ # === Nested resources
+ #
+ # As outlined above, the +resources+ argument passed to +respond_with+
+ # can play two roles. It can be used to generate the redirect url
+ # for successful html requests (e.g. for +create+ actions when
+ # no template exists), while for formats other than html and javascript
+ # it is the object that gets rendered, by being converted directly to the
+ # required format (again assuming no template exists).
+ #
+ # For redirecting successful html requests, +respond_with+ also supports
+ # the use of nested resources, which are supplied in the same way as
+ # in <code>form_for</code> and <code>polymorphic_url</code>. For example -
+ #
+ # def create
+ # @project = Project.find(params[:project_id])
+ # @task = @project.comments.build(params[:task])
+ # flash[:notice] = 'Task was successfully created.' if @task.save
+ # respond_with(@project, @task)
+ # end
+ #
+ # This would cause +respond_with+ to redirect to <code>project_task_url</code>
+ # instead of <code>task_url</code>. For request formats other than html or
+ # javascript, if multiple resources are passed in this way, it is the last
+ # one specified that is rendered.
+ #
+ # === Customizing response behavior
+ #
+ # Like +respond_to+, +respond_with+ may also be called with a block that
+ # can be used to overwrite any of the default responses, e.g. -
#
# def create
# @user = User.new(params[:user])
@@ -220,21 +305,32 @@ module ActionController #:nodoc:
# end
# end
#
- # All options given to respond_with are sent to the underlying responder,
- # except for the option :responder itself. Since the responder interface
- # is quite simple (it just needs to respond to call), you can even give
- # a proc to it.
- #
- # In order to use respond_with, first you need to declare the formats your
- # controller responds to in the class level with a call to <tt>respond_to</tt>.
+ # The argument passed to the block is an ActionController::MimeResponds::Collector
+ # object which stores the responses for the formats defined within the
+ # block. Note that formats with responses defined explicitly in this way
+ # do not have to first be declared using the class method +respond_to+.
+ #
+ # Also, a hash passed to +respond_with+ immediately after the specified
+ # resource(s) is interpreted as a set of options relevant to all
+ # formats. Any option accepted by +render+ can be used, e.g.
+ # respond_with @people, :status => 200
+ # However, note that these options are ignored after an unsuccessful attempt
+ # to save a resource, e.g. when automatically rendering <tt>:new</tt>
+ # after a post request.
+ #
+ # Two additional options are relevant specifically to +respond_with+ -
+ # 1. <tt>:location</tt> - overwrites the default redirect location used after
+ # a successful html +post+ request.
+ # 2. <tt>:action</tt> - overwrites the default render action used after an
+ # unsuccessful html +post+ request.
#
def respond_with(*resources, &block)
raise "In order to use respond_with, first you need to declare the formats your " <<
"controller responds to in the class level" if self.class.mimes_for_respond_to.empty?
- if response = retrieve_response_from_mimes(&block)
+ if collector = retrieve_collector_from_mimes(&block)
options = resources.size == 1 ? {} : resources.extract_options!
- options.merge!(:default_response => response)
+ options[:default_response] = collector.response
(options.delete(:responder) || self.class.responder).call(self, resources, options)
end
end
@@ -260,30 +356,59 @@ module ActionController #:nodoc:
end
end
- # Collects mimes and return the response for the negotiated format. Returns
- # nil if :not_acceptable was sent to the client.
+ # Returns a Collector object containing the appropriate mime-type response
+ # for the current request, based on the available responses defined by a block.
+ # In typical usage this is the block passed to +respond_with+ or +respond_to+.
#
- def retrieve_response_from_mimes(mimes=nil, &block) #:nodoc:
+ # Sends :not_acceptable to the client and returns nil if no suitable format
+ # is available.
+ #
+ def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc:
mimes ||= collect_mimes_from_class_level
- collector = Collector.new(mimes) { |options| default_render(options || {}) }
+ collector = Collector.new(mimes)
block.call(collector) if block_given?
+ format = collector.negotiate_format(request)
- if format = request.negotiate_mime(collector.order)
+ if format
self.content_type ||= format.to_s
- lookup_context.freeze_formats([format.to_sym])
- collector.response_for(format)
+ lookup_context.formats = [format.to_sym]
+ lookup_context.rendered_format = lookup_context.formats.first
+ collector
else
head :not_acceptable
nil
end
end
- class Collector #:nodoc:
+ # A container for responses available from the current controller for
+ # requests for different mime-types sent to a particular action.
+ #
+ # The public controller methods +respond_with+ and +respond_to+ may be called
+ # with a block that is used to define responses to different mime-types, e.g.
+ # for +respond_to+ :
+ #
+ # respond_to do |format|
+ # format.html
+ # format.xml { render :xml => @people }
+ # end
+ #
+ # In this usage, the argument passed to the block (+format+ above) is an
+ # instance of the ActionController::MimeResponds::Collector class. This
+ # object serves as a container in which available responses can be stored by
+ # calling any of the dynamically generated, mime-type-specific methods such
+ # as +html+, +xml+ etc on the Collector. Each response is represented by a
+ # corresponding block if present.
+ #
+ # A subsequent call to #negotiate_format(request) will enable the Collector
+ # to determine which specific mime-type it should respond with for the current
+ # request, with this response then being accessible by calling #response.
+ #
+ class Collector
include AbstractController::Collector
- attr_accessor :order
+ attr_accessor :order, :format
- def initialize(mimes, &block)
- @order, @responses, @default_response = [], {}, block
+ def initialize(mimes)
+ @order, @responses = [], {}
mimes.each { |mime| send(mime) }
end
@@ -302,8 +427,12 @@ module ActionController #:nodoc:
@responses[mime_type] ||= block
end
- def response_for(mime)
- @responses[mime] || @responses[Mime::ALL] || @default_response
+ def response
+ @responses[format] || @responses[Mime::ALL]
+ end
+
+ def negotiate_format(request)
+ @format = request.negotiate_mime(order)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index fa760f2658..7e2316d01c 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -48,7 +48,7 @@ module ActionController
# method attribute_names.
#
# If you're going to pass the parameters to an +ActiveModel+ object (such as
- # +User.new(params[:user])+), you might consider passing the model class to
+ # <tt>User.new(params[:user])</tt>), you might consider passing the model class to
# the method instead. The +ParamsWrapper+ will actually try to determine the
# list of attribute names from the model and only wrap those attributes:
#
@@ -66,7 +66,7 @@ module ActionController
# class Admin::UsersController < ApplicationController
# end
#
- # will try to check if +Admin::User+ or +User+ model exists, and use it to
+ # will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to
# determine the wrapper key respectively. If both models don't exist,
# it will then fallback to use +user+ as the key.
module ParamsWrapper
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index b07742e0e1..5e7bd44562 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -45,6 +45,16 @@ module ActionController
# integer, or a symbol representing the downcased, underscored and symbolized description.
# Note that the status code must be a 3xx HTTP code, or redirection will not occur.
#
+ # If you are using XHR requests other than GET or POST and redirecting after the
+ # request then some browsers will follow the redirect using the original request
+ # method. This may lead to undesirable behavior such as a double DELETE. To work
+ # around this you can return a <tt>303 See Other</tt> status code which will be
+ # followed using a GET request.
+ #
+ # Examples:
+ # redirect_to posts_url, :status => :see_other
+ # redirect_to :action => 'index', :status => 303
+ #
# It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
#
@@ -93,7 +103,7 @@ module ActionController
_compute_redirect_to_location options.call
else
url_for(options)
- end.gsub(/[\r\n]/, '')
+ end.delete("\0\r\n")
end
end
end
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 6e9ce450ac..4a0c1c7dd7 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -91,9 +91,14 @@ module ActionController
add :json do |json, options|
json = json.to_json(options) unless json.kind_of?(String)
- json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
- self.content_type ||= Mime::JSON
- json
+
+ if options[:callback].present?
+ self.content_type ||= Mime::JS
+ "#{options[:callback]}(#{json})"
+ else
+ self.content_type ||= Mime::JSON
+ json
+ end
end
add :js do |js, options|
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 70fd79bb8b..c5e7d4e357 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -14,7 +14,7 @@ module ActionController
def render(*args) #:nodoc:
raise ::AbstractController::DoubleRenderError if response_body
super
- self.content_type ||= Mime[formats.first].to_s
+ self.content_type ||= Mime[lookup_context.rendered_format].to_s
response_body
end
@@ -29,6 +29,10 @@ module ActionController
self.response_body = nil
end
+ def render_to_body(*)
+ super || " "
+ end
+
private
# Normalize arguments by catching blocks and setting them on :update.
@@ -44,6 +48,10 @@ module ActionController
options[:text] = options[:text].to_text
end
+ if options.delete(:nothing) || (options.key?(:text) && options[:text].nil?)
+ options[:text] = " "
+ end
+
if options[:status]
options[:status] = Rack::Utils.status_code(options[:status])
end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index afa9243f02..0bff1825d9 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -17,7 +17,6 @@ module ActionController #:nodoc:
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
# which checks the token and resets the session if it doesn't match what was expected.
# A call to this method is generated for new \Rails applications by default.
- # You can customize the error message by editing public/422.html.
#
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
# value of this token must be added to every layout that renders forms by including
@@ -37,6 +36,10 @@ module ActionController #:nodoc:
config_accessor :request_forgery_protection_token
self.request_forgery_protection_token ||= :authenticity_token
+ # Controls how unverified request will be handled
+ config_accessor :request_forgery_protection_method
+ self.request_forgery_protection_method ||= :reset_session
+
# Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
config_accessor :allow_forgery_protection
self.allow_forgery_protection = true if allow_forgery_protection.nil?
@@ -64,8 +67,10 @@ module ActionController #:nodoc:
# Valid Options:
#
# * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
+ # * <tt>:with</tt> - Set the method to handle unverified request. Valid values: <tt>:exception</tt> and <tt>:reset_session</tt> (default).
def protect_from_forgery(options = {})
self.request_forgery_protection_token ||= :authenticity_token
+ self.request_forgery_protection_method = options.delete(:with) if options.key?(:with)
prepend_before_filter :verify_authenticity_token, options
end
end
@@ -80,9 +85,19 @@ module ActionController #:nodoc:
end
# This is the method that defines the application behavior when a request is found to be unverified.
- # By default, \Rails resets the session when it finds an unverified request.
+ # By default, \Rails uses <tt>request_forgery_protection_method</tt> when it finds an unverified request:
+ #
+ # * <tt>:reset_session</tt> - Resets the session.
+ # * <tt>:exception</tt>: - Raises ActionController::InvalidAuthenticityToken exception.
def handle_unverified_request
- reset_session
+ case request_forgery_protection_method
+ when :exception
+ raise ActionController::InvalidAuthenticityToken
+ when :reset_session
+ reset_session
+ else
+ raise ArgumentError, 'Invalid request forgery protection method, use :exception or :reset_session'
+ end
end
# Returns true or false if a request is verified. Checks:
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index 9500a349cb..83407846dc 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -53,7 +53,7 @@ module ActionController #:nodoc:
# end
# end
#
- # The same happens for PUT and DELETE requests.
+ # The same happens for PATCH/PUT and DELETE requests.
#
# === Nested resources
#
@@ -63,7 +63,7 @@ module ActionController #:nodoc:
#
# def create
# @project = Project.find(params[:project_id])
- # @task = @project.comments.build(params[:task])
+ # @task = @project.tasks.build(params[:task])
# flash[:notice] = 'Task was successfully created.' if @task.save
# respond_with(@project, @task)
# end
@@ -116,8 +116,9 @@ module ActionController #:nodoc:
class Responder
attr_reader :controller, :request, :format, :resource, :resources, :options
- ACTIONS_FOR_VERBS = {
+ DEFAULT_ACTIONS_FOR_VERBS = {
:post => :new,
+ :patch => :edit,
:put => :edit
}
@@ -133,7 +134,7 @@ module ActionController #:nodoc:
end
delegate :head, :render, :redirect_to, :to => :controller
- delegate :get?, :post?, :put?, :delete?, :to => :request
+ delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request
# Undefine :to_json and :to_yaml since it's defined on Object
undef_method(:to_json) if method_defined?(:to_json)
@@ -172,7 +173,7 @@ module ActionController #:nodoc:
# responds to :to_format and display it.
#
def to_format
- if get? || !has_errors?
+ if get? || !has_errors? || response_overridden?
default_render
else
display_errors
@@ -226,7 +227,11 @@ module ActionController #:nodoc:
# controller.
#
def default_render
- @default_response.call(options)
+ if @default_response
+ @default_response.call(options)
+ else
+ controller.default_render(options)
+ end
end
# Display is just a shortcut to render a resource with the current format.
@@ -260,19 +265,23 @@ module ActionController #:nodoc:
resource.respond_to?(:errors) && !resource.errors.empty?
end
- # By default, render the <code>:edit</code> action for HTML requests with failure, unless
- # the verb is POST.
+ # By default, render the <code>:edit</code> action for HTML requests with errors, unless
+ # the verb was POST.
#
def default_action
- @action ||= ACTIONS_FOR_VERBS[request.request_method_symbol]
+ @action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol]
end
def resource_errors
- respond_to?("#{format}_resource_errors") ? send("#{format}_resource_errors") : resource.errors
+ respond_to?("#{format}_resource_errors", true) ? send("#{format}_resource_errors") : resource.errors
end
def json_resource_errors
{:errors => resource.errors}
end
+
+ def response_overridden?
+ @default_response.present?
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb
deleted file mode 100644
index 91d89ff9a4..0000000000
--- a/actionpack/lib/action_controller/metal/session_management.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module ActionController #:nodoc:
- module SessionManagement #:nodoc:
- extend ActiveSupport::Concern
-
- module ClassMethods
-
- end
- end
-end
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 0e46402962..eeb37db2e7 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -139,17 +139,17 @@ module ActionController #:nodoc:
# session or flash after the template starts rendering will not propagate
# to the client.
#
- # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+
+ # If you try to modify cookies, session or flash, an <tt>ActionDispatch::ClosedError</tt>
# will be raised, showing those objects are closed for modification.
#
# == Middlewares
#
# Middlewares that need to manipulate the body won't work with streaming.
# You should disable those middlewares whenever streaming in development
- # or production. For instance, +Rack::Bug+ won't work when streaming as it
+ # or production. For instance, <tt>Rack::Bug</tt> won't work when streaming as it
# needs to inject contents in the HTML body.
#
- # Also +Rack::Cache+ won't work with streaming as it does not support
+ # Also <tt>Rack::Cache</tt> won't work with streaming as it does not support
# streaming bodies yet. Whenever streaming Cache-Control is automatically
# set to "no-cache".
#
@@ -162,7 +162,7 @@ module ActionController #:nodoc:
# Currently, when an exception happens in development or production, Rails
# will automatically stream to the client:
#
- # "><script type="text/javascript">window.location = "/500.html"</script></html>
+ # "><script>window.location = "/500.html"</script></html>
#
# The first two characters (">) are required in case the exception happens
# while rendering attributes for a given tag. You can check the real cause
@@ -216,7 +216,7 @@ module ActionController #:nodoc:
end
end
- # Call render_to_body if we are streaming instead of usual +render+.
+ # Call render_body if we are streaming instead of usual +render+.
def _render_template(options) #:nodoc:
if options.delete(:stream)
Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 0b40b1fc4c..8e7b56dbcc 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -1,25 +1,25 @@
-# Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
-# the <tt>_routes</tt> method. Otherwise, an exception will be raised.
-#
-# In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
-# url options like the +host+. In order to do so, this module requires the host class
-# to implement +env+ and +request+, which need to be a Rack-compatible.
-#
-# Example:
-#
-# class RootUrl
-# include ActionController::UrlFor
-# include Rails.application.routes.url_helpers
-#
-# delegate :env, :request, :to => :controller
-#
-# def initialize(controller)
-# @controller = controller
-# @url = root_path # named route from the application.
-# end
-# end
-#
module ActionController
+ # Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
+ # the <tt>_routes</tt> method. Otherwise, an exception will be raised.
+ #
+ # In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
+ # url options like the +host+. In order to do so, this module requires the host class
+ # to implement +env+ and +request+, which need to be a Rack-compatible.
+ #
+ # Example:
+ #
+ # class RootUrl
+ # include ActionController::UrlFor
+ # include Rails.application.routes.url_helpers
+ #
+ # delegate :env, :request, :to => :controller
+ #
+ # def initialize(controller)
+ # @controller = controller
+ # @url = root_path # named route from the application.
+ # end
+ # end
+ #
module UrlFor
extend ActiveSupport::Concern
@@ -42,6 +42,5 @@ module ActionController
@_url_options
end
end
-
end
end
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index fb810c41b1..851a2c4aee 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -3,33 +3,32 @@ require "action_controller"
require "action_dispatch/railtie"
require "action_view/railtie"
require "abstract_controller/railties/routes_helpers"
-require "action_controller/railties/paths"
+require "action_controller/railties/helpers"
module ActionController
- class Railtie < Rails::Railtie
+ class Railtie < Rails::Railtie #:nodoc:
config.action_controller = ActiveSupport::OrderedOptions.new
- initializer "action_controller.logger" do
- ActiveSupport.on_load(:action_controller) { self.logger ||= Rails.logger }
- end
-
- initializer "action_controller.initialize_framework_caches" do
- ActiveSupport.on_load(:action_controller) { self.cache_store ||= RAILS_CACHE }
- end
-
initializer "action_controller.assets_config", :group => :all do |app|
app.config.action_controller.assets_dir ||= app.config.paths["public"].first
end
+ initializer "action_controller.set_helpers_path" do |app|
+ ActionController::Helpers.helpers_path = app.helpers_paths
+ end
+
initializer "action_controller.set_configs" do |app|
paths = app.config.paths
options = app.config.action_controller
+ options.logger ||= Rails.logger
+ options.cache_store ||= Rails.cache
+
options.javascripts_dir ||= paths["public/javascripts"].first
options.stylesheets_dir ||= paths["public/stylesheets"].first
options.page_cache_directory ||= paths["public"].first
- # make sure readers methods get compiled
+ # Ensure readers methods get compiled
options.asset_path ||= app.config.asset_path
options.asset_host ||= app.config.asset_host
options.relative_url_root ||= app.config.relative_url_root
@@ -37,8 +36,16 @@ module ActionController
ActiveSupport.on_load(:action_controller) do
include app.routes.mounted_helpers
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
- extend ::ActionController::Railties::Paths.with(app)
- options.each { |k,v| send("#{k}=", v) }
+ extend ::ActionController::Railties::Helpers
+
+ options.each do |k,v|
+ k = "#{k}="
+ if respond_to?(k)
+ send(k, v)
+ elsif !Base.respond_to?(k)
+ raise "Invalid option key: #{k}"
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/railties/helpers.rb b/actionpack/lib/action_controller/railties/helpers.rb
new file mode 100644
index 0000000000..3985c6b273
--- /dev/null
+++ b/actionpack/lib/action_controller/railties/helpers.rb
@@ -0,0 +1,22 @@
+module ActionController
+ module Railties
+ module Helpers
+ def inherited(klass)
+ super
+ return unless klass.respond_to?(:helpers_path=)
+
+ if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_helpers_paths) }
+ paths = namespace.railtie_helpers_paths
+ else
+ paths = ActionController::Helpers.helpers_path
+ end
+
+ klass.helpers_path = paths
+
+ if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
+ klass.helper :all
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb
deleted file mode 100644
index bbe63149ad..0000000000
--- a/actionpack/lib/action_controller/railties/paths.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module ActionController
- module Railties
- module Paths
- def self.with(app)
- Module.new do
- define_method(:inherited) do |klass|
- super(klass)
-
- if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_helpers_paths) }
- paths = namespace.railtie_helpers_paths
- else
- paths = app.helpers_paths
- end
-
- klass.helpers_path = paths
-
- if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
- klass.helper :all
- end
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 9c38ff44d8..e7af3f5b8d 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -2,8 +2,8 @@ require 'active_support/core_ext/module'
module ActionController
# The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
- # Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate
- # the view actions to a higher logical level. Example:
+ # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to
+ # a higher logical level. Example:
#
# # routes
# resources :posts
@@ -53,6 +53,7 @@ module ActionController
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
#
# dom_id(Post.find(45), :edit) # => "edit_post_45"
+ # dom_id(Post.new, :custom) # => "custom_post"
def dom_id(record, prefix = nil)
if record_id = record_key_for_dom_id(record)
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
@@ -74,12 +75,7 @@ module ActionController
def record_key_for_dom_id(record)
record = record.to_model if record.respond_to?(:to_model)
key = record.to_key
- key ? sanitize_dom_id(key.join('_')) : key
- end
-
- # Replaces characters that are invalid in HTML DOM ids with valid ones.
- def sanitize_dom_id(candidate_id)
- candidate_id # TODO implement conversion to valid DOM id values
+ key ? key.join('_') : key
end
end
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index fce6e29d5f..21997c4d79 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -27,13 +27,13 @@ module ActionController
path = payload[:virtual_path]
next unless path
partial = path =~ /^.*\/_[^\/]*$/
+
if partial
@partials[path] += 1
@partials[path.split("/").last] += 1
- @templates[path] += 1
- else
- @templates[path] += 1
end
+
+ @templates[path] += 1
end
end
@@ -56,6 +56,9 @@ module ActionController
# # assert that the "new" view template was rendered
# assert_template "new"
#
+ # # assert that the exact template "admin/posts/new" was rendered
+ # assert_template %r{\Aadmin/posts/new\Z}
+ #
# # assert that the "_customer" partial was rendered twice
# assert_template :partial => '_customer', :count => 2
#
@@ -69,12 +72,16 @@ module ActionController
# assert_template :partial => '_customer', :locals => { :customer => @customer }
#
def assert_template(options = {}, message = nil)
+ # Force body to be read in case the
+ # template is being streamed
+ response.body
+
case options
- when NilClass, String, Symbol
+ when NilClass, String, Symbol, Regexp
options = options.to_s if Symbol === options
rendered = @templates
msg = message || sprintf("expecting <%s> but rendering with <%s>",
- options, rendered.keys)
+ options.inspect, rendered.keys)
assert_block(msg) do
if options
rendered.any? { |t,num| t.match(options) }
@@ -117,6 +124,8 @@ module ActionController
assert @partials.empty?,
"Expected no partials to be rendered"
end
+ else
+ raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil"
end
end
end
@@ -131,9 +140,6 @@ module ActionController
class Result < ::Array #:nodoc:
def to_s() join '/' end
- def self.new_escaped(strings)
- new strings.collect {|str| uri_parser.unescape str}
- end
end
def assign_parameters(routes, controller_path, action, parameters = {})
@@ -141,17 +147,23 @@ module ActionController
extra_keys = routes.extra_keys(parameters)
non_path_parameters = get? ? query_parameters : request_parameters
parameters.each do |key, value|
- if value.is_a? Fixnum
- value = value.to_s
- elsif value.is_a? Array
- value = Result.new(value.map { |v| v.is_a?(String) ? v.dup : v })
- elsif value.is_a? String
+ if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?))
+ value = value.map{ |v| v.duplicable? ? v.dup : v }
+ elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? })
+ value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }]
+ elsif value.frozen? && value.duplicable?
value = value.dup
end
if extra_keys.include?(key.to_sym)
non_path_parameters[key] = value
else
+ if value.is_a?(Array)
+ value = Result.new(value.map(&:to_param))
+ else
+ value = value.to_param
+ end
+
path_parameters[key.to_s] = value
end
end
@@ -225,7 +237,7 @@ module ActionController
# == Basic example
#
# Functional tests are written as follows:
- # 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
+ # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
# an HTTP request.
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
# the controller's HTTP response, the database contents, etc.
@@ -351,7 +363,7 @@ module ActionController
def tests(controller_class)
case controller_class
when String, Symbol
- self.controller_class = "#{controller_class.to_s.underscore}_controller".camelize.constantize
+ self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize
when Class
self.controller_class = controller_class
else
@@ -392,6 +404,11 @@ module ActionController
process(action, "POST", *args)
end
+ # Executes a request simulating PATCH HTTP method and set/volley the response
+ def patch(action, *args)
+ process(action, "PATCH", *args)
+ end
+
# Executes a request simulating PUT HTTP method and set/volley the response
def put(action, *args)
process(action, "PUT", *args)
@@ -442,7 +459,7 @@ module ActionController
# Ensure that numbers and symbols passed as params are converted to
# proper params, as is the case when engaging rack.
- parameters = paramify_values(parameters)
+ parameters = paramify_values(parameters) if html_format?(parameters)
@request.recycle!
@response.recycle!
@@ -455,17 +472,15 @@ module ActionController
parameters ||= {}
controller_class_name = @controller.class.anonymous? ?
- "anonymous_controller" :
+ "anonymous" :
@controller.class.name.underscore.sub(/_controller$/, '')
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
- @request.session = ActionController::TestSession.new(session) if session
+ @request.session.update(session) if session
@request.session["flash"] = @request.flash.update(flash || {})
- @request.session["flash"].sweep
@controller.request = @request
- @controller.params.merge!(parameters)
build_request_uri(action, parameters)
@controller.class.class_eval { include Testing }
@controller.recycle!
@@ -491,11 +506,6 @@ module ActionController
end
end
- # Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
- def rescue_action_in_public!
- @request.remote_addr = '208.77.188.166' # example.com
- end
-
included do
include ActionController::TemplateAssertions
include ActionDispatch::Assertions
@@ -541,6 +551,12 @@ module ActionController
@request.env["QUERY_STRING"] = query_string || ""
end
end
+
+ def html_format?(parameters)
+ return true unless parameters.is_a?(Hash)
+ format = Mime[parameters[:format]]
+ format.nil? || format.html?
+ end
end
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
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 24ffc28710..114b0e73c9 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
@@ -5,6 +5,7 @@ require 'active_support/core_ext/class/attribute'
module HTML
class Sanitizer
def sanitize(text, options = {})
+ validate_options(options)
return text unless sanitizeable?(text)
tokenize(text, options).join
end
@@ -27,6 +28,16 @@ module HTML
def process_node(node, result, options)
result << node.to_s
end
+
+ def validate_options(options)
+ if options[:tags] && !options[:tags].is_a?(Enumerable)
+ raise ArgumentError, "You should pass :tags as an Enumerable"
+ end
+
+ if options[:attributes] && !options[:attributes].is_a?(Enumerable)
+ raise ArgumentError, "You should pass :attributes as an Enumerable"
+ end
+ end
end
class FullSanitizer < Sanitizer
@@ -88,7 +99,7 @@ module HTML
self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto
feed svn urn aim rsync tag ssh sftp rtsp afs))
- # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept.
+ # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept.
self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse
border-color border-left-color border-right-color border-top-color clear color cursor direction display
elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index a9542a7d1b..e3b04ac097 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -61,6 +61,7 @@ module ActionDispatch
autoload :Reloader
autoload :RemoteIp
autoload :ShowExceptions
+ autoload :SSL
autoload :Static
end
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index bea62b94d2..5ee4c044ea 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -60,6 +60,20 @@ module ActionDispatch
headers[LAST_MODIFIED] = utc_time.httpdate
end
+ def date
+ if date_header = headers['Date']
+ Time.httpdate(date_header)
+ end
+ end
+
+ def date?
+ headers.include?('Date')
+ end
+
+ def date=(utc_time)
+ headers['Date'] = utc_time.httpdate
+ end
+
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
@etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}")
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 02a15ad599..132b0c82bc 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -50,7 +50,7 @@ module ActionDispatch
end
def env_filter
- parameter_filter_for(Array(@env["action_dispatch.parameter_filter"]) << /RAW_POST_DATA/)
+ parameter_filter_for(Array(@env["action_dispatch.parameter_filter"]) + [/RAW_POST_DATA/, "rack.request.form_vars"])
end
def parameter_filter_for(filters)
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 040b51e040..a3bb25f75a 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -14,17 +14,18 @@ module ActionDispatch
end
def [](header_name)
- if include?(header_name)
- super
- else
- super(env_name(header_name))
- end
+ super env_name(header_name)
+ end
+
+ def fetch(header_name, default=nil, &block)
+ super env_name(header_name), default, &block
end
private
- # Converts a HTTP header name to an environment variable name.
+ # Converts a HTTP header name to an environment variable name if it is
+ # not contained within the headers hash.
def env_name(header_name)
- @@env_cache[header_name]
+ include?(header_name) ? header_name : @@env_cache[header_name]
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 5c48a60469..e31f3b823d 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/attribute_accessors'
+
module ActionDispatch
module Http
module MimeNegotiation
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 25affb9f50..0eaae80461 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -38,7 +38,7 @@ module Mime
# respond_to do |format|
# format.html
# format.ics { render :text => post.to_ics, :mime_type => Mime::Type["text/calendar"] }
- # format.xml { render :xml => @people.to_xml }
+ # format.xml { render :xml => @people }
# end
# end
# end
@@ -82,6 +82,7 @@ module Mime
class << self
TRAILING_STAR_REGEXP = /(text|application)\/\*/
+ PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
def lookup(string)
LOOKUP[string]
@@ -108,6 +109,7 @@ module Mime
def parse(accept_header)
if accept_header !~ /,/
+ accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
if accept_header =~ TRAILING_STAR_REGEXP
parse_data_with_trailing_star($1)
else
@@ -117,7 +119,7 @@ module Mime
# keep track of creation order to keep the subsequent sort stable
list, index = [], 0
accept_header.split(/,/).each do |header|
- params, q = header.split(/;\s*q=/)
+ params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
if params.present?
params.strip!
@@ -177,11 +179,11 @@ module Mime
end
end
- # input: 'text'
- # returned value: [Mime::JSON, Mime::XML, Mime::ICS, Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]
+ # For an input of <tt>'text'</tt>, returns <tt>[Mime::JSON, Mime::XML, Mime::ICS,
+ # Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]</tt>.
#
- # input: 'application'
- # returned value: [Mime::HTML, Mime::JS, Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]
+ # For an input of <tt>'application'</tt>, returns <tt>[Mime::HTML, Mime::JS,
+ # Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]</tt>.
def parse_data_with_trailing_star(input)
Mime::SET.select { |m| m =~ input }
end
@@ -190,7 +192,7 @@ module Mime
#
# Usage:
#
- # Mime::Type.unregister(:mobile)
+ # Mime::Type.unregister(:mobile)
def unregister(symbol)
symbol = symbol.to_s.upcase
mime = Mime.const_get(symbol)
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index 3da4f91051..a6b3aee5e7 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -9,7 +9,7 @@ Mime::Type.register "text/calendar", :ics
Mime::Type.register "text/csv", :csv
Mime::Type.register "image/png", :png, [], %w(png)
-Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe)
+Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
Mime::Type.register "image/gif", :gif, [], %w(gif)
Mime::Type.register "image/bmp", :bmp, [], %w(bmp)
Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff)
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index d9b63faf5e..bcfd0b0d00 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -35,6 +35,10 @@ module ActionDispatch
@env["action_dispatch.request.path_parameters"] ||= {}
end
+ def reset_parameters #:nodoc:
+ @env.delete("action_dispatch.request.parameters")
+ end
+
private
# TODO: Validate that the characters are UTF-8. If they aren't,
diff --git a/actionpack/lib/action_dispatch/http/rack_cache.rb b/actionpack/lib/action_dispatch/http/rack_cache.rb
index cc8edee300..003ae4029d 100644
--- a/actionpack/lib/action_dispatch/http/rack_cache.rb
+++ b/actionpack/lib/action_dispatch/http/rack_cache.rb
@@ -8,8 +8,7 @@ module ActionDispatch
new
end
- # TODO: Finally deal with the RAILS_CACHE global
- def initialize(store = RAILS_CACHE)
+ def initialize(store = Rails.cache)
@store = store
end
@@ -33,7 +32,7 @@ module ActionDispatch
new
end
- def initialize(store = RAILS_CACHE)
+ def initialize(store = Rails.cache)
@store = store
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 0a0ebe7fad..796e0dbc45 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -17,7 +17,8 @@ module ActionDispatch
include ActionDispatch::Http::Upload
include ActionDispatch::Http::URL
- LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze
+ LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
+
ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
REMOTE_IDENT REMOTE_USER REMOTE_ADDR
@@ -97,6 +98,12 @@ module ActionDispatch
HTTP_METHOD_LOOKUP[request_method] == :post
end
+ # Is this a PATCH request?
+ # Equivalent to <tt>request.request_method == :patch</tt>.
+ def patch?
+ HTTP_METHOD_LOOKUP[request_method] == :patch
+ end
+
# Is this a PUT request?
# Equivalent to <tt>request.request_method_symbol == :put</tt>.
def put?
@@ -244,7 +251,7 @@ module ActionDispatch
# True if the request came from localhost, 127.0.0.1.
def local?
- LOCALHOST.any? { |local_ip| local_ip === remote_addr && local_ip === remote_ip }
+ LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end
private
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 84732085f0..cc46f9983c 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -29,7 +29,7 @@ module ActionDispatch # :nodoc:
# class DemoControllerTest < ActionDispatch::IntegrationTest
# def test_print_root_path_to_console
# get('/')
- # puts @response.body
+ # puts response.body
# end
# end
class Response
@@ -51,12 +51,13 @@ module ActionDispatch # :nodoc:
# If a character set has been defined for this response (see charset=) then
# the character set information will also be included in the content type
# information.
- attr_accessor :charset, :content_type
+ attr_accessor :charset
+ attr_reader :content_type
CONTENT_TYPE = "Content-Type".freeze
SET_COOKIE = "Set-Cookie".freeze
LOCATION = "Location".freeze
-
+
cattr_accessor(:default_charset) { "utf-8" }
include Rack::Response::Helpers
@@ -83,6 +84,10 @@ module ActionDispatch # :nodoc:
@status = Rack::Utils.status_code(status)
end
+ def content_type=(content_type)
+ @content_type = content_type.to_s
+ end
+
# The response code of the request
def response_code
@status
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 5ab99d1061..ce8c2729e9 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -4,11 +4,12 @@ module ActionDispatch
attr_accessor :original_filename, :content_type, :tempfile, :headers
def initialize(hash)
+ @tempfile = hash[:tempfile]
+ raise(ArgumentError, ':tempfile is required') unless @tempfile
+
@original_filename = encode_filename(hash[:filename])
@content_type = hash[:type]
@headers = hash[:head]
- @tempfile = hash[:tempfile]
- raise(ArgumentError, ':tempfile is required') unless @tempfile
end
def read(*args)
@@ -16,18 +17,15 @@ module ActionDispatch
end
# Delegate these methods to the tempfile.
- [:open, :path, :rewind, :size].each do |method|
+ [:open, :path, :rewind, :size, :eof?].each do |method|
class_eval "def #{method}; @tempfile.#{method}; end"
end
-
+
private
+
def encode_filename(filename)
# Encode the filename in the utf8 encoding, unless it is nil
- if filename
- filename.force_encoding("UTF-8").encode!
- else
- filename
- end
+ filename.force_encoding("UTF-8").encode! if filename
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 80ffbe575b..4266ec042e 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -23,36 +23,43 @@ module ActionDispatch
end
def url_for(options = {})
+ path = ""
+ path << options.delete(:script_name).to_s.chomp("/")
+ path << options.delete(:path).to_s
+
+ params = options[:params] || {}
+ params.reject! {|k,v| v.to_param.nil? }
+
+ result = build_host_url(options)
+
+ result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
+ result << "?#{params.to_query}" unless params.empty?
+ result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
+ result
+ end
+
+ private
+
+ def build_host_url(options)
if options[:host].blank? && options[:only_path].blank?
raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
end
- rewritten_url = ""
+ result = ""
unless options[:only_path]
unless options[:protocol] == false
- rewritten_url << (options[:protocol] || "http")
- rewritten_url << ":" unless rewritten_url.match(%r{:|//})
+ result << (options[:protocol] || "http")
+ result << ":" unless result.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]
+ result << "//" unless result.match("//")
+ result << rewrite_authentication(options)
+ result << host_or_subdomain_and_domain(options)
+ result << ":#{options.delete(:port)}" if options[:port]
end
-
- path = options.delete(:path) || ''
-
- params = options[:params] || {}
- params.reject! {|k,v| v.to_param.nil? }
-
- rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
- rewritten_url << "?#{params.to_query}" unless params.empty?
- rewritten_url << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
- rewritten_url
+ result
end
- private
-
def named_host?(host)
host && IP_HOST_REGEXP !~ host
end
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index 8c0f4052ec..338b116940 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -5,7 +5,7 @@ module ActionDispatch
class Callbacks
include ActiveSupport::Callbacks
- define_callbacks :call, :rescuable => true
+ define_callbacks :call
class << self
delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader"
@@ -24,9 +24,15 @@ module ActionDispatch
end
def call(env)
- run_callbacks :call do
- @app.call(env)
+ error = nil
+ result = run_callbacks :call do
+ begin
+ @app.call(env)
+ rescue => error
+ end
end
+ raise error if error
+ result
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 39ff58a447..2c0c31de9d 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -82,7 +82,7 @@ module ActionDispatch
TOKEN_KEY = "action_dispatch.secret_token".freeze
# Raised when storing more than 4K of session data.
- class CookieOverflow < StandardError; end
+ CookieOverflow = Class.new StandardError
class CookieJar #:nodoc:
include Enumerable
@@ -117,7 +117,6 @@ module ActionDispatch
@delete_cookies = {}
@host = host
@secure = secure
- @closed = false
@cookies = {}
end
@@ -154,7 +153,7 @@ module ActionDispatch
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] }
+ options[:domain] = options[:domain].find {|domain| @host.include? domain.sub(/^\./, '') }
end
end
@@ -169,12 +168,14 @@ module ActionDispatch
options = { :value => value }
end
- @cookies[key.to_s] = value
-
handle_options(options)
- @set_cookies[key.to_s] = options
- @delete_cookies.delete(key.to_s)
+ if @cookies[key.to_s] != value or options[:expires]
+ @cookies[key.to_s] = value
+ @set_cookies[key.to_s] = options
+ @delete_cookies.delete(key.to_s)
+ end
+
value
end
@@ -182,8 +183,9 @@ module ActionDispatch
# and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
def delete(key, options = {})
- options.symbolize_keys!
+ return unless @cookies.has_key? key.to_s
+ options.symbolize_keys!
handle_options(options)
value = @cookies.delete(key.to_s)
@@ -191,6 +193,15 @@ module ActionDispatch
value
end
+ # Whether the given cookie is to be deleted by this CookieJar.
+ # Like <tt>[]=</tt>, you can pass in an options hash to test if a
+ # deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc.
+ def deleted?(key, options = {})
+ options.symbolize_keys!
+ handle_options(options)
+ @delete_cookies[key.to_s] == options
+ end
+
# Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
def clear(options = {})
@cookies.each_key{ |k| delete(k, options) }
@@ -216,7 +227,7 @@ module ActionDispatch
# cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
# be raised.
#
- # This jar requires that you set a suitable secret for the verification on your app's config.secret_token.
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_token+.
#
# Example:
#
@@ -264,10 +275,6 @@ module ActionDispatch
@parent_jar[key] = options
end
- def signed
- @signed ||= SignedCookieJar.new(self, @secret)
- end
-
def method_missing(method, *arguments, &block)
@parent_jar.send(method, *arguments, &block)
end
@@ -334,7 +341,6 @@ module ActionDispatch
end
def call(env)
- cookie_jar = nil
status, headers, body = @app.call(env)
if cookie_jar = env['action_dispatch.cookies']
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index cff0877030..fd0ce62a3b 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -4,7 +4,7 @@ module ActionDispatch
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
# to put a new one.
def flash
- @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new)
+ @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new).tap(&:sweep)
end
end
@@ -17,7 +17,7 @@ module ActionDispatch
# def create
# # save post
# flash[:notice] = "Post successfully created"
- # redirect_to posts_path(@post)
+ # redirect_to @post
# end
#
# def show
@@ -79,7 +79,6 @@ module ActionDispatch
def initialize #:nodoc:
@discard = Set.new
- @closed = false
@flashes = {}
@now = nil
end
@@ -217,10 +216,6 @@ module ActionDispatch
end
def call(env)
- if (session = env['rack.session']) && (flash = session['flash'])
- flash.sweep
- end
-
@app.call(env)
ensure
session = env['rack.session'] || {}
@@ -237,7 +232,8 @@ module ActionDispatch
env[KEY] = new_hash
end
- if session.key?('flash') && session['flash'].empty?
+ if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
+ session.key?('flash') && session['flash'].empty?
session.delete('flash')
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index 4f48f1c974..2f6968eb2e 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -18,10 +18,10 @@ module ActionDispatch
# 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
+ # 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.
+ # middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
+ # or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
#
class Reloader
include ActiveSupport::Callbacks
@@ -60,8 +60,10 @@ module ActionDispatch
def call(env)
@validated = @condition.call
prepare!
+
response = @app.call(env)
- response[2].extend(module_hook)
+ response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! }
+
response
rescue Exception
cleanup!
@@ -83,18 +85,5 @@ module ActionDispatch
def validated? #:nodoc:
@validated
end
-
- def module_hook #:nodoc:
- middleware = self
- Module.new do
- define_method :close do
- begin
- super() if defined?(super)
- ensure
- middleware.cleanup!
- end
- end
- end
- end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 030ccb2017..ec15a2a715 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -5,11 +5,14 @@ module ActionDispatch
# IP addresses that are "trusted proxies" that can be stripped from
# the comma-delimited list in the X-Forwarded-For header. See also:
# http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
+ # http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses.
TRUSTED_PROXIES = %r{
^127\.0\.0\.1$ | # localhost
+ ^::1$ |
^(10 | # private IP 10.x.x.x
172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
- 192\.168 # private IP 192.168.x.x
+ 192\.168 | # private IP 192.168.x.x
+ fc00:: # private IP fc00
)\.
}x
@@ -18,12 +21,14 @@ module ActionDispatch
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
@app = app
@check_ip = check_ip_spoofing
- if custom_proxies
- custom_regexp = Regexp.new(custom_proxies)
- @proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp)
- else
- @proxies = TRUSTED_PROXIES
- end
+ @proxies = case custom_proxies
+ when Regexp
+ custom_proxies
+ when nil
+ TRUSTED_PROXIES
+ else
+ Regexp.union(TRUSTED_PROXIES, custom_proxies)
+ end
end
def call(env)
@@ -32,6 +37,31 @@ module ActionDispatch
end
class GetIp
+
+ # IP v4 and v6 (with compression) validation regexp
+ # https://gist.github.com/1289635
+ VALID_IP = %r{
+ (^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4
+ (^(
+ (([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated
+ (([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end
+ (([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6
+ (([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with
+ (([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon
+ (([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle
+ (([0-9A-Fa-f]{1,4}:){6} ((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3} (\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){1,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){1}:([0-9A-Fa-f]{1,4}:){0,4}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){0,2}:([0-9A-Fa-f]{1,4}:){0,3}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){0,3}:([0-9A-Fa-f]{1,4}:){0,2}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ ([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
+ (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining
+ (([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
+ )$)
+ }x
+
def initialize(env, middleware)
@env = env
@middleware = middleware
@@ -42,25 +72,31 @@ module ActionDispatch
# but will be wrong if the user is behind a proxy. Proxies will set
# HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those.
# HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of
- # multiple chained proxies. The last address which is not a known proxy
- # will be the originating IP.
+ # multiple chained proxies. The first address which is in this list
+ # if it's not a known proxy will be the originating IP.
+ # Format of HTTP_X_FORWARDED_FOR:
+ # client_ip, proxy_ip1, proxy_ip2...
+ # http://en.wikipedia.org/wiki/X-Forwarded-For
def calculate_ip
- client_ip = @env['HTTP_CLIENT_IP']
- forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR')
- remote_addrs = ips_from('REMOTE_ADDR')
+ client_ip = @env['HTTP_CLIENT_IP']
+ forwarded_ip = ips_from('HTTP_X_FORWARDED_FOR').first
+ remote_addrs = ips_from('REMOTE_ADDR')
check_ip = client_ip && @middleware.check_ip
- if check_ip && !forwarded_ips.include?(client_ip)
+ if check_ip && forwarded_ip != client_ip
# We don't know which came from the proxy, and which from the user
raise IpSpoofAttackError, "IP spoofing attack?!" \
"HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
"HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
end
- not_proxy = client_ip || forwarded_ips.last || remote_addrs.first
-
- # Return first REMOTE_ADDR if there are no other options
- not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first
+ client_ips = remove_proxies [client_ip, forwarded_ip, remote_addrs].flatten
+ if client_ips.present?
+ client_ips.first
+ else
+ # If there is no client ip we can return first valid proxy ip from REMOTE_ADDR
+ remote_addrs.find { |ip| valid_ip? ip }
+ end
end
def to_s
@@ -69,12 +105,24 @@ module ActionDispatch
@ip = calculate_ip
end
- protected
+ private
- def ips_from(header, allow_proxies = false)
- ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
- allow_proxies ? ips : ips.reject{|ip| ip =~ @middleware.proxies }
+ def ips_from(header)
+ @env[header] ? @env[header].strip.split(/[,\s]+/) : []
end
+
+ def valid_ip?(ip)
+ ip =~ VALID_IP
+ end
+
+ def not_a_proxy?(ip)
+ ip !~ @middleware.proxies
+ end
+
+ def remove_proxies(ips)
+ ips.select { |ip| valid_ip?(ip) && not_a_proxy?(ip) }
+ end
+
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index d5a0b80fd5..6fff94707c 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -19,10 +19,7 @@ module ActionDispatch
def call(env)
env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
- status, headers, body = @app.call(env)
-
- headers["X-Request-Id"] = env["action_dispatch.request_id"]
- [ status, headers, body ]
+ @app.call(env).tap { |status, headers, body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
end
private
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 6a8e690d18..e82132b445 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -7,6 +7,15 @@ require 'active_support/core_ext/object/blank'
module ActionDispatch
module Session
class SessionRestoreError < StandardError #:nodoc:
+ attr_reader :original_exception
+
+ def initialize(const_error)
+ @original_exception = const_error
+
+ super("Session contains objects whose class definition isn't available.\n" +
+ "Remember to require the classes for all objects kept in the session.\n" +
+ "(Original exception: #{const_error.message} [#{const_error.class}])\n")
+ end
end
module DestroyableSession
@@ -58,11 +67,8 @@ module ActionDispatch
begin
# Note that the regexp does not allow $1 to end with a ':'
$1.constantize
- rescue LoadError, NameError => const_error
- raise ActionDispatch::Session::SessionRestoreError,
- "Session contains objects whose class definition isn't available.\n" +
- "Remember to require the classes for all objects kept in the session.\n" +
- "(Original exception: #{const_error.message} [#{const_error.class}])\n"
+ rescue LoadError, NameError => e
+ raise ActionDispatch::Session::SessionRestoreError, e, e.backtrace
end
retry
else
@@ -74,6 +80,13 @@ module ActionDispatch
class AbstractStore < Rack::Session::Abstract::ID
include Compatibility
include StaleSessionCheck
+
+ private
+
+ def set_cookie(env, session_id, cookie)
+ request = ActionDispatch::Request.new(env)
+ request.cookie_jar[key] = cookie
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
index d3b6fd12fa..1db6194271 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -1,5 +1,4 @@
require 'action_dispatch/middleware/session/abstract_store'
-require 'rack/session/memcache'
module ActionDispatch
module Session
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 8ebf870b95..a4866f5a8f 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -59,7 +59,8 @@ module ActionDispatch
end
def set_session(env, sid, session_data, options)
- session_data.merge("session_id" => sid)
+ session_data["session_id"] = sid
+ session_data
end
def set_cookie(env, session_id, cookie)
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 836136eb95..ab740a0190 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -9,7 +9,7 @@ module ActionDispatch
# of ShowExceptions. Everytime there is an exception, ShowExceptions will
# store the exception in env["action_dispatch.exception"], rewrite the
# PATH_INFO to the exception status code and call the rack app.
- #
+ #
# If the application returns a "X-Cascade" pass response, this middleware
# will send an empty response as result with the correct status code.
# If any exception happens inside the exceptions app, this middleware
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
new file mode 100644
index 0000000000..9098f4e170
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -0,0 +1,70 @@
+module ActionDispatch
+ class SSL
+ YEAR = 31536000
+
+ def self.default_hsts_options
+ { :expires => YEAR, :subdomains => false }
+ end
+
+ def initialize(app, options = {})
+ @app = app
+
+ @hsts = options.fetch(:hsts, {})
+ @hsts = {} if @hsts == true
+ @hsts = self.class.default_hsts_options.merge(@hsts) if @hsts
+
+ @host = options[:host]
+ @port = options[:port]
+ end
+
+ def call(env)
+ request = Request.new(env)
+
+ if request.ssl?
+ status, headers, body = @app.call(env)
+ headers = hsts_headers.merge(headers)
+ flag_cookies_as_secure!(headers)
+ [status, headers, body]
+ else
+ redirect_to_https(request)
+ end
+ end
+
+ private
+ def redirect_to_https(request)
+ url = URI(request.url)
+ url.scheme = "https"
+ url.host = @host if @host
+ url.port = @port if @port
+ headers = hsts_headers.merge('Content-Type' => 'text/html',
+ 'Location' => url.to_s)
+
+ [301, headers, []]
+ end
+
+ # http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02
+ def hsts_headers
+ if @hsts
+ value = "max-age=#{@hsts[:expires]}"
+ value += "; includeSubDomains" if @hsts[:subdomains]
+ { 'Strict-Transport-Security' => value }
+ else
+ {}
+ end
+ end
+
+ def flag_cookies_as_secure!(headers)
+ if cookies = headers['Set-Cookie']
+ cookies = cookies.split("\n")
+
+ headers['Set-Cookie'] = cookies.map { |cookie|
+ if cookie !~ /;\s+secure(;|$)/
+ "#{cookie}; secure"
+ else
+ cookie
+ end
+ }.join("\n")
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index a4308f528c..12bc438be3 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -93,8 +93,9 @@ module ActionDispatch
end
def swap(target, *args, &block)
- insert_before(target, *args, &block)
- delete(target)
+ index = assert_index(target, :before)
+ insert(index, *args, &block)
+ middlewares.delete_at(index + 1)
end
def delete(target)
@@ -109,7 +110,7 @@ module ActionDispatch
def build(app = nil, &block)
app ||= block
raise "MiddlewareStack#build requires an app" unless app
- middlewares.reverse.inject(app) { |a, e| e.build(a) }
+ middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
end
protected
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 404943d720..9073e6582d 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -1,4 +1,5 @@
require 'rack/utils'
+require 'active_support/core_ext/uri'
module ActionDispatch
class FileHandler
@@ -11,14 +12,14 @@ module ActionDispatch
def match?(path)
path = path.dup
- full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path))
+ full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path)))
paths = "#{full_path}#{ext}"
matches = Dir[paths]
match = matches.detect { |m| File.file?(m) }
if match
match.sub!(@compiled_root, '')
- match
+ ::Rack::Utils.escape(match)
end
end
@@ -32,6 +33,15 @@ module ActionDispatch
"{,#{ext},/index#{ext}}"
end
end
+
+ def unescape_path(path)
+ URI.parser.unescape(path)
+ end
+
+ def escape_glob_chars(path)
+ path.force_encoding('binary') if path.respond_to? :force_encoding
+ path.gsub(/[*?{}\[\]]/, "\\\\\\&")
+ end
end
class Static
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
index f06c07daa5..177d383e94 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
@@ -12,4 +12,6 @@
<% end %>
<p>
Try running <code>rake routes</code> for more information on available routes.
-</p> \ No newline at end of file
+</p>
+
+<%= render :template => "rescues/_trace" %>
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 46c06386d8..62f906219c 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -11,17 +11,18 @@ module ActionDispatch
config.action_dispatch.ignore_accept_header = false
config.action_dispatch.rescue_templates = { }
config.action_dispatch.rescue_responses = { }
+ config.action_dispatch.default_charset = nil
config.action_dispatch.rack_cache = {
:metastore => "rails:/",
:entitystore => "rails:/",
- :verbose => true
+ :verbose => false
}
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
- ActionDispatch::Response.default_charset = app.config.encoding
+ ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 2f6b9d266d..38a0270151 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -182,15 +182,18 @@ module ActionDispatch
#
# == HTTP Methods
#
- # Using the <tt>:via</tt> option when specifying a route allows you to restrict it to a specific HTTP method.
- # Possible values are <tt>:post</tt>, <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>.
- # If your route needs to respond to more than one method you can use an array, e.g. <tt>[ :get, :post ]</tt>.
- # The default value is <tt>:any</tt> which means that the route will respond to any of the HTTP methods.
+ # Using the <tt>:via</tt> option when specifying a route allows you to
+ # restrict it to a specific HTTP method. Possible values are <tt>:post</tt>,
+ # <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
+ # <tt>:any</tt>. If your route needs to respond to more than one method you
+ # can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is
+ # <tt>:any</tt> which means that the route will respond to any of the HTTP
+ # methods.
#
# Examples:
#
# match 'post/:id' => 'posts#show', :via => :get
- # match 'post/:id' => "posts#create_comment', :via => :post
+ # match 'post/:id' => 'posts#create_comment', :via => :post
#
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
# URL will route to the <tt>show</tt> action.
@@ -198,12 +201,12 @@ module ActionDispatch
# === HTTP helper methods
#
# An alternative method of specifying which HTTP method a route should respond to is to use the helper
- # methods <tt>get</tt>, <tt>post</tt>, <tt>put</tt> and <tt>delete</tt>.
+ # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
#
# Examples:
#
# get 'post/:id' => 'posts#show'
- # post 'post/:id' => "posts#create_comment'
+ # post 'post/:id' => 'posts#create_comment'
#
# This syntax is less verbose and the intention is more apparent to someone else reading your code,
# however if your route needs to respond to more than one HTTP method (or all methods) then using the
@@ -283,6 +286,6 @@ module ActionDispatch
autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
SEPARATORS = %w( / . ? ) #:nodoc:
- HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc:
+ HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index db1e3198b3..4ea3937057 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/object/inclusion'
+require 'active_support/core_ext/enumerable'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
@@ -34,6 +35,8 @@ module ActionDispatch
}
return true
+ ensure
+ req.reset_parameters
end
def call(env)
@@ -54,9 +57,20 @@ module ActionDispatch
def initialize(set, scope, path, options)
@set, @scope = set, scope
+ @segment_keys = nil
@options = (@scope[:options] || {}).merge(options)
@path = normalize_path(path)
normalize_options!
+
+ via_all = @options.delete(:via) if @options[:via] == :all
+
+ if !via_all && request_method_condition.empty?
+ msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
+ "If you want to expose your action to GET, use `get` in the router:\n\n" \
+ " Instead of: match \"controller#action\"\n" \
+ " Do: get \"controller#action\""
+ raise msg
+ end
end
def to_route
@@ -213,7 +227,9 @@ module ActionDispatch
end
def segment_keys
- @segment_keys ||= Journey::Path::Pattern.new(
+ return @segment_keys if @segment_keys
+
+ @segment_keys = Journey::Path::Pattern.new(
Journey::Router::Strexp.compile(@path, requirements, SEPARATORS)
).names
end
@@ -241,7 +257,7 @@ module ActionDispatch
end
def self.normalize_name(name)
- normalize_path(name)[1..-1].gsub("/", "_")
+ normalize_path(name)[1..-1].tr("/", "_")
end
module Base
@@ -251,11 +267,16 @@ module ActionDispatch
#
# For options, see +match+, as +root+ uses it internally.
#
+ # You can also pass a string which will expand
+ #
+ # root 'pages#main'
+ #
# 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
# of most Rails applications, this is beneficial.
def root(options = {})
- match '/', { :as => :root }.merge(options)
+ options = { :to => options } if options.is_a?(String)
+ match '/', { :as => :root, :via => :get }.merge(options)
end
# Matches a url pattern to one or more routes. Any symbols in a pattern
@@ -325,7 +346,7 @@ module ActionDispatch
# +call+ or a string representing a controller's action.
#
# match 'path', :to => 'controller#action'
- # match 'path', :to => lambda { [200, {}, "Success!"] }
+ # match 'path', :to => lambda { |env| [200, {}, "Success!"] }
# match 'path', :to => RackApp
#
# [:on]
@@ -408,7 +429,7 @@ module ActionDispatch
options[:as] ||= app_name(app)
- match(path, options.merge(:to => app, :anchor => false, :format => false))
+ match(path, options.merge(:to => app, :anchor => false, :format => false, :via => :all))
define_generate_prefix(app, options[:as])
self
@@ -433,7 +454,7 @@ module ActionDispatch
app.railtie_name
else
class_name = app.class.is_a?(Class) ? app.name : app.class.name
- ActiveSupport::Inflector.underscore(class_name).gsub("/", "_")
+ ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
end
end
@@ -443,7 +464,11 @@ module ActionDispatch
_route = @set.named_routes.routes[name.to_sym]
_routes = @set
app.routes.define_mounted_helper(name)
- app.routes.class_eval do
+ app.routes.singleton_class.class_eval do
+ define_method :mounted? do
+ true
+ end
+
define_method :_generate_prefix do |options|
prefix_options = options.slice(*_route.segment_keys)
# we must actually delete prefix segment keys to avoid passing them to next url_for
@@ -462,9 +487,9 @@ module ActionDispatch
#
# Example:
#
- # get 'bacon', :to => 'food#bacon'
+ # get 'bacon', :to => 'food#bacon'
def get(*args, &block)
- map_method(:get, *args, &block)
+ map_method(:get, args, &block)
end
# Define a route that only recognizes HTTP POST.
@@ -472,9 +497,19 @@ module ActionDispatch
#
# Example:
#
- # post 'bacon', :to => 'food#bacon'
+ # post 'bacon', :to => 'food#bacon'
def post(*args, &block)
- map_method(:post, *args, &block)
+ map_method(:post, args, &block)
+ end
+
+ # Define a route that only recognizes HTTP PATCH.
+ # For supported arguments, see <tt>Base#match</tt>.
+ #
+ # Example:
+ #
+ # patch 'bacon', :to => 'food#bacon'
+ def patch(*args, &block)
+ map_method(:patch, args, &block)
end
# Define a route that only recognizes HTTP PUT.
@@ -482,27 +517,26 @@ module ActionDispatch
#
# Example:
#
- # put 'bacon', :to => 'food#bacon'
+ # put 'bacon', :to => 'food#bacon'
def put(*args, &block)
- map_method(:put, *args, &block)
+ map_method(:put, args, &block)
end
- # Define a route that only recognizes HTTP PUT.
+ # Define a route that only recognizes HTTP DELETE.
# For supported arguments, see <tt>Base#match</tt>.
#
# Example:
#
- # delete 'broccoli', :to => 'food#broccoli'
+ # delete 'broccoli', :to => 'food#broccoli'
def delete(*args, &block)
- map_method(:delete, *args, &block)
+ map_method(:delete, args, &block)
end
private
- def map_method(method, *args, &block)
+ def map_method(method, args, &block)
options = args.extract_options!
options[:via] = method
- args.push(options)
- match(*args, &block)
+ match(*args, options, &block)
self
end
end
@@ -520,13 +554,13 @@ module ActionDispatch
# This will create a number of routes for each of the posts and comments
# controller. For <tt>Admin::PostsController</tt>, Rails will create:
#
- # GET /admin/posts
- # GET /admin/posts/new
- # POST /admin/posts
- # GET /admin/posts/1
- # GET /admin/posts/1/edit
- # PUT /admin/posts/1
- # DELETE /admin/posts/1
+ # GET /admin/posts
+ # GET /admin/posts/new
+ # POST /admin/posts
+ # GET /admin/posts/1
+ # GET /admin/posts/1/edit
+ # PATCH/PUT /admin/posts/1
+ # DELETE /admin/posts/1
#
# If you want to route /posts (without the prefix /admin) to
# <tt>Admin::PostsController</tt>, you could use
@@ -554,13 +588,13 @@ module ActionDispatch
# not use scope. In the last case, the following paths map to
# +PostsController+:
#
- # GET /admin/posts
- # GET /admin/posts/new
- # POST /admin/posts
- # GET /admin/posts/1
- # GET /admin/posts/1/edit
- # PUT /admin/posts/1
- # DELETE /admin/posts/1
+ # GET /admin/posts
+ # GET /admin/posts/new
+ # POST /admin/posts
+ # GET /admin/posts/1
+ # GET /admin/posts/1/edit
+ # PATCH/PUT /admin/posts/1
+ # DELETE /admin/posts/1
module Scoping
# Scopes a set of routes to the given default options.
#
@@ -649,13 +683,13 @@ module ActionDispatch
#
# This generates the following routes:
#
- # admin_posts GET /admin/posts(.:format) admin/posts#index
- # admin_posts POST /admin/posts(.:format) admin/posts#create
- # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
- # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
- # admin_post GET /admin/posts/:id(.:format) admin/posts#show
- # admin_post PUT /admin/posts/:id(.:format) admin/posts#update
- # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
+ # admin_posts GET /admin/posts(.:format) admin/posts#index
+ # admin_posts POST /admin/posts(.:format) admin/posts#create
+ # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
+ # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
+ # admin_post GET /admin/posts/:id(.:format) admin/posts#show
+ # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
+ # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
#
# === Options
#
@@ -859,17 +893,18 @@ module ActionDispatch
# CANONICAL_ACTIONS holds all actions that does not need a prefix or
# a path appended since they fit properly in their scope level.
VALID_ON_OPTIONS = [:new, :collection, :member]
- RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except]
+ RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param]
CANONICAL_ACTIONS = %w(index create new show update destroy)
class Resource #:nodoc:
- attr_reader :controller, :path, :options
+ attr_reader :controller, :path, :options, :param
def initialize(entities, options = {})
@name = entities.to_s
@path = (options[:path] || @name).to_s
@controller = (options[:controller] || @name).to_s
@as = options[:as]
+ @param = options[:param] || :id
@options = options
end
@@ -914,7 +949,7 @@ module ActionDispatch
alias :collection_scope :path
def member_scope
- "#{path}/:id"
+ "#{path}/:#{param}"
end
def new_scope(new_path)
@@ -922,7 +957,7 @@ module ActionDispatch
end
def nested_scope
- "#{path}/:#{singular}_id"
+ "#{path}/:#{singular}_#{param}"
end
end
@@ -970,12 +1005,12 @@ module ActionDispatch
# the +GeoCoders+ controller (note that the controller is named after
# the plural):
#
- # GET /geocoder/new
- # POST /geocoder
- # GET /geocoder
- # GET /geocoder/edit
- # PUT /geocoder
- # DELETE /geocoder
+ # GET /geocoder/new
+ # POST /geocoder
+ # GET /geocoder
+ # GET /geocoder/edit
+ # PATCH/PUT /geocoder
+ # DELETE /geocoder
#
# === Options
# Takes same options as +resources+.
@@ -998,9 +1033,12 @@ module ActionDispatch
end if parent_resource.actions.include?(:new)
member do
- get :edit if parent_resource.actions.include?(:edit)
- get :show if parent_resource.actions.include?(:show)
- put :update if parent_resource.actions.include?(:update)
+ get :edit if parent_resource.actions.include?(:edit)
+ get :show if parent_resource.actions.include?(:show)
+ if parent_resource.actions.include?(:update)
+ patch :update
+ put :update
+ end
delete :destroy if parent_resource.actions.include?(:destroy)
end
end
@@ -1018,13 +1056,13 @@ module ActionDispatch
# creates seven different routes in your application, all mapping to
# the +Photos+ controller:
#
- # GET /photos
- # GET /photos/new
- # POST /photos
- # GET /photos/:id
- # GET /photos/:id/edit
- # PUT /photos/:id
- # DELETE /photos/:id
+ # GET /photos
+ # GET /photos/new
+ # POST /photos
+ # GET /photos/:id
+ # GET /photos/:id/edit
+ # PATCH/PUT /photos/:id
+ # DELETE /photos/:id
#
# Resources can also be nested infinitely by using this block syntax:
#
@@ -1034,13 +1072,13 @@ module ActionDispatch
#
# This generates the following comments routes:
#
- # GET /photos/:photo_id/comments
- # GET /photos/:photo_id/comments/new
- # POST /photos/:photo_id/comments
- # GET /photos/:photo_id/comments/:id
- # GET /photos/:photo_id/comments/:id/edit
- # PUT /photos/:photo_id/comments/:id
- # DELETE /photos/:photo_id/comments/:id
+ # GET /photos/:photo_id/comments
+ # GET /photos/:photo_id/comments/new
+ # POST /photos/:photo_id/comments
+ # GET /photos/:photo_id/comments/:id
+ # GET /photos/:photo_id/comments/:id/edit
+ # PATCH/PUT /photos/:photo_id/comments/:id
+ # DELETE /photos/:photo_id/comments/:id
#
# === Options
# Takes same options as <tt>Base#match</tt> as well as:
@@ -1102,13 +1140,32 @@ module ActionDispatch
#
# The +comments+ resource here will have the following routes generated for it:
#
- # post_comments GET /posts/:post_id/comments(.:format)
- # post_comments POST /posts/:post_id/comments(.:format)
- # new_post_comment GET /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)
+ # post_comments GET /posts/:post_id/comments(.:format)
+ # post_comments POST /posts/:post_id/comments(.:format)
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
+ # edit_comment GET /sekret/comments/:id/edit(.:format)
+ # comment GET /sekret/comments/:id(.:format)
+ # comment PATCH/PUT /sekret/comments/:id(.:format)
+ # comment DELETE /sekret/comments/:id(.:format)
+ #
+ # [:shallow_prefix]
+ # Prefixes nested shallow route names with specified prefix.
+ #
+ # scope :shallow_prefix => "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 /posts/:post_id/comments(.:format)
+ # post_comments POST /posts/:post_id/comments(.:format)
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
+ # edit_sekret_comment GET /comments/:id/edit(.:format)
+ # sekret_comment GET /comments/:id(.:format)
+ # sekret_comment PATCH/PUT /comments/:id(.:format)
+ # sekret_comment DELETE /comments/:id(.:format)
#
# === Examples
#
@@ -1137,9 +1194,12 @@ module ActionDispatch
end if parent_resource.actions.include?(:new)
member do
- get :edit if parent_resource.actions.include?(:edit)
- get :show if parent_resource.actions.include?(:show)
- put :update if parent_resource.actions.include?(:update)
+ get :edit if parent_resource.actions.include?(:edit)
+ get :show if parent_resource.actions.include?(:show)
+ if parent_resource.actions.include?(:update)
+ patch :update
+ put :update
+ end
delete :destroy if parent_resource.actions.include?(:destroy)
end
end
@@ -1247,6 +1307,24 @@ module ActionDispatch
parent_resource.instance_of?(Resource) && @scope[:shallow]
end
+ def draw(name)
+ path = @draw_paths.find do |_path|
+ _path.join("#{name}.rb").file?
+ end
+
+ unless path
+ msg = "Your router tried to #draw the external file #{name}.rb,\n" \
+ "but the file was not found in:\n\n"
+ msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
+ raise msg
+ end
+
+ instance_eval(path.join("#{name}.rb").read)
+ end
+
+ # match 'path' => 'controller#action'
+ # match 'path', to: 'controller#action'
+ # match 'path', 'otherpath', on: :member, via: :get
def match(path, *rest)
if rest.empty? && Hash === path
options = path
@@ -1493,6 +1571,7 @@ module ActionDispatch
def initialize(set) #:nodoc:
@set = set
+ @draw_paths = set.draw_paths
@scope = { :path_names => @set.resources_path_names }
end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 013cf93dbc..8fde667108 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -43,16 +43,14 @@ module ActionDispatch
# edit_polymorphic_path(@post) # => "/posts/1/edit"
# polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
#
- # == Using with mounted engines
+ # == Usage with mounted engines
#
- # If you use mounted engine, there is a possibility that you will need to use
- # polymorphic_url pointing at engine's routes. To do that, just pass proxy used
- # to reach engine's routes as a first argument:
+ # If you are using a mounted engine and you need to use a polymorphic_url
+ # pointing at the engine's routes, pass in the engine's route proxy as the first
+ # argument to the method. For example:
#
- # For example:
- #
- # polymorphic_url([blog, @post]) # it will call blog.post_path(@post)
- # form_for([blog, @post]) # => "/blog/posts/1
+ # polymorphic_url([blog, @post]) # calls blog.post_path(@post)
+ # form_for([blog, @post]) # => "/blog/posts/1"
#
module PolymorphicRoutes
# Constructs a call to a named RESTful route for the given record and returns the
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 617b24b46a..95c588c00a 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -1,4 +1,7 @@
require 'action_dispatch/http/request'
+require 'active_support/core_ext/uri'
+require 'active_support/core_ext/array/extract_options'
+require 'rack/utils'
module ActionDispatch
module Routing
@@ -46,8 +49,17 @@ module ActionDispatch
:params => request.query_parameters
}.merge options
+ if !params.empty? && url_options[:path].match(/%\{\w*\}/)
+ url_options[:path] = (url_options[:path] % escape_path(params))
+ end
+
ActionDispatch::Http::URL.url_for url_options
end
+
+ private
+ def escape_path(params)
+ Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }]
+ end
end
module Redirection
@@ -67,10 +79,13 @@ module ActionDispatch
# params, depending of how many arguments your block accepts. A string is required as a
# return value.
#
- # match 'jokes/:number', :to => redirect do |params, request|
- # path = (params[:number].to_i.even? ? "/wheres-the-beef" : "/i-love-lamp")
+ # match 'jokes/:number', :to => redirect { |params, request|
+ # path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp")
# "http://#{request.host_with_port}/#{path}"
- # end
+ # }
+ #
+ # Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass
+ # the block to +match+ instead of +redirect+. Use <tt>{ ... }</tt> instead.
#
# The options version of redirect allows you to supply only the parts of the url which need
# to change, it also supports interpolation of the path similar to the first example.
@@ -85,7 +100,7 @@ module ActionDispatch
# match 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
#
def redirect(*args, &block)
- options = args.last.is_a?(Hash) ? args.pop : {}
+ options = args.extract_options!
status = options.delete(:status) || 301
return OptionRedirect.new(status, options) if options.any?
@@ -93,13 +108,18 @@ module ActionDispatch
path = args.shift
block = lambda { |params, request|
- (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params)
+ (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % escape(params))
} if String === path
block = path if path.respond_to? :call
raise ArgumentError, "redirection argument not supported" unless block
Redirect.new status, block
end
+
+ private
+ def escape(params)
+ Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 2c21887220..7abd7bd008 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -9,6 +9,12 @@ require 'action_controller/metal/exceptions'
module ActionDispatch
module Routing
class RouteSet #:nodoc:
+ # Since the router holds references to many parts of the system
+ # like engines, controllers and the application itself, inspecting
+ # the route set can actually be really slow, therefore we default
+ # alias inspect to to_s.
+ alias inspect to_s
+
PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
class Dispatcher #:nodoc:
@@ -31,6 +37,7 @@ module ActionDispatch
end
def prepare_params!(params)
+ normalize_controller!(params)
merge_default_action!(params)
split_glob_param!(params) if @glob_param
end
@@ -66,6 +73,10 @@ module ActionDispatch
controller.action(action).call(env)
end
+ def normalize_controller!(params)
+ params[:controller] = params[:controller].underscore if params.key?(:controller)
+ end
+
def merge_default_action!(params)
params[:action] ||= 'index'
end
@@ -85,7 +96,25 @@ module ActionDispatch
def initialize
@routes = {}
@helpers = []
- @module = Module.new
+ @module = Module.new do
+ protected
+
+ def handle_positional_args(args, options, segment_keys)
+ inner_options = args.extract_options!
+ result = options.dup
+
+ if args.any?
+ keys = segment_keys
+ if args.size < keys.size - 1 # take format into account
+ keys -= self.url_options.keys if self.respond_to?(:url_options)
+ keys -= options.keys
+ end
+ result.merge!(Hash[keys.zip(args)])
+ end
+
+ result.merge!(inner_options)
+ end
+ end
end
def helper_names
@@ -124,43 +153,19 @@ module ActionDispatch
end
private
- def url_helper_name(name, kind = :url)
- :"#{name}_#{kind}"
- end
-
- def hash_access_name(name, kind = :url)
- :"hash_for_#{name}_#{kind}"
- end
-
- def define_named_route_methods(name, route)
- {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
- hash = route.defaults.merge(:use_route => name).merge(opts)
- define_hash_access route, name, kind, hash
- define_url_helper route, name, kind, hash
+ def url_helper_name(name, only_path)
+ if only_path
+ :"#{name}_path"
+ else
+ :"#{name}_url"
end
end
- def define_hash_access(route, name, kind, options)
- selector = hash_access_name(name, kind)
-
- @module.module_eval do
- remove_possible_method selector
-
- define_method(selector) do |*args|
- inner_options = args.extract_options!
- result = options.dup
-
- if args.any?
- result[:_positional_args] = args
- result[:_positional_keys] = route.segment_keys
- end
-
- result.merge(inner_options)
- end
-
- protected selector
+ def define_named_route_methods(name, route)
+ [true, false].each do |only_path|
+ hash = route.defaults.merge(:use_route => name, :only_path => only_path)
+ define_url_helper route, name, hash
end
- helpers << selector
end
# Create a url helper allowing ordered parameters to be associated
@@ -176,23 +181,53 @@ module ActionDispatch
#
# foo_url(bar, baz, bang, :sort_by => 'baz')
#
- def define_url_helper(route, name, kind, options)
- selector = url_helper_name(name, kind)
- hash_access_method = hash_access_name(name, kind)
+ def define_url_helper(route, name, options)
+ selector = url_helper_name(name, options[:only_path])
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
remove_possible_method :#{selector}
def #{selector}(*args)
- url_for(#{hash_access_method}(*args))
+ if #{optimize_helper?(route)} && args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation?
+ options = #{options.inspect}.merge!(url_options)
+ options[:path] = "#{optimized_helper(route)}"
+ ActionDispatch::Http::URL.url_for(options)
+ else
+ url_for(handle_positional_args(args, #{options.inspect}, #{route.segment_keys.inspect}))
+ end
end
END_EVAL
+
helpers << selector
end
+
+ # Clause check about when we need to generate an optimized helper.
+ def optimize_helper?(route) #:nodoc:
+ route.requirements.except(:controller, :action).empty?
+ end
+
+ # Generates the interpolation to be used in the optimized helper.
+ def optimized_helper(route)
+ string_route = route.ast.to_s
+
+ while string_route.gsub!(/\([^\)]*\)/, "")
+ true
+ end
+
+ route.required_parts.each_with_index do |part, i|
+ # Replace each route parameter
+ # e.g. :id for regular parameter or *path for globbing
+ # with ruby string interpolation code
+ string_route.gsub!(/(\*|:)#{part}/, "\#{Journey::Router::Utils.escape_fragment(args[#{i}].to_param)}")
+ end
+
+ string_route
+ end
end
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options, :request_class, :valid_conditions
+ attr_accessor :draw_paths
alias :routes :set
@@ -204,6 +239,7 @@ module ActionDispatch
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names.dup
self.default_url_options = {}
+ self.draw_paths = []
self.request_class = request_class
@valid_conditions = {}
@@ -246,8 +282,7 @@ module ActionDispatch
def eval_block(block)
if block.arity == 1
raise "You are using the old router DSL which has been removed in Rails 3.1. " <<
- "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/ " <<
- "or add the rails_legacy_mapper gem to your Gemfile"
+ "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
end
mapper = Mapper.new(self)
if default_scope
@@ -313,7 +348,7 @@ module ActionDispatch
# Rails.application.routes.url_helpers.url_for(args)
@_routes = routes
class << self
- delegate :url_for, :to => '@_routes'
+ delegate :url_for, :optimize_routes_generation?, :to => '@_routes'
end
# Make named_routes available in the module singleton
@@ -360,7 +395,26 @@ module ActionDispatch
SEPARATORS,
anchor)
- Journey::Path::Pattern.new(strexp)
+ pattern = Journey::Path::Pattern.new(strexp)
+
+ builder = Journey::GTG::Builder.new pattern.spec
+
+ # Get all the symbol nodes followed by literals that are not the
+ # dummy node.
+ symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n|
+ builder.followpos(n).first.literal?
+ }
+
+ # Get all the symbol nodes preceded by literals.
+ symbols.concat pattern.spec.find_all(&:literal?).map { |n|
+ builder.followpos(n).first
+ }.find_all(&:symbol?)
+
+ symbols.each { |x|
+ x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
+ }
+
+ pattern
end
private :build_path
@@ -463,7 +517,7 @@ module ActionDispatch
# if the current controller is "foo/bar/baz" and :controller => "baz/bat"
# is specified, the controller becomes "foo/baz/bat"
def use_relative_controller!
- if !named_route && different_controller?
+ if !named_route && different_controller? && !controller.start_with?("/")
old_parts = current_controller.split('/')
size = controller.count("/") + 1
parts = old_parts[0...-size] << controller
@@ -528,29 +582,35 @@ module ActionDispatch
RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
:trailing_slash, :anchor, :params, :only_path, :script_name]
+ def mounted?
+ false
+ end
+
+ def optimize_routes_generation?
+ !mounted? && default_url_options.empty?
+ end
+
def _generate_prefix(options = {})
nil
end
+ # The +options+ argument must be +nil+ or a hash whose keys are *symbols*.
def url_for(options)
- options = (options || {}).reverse_merge!(default_url_options)
-
- handle_positional_args(options)
+ options = default_url_options.merge(options || {})
user, password = extract_authentication(options)
path_segments = options.delete(:_path_segments)
- script_name = options.delete(:script_name)
-
- path = (script_name.blank? ? _generate_prefix(options) : script_name.chomp('/')).to_s
+ script_name = options.delete(:script_name).presence || _generate_prefix(options)
path_options = options.except(*RESERVED_OPTIONS)
path_options = yield(path_options) if block_given?
- path_addition, params = generate(path_options, path_segments || {})
- path << path_addition
+ path, params = generate(path_options, path_segments || {})
+ params.merge!(options[:params] || {})
ActionDispatch::Http::URL.url_for(options.merge!({
:path => path,
+ :script_name => script_name,
:params => params,
:user => user,
:password => password
@@ -564,6 +624,7 @@ module ActionDispatch
def recognize_path(path, environment = {})
method = (environment[:method] || "GET").to_s.upcase
path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://}
+ extras = environment[:extras] || {}
begin
env = Rack::MockRequest.env_for(path, {:method => method})
@@ -573,13 +634,15 @@ module ActionDispatch
req = @request_class.new(env)
@router.recognize(req) do |route, matches, params|
+ params.merge!(extras)
params.each do |key, value|
if value.is_a?(String)
value = value.dup.force_encoding(Encoding::BINARY)
params[key] = URI.parser.unescape(value)
end
end
-
+ old_params = env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] = (old_params || {}).merge(params)
dispatcher = route.app
while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
dispatcher = dispatcher.app
@@ -604,16 +667,6 @@ module ActionDispatch
end
end
- def handle_positional_args(options)
- return unless args = options.delete(:_positional_args)
-
- keys = options.delete(:_positional_keys)
- keys -= options.keys if args.size < keys.size - 1 # take format into account
-
- # Tell url_for to skip default_url_options
- options.merge!(Hash[args.zip(keys).map { |v, k| [k, v] }])
- end
-
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index ee6616c5d3..d75bb1c2de 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -102,6 +102,9 @@ module ActionDispatch
super
end
+ # Hook overriden in controller to add request information
+ # with `default_url_options`. Application logic should not
+ # go into url_options.
def url_options
default_url_options
end
@@ -141,10 +144,12 @@ module ActionDispatch
# # => 'http://somehost.org/tasks/testing?number=33'
def url_for(options = nil)
case options
+ when nil
+ _routes.url_for(url_options.symbolize_keys)
+ when Hash
+ _routes.url_for(options.symbolize_keys.reverse_merge!(url_options))
when String
options
- when nil, Hash
- _routes.url_for((options || {}).symbolize_keys.reverse_merge!(url_options))
else
polymorphic_url(options)
end
@@ -152,6 +157,11 @@ module ActionDispatch
protected
+ def optimize_routes_generation?
+ return @_optimized_routes if defined?(@_optimized_routes)
+ @_optimized_routes = _routes.optimize_routes_generation? && default_url_options.empty?
+ end
+
def _with_routes(routes)
old_routes, @_routes = @_routes, routes
yield
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 094cfbfc76..8f6fff5d32 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -4,11 +4,9 @@ module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
module ResponseAssertions
- extend ActiveSupport::Concern
-
# Asserts that the response is one of the following types:
#
- # * <tt>:success</tt> - Status code was 200
+ # * <tt>:success</tt> - Status code was in the 200-299 range
# * <tt>:redirect</tt> - Status code was in the 300-399 range
# * <tt>:missing</tt> - Status code was 404
# * <tt>:error</tt> - Status code was in the 500-599 range
@@ -55,15 +53,18 @@ module ActionDispatch
# # assert that the redirection was to the url for @customer
# assert_redirected_to @customer
#
+ # # asserts that the redirection matches the regular expression
+ # assert_redirected_to %r(\Ahttp://example.org)
+ #
def assert_redirected_to(options = {}, message=nil)
assert_response(:redirect, message)
- return true if options == @response.location
+ return true if options === @response.location
redirect_is = normalize_argument_to_redirection(@response.location)
redirect_expected = normalize_argument_to_redirection(options)
message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
- assert_equal redirect_expected, redirect_is, message
+ assert_operator redirect_expected, :===, redirect_is, message
end
private
@@ -73,17 +74,21 @@ module ActionDispatch
end
def normalize_argument_to_redirection(fragment)
- case fragment
- when %r{^\w[A-Za-z\d+.-]*:.*}
- fragment
- when String
- @request.protocol + @request.host_with_port + fragment
- when :back
- raise RedirectBackError unless refer = @request.headers["Referer"]
- refer
- else
- @controller.url_for(fragment)
- end.gsub(/[\r\n]/, '')
+ normalized = case fragment
+ when Regexp
+ fragment
+ when %r{^\w[A-Za-z\d+.-]*:.*}
+ fragment
+ when String
+ @request.protocol + @request.host_with_port + fragment
+ when :back
+ raise RedirectBackError unless refer = @request.headers["Referer"]
+ refer
+ else
+ @controller.url_for(fragment)
+ end
+
+ normalized.respond_to?(:delete) ? normalized.delete("\0\r\n") : normalized
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 1552676fbb..1f4b905d18 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -39,10 +39,9 @@ module ActionDispatch
# # Test a custom route
# assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1')
def assert_recognizes(expected_options, path, extras={}, message=nil)
- request = recognized_request_for(path)
+ request = recognized_request_for(path, extras)
expected_options = expected_options.clone
- extras.each_key { |key| expected_options.delete key } unless extras.nil?
expected_options.stringify_keys!
@@ -181,7 +180,7 @@ module ActionDispatch
private
# Recognizes the route for a given path.
- def recognized_request_for(path)
+ def recognized_request_for(path, extras = {})
if path.is_a?(Hash)
method = path[:method]
path = path[:path]
@@ -209,7 +208,7 @@ module ActionDispatch
request.request_method = method if method
- params = @routes.recognize_path(path, { :method => method })
+ params = @routes.recognize_path(path, { :method => method, :extras => extras })
request.path_parameters = params.with_indifferent_access
request
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 4d963803e6..ea1ed20f3c 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -269,6 +269,7 @@ module ActionDispatch
end
end
text.strip! unless NO_STRIP.include?(match.name)
+ text.sub!(/\A\n/, '') if match.name == "textarea"
unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s)
content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, text)
true
@@ -340,8 +341,8 @@ module ActionDispatch
# element +encoded+. It then calls the block with all un-encoded elements.
#
# ==== Examples
- # # Selects all bold tags from within the title of an ATOM feed's entries (perhaps to nab a section name prefix)
- # assert_select_feed :atom, 1.0 do
+ # # Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix)
+ # assert_select "feed[xmlns='http://www.w3.org/2005/Atom']" do
# # Select each entry item and then the title item
# assert_select "entry>title" do
# # Run assertions on the encoded title elements
@@ -353,7 +354,7 @@ module ActionDispatch
#
#
# # Selects all paragraph tags from within the description of an RSS feed
- # assert_select_feed :rss, 2.0 do
+ # assert_select "rss[version=2.0]" do
# # Select description element of each feed item.
# assert_select "channel>item>description" do
# # Run assertions on the encoded elements.
@@ -417,8 +418,8 @@ module ActionDispatch
deliveries = ActionMailer::Base.deliveries
assert !deliveries.empty?, "No e-mail in delivery list"
- for delivery in deliveries
- for part in (delivery.parts.empty? ? [delivery] : delivery.parts)
+ deliveries.each do |delivery|
+ (delivery.parts.empty? ? [delivery] : delivery.parts).each do |part|
if part["Content-Type"].to_s =~ /^text\/html\W/
root = HTML::Document.new(part.body.to_s).root
assert_select root, ":root", &block
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 08b7ff49c2..69d54f6981 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -26,8 +26,8 @@ module ActionDispatch
# object's <tt>@response</tt> instance variable will point to the same
# response object.
#
- # You can also perform POST, PUT, DELETE, and HEAD requests with +#post+,
- # +#put+, +#delete+, and +#head+.
+ # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
+ # +#post+, +#patch+, +#put+, +#delete+, and +#head+.
def get(path, parameters = nil, headers = nil)
process :get, path, parameters, headers
end
@@ -38,6 +38,12 @@ module ActionDispatch
process :post, path, parameters, headers
end
+ # Performs a PATCH request with the given parameters. See +#get+ for more
+ # details.
+ def patch(path, parameters = nil, headers = nil)
+ process :patch, path, parameters, headers
+ end
+
# Performs a PUT request with the given parameters. See +#get+ for more
# details.
def put(path, parameters = nil, headers = nil)
@@ -56,13 +62,19 @@ module ActionDispatch
process :head, path, parameters, headers
end
+ # Performs a OPTIONS request with the given parameters. See +#get+ for
+ # more details.
+ def options(path, parameters = nil, headers = nil)
+ process :options, path, parameters, headers
+ end
+
# Performs an XMLHttpRequest request with the given parameters, mirroring
# a request from the Prototype library.
#
- # The request_method is +:get+, +:post+, +:put+, +:delete+ or +:head+; the
- # parameters are +nil+, a hash, or a url-encoded or multipart string;
- # the headers are a hash. Keys are automatically upcased and prefixed
- # with 'HTTP_' if not already.
+ # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
+ # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
+ # string; the headers are a hash. Keys are automatically upcased and
+ # prefixed with 'HTTP_' if not already.
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@@ -102,6 +114,12 @@ module ActionDispatch
request_via_redirect(:post, path, parameters, headers)
end
+ # Performs a PATCH request, following any subsequent redirect.
+ # See +request_via_redirect+ for more information.
+ def patch_via_redirect(path, parameters = nil, headers = nil)
+ request_via_redirect(:patch, path, parameters, headers)
+ end
+
# Performs a PUT request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def put_via_redirect(path, parameters = nil, headers = nil)
@@ -312,7 +330,7 @@ module ActionDispatch
@integration_session = Integration::Session.new(app)
end
- %w(get post put head delete cookies assigns
+ %w(get post patch put head delete options cookies assigns
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless integration_session
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index b08ff41950..3a6d081721 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -5,7 +5,8 @@ require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch
module TestProcess
def assigns(key = nil)
- assigns = @controller.view_assigns.with_indifferent_access
+ assigns = {}.with_indifferent_access
+ @controller.view_assigns.each {|k, v| assigns.regular_writer(k, v)}
key.nil? ? assigns : assigns[key]
end
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index 7280e9a93b..d04be2099c 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/indifferent_access'
-require 'active_support/core_ext/hash/reverse_merge'
require 'rack/utils'
module ActionDispatch
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
index 2a28e780bf..add8d94b70 100644
--- a/actionpack/lib/action_view/asset_paths.rb
+++ b/actionpack/lib/action_view/asset_paths.rb
@@ -117,7 +117,7 @@ module ActionView
end
def relative_url_root
- config.relative_url_root
+ config.relative_url_root || current_request.try(:script_name)
end
def asset_host_config
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 0b026882ae..5f81f24a2e 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -139,14 +139,20 @@ module ActionView #:nodoc:
# How to complete the streaming when an exception occurs.
# This is our best guess: first try to close the attribute, then the tag.
cattr_accessor :streaming_completion_on_exception
- @@streaming_completion_on_exception = %("><script type="text/javascript">window.location = "/500.html"</script></html>)
+ @@streaming_completion_on_exception = %("><script>window.location = "/500.html"</script></html>)
+
+ # Specify whether rendering within namespaced controllers should prefix
+ # the partial paths for ActiveModel objects with the namespace.
+ # (e.g., an Admin::PostsController would render @post using /admin/posts/_post.erb)
+ cattr_accessor :prefix_partial_path_with_controller_namespace
+ @@prefix_partial_path_with_controller_namespace = true
class_attribute :helpers
class_attribute :_routes
+ class_attribute :logger
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?
@@ -194,7 +200,7 @@ module ActionView #:nodoc:
# TODO Provide a new API for AV::Base and deprecate this one.
if context.is_a?(ActionView::Renderer)
@view_renderer = context
- elsif
+ else
lookup_context = context.is_a?(ActionView::LookupContext) ?
context : ActionView::LookupContext.new(context)
lookup_context.formats = formats if formats
diff --git a/actionpack/lib/action_view/flows.rb b/actionpack/lib/action_view/flows.rb
index a8f740713f..c0e458cd41 100644
--- a/actionpack/lib/action_view/flows.rb
+++ b/actionpack/lib/action_view/flows.rb
@@ -22,11 +22,8 @@ module ActionView
def append(key, value)
@content[key] << value
end
+ alias_method :append!, :append
- # Called by provide
- def append!(key, value)
- @content[key] << value
- end
end
class StreamingFlow < OutputFlow #:nodoc:
diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
index 56f15604a6..e27111012d 100644
--- a/actionpack/lib/action_view/helpers/active_model_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -16,8 +16,8 @@ module ActionView
end
end
- %w(content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth|
- module_eval "def #{meth}(*) error_wrapping(super) end", __FILE__, __LINE__
+ def content_tag(*)
+ error_wrapping(super)
end
def tag(type, options, *)
@@ -39,7 +39,7 @@ module ActionView
private
def object_has_errors?
- object.respond_to?(:errors) && object.errors.respond_to?(:full_messages) && error_message.any?
+ object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
end
def tag_generate_errors?(options)
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 5dbba3c4a7..7985683a72 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/array/extract_options'
+require 'active_support/core_ext/hash/keys'
require 'action_view/helpers/asset_tag_helpers/javascript_tag_helpers'
require 'action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers'
require 'action_view/helpers/asset_tag_helpers/asset_paths'
@@ -13,7 +15,7 @@ module ActionView
# image_tag("rails.png")
# # => <img alt="Rails" src="/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" />
#
# === Using asset hosts
#
@@ -22,16 +24,17 @@ module ActionView
# server by setting ActionController::Base.asset_host in the application
# configuration, typically in <tt>config/environments/production.rb</tt>.
# For example, you'd define <tt>assets.example.com</tt> to be your asset
- # host this way:
+ # host this way, inside the <tt>configure</tt> block of your environment-specific
+ # configuration files or <tt>config/application.rb</tt>:
#
- # ActionController::Base.asset_host = "assets.example.com"
+ # config.action_controller.asset_host = "assets.example.com"
#
# Helpers take that into account:
#
# image_tag("rails.png")
# # => <img alt="Rails" src="http://assets.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" />
#
# Browsers typically open at most two simultaneous connections to a single
# host, which means your assets often have to wait for other assets to finish
@@ -44,7 +47,7 @@ module ActionView
# image_tag("rails.png")
# # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" />
#
# To do this, you can either setup four actual hosts, or you can use wildcard
# DNS to CNAME the wildcard to a single asset host. You can read more about
@@ -63,7 +66,7 @@ module ActionView
# image_tag("rails.png")
# # => <img alt="Rails" src="http://assets1.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" />
#
# The example above generates "http://assets1.example.com" and
# "http://assets2.example.com". This option is useful for example if
@@ -83,7 +86,7 @@ module ActionView
# image_tag("rails.png")
# # => <img alt="Rails" src="http://images.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" />
#
# Alternatively you may ask for a second parameter +request+. That one is
# particularly useful for serving assets from an SSL-protected page. The
@@ -160,7 +163,7 @@ module ActionView
# image_tag("rails.png")
# # => <img alt="Rails" src="/release-12345/images/rails.png" />
# stylesheet_link_tag("application")
- # # => <link href="/release-12345/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="/release-12345/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" />
#
# Changing the asset_path does require that your web servers have
# knowledge of the asset template paths that you rewrite to so it's not
@@ -196,7 +199,7 @@ module ActionView
include JavascriptTagHelpers
include StylesheetTagHelpers
# Returns a link tag that browsers and news readers can use to auto-detect
- # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
+ # an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or
# <tt>:atom</tt>. Control the link options in url_for format using the
# +url_options+. You can modify the LINK tag itself in +tag_options+.
#
@@ -232,7 +235,7 @@ module ActionView
#
# generates
#
- # <link href="/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ # <link href="/assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
#
# You may specify a different file in the first argument:
#
@@ -250,7 +253,7 @@ module ActionView
#
# <%= favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png' %>
#
- def favicon_link_tag(source='/favicon.ico', options={})
+ def favicon_link_tag(source='favicon.ico', options={})
tag('link', {
:rel => 'shortcut icon',
:type => 'image/vnd.microsoft.icon',
@@ -276,6 +279,13 @@ module ActionView
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
+ # Computes the full URL to an image asset in the public images directory.
+ # This will use +image_path+ internally, so most of their behaviors will be the same.
+ def image_url(source)
+ URI.join(current_host, path_to_image(source)).to_s
+ end
+ alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route
+
# Computes the path to a video asset in the public videos directory.
# Full paths from the document root will be passed through.
# Used internally by +video_tag+ to build the video path.
@@ -291,6 +301,13 @@ module ActionView
end
alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
+ # Computes the full URL to a video asset in the public videos directory.
+ # This will use +video_path+ internally, so most of their behaviors will be the same.
+ def video_url(source)
+ URI.join(current_host, path_to_video(source)).to_s
+ end
+ alias_method :url_to_video, :video_url # aliased to avoid conflicts with an video_url named route
+
# Computes the path to an audio asset in the public audios directory.
# Full paths from the document root will be passed through.
# Used internally by +audio_tag+ to build the audio path.
@@ -306,6 +323,13 @@ module ActionView
end
alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
+ # Computes the full URL to a audio asset in the public audios directory.
+ # This will use +audio_path+ internally, so most of their behaviors will be the same.
+ def audio_url(source)
+ URI.join(current_host, path_to_audio(source)).to_s
+ end
+ alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route
+
# Computes the path to a font asset in the public fonts directory.
# Full paths from the document root will be passed through.
#
@@ -320,6 +344,13 @@ module ActionView
end
alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route
+ # Computes the full URL to a font asset in the public fonts directory.
+ # This will use +font_path+ internally, so most of their behaviors will be the same.
+ def font_url(source)
+ URI.join(current_host, path_to_font(source)).to_s
+ end
+ alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route
+
# Returns an html image tag for the +source+. The +source+ can be a full
# path or a file that exists in your public images directory.
#
@@ -353,8 +384,8 @@ module ActionView
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
# image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
- def image_tag(source, options = {})
- options.symbolize_keys!
+ def image_tag(source, options={})
+ options = options.symbolize_keys
src = options[:src] = path_to_image(source)
@@ -407,26 +438,19 @@ module ActionView
# <video src="/trailers/hd.avi" width="16" height="16" />
# video_tag("/trailers/hd.avi", :height => '32', :width => '32') # =>
# <video height="32" src="/trailers/hd.avi" width="32" />
+ # video_tag("trailer.ogg", "trailer.flv") # =>
+ # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
# video_tag(["trailer.ogg", "trailer.flv"]) # =>
- # <video><source src="trailer.ogg" /><source src="trailer.ogg" /><source src="trailer.flv" /></video>
- # video_tag(["trailer.ogg", "trailer.flv"] :size => "160x120") # =>
- # <video height="120" width="160"><source src="trailer.ogg" /><source src="trailer.flv" /></video>
- def video_tag(sources, options = {})
- options.symbolize_keys!
-
- options[:poster] = path_to_image(options[:poster]) if options[:poster]
-
- if size = options.delete(:size)
- options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
- end
+ # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
+ # video_tag(["trailer.ogg", "trailer.flv"], :size => "160x120") # =>
+ # <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
+ def video_tag(*sources)
+ multiple_sources_tag('video', sources) do |options|
+ options[:poster] = path_to_image(options[:poster]) if options[:poster]
- if sources.is_a?(Array)
- content_tag("video", options) do
- sources.map { |source| tag("source", :src => source) }.join.html_safe
+ if size = options.delete(:size)
+ options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
end
- else
- options[:src] = path_to_video(sources)
- tag("video", options)
end
end
@@ -441,10 +465,10 @@ module ActionView
# <audio src="/audios/sound.wav" />
# audio_tag("sound.wav", :autoplay => true, :controls => true) # =>
# <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
- def audio_tag(source, options = {})
- options.symbolize_keys!
- options[:src] = path_to_audio(source)
- tag("audio", options)
+ # audio_tag("sound.wav", "sound.mid") # =>
+ # <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
+ def audio_tag(*sources)
+ multiple_sources_tag('audio', sources)
end
private
@@ -452,6 +476,26 @@ module ActionView
def asset_paths
@asset_paths ||= AssetTagHelper::AssetPaths.new(config, controller)
end
+
+ def multiple_sources_tag(type, sources)
+ options = sources.extract_options!.symbolize_keys
+ sources.flatten!
+
+ yield options if block_given?
+
+ if sources.size > 1
+ content_tag(type, options) do
+ safe_join sources.map { |source| tag("source", :src => send("path_to_#{type}", source)) }
+ end
+ else
+ options[:src] = send("path_to_#{type}", sources.first)
+ content_tag(type, nil, options)
+ end
+ end
+
+ def current_host
+ url_for(:only_path => false)
+ end
end
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 dd4e9ae4cc..35f91cec18 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
@@ -1,5 +1,6 @@
require 'thread'
require 'active_support/core_ext/file'
+require 'active_support/core_ext/module/attribute_accessors'
module ActionView
module Helpers
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 d9f1f88ade..e3329c0345 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
@@ -16,7 +16,7 @@ module ActionView
end
def asset_tag(source, options)
- content_tag("script", "", { "type" => Mime::JS, "src" => path_to_asset(source) }.merge(options))
+ content_tag("script", "", { "src" => path_to_asset(source) }.merge(options))
end
def custom_dir
@@ -60,9 +60,9 @@ module ActionView
# ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"]
#
# javascript_include_tag :monkey # =>
- # <script type="text/javascript" src="/javascripts/head.js"></script>
- # <script type="text/javascript" src="/javascripts/body.js"></script>
- # <script type="text/javascript" src="/javascripts/tail.js"></script>
+ # <script src="/javascripts/head.js"></script>
+ # <script src="/javascripts/body.js"></script>
+ # <script src="/javascripts/tail.js"></script>
def register_javascript_expansion(expansions)
js_expansions = JavascriptIncludeTag.expansions
expansions.each do |key, values|
@@ -87,6 +87,13 @@ module ActionView
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
+ # Computes the full URL to a javascript asset in the public javascripts directory.
+ # This will use +javascript_path+ internally, so most of their behaviors will be the same.
+ def javascript_url(source)
+ URI.join(current_host, path_to_javascript(source)).to_s
+ end
+ alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
+
# Returns an HTML script tag for each of the +sources+ provided.
#
# Sources may be paths to JavaScript files. Relative paths are assumed to be relative
@@ -109,36 +116,36 @@ module ActionView
#
# ==== Examples
# javascript_include_tag "xmlhr"
- # # => <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
+ # # => <script src="/javascripts/xmlhr.js?1284139606"></script>
#
# javascript_include_tag "xmlhr.js"
- # # => <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
+ # # => <script src="/javascripts/xmlhr.js?1284139606"></script>
#
# javascript_include_tag "common.javascript", "/elsewhere/cools"
- # # => <script type="text/javascript" src="/javascripts/common.javascript?1284139606"></script>
- # # <script type="text/javascript" src="/elsewhere/cools.js?1423139606"></script>
+ # # => <script src="/javascripts/common.javascript?1284139606"></script>
+ # # <script src="/elsewhere/cools.js?1423139606"></script>
#
# javascript_include_tag "http://www.example.com/xmlhr"
- # # => <script type="text/javascript" src="http://www.example.com/xmlhr"></script>
+ # # => <script src="http://www.example.com/xmlhr"></script>
#
# javascript_include_tag "http://www.example.com/xmlhr.js"
- # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js"></script>
+ # # => <script src="http://www.example.com/xmlhr.js"></script>
#
# javascript_include_tag :defaults
- # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
- # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script>
- # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
+ # # => <script src="/javascripts/jquery.js?1284139606"></script>
+ # # <script src="/javascripts/rails.js?1284139606"></script>
+ # # <script src="/javascripts/application.js?1284139606"></script>
#
# * = The application.js file is only referenced if it exists
#
# You can also include all JavaScripts in the +javascripts+ directory using <tt>:all</tt> as the source:
#
# javascript_include_tag :all
- # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
- # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script>
- # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
- # # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
- # # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
+ # # => <script src="/javascripts/jquery.js?1284139606"></script>
+ # # <script src="/javascripts/rails.js?1284139606"></script>
+ # # <script src="/javascripts/application.js?1284139606"></script>
+ # # <script src="/javascripts/shop.js?1284139606"></script>
+ # # <script src="/javascripts/checkout.js?1284139606"></script>
#
# Note that your defaults of choice will be included first, so they will be available to all subsequently
# included files.
@@ -159,25 +166,25 @@ module ActionView
#
# # assuming config.perform_caching is false
# javascript_include_tag :all, :cache => true
- # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
- # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script>
- # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
- # # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
- # # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
+ # # => <script src="/javascripts/jquery.js?1284139606"></script>
+ # # <script src="/javascripts/rails.js?1284139606"></script>
+ # # <script src="/javascripts/application.js?1284139606"></script>
+ # # <script src="/javascripts/shop.js?1284139606"></script>
+ # # <script src="/javascripts/checkout.js?1284139606"></script>
#
# # assuming config.perform_caching is true
# javascript_include_tag :all, :cache => true
- # # => <script type="text/javascript" src="/javascripts/all.js?1344139789"></script>
+ # # => <script src="/javascripts/all.js?1344139789"></script>
#
# # assuming config.perform_caching is false
# javascript_include_tag "jquery", "cart", "checkout", :cache => "shop"
- # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
- # # <script type="text/javascript" src="/javascripts/cart.js?1289139157"></script>
- # # <script type="text/javascript" src="/javascripts/checkout.js?1299139816"></script>
+ # # => <script src="/javascripts/jquery.js?1284139606"></script>
+ # # <script src="/javascripts/cart.js?1289139157"></script>
+ # # <script src="/javascripts/checkout.js?1299139816"></script>
#
# # assuming config.perform_caching is true
# javascript_include_tag "jquery", "cart", "checkout", :cache => "shop"
- # # => <script type="text/javascript" src="/javascripts/shop.js?1299139816"></script>
+ # # => <script src="/javascripts/shop.js?1299139816"></script>
#
# The <tt>:recursive</tt> option is also available for caching:
#
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 41958c6559..4bcb8b9718 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
@@ -17,7 +17,7 @@ module ActionView
def asset_tag(source, options)
# We force the :request protocol here to avoid a double-download bug in IE7 and IE8
- tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => path_to_asset(source, :protocol => :request) }.merge(options))
+ tag("link", { "rel" => "stylesheet", "media" => "screen", "href" => path_to_asset(source, :protocol => :request) }.merge(options))
end
def custom_dir
@@ -38,9 +38,9 @@ module ActionView
# ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"]
#
# stylesheet_link_tag :monkey # =>
- # <link href="/stylesheets/head.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/head.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/body.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" />
def register_stylesheet_expansion(expansions)
style_expansions = StylesheetIncludeTag.expansions
expansions.each do |key, values|
@@ -65,6 +65,13 @@ module ActionView
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
+ # Computes the full URL to a stylesheet asset in the public stylesheets directory.
+ # This will use +stylesheet_path+ internally, so most of their behaviors will be the same.
+ def stylesheet_url(source)
+ URI.join(current_host, path_to_stylesheet(source)).to_s
+ end
+ alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route
+
# Returns a stylesheet link tag for the sources specified as arguments. If
# you don't specify an extension, <tt>.css</tt> will be appended automatically.
# You can modify the link attributes by passing a hash as the last argument.
@@ -74,30 +81,30 @@ module ActionView
#
# ==== Examples
# stylesheet_link_tag "style" # =>
- # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "style.css" # =>
- # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "http://www.example.com/style.css" # =>
- # <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "style", :media => "all" # =>
- # <link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style.css" media="all" rel="stylesheet" />
#
# stylesheet_link_tag "style", :media => "print" # =>
- # <link href="/stylesheets/style.css" media="print" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style.css" media="print" rel="stylesheet" />
#
# stylesheet_link_tag "random.styles", "/css/stylish" # =>
- # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" />
+ # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
#
# You can also include all styles in the stylesheets directory using <tt>:all</tt> as the source:
#
# stylesheet_link_tag :all # =>
- # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" />
#
# If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set <tt>:recursive</tt>:
#
@@ -106,26 +113,26 @@ module ActionView
# == Caching multiple stylesheets into one
#
# You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
- # compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching
+ # compressed by gzip (leading to faster transfers). Caching will only happen if +config.perform_caching+
# is set to true (which is the case by default for the Rails production environment, but not for the development
# environment). Examples:
#
# ==== Examples
# stylesheet_link_tag :all, :cache => true # when config.perform_caching is false =>
- # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag :all, :cache => true # when config.perform_caching is true =>
- # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is false =>
- # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is true =>
- # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" />
#
# The <tt>:recursive</tt> option is also available for caching:
#
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
index 73824dc1f8..f9aa8d7cee 100644
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -176,6 +176,7 @@ module ActionView
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
# * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
# * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
+ # * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
def entry(record, options = {})
@xml.entry do
@xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
@@ -188,7 +189,9 @@ module ActionView
@xml.updated((options[:updated] || record.updated_at).xmlschema)
end
- @xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:url] || @view.polymorphic_url(record))
+ type = options.fetch(:type, 'text/html')
+
+ @xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record))
yield AtomBuilder.new(@xml)
end
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 17bbfe2efd..d9d6f90211 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -96,7 +96,7 @@ module ActionView
# Please login!
#
# <% content_for :script do %>
- # <script type="text/javascript">alert('You are not authorized to view this page!')</script>
+ # <script>alert('You are not authorized to view this page!')</script>
# <% end %>
#
# Then, in another view, you could to do something like this:
@@ -215,7 +215,7 @@ module ActionView
def flush_output_buffer #:nodoc:
if output_buffer && !output_buffer.empty?
response.body_parts << output_buffer
- self.output_buffer = output_buffer[0,0]
+ self.output_buffer = output_buffer.respond_to?(:clone_empty) ? output_buffer.clone_empty : output_buffer[0, 0]
nil
end
end
diff --git a/actionpack/lib/action_view/helpers/csrf_helper.rb b/actionpack/lib/action_view/helpers/csrf_helper.rb
index 1f2bc28cac..eeb0ed94b9 100644
--- a/actionpack/lib/action_view/helpers/csrf_helper.rb
+++ b/actionpack/lib/action_view/helpers/csrf_helper.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/string/strip'
-
module ActionView
# = Action View CSRF Helper
module Helpers
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 2806348337..6bd8e62e0d 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -12,14 +12,14 @@ module ActionView
# elements. All of the select-type methods share a number of common options that are as follows:
#
# * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday"
- # would give birthday[month] instead of date[month] if passed to the <tt>select_month</tt> method.
+ # would give \birthday[month] instead of \date[month] if passed to the <tt>select_month</tt> method.
# * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date.
# * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true,
# the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead
- # of "date[month]".
+ # of \date[month].
module DateHelper
# Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds.
- # Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs.
+ # Pass <tt>:include_seconds => true</tt> if you want more detailed approximations when distance < 1 min, 29 secs.
# Distances are reported based on the following table:
#
# 0 <-> 29 secs # => less than a minute
@@ -29,14 +29,15 @@ module ActionView
# 89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
# 23 hrs, 59 mins, 30 secs <-> 41 hrs, 59 mins, 29 secs # => 1 day
# 41 hrs, 59 mins, 30 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
- # 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
+ # 29 days, 23 hrs, 59 mins, 30 secs <-> 44 days, 23 hrs, 59 mins, 29 secs # => about 1 month
+ # 44 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 2 months
# 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
# 1 yr <-> 1 yr, 3 months # => about 1 year
# 1 yr, 3 months <-> 1 yr, 9 months # => over 1 year
# 1 yr, 9 months <-> 2 yr minus 1 sec # => almost 2 years
# 2 yrs <-> max time or date # => (same rules as 1 yr)
#
- # With <tt>include_seconds</tt> = true and the difference < 1 minute 29 seconds:
+ # With <tt>:include_seconds => true</tt> and the difference < 1 minute 29 seconds:
# 0-4 secs # => less than 5 seconds
# 5-9 secs # => less than 10 seconds
# 10-19 secs # => less than 20 seconds
@@ -46,36 +47,45 @@ module ActionView
#
# ==== Examples
# from_time = Time.now
- # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
- # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
- # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
- # distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
- # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years
- # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days
- # distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute
- # distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
- # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
- # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
- # distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years
+ # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
+ # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
+ # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
+ # distance_of_time_in_words(from_time, from_time + 15.seconds, :include_seconds => true) # => less than 20 seconds
+ # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years
+ # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days
+ # distance_of_time_in_words(from_time, from_time + 45.seconds, :include_seconds => true) # => less than a minute
+ # distance_of_time_in_words(from_time, from_time - 45.seconds, :include_seconds => true) # => less than a minute
+ # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
+ # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
+ # distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years
# distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years
#
# to_time = Time.now + 6.years + 19.days
- # distance_of_time_in_words(from_time, to_time, true) # => about 6 years
- # distance_of_time_in_words(to_time, from_time, true) # => about 6 years
- # distance_of_time_in_words(Time.now, Time.now) # => less than a minute
- #
- def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {})
+ # distance_of_time_in_words(from_time, to_time, :include_seconds => true) # => about 6 years
+ # distance_of_time_in_words(to_time, from_time, :include_seconds => true) # => about 6 years
+ # distance_of_time_in_words(Time.now, Time.now) # => less than a minute
+ #
+ def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {})
+ unless include_seconds_or_options.is_a?(Hash)
+ ActiveSupport::Deprecation.warn "distance_of_time_in_words and time_ago_in_words now accept :include_seconds " +
+ "as a part of options hash, not a boolean argument", caller
+ options[:include_seconds] ||= !!include_seconds_or_options
+ else
+ options = include_seconds_or_options
+ end
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
- distance_in_minutes = (((to_time - from_time).abs)/60).round
- distance_in_seconds = ((to_time - from_time).abs).round
+ from_time, to_time = to_time, from_time if from_time > to_time
+ distance_in_minutes = ((to_time - from_time)/60.0).round
+ distance_in_seconds = (to_time - from_time).round
I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
case distance_in_minutes
when 0..1
return distance_in_minutes == 0 ?
locale.t(:less_than_x_minutes, :count => 1) :
- locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds
+ locale.t(:x_minutes, :count => distance_in_minutes) unless options[:include_seconds]
case distance_in_seconds
when 0..4 then locale.t :less_than_x_seconds, :count => 5
@@ -86,26 +96,35 @@ module ActionView
else locale.t :x_minutes, :count => 1
end
- when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
- when 45..89 then locale.t :about_x_hours, :count => 1
- when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
- when 1440..2519 then locale.t :x_days, :count => 1
- when 2520..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
- when 43200..86399 then locale.t :about_x_months, :count => 1
- when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
+ when 2...45 then locale.t :x_minutes, :count => distance_in_minutes
+ when 45...90 then locale.t :about_x_hours, :count => 1
+ # 90 mins up to 24 hours
+ when 90...1440 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
+ # 24 hours up to 42 hours
+ when 1440...2520 then locale.t :x_days, :count => 1
+ # 42 hours up to 30 days
+ when 2520...43200 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
+ # 30 days up to 60 days
+ when 43200...86400 then locale.t :about_x_months, :count => (distance_in_minutes.to_f / 43200.0).round
+ # 60 days up to 365 days
+ when 86400...525600 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
else
- fyear = from_time.year
- fyear += 1 if from_time.month >= 3
- tyear = to_time.year
- tyear -= 1 if to_time.month < 3
- leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
- minute_offset_for_leap_year = leap_years * 1440
- # Discount the leap year days when calculating year distance.
- # e.g. if there are 20 leap year days between 2 dates having the same day
- # and month then the based on 365 days calculation
- # the distance in years will come out to over 80 years when in written
- # english it would read better as about 80 years.
- minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
+ if from_time.acts_like?(:time) && to_time.acts_like?(:time)
+ fyear = from_time.year
+ fyear += 1 if from_time.month >= 3
+ tyear = to_time.year
+ tyear -= 1 if to_time.month < 3
+ leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
+ minute_offset_for_leap_year = leap_years * 1440
+ # Discount the leap year days when calculating year distance.
+ # e.g. if there are 20 leap year days between 2 dates having the same day
+ # and month then the based on 365 days calculation
+ # the distance in years will come out to over 80 years when in written
+ # english it would read better as about 80 years.
+ minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
+ else
+ minutes_with_offset = distance_in_minutes
+ end
remainder = (minutes_with_offset % 525600)
distance_in_years = (minutes_with_offset / 525600)
if remainder < 131400
@@ -122,15 +141,16 @@ module ActionView
# Like <tt>distance_of_time_in_words</tt>, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
#
# ==== Examples
- # time_ago_in_words(3.minutes.from_now) # => 3 minutes
- # time_ago_in_words(Time.now - 15.hours) # => about 15 hours
- # time_ago_in_words(Time.now) # => less than a minute
+ # time_ago_in_words(3.minutes.from_now) # => 3 minutes
+ # time_ago_in_words(Time.now - 15.hours) # => about 15 hours
+ # time_ago_in_words(Time.now) # => less than a minute
+ # time_ago_in_words(Time.now, :include_seconds => true) # => less than 5 seconds
#
# from_time = Time.now - 3.days - 14.minutes - 25.seconds
# time_ago_in_words(from_time) # => 3 days
#
- def time_ago_in_words(from_time, include_seconds = false)
- distance_of_time_in_words(from_time, Time.now, include_seconds)
+ def time_ago_in_words(from_time, include_seconds_or_options = {})
+ distance_of_time_in_words(from_time, Time.now, include_seconds_or_options)
end
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
@@ -142,6 +162,8 @@ module ActionView
# ==== Options
# * <tt>:use_month_numbers</tt> - Set to true if you want to use month numbers rather than month names (e.g.
# "2" instead of "February").
+ # * <tt>:use_two_digit_numbers</tt> - Set to true if you want to display two digit month and day numbers (e.g.
+ # "02" instead of "February" and "08" instead of "8").
# * <tt>:use_short_month</tt> - Set to true if you want to use abbreviated month names instead of full
# month names (e.g. "Feb" instead of "February").
# * <tt>:add_month_numbers</tt> - Set to true if you want to use both month numbers and month names (e.g.
@@ -189,6 +211,10 @@ module ActionView
# date_select("article", "written_on", :start_year => 1995, :use_month_numbers => true,
# :discard_day => true, :include_blank => true)
#
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
+ # # with two digit numbers used for months and days.
+ # date_select("article", "written_on", :use_two_digit_numbers => true)
+ #
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
# # with the fields ordered as day, month, year rather than month, day, year.
# date_select("article", "written_on", :order => [:day, :month, :year])
@@ -213,7 +239,7 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
# all month choices are valid.
def date_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
+ Tags::DateSelect.new(object_name, method, self, options, html_options).render
end
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a
@@ -251,7 +277,7 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
# all month choices are valid.
def time_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options)
+ Tags::TimeSelect.new(object_name, method, self, options, html_options).render
end
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a
@@ -287,7 +313,7 @@ module ActionView
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
def datetime_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
+ Tags::DatetimeSelect.new(object_name, method, self, options, html_options).render
end
# Returns a set of html select-tags (one for year, month, day, hour, minute, and second) pre-selected with the
@@ -502,6 +528,7 @@ module ActionView
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
# The <tt>date</tt> can also be substituted for a day number.
+ # If you want to display days with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true.
# Override the field name using the <tt>:field_name</tt> option, 'day' by default.
#
# ==== Examples
@@ -513,6 +540,9 @@ module ActionView
# # Generates a select field for days that defaults to the number given.
# select_day(5)
#
+ # # Generates a select field for days that defaults to the number given, but displays it with two digits.
+ # select_day(5, :use_two_digit_numbers => true)
+ #
# # Generates a select field for days that defaults to the day for the date in my_date
# # that is named 'due' rather than 'day'.
# select_day(my_time, :field_name => 'due')
@@ -532,6 +562,7 @@ module ActionView
# want both numbers and names, set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer
# to show month names as abbreviations, set the <tt>:use_short_month</tt> key in +options+ to true. If you want
# to use your own month names, set the <tt>:use_month_names</tt> key in +options+ to an array of 12 month names.
+ # If you want to display months with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true.
# Override the field name using the <tt>:field_name</tt> option, 'month' by default.
#
# ==== Examples
@@ -559,6 +590,10 @@ module ActionView
# # will use keys like "Januar", "Marts."
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
#
+ # # Generates a select field for months that defaults to the current month that
+ # # will use keys with two digit numbers like "01", "03".
+ # select_month(Date.today, :use_two_digit_numbers => true)
+ #
# # Generates a select field for months with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
# select_month(14, :prompt => 'Choose month')
@@ -610,13 +645,18 @@ module ActionView
# time_tag Date.today, :pubdate => true # =>
# <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
#
- def time_tag(date_or_time, *args)
+ # <%= time_tag Time.now do %>
+ # <span>Right now</span>
+ # <% end %>
+ # # => <time datetime="2010-11-04T17:55:45+01:00"><span>Right now</span></time>
+ #
+ def time_tag(date_or_time, *args, &block)
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))
+ content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block)
end
end
@@ -654,11 +694,7 @@ module ActionView
@options[:discard_minute] ||= true if @options[:discard_hour]
@options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute]
- # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
- # valid (otherwise it could be 31 and February wouldn't be a valid date)
- if @datetime && @options[:discard_day] && !@options[:discard_month]
- @datetime = @datetime.change(:day => 1)
- end
+ set_day_if_discarded
if @options[:tag] && @options[:ignore_date]
select_time
@@ -681,11 +717,7 @@ module ActionView
@options[:discard_month] ||= true unless order.include?(:month)
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
- # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
- # valid (otherwise it could be 31 and February wouldn't be a valid date)
- if @datetime && @options[:discard_day] && !@options[:discard_month]
- @datetime = @datetime.change(:day => 1)
- end
+ set_day_if_discarded
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
@@ -734,7 +766,7 @@ module ActionView
def select_day
if @options[:use_hidden] || @options[:discard_day]
- build_hidden(:day, day)
+ build_hidden(:day, day || 1)
else
build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false, :use_two_digit_numbers => @options[:use_two_digit_numbers])
end
@@ -742,7 +774,7 @@ module ActionView
def select_month
if @options[:use_hidden] || @options[:discard_month]
- build_hidden(:month, month)
+ build_hidden(:month, month || 1)
else
month_options = []
1.upto(12) do |month_number|
@@ -756,7 +788,7 @@ module ActionView
def select_year
if !@datetime || @datetime == 0
- val = ''
+ val = '1'
middle_year = Date.today.year
else
val = middle_year = year
@@ -783,7 +815,15 @@ module ActionView
private
%w( sec min hour day month year ).each do |method|
define_method(method) do
- @datetime.kind_of?(Fixnum) ? @datetime : @datetime.send(method) if @datetime
+ @datetime.kind_of?(Numeric) ? @datetime : @datetime.send(method) if @datetime
+ end
+ end
+
+ # If the day is hidden, the day should be set to the 1st so all month and year choices are
+ # valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid.
+ def set_day_if_discarded
+ if @datetime && @options[:discard_day]
+ @datetime = @datetime.change(:day => 1)
end
end
@@ -817,6 +857,9 @@ module ActionView
# If <tt>:use_month_numbers</tt> option is passed
# month_name(1) => 1
#
+ # If <tt>:use_two_month_numbers</tt> option is passed
+ # month_name(1) => '01'
+ #
# If <tt>:add_month_numbers</tt> option is passed
# month_name(1) => "1 - January"
def month_name(number)
@@ -836,7 +879,15 @@ module ActionView
end
def translated_date_order
- I18n.translate(:'date.order', :locale => @options[:locale]) || []
+ date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => [])
+
+ forbidden_elements = date_order - [:year, :month, :day]
+ if forbidden_elements.any?
+ raise StandardError,
+ "#{@options[:locale]}.date.order only accepts :year, :month and :day"
+ end
+
+ date_order
end
# Build full select tag from date type and options.
@@ -850,6 +901,12 @@ module ActionView
# <option value="2">2</option>
# <option value="3">3</option>..."
#
+ # If <tt>:use_two_digit_numbers => true</tt> option is passed
+ # build_options(15, :start => 1, :end => 31, :use_two_digit_numbers => true)
+ # => "<option value="1">01</option>
+ # <option value="2">02</option>
+ # <option value="3">03</option>..."
+ #
# If <tt>:step</tt> options is passed
# build_options(15, :start => 1, :end => 31, :step => 2)
# => "<option value="1">1</option>
@@ -940,7 +997,10 @@ module ActionView
# Returns the id attribute for the input tag.
# => "post_written_on_1i"
def input_id_from_type(type)
- input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
+ id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
+ id = @options[:namespace] + '_' + id if @options[:namespace]
+
+ id
end
# Given an ordering of datetime components, create the selection HTML
@@ -957,83 +1017,19 @@ module ActionView
# Returns the separator for a given datetime component.
def separator(type)
+ return "" if @options[:use_hidden]
+
case type
- when :year
- @options[:discard_year] ? "" : @options[:date_separator]
- when :month
- @options[:discard_month] ? "" : @options[:date_separator]
- when :day
- @options[:discard_day] ? "" : @options[:date_separator]
+ when :year, :month, :day
+ @options[:"discard_#{type}"] ? "" : @options[:date_separator]
when :hour
(@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
- when :minute
- @options[:discard_minute] ? "" : @options[:time_separator]
- when :second
- @options[:include_seconds] ? @options[:time_separator] : ""
+ when :minute, :second
+ @options[:"discard_#{type}"] ? "" : @options[:time_separator]
end
end
end
- module DateHelperInstanceTag
- def to_date_select_tag(options = {}, html_options = {})
- datetime_selector(options, html_options).select_date.html_safe
- end
-
- def to_time_select_tag(options = {}, html_options = {})
- datetime_selector(options, html_options).select_time.html_safe
- end
-
- def to_datetime_select_tag(options = {}, html_options = {})
- datetime_selector(options, html_options).select_datetime.html_safe
- end
-
- private
- def datetime_selector(options, html_options)
- datetime = value(object) || default_datetime(options)
- @auto_index ||= nil
-
- options = options.dup
- options[:field_name] = @method_name
- options[:include_position] = true
- options[:prefix] ||= @object_name
- options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
-
- DateTimeSelector.new(datetime, options, html_options)
- end
-
- def default_datetime(options)
- return if options[:include_blank] || options[:prompt]
-
- case options[:default]
- when nil
- Time.current
- when Date, Time
- options[:default]
- else
- default = options[:default].dup
-
- # Rename :minute and :second to :min and :sec
- default[:min] ||= default[:minute]
- default[:sec] ||= default[:second]
-
- time = Time.current
-
- [:year, :month, :day, :hour, :min, :sec].each do |key|
- default[key] ||= time.send(key)
- end
-
- Time.utc_time(
- default[:year], default[:month], default[:day],
- default[:hour], default[:min], default[:sec]
- )
- end
- end
- end
-
- class InstanceTag #:nodoc:
- include DateHelperInstanceTag
- end
-
class FormBuilder
def date_select(method, options = {}, html_options = {})
@template.date_select(@object_name, method, objectify_options(options), html_options)
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index ca2eb1ac10..67f2abe509 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -3,11 +3,14 @@ require 'action_view/helpers/date_helper'
require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
require 'action_view/helpers/active_model_helper'
+require 'action_view/helpers/tags'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/array/extract_options'
+require 'active_support/deprecation'
+require 'active_support/core_ext/string/inflections'
module ActionView
# = Action View Form Helpers
@@ -15,17 +18,28 @@ module ActionView
# Form helpers are designed to make working with resources much easier
# compared to using vanilla HTML.
#
- # Forms for models are created with +form_for+. That method yields a form
- # builder that knows the model the form is about. The form builder is thus
- # able to generate default values for input fields that correspond to model
- # attributes, and also convenient names, IDs, endpoints, etc.
+ # Typically, a form designed to create or update a resource reflects the
+ # identity of the resource in several ways: (i) the url that the form is
+ # sent to (the form element's +action+ attribute) should result in a request
+ # being routed to the appropriate controller action (with the appropriate <tt>:id</tt>
+ # parameter in the case of an existing resource), (ii) input fields should
+ # be named in such a way that in the controller their values appear in the
+ # appropriate places within the +params+ hash, and (iii) for an existing record,
+ # when the form is initially displayed, input fields corresponding to attributes
+ # of the resource should show the current values of those attributes.
#
- # Conventions in the generated field names allow controllers to receive form
- # data nicely structured in +params+ with no effort on your side.
+ # In Rails, this is usually achieved by creating the form using +form_for+ and
+ # a number of related helper methods. +form_for+ generates an appropriate <tt>form</tt>
+ # tag and yields a form builder object that knows the model the form is about.
+ # Input fields are created by calling methods defined on the form builder, which
+ # means they are able to generate the appropriate names and default values
+ # corresponding to the model attributes, as well as convenient IDs, etc.
+ # Conventions in the generated field names allow controllers to receive form data
+ # nicely structured in +params+ with no effort on your side.
#
# For example, to create a new person you typically set up a new instance of
# +Person+ in the <tt>PeopleController#new</tt> action, <tt>@person</tt>, and
- # pass it to +form_for+:
+ # in the view template pass that object to +form_for+:
#
# <%= form_for @person do |f| %>
# <%= f.label :first_name %>:
@@ -44,10 +58,10 @@ module ActionView
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
# </div>
# <label for="person_first_name">First name</label>:
- # <input id="person_first_name" name="person[first_name]" size="30" type="text" /><br />
+ # <input id="person_first_name" name="person[first_name]" type="text" /><br />
#
# <label for="person_last_name">Last name</label>:
- # <input id="person_last_name" name="person[last_name]" size="30" type="text" /><br />
+ # <input id="person_last_name" name="person[last_name]" type="text" /><br />
#
# <input name="commit" type="submit" value="Create Person" />
# </form>
@@ -75,10 +89,10 @@ module ActionView
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
# </div>
# <label for="person_first_name">First name</label>:
- # <input id="person_first_name" name="person[first_name]" size="30" type="text" value="John" /><br />
+ # <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br />
#
# <label for="person_last_name">Last name</label>:
- # <input id="person_last_name" name="person[last_name]" size="30" type="text" value="Smith" /><br />
+ # <input id="person_last_name" name="person[last_name]" type="text" value="Smith" /><br />
#
# <input name="commit" type="submit" value="Update Person" />
# </form>
@@ -108,29 +122,14 @@ module ActionView
object.respond_to?(:to_model) ? object.to_model : object
end
- # Creates a form and a scope around a specific model object that is used
- # as a base for questioning about values for the fields.
+ # Creates a form that allows the user to create or update the attributes
+ # of a specific model object.
#
- # Rails provides succinct resource-oriented form generation with +form_for+
- # like this:
- #
- # <%= form_for @offer do |f| %>
- # <%= f.label :version, 'Version' %>:
- # <%= f.text_field :version %><br />
- # <%= f.label :author, 'Author' %>:
- # <%= f.text_field :author %><br />
- # <%= f.submit %>
- # <% end %>
- #
- # There, +form_for+ is able to generate the rest of RESTful form
- # parameters based on introspection on the record, but to understand what
- # it does we need to dig first into the alternative generic usage it is
- # based upon.
- #
- # === Generic form_for
- #
- # The generic way to call +form_for+ yields a form builder around a
- # model:
+ # The method can be used in several slightly different ways, depending on
+ # how much you wish to rely on Rails to infer automatically from the model
+ # how the form should be constructed. For a generic model object, a form
+ # can be created by passing +form_for+ a string or symbol representing
+ # the object we are concerned with:
#
# <%= form_for :person do |f| %>
# First name: <%= f.text_field :first_name %><br />
@@ -140,24 +139,39 @@ module ActionView
# <%= f.submit %>
# <% end %>
#
- # There, the argument is a symbol or string with the name of the
- # object the form is about.
- #
- # The form builder acts as a regular form helper that somehow carries the
- # model. Thus, the idea is that
+ # The variable +f+ yielded to the block is a FormBuilder object that
+ # incorporates the knowledge about the model object represented by
+ # <tt>:person</tt> passed to +form_for+. Methods defined on the FormBuilder
+ # are used to generate fields bound to this model. Thus, for example,
#
# <%= f.text_field :first_name %>
#
- # gets expanded to
+ # will get expanded to
#
# <%= text_field :person, :first_name %>
+ # which results in an html <tt><input></tt> tag whose +name+ attribute is
+ # <tt>person[first_name]</tt>. This means that when the form is submitted,
+ # the value entered by the user will be available in the controller as
+ # <tt>params[:person][:first_name]</tt>.
+ #
+ # For fields generated in this way using the FormBuilder,
+ # if <tt>:person</tt> also happens to be the name of an instance variable
+ # <tt>@person</tt>, the default value of the field shown when the form is
+ # initially displayed (e.g. in the situation where you are editing an
+ # existing record) will be the value of the corresponding attribute of
+ # <tt>@person</tt>.
#
# The rightmost argument to +form_for+ is an
- # optional hash of options:
- #
- # * <tt>:url</tt> - The URL the form is submitted to. It takes the same
- # fields you pass to +url_for+ or +link_to+. In particular you may pass
- # here a named route directly as well. Defaults to the current action.
+ # optional hash of options -
+ #
+ # * <tt>:url</tt> - The URL the form is to be submitted to. This may be
+ # represented in the same way as values passed to +url_for+ or +link_to+.
+ # So for example you may use a named route directly. When the model is
+ # represented by a string or symbol, as in the example above, if the
+ # <tt>:url</tt> option is not specified, by default the form will be
+ # sent back to the current url (We will describe below an alternative
+ # resource-oriented usage of +form_for+ in which the URL does not need
+ # to be specified explicitly).
# * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
# id attributes on form elements. The namespace attribute will be prefixed
# with underscore on the generated HTML id.
@@ -167,11 +181,11 @@ module ActionView
# possible to use both the stand-alone FormHelper methods and methods
# from FormTagHelper. For example:
#
- # <%= form_for @person do |f| %>
+ # <%= form_for :person do |f| %>
# First name: <%= f.text_field :first_name %>
# Last name : <%= f.text_field :last_name %>
# Biography : <%= text_area :person, :biography %>
- # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
+ # Admin? : <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
# <%= f.submit %>
# <% end %>
#
@@ -179,26 +193,65 @@ module ActionView
# are designed to work with an object as base, like
# FormOptionHelper#collection_select and DateHelper#datetime_select.
#
- # === Resource-oriented style
+ # === #form_for with a model object
#
- # As we said above, in addition to manually configuring the +form_for+
- # call, you can rely on automated resource identification, which will use
- # the conventions and named routes of that approach. This is the
- # preferred way to use +form_for+ nowadays.
+ # In the examples above, the object to be created or edited was
+ # represented by a symbol passed to +form_for+, and we noted that
+ # a string can also be used equivalently. It is also possible, however,
+ # to pass a model object itself to +form_for+. For example, if <tt>@post</tt>
+ # is an existing record you wish to edit, you can create the form using
#
- # For example, if <tt>@post</tt> is an existing record you want to edit
+ # <%= form_for @post do |f| %>
+ # ...
+ # <% end %>
+ #
+ # This behaves in almost the same way as outlined previously, with a
+ # couple of small exceptions. First, the prefix used to name the input
+ # elements within the form (hence the key that denotes them in the +params+
+ # hash) is actually derived from the object's _class_, e.g. <tt>params[:post]</tt>
+ # if the object's class is +Post+. However, this can be overwritten using
+ # the <tt>:as</tt> option, e.g. -
+ #
+ # <%= form_for(@person, :as => :client) do |f| %>
+ # ...
+ # <% end %>
+ #
+ # would result in <tt>params[:client]</tt>.
+ #
+ # Secondly, the field values shown when the form is initially displayed
+ # are taken from the attributes of the object passed to +form_for+,
+ # regardless of whether the object is an instance
+ # variable. So, for example, if we had a _local_ variable +post+
+ # representing an existing record,
+ #
+ # <%= form_for post do |f| %>
+ # ...
+ # <% end %>
+ #
+ # would produce a form with fields whose initial state reflect the current
+ # values of the attributes of +post+.
+ #
+ # === Resource-oriented style
+ #
+ # In the examples just shown, although not indicated explicitly, we still
+ # need to use the <tt>:url</tt> option in order to specify where the
+ # form is going to be sent. However, further simplification is possible
+ # if the record passed to +form_for+ is a _resource_, i.e. it corresponds
+ # to a set of RESTful routes, e.g. defined using the +resources+ method
+ # in <tt>config/routes.rb</tt>. In this case Rails will simply infer the
+ # appropriate URL from the record itself. For example,
#
# <%= form_for @post do |f| %>
# ...
# <% end %>
#
- # is equivalent to something like:
+ # is then equivalent to something like:
#
# <%= form_for @post, :as => :post, :url => post_path(@post), :method => :put, :html => { :class => "edit_post", :id => "edit_post_45" } do |f| %>
# ...
# <% end %>
#
- # And for new records
+ # And for a new record
#
# <%= form_for(Post.new) do |f| %>
# ...
@@ -210,7 +263,7 @@ module ActionView
# ...
# <% end %>
#
- # You can also overwrite the individual conventions, like this:
+ # However you can still overwrite individual conventions, such as:
#
# <%= form_for(@post, :url => super_posts_path) do |f| %>
# ...
@@ -222,13 +275,6 @@ module ActionView
# ...
# <% end %>
#
- # If you have an object that needs to be represented as a different
- # parameter, like a Person that acts as a Client:
- #
- # <%= form_for(@person, :as => :client) do |f| %>
- # ...
- # <% end %>
- #
# For namespaced routes, like +admin_post_url+:
#
# <%= form_for([:admin, @post]) do |f| %>
@@ -249,11 +295,11 @@ module ActionView
#
# You can force the form to use the full array of HTTP verbs by setting
#
- # :method => (:get|:post|:put|:delete)
+ # :method => (:get|:post|:patch|:put|:delete)
#
- # in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the
- # form will be set to POST and a hidden input called _method will carry the intended verb for the server
- # to interpret.
+ # in the options hash. If the verb is not GET or POST, which are natively
+ # supported by HTML forms, the form will be set to POST and a hidden input
+ # called _method will carry the intended verb for the server to interpret.
#
# === Unobtrusive JavaScript
#
@@ -372,19 +418,19 @@ module ActionView
options[:html][:method] = options.delete(:method) if options.has_key?(:method)
options[:html][:authenticity_token] = options.delete(:authenticity_token)
- builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &proc)
+ builder = options[:parent_builder] = instantiate_builder(object_name, object, options)
fields_for = fields_for(object_name, object, options, &proc)
default_options = builder.multipart? ? { :multipart => true } : {}
- output = form_tag(options.delete(:url) || {}, default_options.merge!(options.delete(:html)))
- output << fields_for
- output.safe_concat('</form>')
+ default_options.merge!(options.delete(:html))
+
+ form_tag(options.delete(:url) || {}, default_options) { fields_for }
end
def apply_form_for_options!(record, object, options) #:nodoc:
object = convert_to_model(object)
as = options[:as]
- action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
+ action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post]
options[:html].reverse_merge!(
:class => as ? "#{action}_#{as}" : dom_class(object, action),
:id => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
@@ -401,30 +447,59 @@ module ActionView
#
# === Generic Examples
#
+ # Although the usage and purpose of +field_for+ is similar to +form_for+'s,
+ # its method signature is slightly different. Like +form_for+, it yields
+ # a FormBuilder object associated with a particular model object to a block,
+ # and within the block allows methods to be called on the builder to
+ # generate fields associated with the model object. Fields may reflect
+ # a model object in two ways - how they are named (hence how submitted
+ # values appear within the +params+ hash in the controller) and what
+ # default values are shown when the form the fields appear in is first
+ # displayed. In order for both of these features to be specified independently,
+ # both an object name (represented by either a symbol or string) and the
+ # object itself can be passed to the method separately -
+ #
# <%= form_for @person do |person_form| %>
# First name: <%= person_form.text_field :first_name %>
# Last name : <%= person_form.text_field :last_name %>
#
- # <%= fields_for @person.permission do |permission_fields| %>
+ # <%= fields_for :permission, @person.permission do |permission_fields| %>
# Admin? : <%= permission_fields.check_box :admin %>
# <% end %>
#
# <%= f.submit %>
# <% end %>
#
- # ...or if you have an object that needs to be represented as a different
- # parameter, like a Client that acts as a Person:
+ # In this case, the checkbox field will be represented by an HTML +input+
+ # tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted
+ # value will appear in the controller as <tt>params[:permission][:admin]</tt>.
+ # If <tt>@person.permission</tt> is an existing record with an attribute
+ # +admin+, the initial state of the checkbox when first displayed will
+ # reflect the value of <tt>@person.permission.admin</tt>.
+ #
+ # Often this can be simplified by passing just the name of the model
+ # object to +fields_for+ -
#
- # <%= fields_for :person, @client do |permission_fields| %>
+ # <%= fields_for :permission do |permission_fields| %>
# Admin?: <%= permission_fields.check_box :admin %>
# <% end %>
#
- # ...or if you don't have an object, just a name of the parameter:
+ # ...in which case, if <tt>:permission</tt> also happens to be the name of an
+ # instance variable <tt>@permission</tt>, the initial state of the input
+ # field will reflect the value of that variable's attribute <tt>@permission.admin</tt>.
#
- # <%= fields_for :person do |permission_fields| %>
+ # Alternatively, you can pass just the model object itself (if the first
+ # argument isn't a string or symbol +fields_for+ will realize that the
+ # name has been omitted) -
+ #
+ # <%= fields_for @person.permission do |permission_fields| %>
# Admin?: <%= permission_fields.check_box :admin %>
# <% end %>
#
+ # and +fields_for+ will derive the required name of the field from the
+ # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
+ # of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
+ #
# Note: This also works for the methods in FormOptionHelper and
# DateHelper that are designed to work with an object as base, like
# FormOptionHelper#collection_select and DateHelper#datetime_select.
@@ -599,8 +674,21 @@ module ActionView
# <% end %>
# ...
# <% end %>
+ #
+ # When a collection is used you might want to know the index of each
+ # object into the array. For this purpose, the <tt>index</tt> method
+ # is available in the FormBuilder object.
+ #
+ # <%= form_for @person do |person_form| %>
+ # ...
+ # <%= person_form.fields_for :projects do |project_fields| %>
+ # Project #<%= project_fields.index %>
+ # ...
+ # <% end %>
+ # ...
+ # <% end %>
def fields_for(record_name, record_object = nil, options = {}, &block)
- builder = instantiate_builder(record_name, record_object, options, &block)
+ builder = instantiate_builder(record_name, record_object, options)
output = capture(builder, &block)
output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
output
@@ -654,16 +742,7 @@ module ActionView
# 'Accept <a href="/terms">Terms</a>.'.html_safe
# end
def label(object_name, method, content_or_options = nil, options = nil, &block)
- content_is_options = content_or_options.is_a?(Hash)
- if content_is_options || block_given?
- options = content_or_options if content_is_options
- text = nil
- else
- text = content_or_options
- end
-
- options ||= {}
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options, &block)
+ Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
end
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -685,7 +764,7 @@ module ActionView
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
#
def text_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
+ Tags::TextField.new(object_name, method, self, options).render
end
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -707,7 +786,7 @@ module ActionView
# # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
#
def password_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", { :value => nil }.merge!(options))
+ Tags::PasswordField.new(object_name, method, self, options).render
end
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -725,7 +804,7 @@ module ActionView
# hidden_field(:user, :token)
# # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
def hidden_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
+ Tags::HiddenField.new(object_name, method, self, options).render
end
# Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -746,7 +825,7 @@ module ActionView
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
#
def file_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options.update({:size => nil}))
+ Tags::FileField.new(object_name, method, self, options).render
end
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@@ -774,7 +853,7 @@ module ActionView
# # #{@entry.body}
# # </textarea>
def text_area(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
+ Tags::TextArea.new(object_name, method, self, options).render
end
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -824,7 +903,7 @@ module ActionView
# # Let's say that @post.validated? is 1:
# check_box("post", "validated")
# # => <input name="post[validated]" type="hidden" value="0" />
- # # <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
+ # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
#
# # Let's say that @puppy.gooddog is "no":
# check_box("puppy", "gooddog", {}, "yes", "no")
@@ -836,7 +915,7 @@ module ActionView
# # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
#
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
+ Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render
end
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
@@ -858,7 +937,7 @@ module ActionView
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
def radio_button(object_name, method, tag_value, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
+ Tags::RadioButton.new(object_name, method, self, tag_value, options).render
end
# Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
@@ -868,64 +947,69 @@ module ActionView
# ==== Examples
#
# search_field(:user, :name)
- # # => <input id="user_name" name="user[name]" size="30" type="search" />
+ # # => <input id="user_name" name="user[name]" type="search" />
# search_field(:user, :name, :autosave => false)
- # # => <input autosave="false" id="user_name" name="user[name]" size="30" type="search" />
+ # # => <input autosave="false" id="user_name" name="user[name]" type="search" />
# search_field(:user, :name, :results => 3)
- # # => <input id="user_name" name="user[name]" results="3" size="30" type="search" />
+ # # => <input id="user_name" name="user[name]" results="3" type="search" />
# # Assume request.host returns "www.example.com"
# search_field(:user, :name, :autosave => true)
- # # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" size="30" type="search" />
+ # # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" type="search" />
# search_field(:user, :name, :onsearch => true)
- # # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" />
+ # # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" />
# search_field(:user, :name, :autosave => false, :onsearch => true)
- # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" />
+ # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" />
# search_field(:user, :name, :autosave => true, :onsearch => true)
- # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" size="30" type="search" />
+ # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" type="search" />
#
def search_field(object_name, method, options = {})
- options = options.stringify_keys
-
- if options["autosave"]
- if options["autosave"] == true
- options["autosave"] = request.host.split(".").reverse.join(".")
- end
- options["results"] ||= 10
- end
-
- if options["onsearch"]
- options["incremental"] = true unless options.has_key?("incremental")
- end
-
- InstanceTag.new(object_name, method, self, options.delete("object")).to_input_field_tag("search", options)
+ Tags::SearchField.new(object_name, method, self, options).render
end
# Returns a text_field of type "tel".
#
# telephone_field("user", "phone")
- # # => <input id="user_phone" name="user[phone]" size="30" type="tel" />
+ # # => <input id="user_phone" name="user[phone]" type="tel" />
#
def telephone_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("tel", options)
+ Tags::TelField.new(object_name, method, self, options).render
end
alias phone_field telephone_field
+ # Returns a text_field of type "date".
+ #
+ # date_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="date" />
+ #
+ # The default value is generated by trying to call "to_date"
+ # on the object's value, which makes it behave as expected for instances
+ # of DateTime and ActiveSupport::TimeWithZone. You can still override that
+ # by passing the "value" option explicitly, e.g.
+ #
+ # @user.born_on = Date.new(1984, 1, 27)
+ # date_field("user", "born_on", value: "1984-05-12")
+ # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" />
+ #
+ def date_field(object_name, method, options = {})
+ Tags::DateField.new(object_name, method, self, options).render
+ end
+
# Returns a text_field of type "url".
#
# url_field("user", "homepage")
- # # => <input id="user_homepage" size="30" name="user[homepage]" type="url" />
+ # # => <input id="user_homepage" name="user[homepage]" type="url" />
#
def url_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("url", options)
+ Tags::UrlField.new(object_name, method, self, options).render
end
# Returns a text_field of type "email".
#
# email_field("user", "address")
- # # => <input id="user_address" size="30" name="user[address]" type="email" />
+ # # => <input id="user_address" name="user[address]" type="email" />
#
def email_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("email", options)
+ Tags::EmailField.new(object_name, method, self, options).render
end
# Returns an input tag of type "number".
@@ -933,7 +1017,7 @@ module ActionView
# ==== Options
# * Accepts same options as number_field_tag
def number_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("number", options)
+ Tags::NumberField.new(object_name, method, self, options).render
end
# Returns an input tag of type "range".
@@ -941,12 +1025,12 @@ module ActionView
# ==== Options
# * Accepts same options as range_field_tag
def range_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("range", options)
+ Tags::RangeField.new(object_name, method, self, options).render
end
private
- def instantiate_builder(record_name, record_object, options, &block)
+ def instantiate_builder(record_name, record_object, options)
case record_name
when String, Symbol
object = record_object
@@ -956,274 +1040,13 @@ module ActionView
object_name = ActiveModel::Naming.param_key(object)
end
- builder = options[:builder] || ActionView::Base.default_form_builder
- builder.new(object_name, object, self, options, block)
- end
- end
-
- class InstanceTag
- include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
-
- attr_reader :object, :method_name, :object_name
-
- DEFAULT_FIELD_OPTIONS = { "size" => 30 }
- DEFAULT_RADIO_OPTIONS = { }
- DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }
-
- def initialize(object_name, method_name, template_object, object = nil)
- @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
- @template_object = template_object
-
- @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
- @object = retrieve_object(object)
- @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
- end
-
- def to_label_tag(text = nil, options = {}, &block)
- options = options.stringify_keys
- tag_value = options.delete("value")
- name_and_id = options.dup
-
- if name_and_id["for"]
- name_and_id["id"] = name_and_id["for"]
- else
- name_and_id.delete("id")
- end
-
- add_default_name_and_id_for_value(tag_value, name_and_id)
- options.delete("index")
- options.delete("namespace")
- options["for"] ||= name_and_id["id"]
-
- if block_given?
- @template_object.label_tag(name_and_id["id"], options, &block)
- else
- content = if text.blank?
- object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
- method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name
-
- if object.respond_to?(:to_model)
- key = object.class.model_name.i18n_key
- i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
- end
-
- i18n_default ||= ""
- I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
- else
- text.to_s
- end
-
- content ||= if object && object.class.respond_to?(:human_attribute_name)
- object.class.human_attribute_name(method_name)
- end
-
- content ||= method_name.humanize
-
- label_tag(name_and_id["id"], content, options)
- end
- end
-
- def to_input_field_tag(field_type, options = {})
- options = options.stringify_keys
- options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
- options = DEFAULT_FIELD_OPTIONS.merge(options)
- if field_type == "hidden"
- options.delete("size")
- end
- options["type"] ||= field_type
- options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
- options["value"] &&= ERB::Util.html_escape(options["value"])
- add_default_name_and_id(options)
- tag("input", options)
- end
-
- def to_number_field_tag(field_type, options = {})
- options = options.stringify_keys
- options['size'] ||= nil
-
- if range = options.delete("in") || options.delete("within")
- options.update("min" => range.min, "max" => range.max)
- end
- to_input_field_tag(field_type, options)
- end
-
- def to_radio_button_tag(tag_value, options = {})
- options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
- options["type"] = "radio"
- options["value"] = tag_value
- if options.has_key?("checked")
- cv = options.delete "checked"
- checked = cv == true || cv == "checked"
- else
- checked = self.class.radio_button_checked?(value(object), tag_value)
- end
- options["checked"] = "checked" if checked
- add_default_name_and_id_for_value(tag_value, options)
- tag("input", options)
- end
-
- def to_text_area_tag(options = {})
- options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
- add_default_name_and_id(options)
-
- if size = options.delete("size")
- options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
- end
-
- content_tag("textarea", ERB::Util.html_escape(options.delete('value') || value_before_type_cast(object)), options)
- end
-
- def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
- options = options.stringify_keys
- options["type"] = "checkbox"
- options["value"] = checked_value
- if options.has_key?("checked")
- cv = options.delete "checked"
- checked = cv == true || cv == "checked"
- else
- checked = self.class.check_box_checked?(value(object), checked_value)
- end
- options["checked"] = "checked" if checked
- if options["multiple"]
- add_default_name_and_id_for_value(checked_value, options)
- options.delete("multiple")
- else
- add_default_name_and_id(options)
- end
- hidden = unchecked_value ? tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value, "disabled" => options["disabled"]) : ""
- checkbox = tag("input", options)
- hidden + checkbox
- end
-
- def to_boolean_select_tag(options = {})
- options = options.stringify_keys
- add_default_name_and_id(options)
- value = value(object)
- tag_text = "<select"
- tag_text << tag_options(options)
- tag_text << "><option value=\"false\""
- tag_text << " selected" if value == false
- tag_text << ">False</option><option value=\"true\""
- tag_text << " selected" if value
- tag_text << ">True</option></select>"
- end
-
- def to_content_tag(tag_name, options = {})
- content_tag(tag_name, value(object), options)
- end
-
- def retrieve_object(object)
- if object
- object
- elsif @template_object.instance_variable_defined?("@#{@object_name}")
- @template_object.instance_variable_get("@#{@object_name}")
- end
- rescue NameError
- # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
- nil
- end
-
- def retrieve_autoindex(pre_match)
- object = self.object || @template_object.instance_variable_get("@#{pre_match}")
- if object && object.respond_to?(:to_param)
- object.to_param
- else
- raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
- end
- end
-
- def value(object)
- self.class.value(object, @method_name)
- end
-
- def value_before_type_cast(object)
- self.class.value_before_type_cast(object, @method_name)
- end
-
- class << self
- def value(object, method_name)
- object.send method_name if object
- end
-
- def value_before_type_cast(object, method_name)
- unless object.nil?
- object.respond_to?(method_name + "_before_type_cast") ?
- object.send(method_name + "_before_type_cast") :
- object.send(method_name)
- end
- end
-
- def check_box_checked?(value, checked_value)
- case value
- when TrueClass, FalseClass
- value
- when NilClass
- false
- when Integer
- value != 0
- when String
- value == checked_value
- when Array
- value.include?(checked_value)
- else
- value.to_i != 0
- end
+ builder = options[:builder] || default_form_builder
+ builder.new(object_name, object, self, options)
end
- def radio_button_checked?(value, checked_value)
- value.to_s == checked_value.to_s
- end
- end
-
- private
- def add_default_name_and_id_for_value(tag_value, options)
- unless tag_value.nil?
- pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
- specified_id = options["id"]
- add_default_name_and_id(options)
- options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
- else
- add_default_name_and_id(options)
- end
- end
-
- def add_default_name_and_id(options)
- if options.has_key?("index")
- options["name"] ||= tag_name_with_index(options["index"])
- options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
- options.delete("index")
- elsif defined?(@auto_index)
- options["name"] ||= tag_name_with_index(@auto_index)
- options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
- else
- options["name"] ||= tag_name + (options['multiple'] ? '[]' : '')
- options["id"] = options.fetch("id"){ tag_id }
- end
- options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
- end
-
- def tag_name
- "#{@object_name}[#{sanitized_method_name}]"
- end
-
- def tag_name_with_index(index)
- "#{@object_name}[#{index}][#{sanitized_method_name}]"
- end
-
- def tag_id
- "#{sanitized_object_name}_#{sanitized_method_name}"
- end
-
- def tag_id_with_index(index)
- "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
- end
-
- def sanitized_object_name
- @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
- end
-
- def sanitized_method_name
- @sanitized_method_name ||= @method_name.sub(/\?$/,"")
+ def default_form_builder
+ builder = ActionView::Base.default_form_builder
+ builder.respond_to?(:constantize) ? builder.constantize : builder
end
end
@@ -1234,7 +1057,7 @@ module ActionView
attr_accessor :object_name, :object, :options
- attr_reader :multipart, :parent_builder
+ attr_reader :multipart, :parent_builder, :index
alias :multipart? :multipart
def multipart=(multipart)
@@ -1254,9 +1077,14 @@ module ActionView
self
end
- def initialize(object_name, object, template, options, proc)
+ def initialize(object_name, object, template, options, block=nil)
+ if block
+ ActiveSupport::Deprecation.warn(
+ "Giving a block to FormBuilder is deprecated and has no effect anymore.")
+ end
+
@nested_child_index = {}
- @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
+ @object_name, @object, @template, @options = object_name, object, template, options
@parent_builder = options[:parent_builder]
@default_options = @options ? @options.slice(:index, :namespace) : {}
if @object_name.to_s.match(/\[\]$/)
@@ -1267,6 +1095,7 @@ module ActionView
end
end
@multipart = nil
+ @index = options[:index] || options[:child_index]
end
(field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
@@ -1285,7 +1114,7 @@ module ActionView
fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
fields_options[:builder] ||= options[:builder]
fields_options[:parent_builder] = self
- fields_options[:namespace] = fields_options[:parent_builder].options[:namespace]
+ fields_options[:namespace] = options[:namespace]
case record_name
when String, Symbol
@@ -1298,12 +1127,14 @@ module ActionView
end
index = if options.has_key?(:index)
- "[#{options[:index]}]"
+ options[:index]
elsif defined?(@auto_index)
self.object_name = @object_name.to_s.sub(/\[\]$/,"")
- "[#{@auto_index}]"
+ @auto_index
end
- record_name = "#{object_name}#{index}[#{record_name}]"
+
+ record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
+ fields_options[:child_index] = index
@template.fields_for(record_name, record_object, fields_options, &block)
end
@@ -1371,14 +1202,14 @@ module ActionView
# <% end %>
#
# In the example above, if @post is a new record, it will use "Create Post" as
- # submit button label, otherwise, it uses "Update Post".
+ # button label, otherwise, it uses "Update Post".
#
- # Those labels can be customized using I18n, under the helpers.submit key and accept
- # the %{model} as translation interpolation:
+ # Those labels can be customized using I18n, under the helpers.submit key
+ # (the same as submit helper) and accept the %{model} as translation interpolation:
#
# en:
# helpers:
- # button:
+ # submit:
# create: "Create a %{model}"
# update: "Confirm changes to %{model}"
#
@@ -1386,7 +1217,7 @@ module ActionView
#
# en:
# helpers:
- # button:
+ # submit:
# post:
# create: "Add %{model}"
#
@@ -1441,7 +1272,8 @@ module ActionView
explicit_child_index = options[:child_index]
output = ActiveSupport::SafeBuffer.new
association.each do |child|
- output << fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, options, block)
+ options[:child_index] = nested_child_index(name) unless explicit_child_index
+ output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
end
output
elsif association
@@ -1470,9 +1302,6 @@ module ActionView
end
ActiveSupport.on_load(:action_view) do
- class ActionView::Base
- cattr_accessor :default_form_builder
- @@default_form_builder = ::ActionView::Helpers::FormBuilder
- end
+ cattr_accessor(:default_form_builder) { ::ActionView::Helpers::FormBuilder }
end
end
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index ba9ff1d5aa..d61c2bbee2 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -153,8 +153,10 @@ module ActionView
# form, and parameters extraction gets the last occurrence of any repeated
# key in the query string, that works for ordinary forms.
#
+ # In case if you don't want the helper to generate this hidden field you can specify <tt>:include_blank => false</tt> option.
+ #
def select(object, method, choices, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
+ Tags::Select.new(object, method, self, choices, options, html_options).render
end
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
@@ -164,7 +166,9 @@ module ActionView
#
# The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member
# of +collection+. The return values are used as the +value+ attribute and contents of each
- # <tt><option></tt> tag, respectively.
+ # <tt><option></tt> tag, respectively. They can also be any object that responds to +call+, such
+ # as a +proc+, that will be called for each member of the +collection+ to
+ # retrieve the value/text.
#
# Example object structure for use with this method:
# class Post < ActiveRecord::Base
@@ -188,10 +192,9 @@ module ActionView
# <option value="3">M. Clark</option>
# </select>
def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
+ Tags::CollectionSelect.new(object, method, self, collection, value_method, text_method, options, html_options).render
end
-
# Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of
# +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
# be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
@@ -240,7 +243,7 @@ module ActionView
# </select>
#
def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
+ Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render
end
# Return select and option tags for the given object and method, using
@@ -274,7 +277,7 @@ module ActionView
#
# time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, :model => ActiveSupport::TimeZone)
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
+ Tags::TimeZoneSelect.new(object, method, self, priority_zones, options, html_options).render
end
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
@@ -329,13 +332,16 @@ module ActionView
container.map do |element|
html_attributes = option_html_attributes(element)
text, value = option_text_and_value(element).map { |item| item.to_s }
- selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
- disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
- %(<option value="#{ERB::Util.html_escape(value)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{ERB::Util.html_escape(text)}</option>)
+
+ html_attributes[:selected] = 'selected' if option_value_selected?(value, selected)
+ html_attributes[:disabled] = 'disabled' if disabled && option_value_selected?(value, disabled)
+ html_attributes[:value] = value
+
+ content_tag(:option, text, html_attributes)
end.join("\n").html_safe
end
- # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the
+ # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning
# the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
# Example:
# options_from_collection_for_select(@people, 'id', 'name')
@@ -361,12 +367,13 @@ module ActionView
# should produce the desired results.
def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
options = collection.map do |element|
- [element.send(text_method), element.send(value_method)]
+ [value_for_collection(element, text_method), value_for_collection(element, value_method)]
end
selected, disabled = extract_selected_and_disabled(selected)
- select_deselect = {}
- select_deselect[:selected] = extract_values_from_collection(collection, value_method, selected)
- select_deselect[:disabled] = extract_values_from_collection(collection, value_method, disabled)
+ select_deselect = {
+ :selected => extract_values_from_collection(collection, value_method, selected),
+ :disabled => extract_values_from_collection(collection, value_method, disabled)
+ }
options_for_select(options, select_deselect)
end
@@ -419,10 +426,10 @@ module ActionView
# wrap the output in an appropriate <tt><select></tt> tag.
def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
collection.map do |group|
- group_label_string = eval("group.#{group_label_method}")
- "<optgroup label=\"#{ERB::Util.html_escape(group_label_string)}\">" +
- options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key) +
- '</optgroup>'
+ option_tags = options_from_collection_for_select(
+ group.send(group_method), option_key_method, option_value_method, selected_key)
+
+ content_tag(:optgroup, option_tags, :label => group.send(group_label_method))
end.join.html_safe
end
@@ -470,16 +477,16 @@ module ActionView
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
# wrap the output in an appropriate <tt><select></tt> tag.
def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil)
- body = ''
- body << content_tag(:option, prompt, { :value => "" }, true) if prompt
+ body = "".html_safe
+ body.safe_concat content_tag(:option, prompt, :value => "") if prompt
grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
- grouped_options.each do |group|
- body << content_tag(:optgroup, options_for_select(group[1], selected_key), :label => group[0])
+ grouped_options.each do |label, container|
+ body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label)
end
- body.html_safe
+ body
end
# Returns a string of option tags for pretty much any time zone in the
@@ -501,42 +508,162 @@ module ActionView
# NOTE: Only the option tags are returned, you have to wrap this call in
# a regular HTML select tag.
def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
- zone_options = ""
+ zone_options = "".html_safe
zones = model.all
convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
if priority_zones
if priority_zones.is_a?(Regexp)
- priority_zones = model.all.find_all {|z| z =~ priority_zones}
+ priority_zones = zones.select { |z| z =~ priority_zones }
end
- zone_options += options_for_select(convert_zones[priority_zones], selected)
- zone_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
- zones = zones.reject { |z| priority_zones.include?( z ) }
+ zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
+ zone_options.safe_concat content_tag(:option, '-------------', :value => '', :disabled => 'disabled')
+ zone_options.safe_concat "\n"
+
+ zones.reject! { |z| priority_zones.include?(z) }
end
- zone_options += options_for_select(convert_zones[zones], selected)
- zone_options.html_safe
+ zone_options.safe_concat options_for_select(convert_zones[zones], selected)
+ end
+
+ # Returns radio button tags for the collection of existing return values
+ # of +method+ for +object+'s class. The value returned from calling
+ # +method+ on the instance +object+ will be selected. If calling +method+
+ # returns +nil+, no selection is made.
+ #
+ # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
+ # methods to be called on each member of +collection+. The return values
+ # are used as the +value+ attribute and contents of each radio button tag,
+ # respectively. They can also be any object that responds to +call+, such
+ # as a +proc+, that will be called for each member of the +collection+ to
+ # retrieve the value/text.
+ #
+ # Example object structure for use with this method:
+ # class Post < ActiveRecord::Base
+ # belongs_to :author
+ # end
+ # class Author < ActiveRecord::Base
+ # has_many :posts
+ # def name_with_initial
+ # "#{first_name.first}. #{last_name}"
+ # end
+ # end
+ #
+ # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial)
+ #
+ # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
+ # <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" />
+ # <label for="post_author_id_1">D. Heinemeier Hansson</label>
+ # <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
+ # <label for="post_author_id_2">D. Thomas</label>
+ # <input id="post_author_id_3" name="post[author_id]" type="radio" value="3" />
+ # <label for="post_author_id_3">M. Clark</label>
+ #
+ # It is also possible to customize the way the elements will be shown by
+ # giving a block to the method:
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
+ # b.label { b.radio_button }
+ # end
+ #
+ # The argument passed to the block is a special kind of builder for this
+ # collection, which has the ability to generate the label and radio button
+ # for the current item in the collection, with proper text and value.
+ # Using it, you can change the label and radio button display order or
+ # even use the label as wrapper, as in the example above.
+ #
+ # The builder methods <tt>label</tt> and <tt>radio_button</tt> also accept
+ # extra html options:
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
+ # b.label(:class => "radio_button") { b.radio_button(:class => "radio_button") }
+ # end
+ #
+ # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
+ # <tt>value</tt>, which are the current item being rendered, its text and value methods,
+ # respectively. You can use them like this:
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
+ # b.label(:"data-value" => b.value) { b.radio_button + b.text }
+ # end
+ def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
+ Tags::CollectionRadioButtons.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
+ end
+
+ # Returns check box tags for the collection of existing return values of
+ # +method+ for +object+'s class. The value returned from calling +method+
+ # on the instance +object+ will be selected. If calling +method+ returns
+ # +nil+, no selection is made.
+ #
+ # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
+ # methods to be called on each member of +collection+. The return values
+ # are used as the +value+ attribute and contents of each check box tag,
+ # respectively. They can also be any object that responds to +call+, such
+ # as a +proc+, that will be called for each member of the +collection+ to
+ # retrieve the value/text.
+ #
+ # Example object structure for use with this method:
+ # class Post < ActiveRecord::Base
+ # has_and_belongs_to_many :author
+ # end
+ # class Author < ActiveRecord::Base
+ # has_and_belongs_to_many :posts
+ # def name_with_initial
+ # "#{first_name.first}. #{last_name}"
+ # end
+ # end
+ #
+ # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial)
+ #
+ # If <tt>@post.author_ids</tt> is already <tt>[1]</tt>, this would return:
+ # <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" />
+ # <label for="post_author_ids_1">D. Heinemeier Hansson</label>
+ # <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
+ # <label for="post_author_ids_2">D. Thomas</label>
+ # <input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" />
+ # <label for="post_author_ids_3">M. Clark</label>
+ # <input name="post[author_ids][]" type="hidden" value="" />
+ #
+ # It is also possible to customize the way the elements will be shown by
+ # giving a block to the method:
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
+ # b.label { b.check_box }
+ # end
+ #
+ # The argument passed to the block is a special kind of builder for this
+ # collection, which has the ability to generate the label and check box
+ # for the current item in the collection, with proper text and value.
+ # Using it, you can change the label and check box display order or even
+ # use the label as wrapper, as in the example above.
+ #
+ # The builder methods <tt>label</tt> and <tt>check_box</tt> also accept
+ # extra html options:
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
+ # b.label(:class => "check_box") { b.check_box(:class => "check_box") }
+ # end
+ #
+ # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
+ # <tt>value</tt>, which are the current item being rendered, its text and value methods,
+ # respectively. You can use them like this:
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
+ # b.label(:"data-value" => b.value) { b.check_box + b.text }
+ # end
+ def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
+ Tags::CollectionCheckBoxes.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
end
private
def option_html_attributes(element)
- return "" unless Array === element
- html_attributes = []
- element.select { |e| Hash === e }.reduce({}, :merge).each do |k, v|
- html_attributes << " #{k}=\"#{ERB::Util.html_escape(v.to_s)}\""
- end
- html_attributes.join
+ return {} unless Array === element
+
+ Hash[element.select { |e| Hash === e }.reduce({}, :merge).map { |k, v| [k, ERB::Util.html_escape(v.to_s)] }]
end
def option_text_and_value(option)
# Options are [text, value] pairs or strings used for both.
- case
- when Array === option
- option = option.reject { |e| Hash === e }
- [option.first, option.last]
- when !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
+ if !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
+ option = option.reject { |e| Hash === e } if Array === option
[option.first, option.last]
else
[option, option]
@@ -544,21 +671,17 @@ module ActionView
end
def option_value_selected?(value, selected)
- if selected.respond_to?(:include?) && !selected.is_a?(String)
- selected.include? value
- else
- value == selected
- end
+ Array(selected).include? value
end
def extract_selected_and_disabled(selected)
if selected.is_a?(Proc)
- [ selected, nil ]
+ [selected, nil]
else
selected = Array.wrap(selected)
options = selected.extract_options!.symbolize_keys
- selected_items = options.include?(:selected) ? options[:selected] : selected
- [ selected_items, options[:disabled] ]
+ selected_items = options.fetch(:selected, selected)
+ [selected_items, options[:disabled]]
end
end
@@ -571,68 +694,9 @@ module ActionView
selected
end
end
- end
-
- class InstanceTag #:nodoc:
- include FormOptionsHelper
-
- def to_select_tag(choices, options, html_options)
- selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
-
- # Grouped choices look like this:
- #
- # [nil, []]
- # { nil => [] }
- #
- if !choices.empty? && choices.first.respond_to?(:last) && Array === choices.first.last
- option_tags = grouped_options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
- else
- option_tags = options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
- end
-
- select_content_tag(option_tags, options, html_options)
- end
-
- def to_collection_select_tag(collection, value_method, text_method, options, html_options)
- selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
- select_content_tag(
- options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => options[:disabled]), options, html_options
- )
- end
-
- def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
- select_content_tag(
- option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value(object)), options, html_options
- )
- end
-
- def to_time_zone_select_tag(priority_zones, options, html_options)
- select_content_tag(
- time_zone_options_for_select(value(object) || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, html_options
- )
- end
-
- private
- def add_options(option_tags, options, value = nil)
- if options[:include_blank]
- option_tags = "<option value=\"\">#{ERB::Util.html_escape(options[:include_blank]) if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
- end
- if value.blank? && options[:prompt]
- prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
- option_tags = "<option value=\"\">#{ERB::Util.html_escape(prompt)}</option>\n" + option_tags
- end
- option_tags.html_safe
- end
- def select_content_tag(option_tags, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
- if html_options["multiple"]
- tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
- else
- select
- end
+ def value_for_collection(item, value)
+ value.respond_to?(:call) ? value.call(item) : item.send(value)
end
end
@@ -652,6 +716,14 @@ module ActionView
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
end
+
+ def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {})
+ @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
+ end
+
+ def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {})
+ @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
+ end
end
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 e3ad96ec1b..b5e0970612 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -2,6 +2,7 @@ require 'cgi'
require 'action_view/helpers/tag_helper'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
+require 'active_support/core_ext/module/attribute_accessors'
module ActionView
# = Action View Form Tag Helpers
@@ -17,6 +18,9 @@ module ActionView
include UrlHelper
include TextHelper
+ mattr_accessor :embed_authenticity_token_in_remote_forms
+ self.embed_authenticity_token_in_remote_forms = false
+
# Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
# ActionController::Base#url_for. The method for the form defaults to POST.
#
@@ -27,7 +31,11 @@ module ActionView
# 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>).
+ # (by passing <tt>false</tt>). Remote forms may omit the embedded authenticity token
+ # by setting <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
+ # This is helpful when you're fragment-caching the form. Remote forms get the
+ # authenticity from the <tt>meta</tt> tag, so embedding is unnecessary unless you
+ # support browsers without JavaScript.
# * 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 behavior. By default this behavior is an ajax submit.
@@ -93,7 +101,7 @@ module ActionView
# # => <select id="colors" multiple="multiple" name="colors[]"><option>Red</option>
# # <option>Green</option><option>Blue</option></select>
#
- # select_tag "locations", "<option>Home</option><option selected="selected">Work</option><option>Out</option>".html_safe
+ # select_tag "locations", "<option>Home</option><option selected='selected'>Work</option><option>Out</option>".html_safe
# # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>
# # <option>Out</option></select>
#
@@ -114,11 +122,11 @@ module ActionView
html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
if options.delete(:include_blank)
- option_tags = "<option value=\"\"></option>".html_safe + option_tags
+ option_tags = content_tag(:option, '', :value => '').safe_concat(option_tags)
end
if prompt = options.delete(:prompt)
- option_tags = "<option value=\"\">#{prompt}</option>".html_safe + option_tags
+ option_tags = content_tag(:option, prompt, :value => '').safe_concat(option_tags)
end
content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
@@ -313,7 +321,7 @@ module ActionView
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
end
- escape = options.key?("escape") ? options.delete("escape") : true
+ escape = options.delete("escape") { true }
content = ERB::Util.html_escape(content) if escape
content_tag :textarea, content.to_s.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options)
@@ -525,10 +533,9 @@ module ActionView
# <% end %>
# # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
def field_set_tag(legend = nil, options = nil, &block)
- content = capture(&block)
output = tag(:fieldset, options, true)
output.safe_concat(content_tag(:legend, legend)) unless legend.blank?
- output.concat(content)
+ output.concat(capture(&block)) if block_given?
output.safe_concat("</fieldset>")
end
@@ -549,6 +556,14 @@ module ActionView
end
alias phone_field_tag telephone_field_tag
+ # Creates a text field of type "date".
+ #
+ # ==== Options
+ # * Accepts the same options as text_field_tag.
+ def date_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "date"))
+ end
+
# Creates a text field of type "url".
#
# ==== Options
@@ -609,8 +624,19 @@ module ActionView
# responsibility of the caller to escape all the values.
html_options["action"] = url_for(url_for_options)
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")
+
+ if html_options["data-remote"] &&
+ !embed_authenticity_token_in_remote_forms &&
+ html_options["authenticity_token"].blank?
+ # The authenticity token is taken from the meta tag in this case
+ html_options["authenticity_token"] = false
+ elsif html_options["authenticity_token"] == true
+ # Include the default authenticity_token, which is only generated when its set to nil,
+ # but we needed the true value to override the default of no authenticity_token on data-remote.
+ html_options["authenticity_token"] = nil
+ end
end
end
@@ -627,7 +653,7 @@ module ActionView
token_tag(authenticity_token)
else
html_options["method"] = "post"
- tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token)
+ method_tag(method) + token_tag(authenticity_token)
end
tags = utf8_enforcer_tag << method_tag
@@ -636,26 +662,16 @@ module ActionView
def form_tag_html(html_options)
extra_tags = extra_tags_for_form(html_options)
- (tag(:form, html_options, true) + extra_tags).html_safe
+ tag(:form, html_options, true) + extra_tags
end
def form_tag_in_block(html_options, &block)
content = capture(&block)
- output = ActiveSupport::SafeBuffer.new
- output.safe_concat(form_tag_html(html_options))
+ output = form_tag_html(html_options)
output << content
output.safe_concat("</form>")
end
- def token_tag(token)
- if token == false || !protect_against_forgery?
- ''
- else
- token ||= form_authenticity_token
- tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
- end
- end
-
# see http://www.w3.org/TR/html4/types.html#type-name
def sanitize_to_id(name)
name.to_s.gsub(']','').gsub(/[^-a-zA-Z0-9:.]/, "_")
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 309923490c..cc20518b93 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -1,5 +1,4 @@
require 'action_view/helpers/tag_helper'
-require 'active_support/core_ext/string/encoding'
module ActionView
module Helpers
@@ -15,6 +14,7 @@ module ActionView
}
JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '&#x2028;'
+ JS_ESCAPE_MAP["\342\200\251".force_encoding('UTF-8').encode!] = '&#x2029;'
# Escapes carriage returns and single and double quotes for JavaScript segments.
#
@@ -23,7 +23,7 @@ module ActionView
# $('some_element').replaceWith('<%=j render 'some/element_template' %>');
def escape_javascript(javascript)
if javascript
- result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
+ result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
javascript.html_safe? ? result.html_safe : result
else
''
@@ -36,7 +36,7 @@ module ActionView
# javascript_tag "alert('All is good')"
#
# Returns:
- # <script type="text/javascript">
+ # <script>
# //<![CDATA[
# alert('All is good')
# //]]>
@@ -45,7 +45,7 @@ module ActionView
# +html_options+ may be a hash of attributes for the <tt>\<script></tt>
# tag. Example:
# javascript_tag "alert('All is good')", :defer => 'defer'
- # # => <script defer="defer" type="text/javascript">alert('All is good')</script>
+ # # => <script defer="defer">alert('All is good')</script>
#
# Instead of passing the content as an argument, you can also use a block
# in which case, you pass your +html_options+ as the first parameter.
@@ -61,46 +61,12 @@ module ActionView
content_or_options_with_block
end
- content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
+ content_tag(:script, javascript_cdata_section(content), html_options)
end
def javascript_cdata_section(content) #:nodoc:
"\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
end
-
- # Returns a button whose +onclick+ handler triggers the passed JavaScript.
- #
- # The helper receives a name, JavaScript code, and an optional hash of HTML options. The
- # name is used as button label and the JavaScript code goes into its +onclick+ attribute.
- # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+.
- #
- # button_to_function "Greeting", "alert('Hello world!')", :class => "ok"
- # # => <input class="ok" onclick="alert('Hello world!');" type="button" value="Greeting" />
- #
- def button_to_function(name, function=nil, html_options={})
- onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
-
- tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
- end
-
- # Returns a link whose +onclick+ handler triggers the passed JavaScript.
- #
- # The helper receives a name, JavaScript code, and an optional hash of HTML options. The
- # name is used as the link text and the JavaScript code goes into the +onclick+ attribute.
- # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+. Once all
- # the JavaScript is set, the helper appends "; return false;".
- #
- # The +href+ attribute of the tag is set to "#" unless +html_options+ has one.
- #
- # link_to_function "Greeting", "alert('Hello world!')", :class => "nav_link"
- # # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a>
- #
- def link_to_function(name, function, html_options={})
- onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
- href = html_options[:href] || '#'
-
- content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
- end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 43122ef2ba..2011351bd2 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -57,15 +57,11 @@ module ActionView
# # => +1.123.555.1234 x 1343
def number_to_phone(number, options = {})
return unless number
+ options = options.symbolize_keys
- begin
- Float(number)
- rescue ArgumentError, TypeError
- raise InvalidNumberError, number
- end if options[:raise]
+ parse_float(number, true) if options[:raise]
number = number.to_s.strip
- options = options.symbolize_keys
area_code = options[:area_code]
delimiter = options[:delimiter] || "-"
extension = options[:extension]
@@ -75,14 +71,14 @@ module ActionView
number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
else
number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
- number.slice!(0, 1) if number.starts_with?(delimiter) && !delimiter.blank?
+ number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
end
- str = []
+ str = ''
str << "+#{country_code}#{delimiter}" unless country_code.blank?
str << number
str << " x #{extension}" unless extension.blank?
- ERB::Util.html_escape(str.join)
+ ERB::Util.html_escape(str)
end
# Formats a +number+ into a currency string (e.g., $13.65). You can customize the format
@@ -122,14 +118,12 @@ module ActionView
# # => 1234567890,50 &pound;
def number_to_currency(number, options = {})
return unless number
+ options = options.symbolize_keys
- options.symbolize_keys!
-
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :default => {})
+ currency = translations_for('currency', options[:locale])
currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
- defaults = DEFAULT_CURRENCY_VALUES.merge(defaults).merge!(currency)
+ defaults = DEFAULT_CURRENCY_VALUES.merge(defaults_translations(options[:locale])).merge!(currency)
defaults[:negative_format] = "-" + options[:format] if options[:format]
options = defaults.merge!(options)
@@ -143,16 +137,15 @@ module ActionView
begin
value = number_with_precision(number, options.merge(:raise => true))
- format.gsub(/%n/, value).gsub(/%u/, unit).html_safe
+ format.gsub('%n', value).gsub('%u', unit).html_safe
rescue InvalidNumberError => e
if options[:raise]
raise
else
- formatted_number = format.gsub(/%n/, e.number).gsub(/%u/, unit)
+ formatted_number = format.gsub('%n', e.number).gsub('%u', unit)
e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
end
end
-
end
# Formats a +number+ as a percentage string (e.g., 65%). You can customize the format in the +options+ hash.
@@ -169,6 +162,8 @@ module ActionView
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator
# (defaults to +false+).
+ # * <tt>:format</tt> - Specifies the format of the percentage string
+ # The number field is <tt>%n</tt> (defaults to "%n%").
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
#
# ==== Examples
@@ -180,26 +175,27 @@ module ActionView
# number_to_percentage(302.24398923423, :precision => 5) # => 302.24399%
# number_to_percentage(1000, :locale => :fr) # => 1 000,000%
# number_to_percentage("98a") # => 98a%
+ # number_to_percentage(100, :format => "%n %") # => 100 %
#
# number_to_percentage("98a", :raise => true) # => InvalidNumberError
def number_to_percentage(number, options = {})
return unless number
+ options = options.symbolize_keys
- options.symbolize_keys!
+ defaults = format_translations('percentage', options[:locale])
+ options = defaults.merge!(options)
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :default => {})
- defaults = defaults.merge(percentage)
-
- options = options.reverse_merge(defaults)
+ format = options[:format] || "%n%"
begin
- "#{number_with_precision(number, options.merge(:raise => true))}%".html_safe
+ value = number_with_precision(number, options.merge(:raise => true))
+ format.gsub(/%n/, value).html_safe
rescue InvalidNumberError => e
if options[:raise]
raise
else
- e.number.to_s.html_safe? ? "#{e.number}%".html_safe : "#{e.number}%"
+ formatted_number = format.gsub(/%n/, e.number)
+ e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
end
end
end
@@ -229,25 +225,15 @@ module ActionView
#
# number_with_delimiter("112a", :raise => true) # => raise InvalidNumberError
def number_with_delimiter(number, options = {})
- options.symbolize_keys!
+ options = options.symbolize_keys
- begin
- Float(number)
- rescue ArgumentError, TypeError
- if options[:raise]
- raise InvalidNumberError, number
- else
- return number
- end
- end
+ parse_float(number, options[:raise]) or return number
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- options = options.reverse_merge(defaults)
+ options = defaults_translations(options[:locale]).merge(options)
parts = number.to_s.to_str.split('.')
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
- parts.join(options[:separator]).html_safe
-
+ safe_join(parts, options[:separator])
end
# Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision
@@ -264,6 +250,7 @@ module ActionView
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator
# (defaults to +false+).
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
#
# ==== Examples
# number_with_precision(111.2345) # => 111.235
@@ -282,23 +269,13 @@ module ActionView
# number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
# # => 1.111,23
def number_with_precision(number, options = {})
- options.symbolize_keys!
+ options = options.symbolize_keys
- number = begin
- Float(number)
- rescue ArgumentError, TypeError
- if options[:raise]
- raise InvalidNumberError, number
- else
- return number
- end
- end
+ number = (parse_float(number, options[:raise]) or return number)
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale], :default => {})
- defaults = defaults.merge(precision_defaults)
+ defaults = format_translations('precision', options[:locale])
+ options = defaults.merge!(options)
- options = options.reverse_merge(defaults) # Allow the user to unset default values: Eg.: :significant => false
precision = options.delete :precision
significant = options.delete :significant
strip_insignificant_zeros = options.delete :strip_insignificant_zeros
@@ -315,6 +292,7 @@ module ActionView
precision = precision > 0 ? precision : 0 #don't let it be negative
else
rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
+ rounded_number = rounded_number.zero? ? rounded_number.abs : rounded_number #prevent showing negative zeros
end
formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options)
if strip_insignificant_zeros
@@ -323,7 +301,6 @@ module ActionView
else
formatted_number
end
-
end
STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
@@ -343,6 +320,7 @@ module ActionView
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
# * <tt>:prefix</tt> - If +:si+ formats the number using the SI prefix (defaults to :binary)
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
# ==== Examples
# number_to_human_size(123) # => 123 Bytes
# number_to_human_size(1234) # => 1.21 KB
@@ -359,23 +337,13 @@ module ActionView
# number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
# number_to_human_size(524288000, :precision => 5) # => "500 MB"
def number_to_human_size(number, options = {})
- options.symbolize_keys!
+ options = options.symbolize_keys
- number = begin
- Float(number)
- rescue ArgumentError, TypeError
- if options[:raise]
- raise InvalidNumberError, number
- else
- return number
- end
- end
+ number = (parse_float(number, options[:raise]) or return number)
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
- defaults = defaults.merge(human)
+ defaults = format_translations('human', options[:locale])
+ options = defaults.merge!(options)
- options = options.reverse_merge(defaults)
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
@@ -424,9 +392,10 @@ module ActionView
# * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, <tt>:billion</tt>, <tt>:trillion</tt>, <tt>:quadrillion</tt>
# * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, <tt>:pico</tt>, <tt>:femto</tt>
# * <tt>:format</tt> - Sets the format of the output string (defaults to "%n %u"). The field types are:
- #
# %u The quantifier (ex.: 'thousand')
# %n The number
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid.
+ #
#
# ==== Examples
# number_to_human(123) # => "123"
@@ -478,23 +447,13 @@ module ActionView
# number_to_human(0.34, :units => :distance) # => "34 centimeters"
#
def number_to_human(number, options = {})
- options.symbolize_keys!
+ options = options.symbolize_keys
- number = begin
- Float(number)
- rescue ArgumentError, TypeError
- if options[:raise]
- raise InvalidNumberError, number
- else
- return number
- end
- end
+ number = (parse_float(number, options[:raise]) or return number)
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
- defaults = defaults.merge(human)
+ defaults = format_translations('human', options[:locale])
+ options = defaults.merge!(options)
- options = options.reverse_merge(defaults)
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
@@ -530,6 +489,25 @@ module ActionView
decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip.html_safe
end
+ private
+
+ def format_translations(namespace, locale)
+ defaults_translations(locale).merge(translations_for(namespace, locale))
+ end
+
+ def defaults_translations(locale)
+ I18n.translate(:'number.format', :locale => locale, :default => {})
+ end
+
+ def translations_for(namespace, locale)
+ I18n.translate(:"number.#{namespace}.format", :locale => locale, :default => {})
+ end
+
+ def parse_float(number, raise_error)
+ Float(number)
+ rescue ArgumentError, TypeError
+ raise InvalidNumberError, number if raise_error
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb
index b351302d01..9b35f076e5 100644
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -81,13 +81,11 @@ module ActionView
# <li id="person_123" class="person bar">...
#
def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block)
- if single_or_multiple_records.respond_to?(:to_ary)
- single_or_multiple_records.to_ary.map do |single_record|
- capture { content_tag_for_single_record(tag_name, single_record, prefix, options, &block) }
- end.join("\n").html_safe
- else
- content_tag_for_single_record(tag_name, single_or_multiple_records, prefix, options, &block)
- end
+ options, prefix = prefix, nil if prefix.is_a?(Hash)
+
+ Array(single_or_multiple_records).map do |single_record|
+ content_tag_for_single_record(tag_name, single_record, prefix, options, &block)
+ end.join("\n").html_safe
end
private
@@ -95,14 +93,11 @@ module ActionView
# Called by <tt>content_tag_for</tt> internally to render a content tag
# for each record.
def content_tag_for_single_record(tag_name, record, prefix, options, &block)
- options, prefix = prefix, nil if prefix.is_a?(Hash)
- options ||= {}
- options.merge!({ :class => "#{dom_class(record, prefix)} #{options[:class]}".strip, :id => dom_id(record, prefix) })
- if block.arity == 0
- content_tag(tag_name, capture(&block), options)
- else
- content_tag(tag_name, capture(record, &block), options)
- end
+ options = options ? options.dup : {}
+ options[:class] = "#{dom_class(record, prefix)} #{options[:class]}".rstrip
+ options[:id] = dom_id(record, prefix)
+
+ content_tag(tag_name, capture(record, &block), options)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index d7a2651bad..f7afa48256 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -14,9 +14,13 @@ 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 pubdate).to_set
+ autofocus novalidate formnovalidate open pubdate itemscope).to_set
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
+ PRE_CONTENT_STRINGS = {
+ :textarea => "\n"
+ }
+
# Returns an empty HTML tag of type +name+ which by default is XHTML
# compliant. Set +open+ to true to create an open tag compatible
# with HTML 4.0 and below. Add HTML attributes by passing an attributes
@@ -105,8 +109,12 @@ module ActionView
#
# cdata_section(File.read("hello_world.txt"))
# # => <![CDATA[<hello from a text file]]>
+ #
+ # cdata_section("hello]]>world")
+ # # => <![CDATA[hello]]]]><![CDATA[>world]]>
def cdata_section(content)
- "<![CDATA[#{content}]]>".html_safe
+ splitted = content.gsub(']]>', ']]]]><![CDATA[>')
+ "<![CDATA[#{splitted}]]>".html_safe
end
# Returns an escaped version of +html+ without affecting existing escaped entities.
@@ -118,7 +126,7 @@ module ActionView
# escape_once("&lt;&lt; Accept & Checkout")
# # => "&lt;&lt; Accept &amp; Checkout"
def escape_once(html)
- html.to_s.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }
+ ERB::Util.html_escape_once(html)
end
private
@@ -126,7 +134,7 @@ module ActionView
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
content = ERB::Util.h(content) if escape
- "<#{name}#{tag_options}>#{content}</#{name}>".html_safe
+ "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe
end
def tag_options(options, escape = true)
diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb
new file mode 100644
index 0000000000..3cf762877f
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags.rb
@@ -0,0 +1,33 @@
+module ActionView
+ module Helpers
+ module Tags #:nodoc:
+ extend ActiveSupport::Autoload
+
+ autoload :Base
+ autoload :CheckBox
+ autoload :CollectionCheckBoxes
+ autoload :CollectionRadioButtons
+ autoload :CollectionSelect
+ autoload :DateField
+ autoload :DateSelect
+ autoload :DatetimeSelect
+ autoload :EmailField
+ autoload :FileField
+ autoload :GroupedCollectionSelect
+ autoload :HiddenField
+ autoload :Label
+ autoload :NumberField
+ autoload :PasswordField
+ autoload :RadioButton
+ autoload :RangeField
+ autoload :SearchField
+ autoload :Select
+ autoload :TelField
+ autoload :TextArea
+ autoload :TextField
+ autoload :TimeSelect
+ autoload :TimeZoneSelect
+ autoload :UrlField
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb
new file mode 100644
index 0000000000..e4f431a6d7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/base.rb
@@ -0,0 +1,146 @@
+module ActionView
+ module Helpers
+ module Tags
+ class Base #:nodoc:
+ include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
+ include FormOptionsHelper
+
+ attr_reader :object
+
+ def initialize(object_name, method_name, template_object, options = {})
+ @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
+ @template_object = template_object
+
+ @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
+ @object = retrieve_object(options.delete(:object))
+ @options = options
+ @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
+ end
+
+ # This is what child classes implement.
+ def render
+ raise NotImplementedError, "Subclasses must implement a render method"
+ end
+
+ private
+
+ def value(object)
+ object.send @method_name if object
+ end
+
+ def value_before_type_cast(object)
+ unless object.nil?
+ method_before_type_cast = @method_name + "_before_type_cast"
+
+ object.respond_to?(method_before_type_cast) ?
+ object.send(method_before_type_cast) :
+ value(object)
+ end
+ end
+
+ def retrieve_object(object)
+ if object
+ object
+ elsif @template_object.instance_variable_defined?("@#{@object_name}")
+ @template_object.instance_variable_get("@#{@object_name}")
+ end
+ rescue NameError
+ # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
+ nil
+ end
+
+ def retrieve_autoindex(pre_match)
+ object = self.object || @template_object.instance_variable_get("@#{pre_match}")
+ if object && object.respond_to?(:to_param)
+ object.to_param
+ else
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
+ end
+ end
+
+ def add_default_name_and_id_for_value(tag_value, options)
+ if tag_value.nil?
+ add_default_name_and_id(options)
+ else
+ specified_id = options["id"]
+ add_default_name_and_id(options)
+
+ if specified_id.blank? && options["id"].present?
+ options["id"] += "_#{sanitized_value(tag_value)}"
+ end
+ end
+ end
+
+ def add_default_name_and_id(options)
+ if options.has_key?("index")
+ options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"]) }
+ options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
+ options.delete("index")
+ elsif defined?(@auto_index)
+ options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index) }
+ options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
+ else
+ options["name"] ||= options.fetch("name"){ options['multiple'] ? tag_name_multiple : tag_name }
+ options["id"] = options.fetch("id"){ tag_id }
+ end
+ options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
+ end
+
+ def tag_name
+ "#{@object_name}[#{sanitized_method_name}]"
+ end
+
+ def tag_name_multiple
+ "#{tag_name}[]"
+ end
+
+ def tag_name_with_index(index)
+ "#{@object_name}[#{index}][#{sanitized_method_name}]"
+ end
+
+ def tag_id
+ "#{sanitized_object_name}_#{sanitized_method_name}"
+ end
+
+ def tag_id_with_index(index)
+ "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
+ end
+
+ def sanitized_object_name
+ @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
+ end
+
+ def sanitized_method_name
+ @sanitized_method_name ||= @method_name.sub(/\?$/,"")
+ end
+
+ def sanitized_value(value)
+ value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
+ end
+
+ def select_content_tag(option_tags, options, html_options)
+ html_options = html_options.stringify_keys
+ add_default_name_and_id(html_options)
+ select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
+
+ if html_options["multiple"] && options.fetch(:include_hidden, true)
+ tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
+ else
+ select
+ end
+ end
+
+ def add_options(option_tags, options, value = nil)
+ if options[:include_blank]
+ option_tags = content_tag('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags
+ end
+ if value.blank? && options[:prompt]
+ prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
+ option_tags = content_tag('option', prompt, :value => '') + "\n" + option_tags
+ end
+ option_tags
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/check_box.rb b/actionpack/lib/action_view/helpers/tags/check_box.rb
new file mode 100644
index 0000000000..9d17a1dde3
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/check_box.rb
@@ -0,0 +1,62 @@
+require 'action_view/helpers/tags/checkable'
+
+module ActionView
+ module Helpers
+ module Tags
+ class CheckBox < Base #:nodoc:
+ include Checkable
+
+ def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options)
+ @checked_value = checked_value
+ @unchecked_value = unchecked_value
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ options = @options.stringify_keys
+ options["type"] = "checkbox"
+ options["value"] = @checked_value
+ options["checked"] = "checked" if input_checked?(object, options)
+
+ if options["multiple"]
+ add_default_name_and_id_for_value(@checked_value, options)
+ options.delete("multiple")
+ else
+ add_default_name_and_id(options)
+ end
+
+ include_hidden = options.delete("include_hidden") { true }
+ checkbox = tag("input", options)
+
+ if include_hidden
+ hidden = hidden_field_for_checkbox(options)
+ hidden + checkbox
+ else
+ checkbox
+ end
+ end
+
+ private
+
+ def checked?(value)
+ case value
+ when TrueClass, FalseClass
+ value == !!@checked_value
+ when NilClass
+ false
+ when String
+ value == @checked_value
+ when Array
+ value.include?(@checked_value)
+ else
+ value.to_i == @checked_value.to_i
+ end
+ end
+
+ def hidden_field_for_checkbox(options)
+ @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/checkable.rb b/actionpack/lib/action_view/helpers/tags/checkable.rb
new file mode 100644
index 0000000000..b97c0c68d7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/checkable.rb
@@ -0,0 +1,16 @@
+module ActionView
+ module Helpers
+ module Tags
+ module Checkable
+ def input_checked?(object, options)
+ if options.has_key?("checked")
+ checked = options.delete "checked"
+ checked == true || checked == "checked"
+ else
+ checked?(value(object))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
new file mode 100644
index 0000000000..e23f5113fb
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -0,0 +1,37 @@
+require 'action_view/helpers/tags/collection_helpers'
+
+module ActionView
+ module Helpers
+ module Tags
+ class CollectionCheckBoxes < Base
+ include CollectionHelpers
+
+ class CheckBoxBuilder < Builder
+ def check_box(extra_html_options={})
+ html_options = extra_html_options.merge(@input_html_options)
+ @template_object.check_box(@object_name, @method_name, html_options, @value, nil)
+ end
+ end
+
+ def render
+ rendered_collection = render_collection do |item, value, text, default_html_options|
+ default_html_options[:multiple] = true
+ builder = instantiate_builder(CheckBoxBuilder, item, value, text, default_html_options)
+
+ if block_given?
+ yield builder
+ else
+ builder.check_box + builder.label
+ end
+ end
+
+ # Append a hidden field to make sure something will be sent back to the
+ # server if all check boxes are unchecked.
+ hidden = @template_object.hidden_field_tag(tag_name_multiple, "", :id => nil)
+
+ rendered_collection + hidden
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
new file mode 100644
index 0000000000..6a1479069f
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
@@ -0,0 +1,82 @@
+module ActionView
+ module Helpers
+ module Tags
+ module CollectionHelpers
+ class Builder
+ attr_reader :object, :text, :value
+
+ def initialize(template_object, object_name, method_name, object,
+ sanitized_attribute_name, text, value, input_html_options)
+ @template_object = template_object
+ @object_name = object_name
+ @method_name = method_name
+ @object = object
+ @sanitized_attribute_name = sanitized_attribute_name
+ @text = text
+ @value = value
+ @input_html_options = input_html_options
+ end
+
+ def label(label_html_options={}, &block)
+ @template_object.label(@object_name, @sanitized_attribute_name, @text, label_html_options, &block)
+ end
+ end
+
+ def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
+ @collection = collection
+ @value_method = value_method
+ @text_method = text_method
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ private
+
+ def instantiate_builder(builder_class, item, value, text, html_options)
+ builder_class.new(@template_object, @object_name, @method_name, item,
+ sanitize_attribute_name(value), text, value, html_options)
+ end
+
+ # Generate default options for collection helpers, such as :checked and
+ # :disabled.
+ def default_html_options_for_collection(item, value) #:nodoc:
+ html_options = @html_options.dup
+
+ [:checked, :selected, :disabled].each do |option|
+ next unless current_value = @options[option]
+
+ accept = if current_value.respond_to?(:call)
+ current_value.call(item)
+ else
+ Array(current_value).include?(value)
+ end
+
+ if accept
+ html_options[option] = true
+ elsif option == :checked
+ html_options[option] = false
+ end
+ end
+
+ html_options[:object] = @object
+ html_options
+ end
+
+ def sanitize_attribute_name(value) #:nodoc:
+ "#{sanitized_method_name}_#{sanitized_value(value)}"
+ end
+
+ def render_collection #:nodoc:
+ @collection.map do |item|
+ value = value_for_collection(item, @value_method)
+ text = value_for_collection(item, @text_method)
+ default_html_options = default_html_options_for_collection(item, value)
+
+ yield item, value, text, default_html_options
+ end.join.html_safe
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb
new file mode 100644
index 0000000000..ba2035f074
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb
@@ -0,0 +1,30 @@
+require 'action_view/helpers/tags/collection_helpers'
+
+module ActionView
+ module Helpers
+ module Tags
+ class CollectionRadioButtons < Base
+ include CollectionHelpers
+
+ class RadioButtonBuilder < Builder
+ def radio_button(extra_html_options={})
+ html_options = extra_html_options.merge(@input_html_options)
+ @template_object.radio_button(@object_name, @method_name, @value, html_options)
+ end
+ end
+
+ def render
+ render_collection do |item, value, text, default_html_options|
+ builder = instantiate_builder(RadioButtonBuilder, item, value, text, default_html_options)
+
+ if block_given?
+ yield builder
+ else
+ builder.radio_button + builder.label
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_select.rb b/actionpack/lib/action_view/helpers/tags/collection_select.rb
new file mode 100644
index 0000000000..ec78e6e5f9
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/collection_select.rb
@@ -0,0 +1,28 @@
+module ActionView
+ module Helpers
+ module Tags
+ class CollectionSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
+ @collection = collection
+ @value_method = value_method
+ @text_method = text_method
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ option_tags_options = {
+ :selected => @options.fetch(:selected) { value(@object) },
+ :disabled => @options[:disabled]
+ }
+
+ select_content_tag(
+ options_from_collection_for_select(@collection, @value_method, @text_method, option_tags_options),
+ @options, @html_options
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/date_field.rb b/actionpack/lib/action_view/helpers/tags/date_field.rb
new file mode 100644
index 0000000000..bb968e9f39
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/date_field.rb
@@ -0,0 +1,15 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DateField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["value"] = @options.fetch("value") { value(object).try(:to_date) }
+ options["size"] = nil
+ @options = options
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionpack/lib/action_view/helpers/tags/date_select.rb
new file mode 100644
index 0000000000..5d706087b0
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/date_select.rb
@@ -0,0 +1,70 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DateSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, options, html_options)
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ error_wrapping(datetime_selector(@options, @html_options).send("select_#{select_type}").html_safe)
+ end
+
+ class << self
+ def select_type
+ @select_type ||= self.name.split("::").last.sub("Select", "").downcase
+ end
+ end
+
+ private
+
+ def select_type
+ self.class.select_type
+ end
+
+ def datetime_selector(options, html_options)
+ datetime = value(object) || default_datetime(options)
+ @auto_index ||= nil
+
+ options = options.dup
+ options[:field_name] = @method_name
+ options[:include_position] = true
+ options[:prefix] ||= @object_name
+ options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
+
+ DateTimeSelector.new(datetime, options, html_options)
+ end
+
+ def default_datetime(options)
+ return if options[:include_blank] || options[:prompt]
+
+ case options[:default]
+ when nil
+ Time.current
+ when Date, Time
+ options[:default]
+ else
+ default = options[:default].dup
+
+ # Rename :minute and :second to :min and :sec
+ default[:min] ||= default[:minute]
+ default[:sec] ||= default[:second]
+
+ time = Time.current
+
+ [:year, :month, :day, :hour, :min, :sec].each do |key|
+ default[key] ||= time.send(key)
+ end
+
+ Time.utc_time(
+ default[:year], default[:month], default[:day],
+ default[:hour], default[:min], default[:sec]
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/datetime_select.rb b/actionpack/lib/action_view/helpers/tags/datetime_select.rb
new file mode 100644
index 0000000000..a32c840bce
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/datetime_select.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DatetimeSelect < DateSelect #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/email_field.rb b/actionpack/lib/action_view/helpers/tags/email_field.rb
new file mode 100644
index 0000000000..45cde507d7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/email_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class EmailField < TextField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/file_field.rb b/actionpack/lib/action_view/helpers/tags/file_field.rb
new file mode 100644
index 0000000000..56442e1c14
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/file_field.rb
@@ -0,0 +1,12 @@
+module ActionView
+ module Helpers
+ module Tags
+ class FileField < TextField #:nodoc:
+ def render
+ @options.update(:size => nil)
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb b/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
new file mode 100644
index 0000000000..507ba8835f
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
@@ -0,0 +1,29 @@
+module ActionView
+ module Helpers
+ module Tags
+ class GroupedCollectionSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
+ @collection = collection
+ @group_method = group_method
+ @group_label_method = group_label_method
+ @option_key_method = option_key_method
+ @option_value_method = option_value_method
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ option_tags_options = {
+ :selected => @options.fetch(:selected) { value(@object) },
+ :disabled => @options[:disabled]
+ }
+
+ select_content_tag(
+ option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, option_tags_options), @options, @html_options
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/hidden_field.rb b/actionpack/lib/action_view/helpers/tags/hidden_field.rb
new file mode 100644
index 0000000000..ea86596e0b
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/hidden_field.rb
@@ -0,0 +1,12 @@
+module ActionView
+ module Helpers
+ module Tags
+ class HiddenField < TextField #:nodoc:
+ def render
+ @options.update(:size => nil)
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/label.rb b/actionpack/lib/action_view/helpers/tags/label.rb
new file mode 100644
index 0000000000..16135fcd5a
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/label.rb
@@ -0,0 +1,65 @@
+module ActionView
+ module Helpers
+ module Tags
+ class Label < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
+ options ||= {}
+
+ content_is_options = content_or_options.is_a?(Hash)
+ if content_is_options
+ options.merge! content_or_options
+ @content = nil
+ else
+ @content = content_or_options
+ end
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render(&block)
+ options = @options.stringify_keys
+ tag_value = options.delete("value")
+ name_and_id = options.dup
+
+ if name_and_id["for"]
+ name_and_id["id"] = name_and_id["for"]
+ else
+ name_and_id.delete("id")
+ end
+
+ add_default_name_and_id_for_value(tag_value, name_and_id)
+ options.delete("index")
+ options.delete("namespace")
+ options["for"] = name_and_id["id"] unless options.key?("for")
+
+ if block_given?
+ content = @template_object.capture(&block)
+ else
+ content = if @content.blank?
+ @object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
+ method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name
+
+ if object.respond_to?(:to_model)
+ key = object.class.model_name.i18n_key
+ i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
+ end
+
+ i18n_default ||= ""
+ I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
+ else
+ @content.to_s
+ end
+
+ content ||= if object && object.class.respond_to?(:human_attribute_name)
+ object.class.human_attribute_name(@method_name)
+ end
+
+ content ||= @method_name.humanize
+ end
+
+ label_tag(name_and_id["id"], content, options)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/number_field.rb b/actionpack/lib/action_view/helpers/tags/number_field.rb
new file mode 100644
index 0000000000..e89fdbec46
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/number_field.rb
@@ -0,0 +1,19 @@
+module ActionView
+ module Helpers
+ module Tags
+ class NumberField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options['size'] ||= nil
+
+ if range = options.delete("in") || options.delete("within")
+ options.update("min" => range.min, "max" => range.max)
+ end
+
+ @options = options
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/password_field.rb b/actionpack/lib/action_view/helpers/tags/password_field.rb
new file mode 100644
index 0000000000..6e7a4d3c36
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/password_field.rb
@@ -0,0 +1,12 @@
+module ActionView
+ module Helpers
+ module Tags
+ class PasswordField < TextField #:nodoc:
+ def render
+ @options = {:value => nil}.merge!(@options)
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/radio_button.rb b/actionpack/lib/action_view/helpers/tags/radio_button.rb
new file mode 100644
index 0000000000..8a0421f061
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/radio_button.rb
@@ -0,0 +1,31 @@
+require 'action_view/helpers/tags/checkable'
+
+module ActionView
+ module Helpers
+ module Tags
+ class RadioButton < Base #:nodoc:
+ include Checkable
+
+ def initialize(object_name, method_name, template_object, tag_value, options)
+ @tag_value = tag_value
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ options = @options.stringify_keys
+ options["type"] = "radio"
+ options["value"] = @tag_value
+ options["checked"] = "checked" if input_checked?(object, options)
+ add_default_name_and_id_for_value(@tag_value, options)
+ tag("input", options)
+ end
+
+ private
+
+ def checked?(value)
+ value.to_s == @tag_value.to_s
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/range_field.rb b/actionpack/lib/action_view/helpers/tags/range_field.rb
new file mode 100644
index 0000000000..47db4680e7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/range_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class RangeField < NumberField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/search_field.rb b/actionpack/lib/action_view/helpers/tags/search_field.rb
new file mode 100644
index 0000000000..818fd4b887
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/search_field.rb
@@ -0,0 +1,24 @@
+module ActionView
+ module Helpers
+ module Tags
+ class SearchField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+
+ if options["autosave"]
+ if options["autosave"] == true
+ options["autosave"] = request.host.split(".").reverse.join(".")
+ end
+ options["results"] ||= 10
+ end
+
+ if options["onsearch"]
+ options["incremental"] = true unless options.has_key?("incremental")
+ end
+
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/select.rb b/actionpack/lib/action_view/helpers/tags/select.rb
new file mode 100644
index 0000000000..53a108b7e6
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/select.rb
@@ -0,0 +1,41 @@
+module ActionView
+ module Helpers
+ module Tags
+ class Select < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, choices, options, html_options)
+ @choices = choices
+ @choices = @choices.to_a if @choices.is_a?(Range)
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ option_tags_options = {
+ :selected => @options.fetch(:selected) { value(@object) },
+ :disabled => @options[:disabled]
+ }
+
+ option_tags = if grouped_choices?
+ grouped_options_for_select(@choices, option_tags_options)
+ else
+ options_for_select(@choices, option_tags_options)
+ end
+
+ select_content_tag(option_tags, @options, @html_options)
+ end
+
+ private
+
+ # Grouped choices look like this:
+ #
+ # [nil, []]
+ # { nil => [] }
+ #
+ def grouped_choices?
+ !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/tel_field.rb b/actionpack/lib/action_view/helpers/tags/tel_field.rb
new file mode 100644
index 0000000000..87c1f6b6b6
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/tel_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TelField < TextField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/text_area.rb b/actionpack/lib/action_view/helpers/tags/text_area.rb
new file mode 100644
index 0000000000..f74652c5e7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/text_area.rb
@@ -0,0 +1,18 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TextArea < Base #:nodoc:
+ def render
+ options = @options.stringify_keys
+ add_default_name_and_id(options)
+
+ if size = options.delete("size")
+ options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
+ end
+
+ content_tag("textarea", options.delete('value') || value_before_type_cast(object), options)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/text_field.rb b/actionpack/lib/action_view/helpers/tags/text_field.rb
new file mode 100644
index 0000000000..024a1a8af2
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/text_field.rb
@@ -0,0 +1,29 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TextField < Base #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["size"] = options["maxlength"] unless options.key?("size")
+ options["type"] ||= field_type
+ options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
+ options["value"] &&= ERB::Util.html_escape(options["value"])
+ add_default_name_and_id(options)
+ tag("input", options)
+ end
+
+ class << self
+ def field_type
+ @field_type ||= self.name.split("::").last.sub("Field", "").downcase
+ end
+ end
+
+ private
+
+ def field_type
+ self.class.field_type
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/time_select.rb b/actionpack/lib/action_view/helpers/tags/time_select.rb
new file mode 100644
index 0000000000..9e97deb706
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/time_select.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TimeSelect < DateSelect #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/time_zone_select.rb b/actionpack/lib/action_view/helpers/tags/time_zone_select.rb
new file mode 100644
index 0000000000..0a176157c3
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/time_zone_select.rb
@@ -0,0 +1,20 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TimeZoneSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, priority_zones, options, html_options)
+ @priority_zones = priority_zones
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ select_content_tag(
+ time_zone_options_for_select(value(@object) || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/url_field.rb b/actionpack/lib/action_view/helpers/tags/url_field.rb
new file mode 100644
index 0000000000..1ffdfe0b3c
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/url_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class UrlField < TextField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index ce79a3da48..12bb162da2 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/filters'
+require 'active_support/core_ext/array/extract_options'
module ActionView
# = Action View Text Helpers
@@ -90,11 +91,11 @@ module ActionView
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
# as a single-quoted string with \1 where the phrase is to be inserted (defaults to
- # '<strong class="highlight">\1</strong>')
+ # '<mark>\1</mark>')
#
# ==== Examples
# highlight('You searched for: rails', 'rails')
- # # => You searched for: <strong class="highlight">rails</strong>
+ # # => You searched for: <mark>rails</mark>
#
# highlight('You searched for: ruby, rails, dhh', 'actionpack')
# # => You searched for: ruby, rails, dhh
@@ -111,9 +112,9 @@ module ActionView
def highlight(text, phrases, *args)
options = args.extract_options!
unless args.empty?
- options[:highlighter] = args[0] || '<strong class="highlight">\1</strong>'
+ options[:highlighter] = args[0]
end
- options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>')
+ options[:highlighter] ||= '<mark>\1</mark>'
text = sanitize(text) unless options[:sanitize] == false
if text.blank? || phrases.blank?
@@ -156,19 +157,20 @@ module ActionView
options = args.extract_options!
unless args.empty?
- options[:radius] = args[0] || 100
- options[:omission] = args[1] || "..."
+ options[:radius] = args[0]
+ options[:omission] = args[1]
end
- options.reverse_merge!(:radius => 100, :omission => "...")
+ radius = options[:radius] || 100
+ omission = options[:omission] || "..."
phrase = Regexp.escape(phrase)
return unless found_pos = text =~ /(#{phrase})/i
- start_pos = [ found_pos - options[:radius], 0 ].max
- end_pos = [ [ found_pos + phrase.length + options[:radius] - 1, 0].max, text.length ].min
+ start_pos = [ found_pos - radius, 0 ].max
+ end_pos = [ [ found_pos + phrase.length + radius - 1, 0].max, text.length ].min
- prefix = start_pos > 0 ? options[:omission] : ""
- postfix = end_pos < text.length - 1 ? options[:omission] : ""
+ prefix = start_pos > 0 ? omission : ""
+ postfix = end_pos < text.length - 1 ? omission : ""
prefix + text[start_pos..end_pos].strip + postfix
end
@@ -217,12 +219,12 @@ module ActionView
def word_wrap(text, *args)
options = args.extract_options!
unless args.blank?
- options[:line_width] = args[0] || 80
+ options[:line_width] = args[0]
end
- options.reverse_merge!(:line_width => 80)
+ line_width = options[:line_width] || 80
text.split("\n").collect do |line|
- line.length > options[:line_width] ? line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n").strip : line
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
end * "\n"
end
@@ -306,12 +308,9 @@ module ActionView
# </tr>
# <% end %>
def cycle(first_value, *values)
- if (values.last.instance_of? Hash)
- params = values.pop
- name = params[:name]
- else
- name = "default"
- end
+ options = values.extract_options!
+ name = options.fetch(:name, "default")
+
values.unshift(first_value)
cycle = get_cycle(name)
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index cc74eff53a..fd06bfa2a8 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -45,6 +45,7 @@ module ActionView
# you know what kind of output to expect when you call translate in a template.
def translate(key, options = {})
options.merge!(:rescue_format => :html) unless options.key?(:rescue_format)
+ options[:default] = wrap_translate_defaults(options[:default]) if options[:default]
if html_safe_translation_key?(key)
html_safe_options = options.dup
options.except(*I18n::RESERVED_KEYS).each do |name, value|
@@ -83,6 +84,21 @@ module ActionView
def html_safe_translation_key?(key)
key.to_s =~ /(\b|_|\.)html$/
end
+
+ def wrap_translate_defaults(defaults)
+ new_defaults = []
+ defaults = Array(defaults)
+ while key = defaults.shift
+ if key.is_a?(Symbol)
+ new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) }
+ break
+ else
+ new_defautls << key
+ end
+ end
+
+ new_defaults
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index ebd1f280a8..d0f716cc80 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -23,20 +23,25 @@ module ActionView
include ActionDispatch::Routing::UrlFor
include TagHelper
- def _routes_context
- controller
- end
+ # We need to override url_optoins, _routes_context
+ # and optimize_routes_generation? to consider the controller.
- # Need to map default url options to controller one.
- # def default_url_options(*args) #:nodoc:
- # controller.send(:default_url_options, *args)
- # end
- #
- def url_options
+ def url_options #:nodoc:
return super unless controller.respond_to?(:url_options)
controller.url_options
end
+ def _routes_context #:nodoc:
+ controller
+ end
+ protected :_routes_context
+
+ def optimize_routes_generation? #:nodoc:
+ controller.respond_to?(:optimize_routes_generation?) ?
+ controller.optimize_routes_generation? : super
+ end
+ protected :optimize_routes_generation?
+
# Returns the URL for the set of +options+ provided. This takes the
# same options as +url_for+ in Action Controller (see the
# documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
@@ -55,7 +60,7 @@ module ActionView
#
# ==== Relying on named routes
#
- # Passing a record (like an Active Record or Active Resource) instead of a Hash as the options parameter will
+ # Passing a record (like an Active Record) instead of a Hash as the options parameter will
# trigger the named route for that record. The lookup will happen on the name of the class. So passing a
# Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as
# +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route).
@@ -146,12 +151,12 @@ module ActionView
# create an HTML form and immediately submit the form for processing using
# the HTTP verb specified. Useful for having links perform a POST operation
# in dangerous actions like deleting a record (which search bots can follow
- # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt> and <tt>:put</tt>.
+ # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>.
# Note that if the user has JavaScript disabled, the request will fall back
# to using GET. If <tt>:href => '#'</tt> is used and the user has JavaScript
# disabled clicking the link will have no effect. If you are relying on the
# POST behavior, you should check for it in your controller's action by using
- # the request object's methods for <tt>post?</tt>, <tt>delete?</tt> or <tt>put?</tt>.
+ # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>.
# * <tt>:remote => true</tt> - This will allow the unobtrusive JavaScript
# driver to make an Ajax request to the URL in question instead of following
# the link. The drivers each provide mechanisms for listening for the
@@ -272,7 +277,7 @@ module ActionView
#
# There are a few special +html_options+:
# * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
- # <tt>:delete</tt> and <tt>:put</tt>. By default it will be <tt>:post</tt>.
+ # <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
# * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
# * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to
# prompt with the question specified. If the user accepts, the link is
@@ -323,35 +328,27 @@ module ActionView
# #
def button_to(name, options = {}, html_options = {})
html_options = html_options.stringify_keys
- convert_boolean_attributes!(html_options, %w( disabled ))
+ convert_boolean_attributes!(html_options, %w(disabled))
- method_tag = ''
- if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s)
- method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
- end
+ url = options.is_a?(String) ? options : url_for(options)
+ remote = html_options.delete('remote')
- form_method = method.to_s == 'get' ? 'get' : 'post'
+ method = html_options.delete('method').to_s
+ method_tag = %w{patch put delete}.include?(method) ? method_tag(method) : ''.html_safe
+
+ form_method = method == 'get' ? 'get' : 'post'
form_options = html_options.delete('form') || {}
form_options[:class] ||= html_options.delete('form_class') || 'button_to'
+ form_options.merge!(:method => form_method, :action => url)
+ form_options.merge!("data-remote" => "true") if remote
- remote = html_options.delete('remote')
-
- request_token_tag = ''
- if form_method == 'post' && protect_against_forgery?
- request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
- end
-
- url = options.is_a?(String) ? options : self.url_for(options)
- name ||= url
+ request_token_tag = form_method == 'post' ? token_tag : ''
html_options = convert_options_to_data_attributes(options, html_options)
+ html_options.merge!("type" => "submit", "value" => name || url)
- html_options.merge!("type" => "submit", "value" => name)
-
- form_options.merge!(:method => form_method, :action => url)
- form_options.merge!("data-remote" => "true") if remote
-
- "#{tag(:form, form_options, true)}<div>#{method_tag}#{tag("input", html_options)}#{request_token_tag}</div></form>".html_safe
+ inner_tags = method_tag.safe_concat tag('input', html_options).safe_concat request_token_tag
+ content_tag('form', content_tag('div', inner_tags), form_options)
end
@@ -476,7 +473,7 @@ module ActionView
# string given as the value.
# * <tt>:subject</tt> - Preset the subject line of the email.
# * <tt>:body</tt> - Preset the body of the email.
- # * <tt>:cc</tt> - Carbon Copy addition recipients on the email.
+ # * <tt>:cc</tt> - Carbon Copy additional recipients on the email.
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
#
# ==== Examples
@@ -484,7 +481,7 @@ module ActionView
# # => <a href="mailto:me@domain.com">me@domain.com</a>
#
# mail_to "me@domain.com", "My email", :encode => "javascript"
- # # => <script type="text/javascript">eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script>
+ # # => <script>eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script>
#
# mail_to "me@domain.com", "My email", :encode => "hex"
# # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
@@ -518,7 +515,7 @@ module ActionView
"document.write('#{html}');".each_byte do |c|
string << sprintf("%%%x", c)
end
- "<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe
+ "<script>eval(decodeURIComponent('#{string}'))</script>".html_safe
when "hex"
email_address_encoded = email_address_obfuscated.unpack('C*').map {|c|
sprintf("&#%d;", c)
@@ -599,11 +596,7 @@ module ActionView
# We ignore any extra parameters in the request_uri if the
# submitted url doesn't have any either. This lets the function
# work with things like ?order=asc
- if url_string.index("?")
- request_uri = request.fullpath
- else
- request_uri = request.path
- end
+ request_uri = url_string.index("?") ? request.fullpath : request.path
if url_string =~ /^\w+:\/\//
url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
@@ -633,12 +626,12 @@ module ActionView
end
def link_to_remote_options?(options)
- options.is_a?(Hash) && options.key?('remote') && options.delete('remote')
+ options.is_a?(Hash) && options.delete('remote')
end
def add_method_to_attributes!(html_options, method)
if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/
- html_options["rel"] = "#{html_options["rel"]} nofollow".strip
+ html_options["rel"] = "#{html_options["rel"]} nofollow".lstrip
end
html_options["data-method"] = method
end
@@ -670,6 +663,19 @@ module ActionView
bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) }
html_options
end
+
+ def token_tag(token=nil)
+ if token == false || !protect_against_forgery?
+ ''
+ else
+ token ||= form_authenticity_token
+ tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
+ end
+ end
+
+ def method_tag(method)
+ tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
+ end
end
end
end
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index f2a83b92a9..8e9db634fb 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -37,6 +37,7 @@
# precision:
# significant: false
# strip_insignificant_zeros: false
+ format: "%n%"
# Used in number_to_precision()
precision:
@@ -146,15 +147,8 @@
# Default value for :prompt => true in FormOptionsHelper
prompt: "Please select"
- # Default translation keys for submit FormHelper
+ # Default translation keys for submit and button FormHelper
submit:
create: 'Create %{model}'
update: 'Update %{model}'
submit: 'Save %{model}'
-
- # Default translation keys for button FormHelper
- button:
- create: 'Create %{model}'
- update: 'Update %{model}'
- submit: 'Save %{model}'
-
diff --git a/actionpack/lib/action_view/log_subscriber.rb b/actionpack/lib/action_view/log_subscriber.rb
index bf90d012bf..cc3a871576 100644
--- a/actionpack/lib/action_view/log_subscriber.rb
+++ b/actionpack/lib/action_view/log_subscriber.rb
@@ -12,9 +12,8 @@ module ActionView
alias :render_partial :render_template
alias :render_collection :render_template
- # TODO: Ideally, ActionView should have its own logger so it does not depend on AC.logger
def logger
- ActionController::Base.logger if defined?(ActionController::Base)
+ ActionView::Base.logger
end
protected
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 90d88ca967..b7945a23be 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -9,7 +9,7 @@ module ActionView
# generate a key, given to view paths, used in the resolver cache lookup. Since
# this key is generated just once during the request, it speeds up all cache accesses.
class LookupContext #:nodoc:
- attr_accessor :prefixes
+ attr_accessor :prefixes, :rendered_format
mattr_accessor :fallbacks
@@fallbacks = FallbackFileSystemResolver.instances
@@ -152,6 +152,7 @@ module ActionView
def normalize_name(name, prefixes) #:nodoc:
prefixes = nil if prefixes.blank?
parts = name.to_s.split('/')
+ parts.shift if parts.first.empty?
name = parts.pop
return name, prefixes || [""] if parts.empty?
@@ -169,23 +170,15 @@ module ActionView
def initialize(view_paths, details = {}, prefixes = [])
@details, @details_key = {}, nil
- @frozen_formats, @skip_default_locale = false, false
+ @skip_default_locale = false
@cache = true
@prefixes = prefixes
+ @rendered_format = nil
self.view_paths = view_paths
initialize_details(details)
end
- # Freeze the current formats in the lookup context. By freezing them, you
- # that next template lookups are not going to modify the formats. The con
- # use this, to ensure that formats won't be further modified (as it does
- def freeze_formats(formats, unless_frozen=false) #:nodoc:
- return if unless_frozen && @frozen_formats
- self.formats = formats
- @frozen_formats = true
- end
-
# Override formats= to expand ["*/*"] values and automatically
# add :html as fallback to :js.
def formats=(values)
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 80391d72cc..9f5e3be454 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -7,6 +7,18 @@ module ActionView
config.action_view = ActiveSupport::OrderedOptions.new
config.action_view.stylesheet_expansions = {}
config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) }
+ config.action_view.embed_authenticity_token_in_remote_forms = false
+
+ initializer "action_view.embed_authenticity_token_in_remote_forms" do |app|
+ ActiveSupport.on_load(:action_view) do
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms =
+ app.config.action_view.delete(:embed_authenticity_token_in_remote_forms)
+ end
+ end
+
+ initializer "action_view.logger" do
+ ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
+ end
initializer "action_view.cache_asset_ids" do |app|
unless app.config.cache_classes
diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb
index a588abcee3..52473cd222 100644
--- a/actionpack/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb
@@ -1,7 +1,7 @@
module ActionView
class AbstractRenderer #:nodoc:
delegate :find_template, :template_exists?, :with_fallbacks, :update_details,
- :with_layout_format, :formats, :freeze_formats, :to => :@lookup_context
+ :with_layout_format, :formats, :to => :@lookup_context
def initialize(lookup_context)
@lookup_context = lookup_context
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index e231aade01..c5d5540510 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -32,7 +32,7 @@ module ActionView
#
# <%= render :partial => "account", :object => @buyer %>
#
- # would provide the +@buyer+ object to the partial, available under the local variable +account+ and is
+ # would provide the <tt>@buyer</tt> object to the partial, available under the local variable +account+ and is
# equivalent to:
#
# <%= render :partial => "account", :locals => { :account => @buyer } %>
@@ -85,8 +85,7 @@ module ActionView
# == Rendering objects that respond to `to_partial_path`
#
# Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
- # and pick the proper path by checking `to_proper_path` method. If the object passed to render is a collection,
- # all objects must return the same path.
+ # and pick the proper path by checking `to_partial_path` method.
#
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
# # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
@@ -159,6 +158,47 @@ module ActionView
# Name: <%= user.name %>
# </div>
#
+ # If a collection is given, the layout will be rendered once for each item in
+ # the collection. Just think these two snippets have the same output:
+ #
+ # <%# app/views/users/_user.html.erb %>
+ # Name: <%= user.name %>
+ #
+ # <%# app/views/users/index.html.erb %>
+ # <%# This does not use layouts %>
+ # <ul>
+ # <% users.each do |user| -%>
+ # <li>
+ # <%= render :partial => "user", :locals => { :user => user } %>
+ # </li>
+ # <% end -%>
+ # </ul>
+ #
+ # <%# app/views/users/_li_layout.html.erb %>
+ # <li>
+ # <%= yield %>
+ # </li>
+ #
+ # <%# app/views/users/index.html.erb %>
+ # <ul>
+ # <%= render :partial => "user", :layout => "li_layout", :collection => users %>
+ # </ul>
+ #
+ # Given two users whose names are Alice and Bob, these snippets return:
+ #
+ # <ul>
+ # <li>
+ # Name: Alice
+ # </li>
+ # <li>
+ # Name: Bob
+ # </li>
+ # </ul>
+ #
+ # The current object being rendered, as well as the object_counter, will be
+ # available as local variables inside the layout template under the same names
+ # as available in the partial.
+ #
# You can also apply a layout to a block within any template:
#
# <%# app/views/users/_chief.html.erb &>
@@ -209,18 +249,25 @@ module ActionView
# <%- end -%>
# <% end %>
class PartialRenderer < AbstractRenderer
- PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} }
+ PREFIXED_PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} }
def initialize(*)
super
@context_prefix = @lookup_context.prefixes.first
- @partial_names = PARTIAL_NAMES[@context_prefix]
end
def render(context, options, block)
setup(context, options, block)
identifier = (@template = find_partial) ? @template.identifier : @path
+ @lookup_context.rendered_format ||= begin
+ if @template && @template.formats.present?
+ @template.formats.first
+ else
+ formats.first
+ end
+ end
+
if @collection
instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
render_collection
@@ -248,7 +295,7 @@ module ActionView
object, as = @object, @variable
if !block && (layout = @options[:layout])
- layout = find_template(layout)
+ layout = find_template(layout, @locals.keys + [@variable])
end
object ||= locals[as]
@@ -334,12 +381,19 @@ module ActionView
segments, locals, template = [], @locals, @template
as, counter = @variable, @variable_counter
+ if layout = @options[:layout]
+ layout = find_template(layout, @locals.keys + [@variable, @variable_counter])
+ end
+
locals[counter] = -1
@collection.each do |object|
locals[counter] += 1
locals[as] = object
- segments << template.render(@view, locals)
+
+ content = template.render(@view, locals)
+ content = layout.render(@view, locals) { content } if layout
+ segments << content
end
segments
@@ -371,7 +425,15 @@ module ActionView
raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
end
- @partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
+ if @view.prefix_partial_path_with_controller_namespace
+ prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
+ else
+ path
+ end
+ end
+
+ def prefixed_partial_names
+ @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
end
def merge_prefix_into_object_path(prefix, object_path)
@@ -392,7 +454,7 @@ module ActionView
end
def retrieve_variable(path)
- variable = @options[:as].try(:to_sym) || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym
+ variable = @options.fetch(:as) { path[%r'_?(\w+)(\.\w+)*$', 1] }.try(: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 f3abc6d533..ae923de24e 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -6,7 +6,13 @@ module ActionView
@view = context
@details = extract_details(options)
template = determine_template(options)
- freeze_formats(template.formats, true)
+ context = @lookup_context
+
+ unless context.rendered_format
+ context.formats = template.formats unless template.formats.empty?
+ context.rendered_format = context.formats.first
+ end
+
render_template(template, options[:layout], options[:locals])
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 593eaa2abf..edb3d427d5 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -288,7 +288,7 @@ module ActionView
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
end
- raise ActionView::Template::Error.new(self, {}, e)
+ raise ActionView::Template::Error.new(self, e)
end
end
@@ -297,13 +297,12 @@ module ActionView
e.sub_template_of(self)
raise e
else
- assigns = view.respond_to?(:assigns) ? view.assigns : {}
template = self
unless template.source
template = refresh(view)
template.encode!
end
- raise Template::Error.new(template, assigns, e)
+ raise Template::Error.new(template, e)
end
end
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
index 83df2604bb..d8258f7b11 100644
--- a/actionpack/lib/action_view/template/error.rb
+++ b/actionpack/lib/action_view/template/error.rb
@@ -55,9 +55,9 @@ module ActionView
attr_reader :original_exception, :backtrace
- def initialize(template, assigns, original_exception)
+ def initialize(template, original_exception)
super(original_exception.message)
- @template, @assigns, @original_exception = template, assigns.dup, original_exception
+ @template, @original_exception = template, original_exception
@sub_templates = nil
@backtrace = original_exception.backtrace
end
diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb
index aa693335e3..4e22bec6cc 100644
--- a/actionpack/lib/action_view/template/handlers.rb
+++ b/actionpack/lib/action_view/template/handlers.rb
@@ -17,15 +17,13 @@ module ActionView #:nodoc:
@@template_extensions ||= @@template_handlers.keys
end
- # Register a class that knows how to handle template files with the given
+ # Register an object that knows how to handle template files with the given
# extension. This can be used to implement new template types.
- # The constructor for the class must take the ActiveView::Base instance
- # as a parameter, and the class must implement a +render+ method that
- # takes the contents of the template to render as well as the Hash of
- # local assigns available to the template. The +render+ method ought to
- # return the rendered template as a string.
- def register_template_handler(extension, klass)
- @@template_handlers[extension.to_sym] = klass
+ # The handler must respond to `:call`, which will be passed the template
+ # and should return the rendered template as a String.
+ def register_template_handler(extension, handler)
+ @@template_handlers[extension.to_sym] = handler
+ @@template_extensions = nil
end
def template_handler_extensions
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index 323df67c97..19b9112afd 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -44,10 +44,6 @@ module ActionView
class_attribute :erb_trim_mode
self.erb_trim_mode = '-'
- # Default format used by ERB.
- class_attribute :default_format
- self.default_format = Mime::HTML
-
# Default implementation used.
class_attribute :erb_implementation
self.erb_implementation = Erubis
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 7fa86866a7..8ea2e5bfe4 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -176,7 +176,7 @@ module ActionView
end
end
- # A resolver that loads files from the filesystem. It allows to set your own
+ # A resolver that loads files from the filesystem. It allows setting your own
# resolving pattern. Such pattern can be a glob string supported by some variables.
#
# ==== Examples
@@ -192,7 +192,7 @@ module ActionView
#
# FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}")
#
- # If you don't specify pattern then the default will be used.
+ # If you don't specify a pattern then the default will be used.
#
# In order to use any of the customized resolvers above in a Rails application, you just need
# to configure ActionController::Base.view_paths in an initializer, for example:
@@ -204,10 +204,10 @@ module ActionView
#
# ==== Pattern format and variables
#
- # Pattern have to be a valid glob string, and it allows you to use the
+ # Pattern has to be a valid glob string, and it allows you to use the
# following variables:
#
- # * <tt>:prefix</tt> - usualy the controller path
+ # * <tt>:prefix</tt> - usually the controller path
# * <tt>:action</tt> - name of the action
# * <tt>:locale</tt> - possible locale versions
# * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index c734c914db..fece499c94 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -59,8 +59,10 @@ module ActionView
end
def determine_default_helper_class(name)
- mod = name.sub(/Test$/, '').safe_constantize
+ mod = name.sub(/Test$/, '').constantize
mod.is_a?(Class) ? nil : mod
+ rescue NameError
+ nil
end
def helper_method(*methods)
@@ -233,10 +235,8 @@ module ActionView
super
end
end
-
end
include Behavior
-
end
end
diff --git a/actionpack/lib/sprockets/assets.rake b/actionpack/lib/sprockets/assets.rake
deleted file mode 100644
index f3547359cd..0000000000
--- a/actionpack/lib/sprockets/assets.rake
+++ /dev/null
@@ -1,101 +0,0 @@
-require "fileutils"
-
-namespace :assets do
- def ruby_rake_task(task, fork = true)
- env = ENV['RAILS_ENV'] || 'production'
- groups = ENV['RAILS_GROUPS'] || 'assets'
- args = [$0, task,"RAILS_ENV=#{env}","RAILS_GROUPS=#{groups}"]
- args << "--trace" if Rake.application.options.trace
- fork ? ruby(*args) : Kernel.exec(FileUtils::RUBY, *args)
- end
-
- # We are currently running with no explicit bundler group
- # and/or no explicit environment - we have to reinvoke rake to
- # execute this task.
- def invoke_or_reboot_rake_task(task)
- if ENV['RAILS_GROUPS'].to_s.empty? || ENV['RAILS_ENV'].to_s.empty?
- ruby_rake_task task
- else
- Rake::Task[task].invoke
- end
- end
-
- desc "Compile all the assets named in config.assets.precompile"
- task :precompile do
- invoke_or_reboot_rake_task "assets:precompile:all"
- end
-
- namespace :precompile do
- def internal_precompile(digest=nil)
- unless Rails.application.config.assets.enabled
- warn "Cannot precompile assets if sprockets is disabled. Please set config.assets.enabled to true"
- exit
- end
-
- # Ensure that action view is loaded and the appropriate
- # sprockets hooks get executed
- _ = ActionView::Base
-
- config = Rails.application.config
- config.assets.compile = true
- config.assets.digest = digest unless digest.nil?
- config.assets.digests = {}
-
- env = Rails.application.assets
- target = File.join(Rails.public_path, config.assets.prefix)
- compiler = Sprockets::StaticCompiler.new(env,
- target,
- config.assets.precompile,
- :manifest_path => config.assets.manifest,
- :digest => config.assets.digest,
- :manifest => digest.nil?)
- compiler.compile
- end
-
- task :all do
- Rake::Task["assets:precompile:primary"].invoke
- # We need to reinvoke in order to run the secondary digestless
- # asset compilation run - a fresh Sprockets environment is
- # required in order to compile digestless assets as the
- # environment has already cached the assets on the primary
- # run.
- ruby_rake_task("assets:precompile:nondigest", false) if Rails.application.config.assets.digest
- end
-
- task :primary => ["assets:cache:clean"] do
- internal_precompile
- end
-
- task :nondigest => ["assets:cache:clean"] do
- internal_precompile(false)
- end
- end
-
- desc "Remove compiled assets"
- task :clean do
- invoke_or_reboot_rake_task "assets:clean:all"
- end
-
- namespace :clean do
- task :all => ["assets:cache:clean"] do
- config = Rails.application.config
- public_asset_path = File.join(Rails.public_path, config.assets.prefix)
- rm_rf public_asset_path, :secure => true
- end
- end
-
- namespace :cache do
- task :clean => ["assets:environment"] do
- Rails.application.assets.cache.clear
- end
- end
-
- task :environment do
- if Rails.application.config.assets.initialize_on_precompile
- Rake::Task["environment"].invoke
- else
- Rails.application.initialize!(:assets)
- Sprockets::Bootstrap.new(Rails.application).run
- end
- end
-end
diff --git a/actionpack/lib/sprockets/bootstrap.rb b/actionpack/lib/sprockets/bootstrap.rb
deleted file mode 100644
index 395b264fe7..0000000000
--- a/actionpack/lib/sprockets/bootstrap.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module Sprockets
- class Bootstrap
- def initialize(app)
- @app = app
- end
-
- # TODO: Get rid of config.assets.enabled
- def run
- app, config = @app, @app.config
- return unless app.assets
-
- config.assets.paths.each { |path| app.assets.append_path(path) }
-
- if config.assets.compress
- # temporarily hardcode default JS compressor to uglify. Soon, it will work
- # the same as SCSS, where a default plugin sets the default.
- unless config.assets.js_compressor == false
- app.assets.js_compressor = LazyCompressor.new { Sprockets::Compressors.registered_js_compressor(config.assets.js_compressor || :uglifier) }
- end
-
- unless config.assets.css_compressor == false
- app.assets.css_compressor = LazyCompressor.new { Sprockets::Compressors.registered_css_compressor(config.assets.css_compressor) }
- end
- end
-
- if config.assets.compile
- app.routes.prepend do
- mount app.assets => config.assets.prefix
- end
- end
-
- if config.assets.digest
- app.assets = app.assets.index
- end
- end
- end
-end
diff --git a/actionpack/lib/sprockets/compressors.rb b/actionpack/lib/sprockets/compressors.rb
deleted file mode 100644
index cb3e13314b..0000000000
--- a/actionpack/lib/sprockets/compressors.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-module Sprockets
- module Compressors
- @@css_compressors = {}
- @@js_compressors = {}
- @@default_css_compressor = nil
- @@default_js_compressor = nil
-
- def self.register_css_compressor(name, klass, options = {})
- @@default_css_compressor = name.to_sym if options[:default] || @@default_css_compressor.nil?
- @@css_compressors[name.to_sym] = {:klass => klass.to_s, :require => options[:require]}
- end
-
- def self.register_js_compressor(name, klass, options = {})
- @@default_js_compressor = name.to_sym if options[:default] || @@default_js_compressor.nil?
- @@js_compressors[name.to_sym] = {:klass => klass.to_s, :require => options[:require]}
- end
-
- def self.registered_css_compressor(name)
- if name.respond_to?(:to_sym)
- compressor = @@css_compressors[name.to_sym] || @@css_compressors[@@default_css_compressor]
- require compressor[:require] if compressor[:require]
- compressor[:klass].constantize.new
- else
- name
- end
- end
-
- def self.registered_js_compressor(name)
- if name.respond_to?(:to_sym)
- compressor = @@js_compressors[name.to_sym] || @@js_compressors[@@default_js_compressor]
- require compressor[:require] if compressor[:require]
- compressor[:klass].constantize.new
- else
- name
- end
- end
-
- # The default compressors must be registered in default plugins (ex. Sass-Rails)
- register_css_compressor(:scss, 'Sass::Rails::Compressor', :require => 'sass/rails/compressor', :default => true)
- register_js_compressor(:uglifier, 'Uglifier', :require => 'uglifier', :default => true)
-
- # Automaticaly register some compressors
- register_css_compressor(:yui, 'YUI::CssCompressor', :require => 'yui/compressor')
- register_js_compressor(:closure, 'Closure::Compiler', :require => 'closure-compiler')
- register_js_compressor(:yui, 'YUI::JavaScriptCompressor', :require => 'yui/compressor')
- end
-
- # An asset compressor which does nothing.
- #
- # This compressor simply returns the asset as-is, without any compression
- # whatsoever. It is useful in development mode, when compression isn't
- # needed but using the same asset pipeline as production is desired.
- class NullCompressor #:nodoc:
- def compress(content)
- content
- end
- end
-
- # An asset compressor which only initializes the underlying compression
- # engine when needed.
- #
- # This postpones the initialization of the compressor until
- # <code>#compress</code> is called the first time.
- class LazyCompressor #:nodoc:
- # Initializes a new LazyCompressor.
- #
- # The block should return a compressor when called, i.e. an object
- # which responds to <code>#compress</code>.
- def initialize(&block)
- @block = block
- end
-
- def compress(content)
- compressor.compress(content)
- end
-
- private
-
- def compressor
- @compressor ||= (@block.call || NullCompressor.new)
- end
- end
-end
diff --git a/actionpack/lib/sprockets/helpers.rb b/actionpack/lib/sprockets/helpers.rb
deleted file mode 100644
index fee48386e0..0000000000
--- a/actionpack/lib/sprockets/helpers.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-module Sprockets
- module Helpers
- autoload :RailsHelper, "sprockets/helpers/rails_helper"
- autoload :IsolatedHelper, "sprockets/helpers/isolated_helper"
- end
-end
diff --git a/actionpack/lib/sprockets/helpers/isolated_helper.rb b/actionpack/lib/sprockets/helpers/isolated_helper.rb
deleted file mode 100644
index 3adb928c45..0000000000
--- a/actionpack/lib/sprockets/helpers/isolated_helper.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module Sprockets
- module Helpers
- module IsolatedHelper
- def controller
- nil
- end
-
- def config
- Rails.application.config.action_controller
- end
- end
- end
-end
diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb
deleted file mode 100644
index 976ae5a76d..0000000000
--- a/actionpack/lib/sprockets/helpers/rails_helper.rb
+++ /dev/null
@@ -1,167 +0,0 @@
-require "action_view"
-
-module Sprockets
- module Helpers
- module RailsHelper
- extend ActiveSupport::Concern
- include ActionView::Helpers::AssetTagHelper
-
- def asset_paths
- @asset_paths ||= begin
- paths = RailsHelper::AssetPaths.new(config, controller)
- paths.asset_environment = asset_environment
- paths.asset_digests = asset_digests
- paths.compile_assets = compile_assets?
- paths.digest_assets = digest_assets?
- paths
- end
- end
-
- def javascript_include_tag(*sources)
- options = sources.extract_options!
- debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
- body = options.key?(:body) ? options.delete(:body) : false
- digest = options.key?(:digest) ? options.delete(:digest) : digest_assets?
-
- sources.collect do |source|
- if debug && asset = asset_paths.asset_for(source, 'js')
- asset.to_a.map { |dep|
- super(dep.pathname.to_s, { :src => path_to_asset(dep, :ext => 'js', :body => true, :digest => digest) }.merge!(options))
- }
- else
- super(source.to_s, { :src => path_to_asset(source, :ext => 'js', :body => body, :digest => digest) }.merge!(options))
- end
- end.join("\n").html_safe
- end
-
- def stylesheet_link_tag(*sources)
- options = sources.extract_options!
- debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
- body = options.key?(:body) ? options.delete(:body) : false
- digest = options.key?(:digest) ? options.delete(:digest) : digest_assets?
-
- sources.collect do |source|
- if debug && asset = asset_paths.asset_for(source, 'css')
- asset.to_a.map { |dep|
- super(dep.pathname.to_s, { :href => path_to_asset(dep, :ext => 'css', :body => true, :protocol => :request, :digest => digest) }.merge!(options))
- }
- else
- super(source.to_s, { :href => path_to_asset(source, :ext => 'css', :body => body, :protocol => :request, :digest => digest) }.merge!(options))
- end
- end.join("\n").html_safe
- end
-
- def asset_path(source, options = {})
- source = source.logical_path if source.respond_to?(:logical_path)
- path = asset_paths.compute_public_path(source, asset_prefix, options.merge(:body => true))
- options[:body] ? "#{path}?body=1" : path
- end
- alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route
-
- def image_path(source)
- path_to_asset(source)
- end
- alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
-
- def font_path(source)
- path_to_asset(source)
- end
- alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route
-
- def javascript_path(source)
- path_to_asset(source, :ext => 'js')
- end
- alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with an javascript_path named route
-
- def stylesheet_path(source)
- path_to_asset(source, :ext => 'css')
- end
- alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with an stylesheet_path named route
-
- private
- def debug_assets?
- compile_assets? && (Rails.application.config.assets.debug || params[:debug_assets])
- rescue NoMethodError
- false
- end
-
- # Override to specify an alternative prefix for asset path generation.
- # When combined with a custom +asset_environment+, this can be used to
- # implement themes that can take advantage of the asset pipeline.
- #
- # If you only want to change where the assets are mounted, refer to
- # +config.assets.prefix+ instead.
- def asset_prefix
- Rails.application.config.assets.prefix
- end
-
- def asset_digests
- Rails.application.config.assets.digests
- end
-
- def compile_assets?
- Rails.application.config.assets.compile
- end
-
- def digest_assets?
- Rails.application.config.assets.digest
- end
-
- # Override to specify an alternative asset environment for asset
- # path generation. The environment should already have been mounted
- # at the prefix returned by +asset_prefix+.
- def asset_environment
- Rails.application.assets
- end
-
- class AssetPaths < ::ActionView::AssetPaths #:nodoc:
- attr_accessor :asset_environment, :asset_prefix, :asset_digests, :compile_assets, :digest_assets
-
- class AssetNotPrecompiledError < StandardError; end
-
- def asset_for(source, ext)
- source = source.to_s
- return nil if is_uri?(source)
- source = rewrite_extension(source, nil, ext)
- asset_environment[source]
- rescue Sprockets::FileOutsidePaths
- nil
- end
-
- def digest_for(logical_path)
- if digest_assets && asset_digests && (digest = asset_digests[logical_path])
- return digest
- end
-
- if compile_assets
- if digest_assets && asset = asset_environment[logical_path]
- return asset.digest_path
- end
- return logical_path
- else
- raise AssetNotPrecompiledError.new("#{logical_path} isn't precompiled")
- end
- end
-
- def rewrite_asset_path(source, dir, options = {})
- if source[0] == ?/
- source
- else
- source = digest_for(source) unless options[:digest] == false
- source = File.join(dir, source)
- source = "/#{source}" unless source =~ /^\//
- source
- end
- end
-
- def rewrite_extension(source, dir, ext)
- if ext && File.extname(source) != ".#{ext}"
- "#{source}.#{ext}"
- else
- source
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
deleted file mode 100644
index a7eb03acaf..0000000000
--- a/actionpack/lib/sprockets/railtie.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require "action_controller/railtie"
-
-module Sprockets
- autoload :Bootstrap, "sprockets/bootstrap"
- autoload :Helpers, "sprockets/helpers"
- autoload :Compressors, "sprockets/compressors"
- autoload :LazyCompressor, "sprockets/compressors"
- autoload :NullCompressor, "sprockets/compressors"
- autoload :StaticCompiler, "sprockets/static_compiler"
-
- # TODO: Get rid of config.assets.enabled
- class Railtie < ::Rails::Railtie
- rake_tasks do
- load "sprockets/assets.rake"
- end
-
- initializer "sprockets.environment", :group => :all do |app|
- config = app.config
- next unless config.assets.enabled
-
- require 'sprockets'
-
- app.assets = Sprockets::Environment.new(app.root.to_s) do |env|
- env.logger = ::Rails.logger
- env.version = ::Rails.env + "-#{config.assets.version}"
-
- if config.assets.cache_store != false
- env.cache = ActiveSupport::Cache.lookup_store(config.assets.cache_store) || ::Rails.cache
- end
- end
-
- if config.assets.manifest
- path = File.join(config.assets.manifest, "manifest.yml")
- else
- path = File.join(Rails.public_path, config.assets.prefix, "manifest.yml")
- end
-
- if File.exist?(path)
- config.assets.digests = YAML.load_file(path)
- end
-
- ActiveSupport.on_load(:action_view) do
- include ::Sprockets::Helpers::RailsHelper
- app.assets.context_class.instance_eval do
- include ::Sprockets::Helpers::IsolatedHelper
- include ::Sprockets::Helpers::RailsHelper
- end
- end
- end
-
- # We need to configure this after initialization to ensure we collect
- # paths from all engines. This hook is invoked exactly before routes
- # are compiled, and so that other Railties have an opportunity to
- # register compressors.
- config.after_initialize do |app|
- Sprockets::Bootstrap.new(app).run
- end
- end
-end
diff --git a/actionpack/lib/sprockets/static_compiler.rb b/actionpack/lib/sprockets/static_compiler.rb
deleted file mode 100644
index 719df0bd51..0000000000
--- a/actionpack/lib/sprockets/static_compiler.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'fileutils'
-
-module Sprockets
- class StaticCompiler
- attr_accessor :env, :target, :paths
-
- def initialize(env, target, paths, options = {})
- @env = env
- @target = target
- @paths = paths
- @digest = options.key?(:digest) ? options.delete(:digest) : true
- @manifest = options.key?(:manifest) ? options.delete(:manifest) : true
- @manifest_path = options.delete(:manifest_path) || target
- @zip_files = options.delete(:zip_files) || /\.(?:css|html|js|svg|txt|xml)$/
- end
-
- def compile
- manifest = {}
- env.each_logical_path do |logical_path|
- next unless compile_path?(logical_path)
- if asset = env.find_asset(logical_path)
- manifest[logical_path] = write_asset(asset)
- end
- end
- write_manifest(manifest) if @manifest
- end
-
- def write_manifest(manifest)
- FileUtils.mkdir_p(@manifest_path)
- File.open("#{@manifest_path}/manifest.yml", 'wb') do |f|
- YAML.dump(manifest, f)
- end
- end
-
- def write_asset(asset)
- path_for(asset).tap do |path|
- filename = File.join(target, path)
- FileUtils.mkdir_p File.dirname(filename)
- asset.write_to(filename)
- asset.write_to("#{filename}.gz") if filename.to_s =~ @zip_files
- end
- end
-
- def compile_path?(logical_path)
- paths.each do |path|
- case path
- when Regexp
- return true if path.match(logical_path)
- when Proc
- return true if path.call(logical_path)
- else
- return true if File.fnmatch(path.to_s, logical_path)
- end
- end
- false
- end
-
- def path_for(asset)
- @digest ? asset.digest_path : asset.logical_path
- end
- end
-end