aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/abstract_controller.rb3
-rw-r--r--actionpack/lib/abstract_controller/asset_paths.rb2
-rw-r--r--actionpack/lib/abstract_controller/base.rb10
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb171
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb39
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb410
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb161
-rw-r--r--actionpack/lib/abstract_controller/translation.rb11
-rw-r--r--actionpack/lib/abstract_controller/view_paths.rb96
-rw-r--r--actionpack/lib/action_controller.rb17
-rw-r--r--actionpack/lib/action_controller/base.rb23
-rw-r--r--actionpack/lib/action_controller/caching.rb34
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb116
-rw-r--r--actionpack/lib/action_controller/deprecated.rb7
-rw-r--r--actionpack/lib/action_controller/deprecated/integration_test.rb5
-rw-r--r--actionpack/lib/action_controller/deprecated/performance_test.rb3
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb7
-rw-r--r--actionpack/lib/action_controller/metal.rb8
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb6
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb11
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb9
-rw-r--r--actionpack/lib/action_controller/metal/flash.rb19
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb71
-rw-r--r--actionpack/lib/action_controller/metal/head.rb7
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb16
-rw-r--r--actionpack/lib/action_controller/metal/hide_actions.rb10
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb77
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb2
-rw-r--r--actionpack/lib/action_controller/metal/live.rb108
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb42
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb166
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb67
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb12
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb30
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb72
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb36
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb54
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb327
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb5
-rw-r--r--actionpack/lib/action_controller/railtie.rb21
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb20
-rw-r--r--actionpack/lib/action_controller/test_case.rb139
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner.rb5
-rw-r--r--actionpack/lib/action_dispatch.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb24
-rw-r--r--actionpack/lib/action_dispatch/http/filter_redirect.rb37
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb65
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb70
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb90
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb54
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb75
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb61
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb19
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb98
-rw-r--r--actionpack/lib/action_dispatch/journey.rb5
-rw-r--r--actionpack/lib/action_dispatch/journey/backwards.rb5
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb150
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/builder.rb162
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/simulator.rb44
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb167
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/builder.rb76
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/dot.rb36
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/simulator.rb47
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/transition_table.rb163
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb124
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.rb206
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.y48
-rw-r--r--actionpack/lib/action_dispatch/journey/parser_extras.rb23
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb196
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb136
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb172
-rw-r--r--actionpack/lib/action_dispatch/journey/router/strexp.rb24
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb57
-rw-r--r--actionpack/lib/action_dispatch/journey/routes.rb76
-rw-r--r--actionpack/lib/action_dispatch/journey/scanner.rb61
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb199
-rw-r--r--actionpack/lib/action_dispatch/journey/visualizer/fsm.css34
-rw-r--r--actionpack/lib/action_dispatch/journey/visualizer/fsm.js134
-rw-r--r--actionpack/lib/action_dispatch/journey/visualizer/index.html.erb52
-rw-r--r--actionpack/lib/action_dispatch/middleware/best_standards_support.rb22
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb345
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb61
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb30
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb66
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb30
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb172
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb104
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb18
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb31
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb34
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb23
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb25
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (renamed from actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb)16
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb15
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb24
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb132
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb23
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb30
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb11
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb17
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb43
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb16
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb144
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb7
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb16
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb24
-rw-r--r--actionpack/lib/action_dispatch/routing.rb136
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb163
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb584
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb19
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb36
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb264
-rw-r--r--actionpack/lib/action_dispatch/routing/routes_proxy.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb37
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/dom.rb8
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb26
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb56
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb8
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/tag.rb38
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb98
-rw-r--r--actionpack/lib/action_dispatch/testing/performance_test.rb10
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb14
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb10
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_pack/version.rb13
-rw-r--r--actionpack/lib/action_view.rb94
-rw-r--r--actionpack/lib/action_view/asset_paths.rb143
-rw-r--r--actionpack/lib/action_view/base.rb201
-rw-r--r--actionpack/lib/action_view/buffers.rb43
-rw-r--r--actionpack/lib/action_view/context.rb36
-rw-r--r--actionpack/lib/action_view/digestor.rb104
-rw-r--r--actionpack/lib/action_view/flows.rb76
-rw-r--r--actionpack/lib/action_view/helpers.rb57
-rw-r--r--actionpack/lib/action_view/helpers/active_model_helper.rb49
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb293
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb93
-rw-r--r--actionpack/lib/action_view/helpers/asset_url_helper.rb334
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb203
-rw-r--r--actionpack/lib/action_view/helpers/benchmark_helper.rb13
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb147
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb222
-rw-r--r--actionpack/lib/action_view/helpers/controller_helper.rb25
-rw-r--r--actionpack/lib/action_view/helpers/csrf_helper.rb30
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb1045
-rw-r--r--actionpack/lib/action_view/helpers/debug_helper.rb41
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb1416
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb788
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb773
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb112
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb442
-rw-r--r--actionpack/lib/action_view/helpers/output_safety_helper.rb37
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb102
-rw-r--r--actionpack/lib/action_view/helpers/rendering_helper.rb90
-rw-r--r--actionpack/lib/action_view/helpers/sanitize_helper.rb256
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb173
-rw-r--r--actionpack/lib/action_view/helpers/tags.rb39
-rw-r--r--actionpack/lib/action_view/helpers/tags/base.rb150
-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/color_field.rb25
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_field.rb13
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_select.rb70
-rw-r--r--actionpack/lib/action_view/helpers/tags/datetime_field.rb22
-rw-r--r--actionpack/lib/action_view/helpers/tags/datetime_local_field.rb19
-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.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb29
-rw-r--r--actionpack/lib/action_view/helpers/tags/hidden_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/label.rb65
-rw-r--r--actionpack/lib/action_view/helpers/tags/month_field.rb13
-rw-r--r--actionpack/lib/action_view/helpers/tags/number_field.rb18
-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_field.rb13
-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/tags/week_field.rb13
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb439
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb107
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb654
-rw-r--r--actionpack/lib/action_view/locale/en.yml56
-rw-r--r--actionpack/lib/action_view/log_subscriber.rb30
-rw-r--r--actionpack/lib/action_view/lookup_context.rb233
-rw-r--r--actionpack/lib/action_view/model_naming.rb12
-rw-r--r--actionpack/lib/action_view/path_set.rb89
-rw-r--r--actionpack/lib/action_view/railtie.rb47
-rw-r--r--actionpack/lib/action_view/record_identifier.rb84
-rw-r--r--actionpack/lib/action_view/renderer/abstract_renderer.rb32
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb475
-rw-r--r--actionpack/lib/action_view/renderer/renderer.rb54
-rw-r--r--actionpack/lib/action_view/renderer/streaming_template_renderer.rb103
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb93
-rw-r--r--actionpack/lib/action_view/routing_url_for.rb107
-rw-r--r--actionpack/lib/action_view/template.rb339
-rw-r--r--actionpack/lib/action_view/template/error.rb130
-rw-r--r--actionpack/lib/action_view/template/handlers.rb53
-rw-r--r--actionpack/lib/action_view/template/handlers/builder.rb26
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb104
-rw-r--r--actionpack/lib/action_view/template/handlers/raw.rb11
-rw-r--r--actionpack/lib/action_view/template/resolver.rb321
-rw-r--r--actionpack/lib/action_view/template/text.rb34
-rw-r--r--actionpack/lib/action_view/template/types.rb58
-rw-r--r--actionpack/lib/action_view/test_case.rb268
-rw-r--r--actionpack/lib/action_view/testing/resolvers.rb50
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner.rb20
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner/html/document.rb68
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner/html/node.rb532
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb188
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner/html/selector.rb830
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner/html/tokenizer.rb107
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner/html/version.rb11
237 files changed, 5899 insertions, 16803 deletions
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb
index 867a7954e0..fe9802e395 100644
--- a/actionpack/lib/abstract_controller.rb
+++ b/actionpack/lib/abstract_controller.rb
@@ -10,12 +10,11 @@ module AbstractController
autoload :Base
autoload :Callbacks
autoload :Collector
+ autoload :DoubleRenderError, "abstract_controller/rendering"
autoload :Helpers
- autoload :Layouts
autoload :Logger
autoload :Rendering
autoload :Translation
autoload :AssetPaths
- autoload :ViewPaths
autoload :UrlFor
end
diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb
index 822254b1a4..e6170228d9 100644
--- a/actionpack/lib/abstract_controller/asset_paths.rb
+++ b/actionpack/lib/abstract_controller/asset_paths.rb
@@ -3,7 +3,7 @@ module AbstractController
extend ActiveSupport::Concern
included do
- config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir,
+ config_accessor :asset_host, :assets_dir, :javascripts_dir,
:stylesheets_dir, :default_asset_host_protocol, :relative_url_root
end
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 388e043f0b..af5de815bb 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -34,6 +34,15 @@ module AbstractController
@abstract = true
end
+ def inherited(klass) # :nodoc:
+ # Define the abstract ivar on subclasses so that we don't get
+ # uninitialized ivar warnings
+ unless klass.instance_variable_defined?(:@abstract)
+ klass.instance_variable_set(:@abstract, false)
+ end
+ super
+ end
+
# A list of all internal methods for a controller. This finds the first
# abstract superclass of a controller, and gets a list of all public
# instance methods on that abstract class. Public instance methods of
@@ -42,6 +51,7 @@ module AbstractController
# (ActionController::Metal and ActionController::Base are defined as abstract)
def internal_methods
controller = self
+
controller = controller.superclass until controller.abstract?
controller.public_instance_methods(true)
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 5705ab590c..d6c941832f 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -8,7 +8,9 @@ module AbstractController
include ActiveSupport::Callbacks
included do
- define_callbacks :process_action, :terminator => "response_body", :skip_after_callbacks_if_terminated => true
+ define_callbacks :process_action,
+ terminator: ->(controller,_) { controller.response_body },
+ skip_after_callbacks_if_terminated: true
end
# Override AbstractController::Base's process_action to run the
@@ -29,29 +31,33 @@ module AbstractController
# * <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
def _normalize_callback_options(options)
- if only = options[:only]
- only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ")
- options[:if] = Array(options[:if]) << only
- end
- if except = options[:except]
- except = Array(except).map {|e| "action_name == '#{e}'"}.join(" || ")
- options[:unless] = Array(options[:unless]) << except
+ _normalize_callback_option(options, :only, :if)
+ _normalize_callback_option(options, :except, :unless)
+ end
+
+ def _normalize_callback_option(options, from, to) # :nodoc:
+ if from = options[from]
+ from = Array(from).map {|o| "action_name == '#{o}'"}.join(" || ")
+ options[to] = Array(options[to]).unshift(from)
end
end
- # Skip before, after, and around filters matching any of the names
+ # Skip before, after, and around action callbacks matching any of the names
+ # Aliased as skip_filter.
#
# ==== Parameters
# * <tt>names</tt> - A list of valid names that could be used for
# callbacks. Note that skipping uses Ruby equality, so it's
# impossible to skip a callback defined using an anonymous proc
# using #skip_filter
- def skip_filter(*names)
- skip_before_filter(*names)
- skip_after_filter(*names)
- skip_around_filter(*names)
+ def skip_action_callback(*names)
+ skip_before_action(*names)
+ skip_after_action(*names)
+ skip_around_action(*names)
end
+ alias_method :skip_filter, :skip_action_callback
+
# Take callback names and an optional callback proc, normalize them,
# then call the block with each callback. This allows us to abstract
# the normalization across several methods that use it.
@@ -65,7 +71,7 @@ module AbstractController
# * <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 = nil)
- options = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
+ options = callbacks.extract_options!
_normalize_callback_options(options)
callbacks.push(block) if block
callbacks.each do |callback|
@@ -74,119 +80,138 @@ module AbstractController
end
##
- # :method: before_filter
+ # :method: before_action
#
- # :call-seq: before_filter(names, block)
+ # :call-seq: before_action(names, block)
#
- # Append a before filter. See _insert_callbacks for parameter details.
+ # Append a callback before actions. See _insert_callbacks for parameter details.
+ # Aliased as before_filter.
##
- # :method: prepend_before_filter
+ # :method: prepend_before_action
#
- # :call-seq: prepend_before_filter(names, block)
+ # :call-seq: prepend_before_action(names, block)
#
- # Prepend a before filter. See _insert_callbacks for parameter details.
+ # Prepend a callback before actions. See _insert_callbacks for parameter details.
+ # Aliased as prepend_before_filter.
##
- # :method: skip_before_filter
+ # :method: skip_before_action
#
- # :call-seq: skip_before_filter(names)
+ # :call-seq: skip_before_action(names)
#
- # Skip a before filter. See _insert_callbacks for parameter details.
+ # Skip a callback before actions. See _insert_callbacks for parameter details.
+ # Aliased as skip_before_filter.
##
- # :method: append_before_filter
+ # :method: append_before_action
#
- # :call-seq: append_before_filter(names, block)
+ # :call-seq: append_before_action(names, block)
#
- # Append a before filter. See _insert_callbacks for parameter details.
+ # Append a callback before actions. See _insert_callbacks for parameter details.
+ # Aliased as append_before_filter.
##
- # :method: after_filter
+ # :method: after_action
#
- # :call-seq: after_filter(names, block)
+ # :call-seq: after_action(names, block)
#
- # Append an after filter. See _insert_callbacks for parameter details.
+ # Append a callback after actions. See _insert_callbacks for parameter details.
+ # Aliased as after_filter.
##
- # :method: prepend_after_filter
+ # :method: prepend_after_action
#
- # :call-seq: prepend_after_filter(names, block)
+ # :call-seq: prepend_after_action(names, block)
#
- # Prepend an after filter. See _insert_callbacks for parameter details.
+ # Prepend a callback after actions. See _insert_callbacks for parameter details.
+ # Aliased as prepend_after_filter.
##
- # :method: skip_after_filter
+ # :method: skip_after_action
#
- # :call-seq: skip_after_filter(names)
+ # :call-seq: skip_after_action(names)
#
- # Skip an after filter. See _insert_callbacks for parameter details.
+ # Skip a callback after actions. See _insert_callbacks for parameter details.
+ # Aliased as skip_after_filter.
##
- # :method: append_after_filter
+ # :method: append_after_action
#
- # :call-seq: append_after_filter(names, block)
+ # :call-seq: append_after_action(names, block)
#
- # Append an after filter. See _insert_callbacks for parameter details.
+ # Append a callback after actions. See _insert_callbacks for parameter details.
+ # Aliased as append_after_filter.
##
- # :method: around_filter
+ # :method: around_action
#
- # :call-seq: around_filter(names, block)
+ # :call-seq: around_action(names, block)
#
- # Append an around filter. See _insert_callbacks for parameter details.
+ # Append a callback around actions. See _insert_callbacks for parameter details.
+ # Aliased as around_filter.
##
- # :method: prepend_around_filter
+ # :method: prepend_around_action
#
- # :call-seq: prepend_around_filter(names, block)
+ # :call-seq: prepend_around_action(names, block)
#
- # Prepend an around filter. See _insert_callbacks for parameter details.
+ # Prepend a callback around actions. See _insert_callbacks for parameter details.
+ # Aliased as prepend_around_filter.
##
- # :method: skip_around_filter
+ # :method: skip_around_action
#
- # :call-seq: skip_around_filter(names)
+ # :call-seq: skip_around_action(names)
#
- # Skip an around filter. See _insert_callbacks for parameter details.
+ # Skip a callback around actions. See _insert_callbacks for parameter details.
+ # Aliased as skip_around_filter.
##
- # :method: append_around_filter
+ # :method: append_around_action
#
- # :call-seq: append_around_filter(names, block)
+ # :call-seq: append_around_action(names, block)
#
- # Append an around filter. See _insert_callbacks for parameter details.
+ # Append a callback around actions. See _insert_callbacks for parameter details.
+ # Aliased as append_around_filter.
- # set up before_filter, prepend_before_filter, skip_before_filter, etc.
+ # set up before_action, prepend_before_action, skip_before_action, etc.
# for each of before, after, and around.
- [:before, :after, :around].each do |filter|
+ [:before, :after, :around].each do |callback|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- # Append a before, after or around filter. See _insert_callbacks
+ # Append a before, after or around callback. See _insert_callbacks
# 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|
- set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options)
- end # end
- end # end
+ def #{callback}_action(*names, &blk) # def before_action(*names, &blk)
+ _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ set_callback(:process_action, :#{callback}, name, options) # set_callback(:process_action, :before, name, options)
+ end # end
+ end # end
- # Prepend a before, after or around filter. See _insert_callbacks
+ alias_method :#{callback}_filter, :#{callback}_action
+
+ # Prepend a before, after or around callback. See _insert_callbacks
# 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|
- set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
- end # end
- end # end
+ def prepend_#{callback}_action(*names, &blk) # def prepend_before_action(*names, &blk)
+ _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ set_callback(:process_action, :#{callback}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
+ end # end
+ end # end
+
+ alias_method :prepend_#{callback}_filter, :prepend_#{callback}_action
- # Skip a before, after or around filter. See _insert_callbacks
+ # Skip a before, after or around callback. See _insert_callbacks
# for details on the allowed parameters.
- 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
-
- # *_filter is the same as append_*_filter
- alias_method :append_#{filter}_filter, :#{filter}_filter # alias_method :append_before_filter, :before_filter
+ def skip_#{callback}_action(*names) # def skip_before_action(*names)
+ _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options|
+ skip_callback(:process_action, :#{callback}, name, options) # skip_callback(:process_action, :before, name, options)
+ end # end
+ end # end
+
+ alias_method :skip_#{callback}_filter, :skip_#{callback}_action
+
+ # *_action is the same as append_*_action
+ alias_method :append_#{callback}_action, :#{callback}_action # alias_method :append_before_action, :before_action
+ alias_method :append_#{callback}_filter, :#{callback}_action # alias_method :append_before_filter, :before_action
RUBY_EVAL
end
end
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index d3929b685c..e77e4e01e9 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -12,14 +12,31 @@ module AbstractController
self._helper_methods = Array.new
end
+ class MissingHelperError < LoadError
+ def initialize(error, path)
+ @error = error
+ @path = "helpers/#{path}.rb"
+ set_backtrace error.backtrace
+
+ if error.path =~ /^#{path}(\.rb)?$/
+ super("Missing helper file helpers/%s.rb" % path)
+ else
+ raise error
+ end
+ end
+ end
+
module ClassMethods
+ MissingHelperError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('AbstractController::Helpers::ClassMethods::MissingHelperError',
+ 'AbstractController::Helpers::MissingHelperError')
+
# When a class is inherited, wrap its helper module in a new module.
# This ensures that the parent class's module can be changed
# independently of the child class's.
def inherited(klass)
helpers = _helpers
klass._helpers = Module.new { include helpers }
- klass.class_eval { default_helper_module! unless anonymous? }
+ klass.class_eval { default_helper_module! } unless klass.anonymous?
super
end
@@ -29,7 +46,7 @@ module AbstractController
# helper_method :current_user, :logged_in?
#
# def current_user
- # @current_user ||= User.find_by_id(session[:user])
+ # @current_user ||= User.find_by(id: session[:user])
# end
#
# def logged_in?
@@ -58,11 +75,10 @@ module AbstractController
# The +helper+ class method can take a series of helper module names, a block, or both.
#
- # ==== Parameters
- # * <tt>*args</tt> - Module, Symbol, String, :all
+ # ==== Options
+ # * <tt>*args</tt> - Module, Symbol, String
# * <tt>block</tt> - A block defining helper methods
#
- # ==== Examples
# When the argument is a module it will be included directly in the template class.
# helper FooHelper # => includes FooHelper
#
@@ -114,7 +130,7 @@ module AbstractController
# helpers with the following behavior:
#
# String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper",
- # and "foo_bar_helper.rb" is loaded using require_dependency.
+ # and "foo_bar_helper.rb" is loaded using require_dependency.
#
# Module:: No further processing
#
@@ -135,7 +151,7 @@ module AbstractController
begin
require_dependency(file_name)
rescue LoadError => e
- raise MissingHelperError.new(e, file_name)
+ raise AbstractController::Helpers::MissingHelperError.new(e, file_name)
end
file_name.camelize.constantize
when Module
@@ -146,15 +162,6 @@ module AbstractController
end
end
- class MissingHelperError < LoadError
- def initialize(error, path)
- @error = error
- @path = "helpers/#{path}.rb"
- set_backtrace error.backtrace
- super("Missing helper file helpers/%s.rb" % path)
- end
- end
-
private
# Makes all the (instance) methods in the helper module available to templates
# rendered through this controller.
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
deleted file mode 100644
index c1b3994035..0000000000
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ /dev/null
@@ -1,410 +0,0 @@
-require "active_support/core_ext/module/remove_method"
-
-module AbstractController
- # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
- # repeated setups. The inclusion pattern has pages that look like this:
- #
- # <%= render "shared/header" %>
- # Hello World
- # <%= render "shared/footer" %>
- #
- # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
- # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
- #
- # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
- # that the header and footer are only mentioned in one place, like this:
- #
- # // The header part of this layout
- # <%= yield %>
- # // The footer part of this layout
- #
- # And then you have content pages that look like this:
- #
- # hello world
- #
- # At rendering time, the content page is computed and then inserted in the layout, like this:
- #
- # // The header part of this layout
- # hello world
- # // The footer part of this layout
- #
- # == Accessing shared variables
- #
- # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
- # references that won't materialize before rendering time:
- #
- # <h1><%= @page_title %></h1>
- # <%= yield %>
- #
- # ...and content pages that fulfill these references _at_ rendering time:
- #
- # <% @page_title = "Welcome" %>
- # Off-world colonies offers you a chance to start a new life
- #
- # The result after rendering is:
- #
- # <h1>Welcome</h1>
- # Off-world colonies offers you a chance to start a new life
- #
- # == Layout assignment
- #
- # You can either specify a layout declaratively (using the #layout class method) or give
- # it the same name as your controller, and place it in <tt>app/views/layouts</tt>.
- # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance.
- #
- # For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>,
- # that template will be used for all actions in PostsController and controllers inheriting
- # from PostsController.
- #
- # If you use a module, for instance Weblog::PostsController, you will need a template named
- # <tt>app/views/layouts/weblog/posts.html.erb</tt>.
- #
- # Since all your controllers inherit from ApplicationController, they will use
- # <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified
- # or provided.
- #
- # == Inheritance Examples
- #
- # class BankController < ActionController::Base
- # # bank.html.erb exists
- #
- # class ExchangeController < BankController
- # # exchange.html.erb exists
- #
- # class CurrencyController < BankController
- #
- # class InformationController < BankController
- # layout "information"
- #
- # class TellerController < InformationController
- # # teller.html.erb exists
- #
- # class EmployeeController < InformationController
- # # employee.html.erb exists
- # layout nil
- #
- # class VaultController < BankController
- # layout :access_level_layout
- #
- # class TillController < BankController
- # layout false
- #
- # 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.
- #
- # However, when a layout is explicitly set, the explicitly set layout wins:
- # * The InformationController uses the "information" layout, explicitly set.
- # * The TellerController also uses the "information" layout, because the parent explicitly set it.
- # * The EmployeeController uses the "employee" layout, because it set the layout to nil, resetting the parent configuration.
- # * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method.
- # * The TillController does not use a layout at all.
- #
- # == Types of layouts
- #
- # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
- # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
- # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
- #
- # The method reference is the preferred approach to variable layouts and is used like this:
- #
- # class WeblogController < ActionController::Base
- # layout :writers_and_readers
- #
- # def index
- # # fetching posts
- # end
- #
- # private
- # def writers_and_readers
- # logged_in? ? "writer_layout" : "reader_layout"
- # end
- # end
- #
- # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
- # is logged in or not.
- #
- # If you want to use an inline method, such as a proc, do something like this:
- #
- # class WeblogController < ActionController::Base
- # layout proc { |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
- # 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:
- #
- # class WeblogController < ActionController::Base
- # layout "weblog_standard"
- # end
- #
- # 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:
- #
- # class ApplicationController < ActionController::Base
- # layout "application"
- # end
- #
- # class PostsController < ApplicationController
- # # Will use "application" layout
- # end
- #
- # class CommentsController < ApplicationController
- # # Will search for "comments" layout and fallback "application" layout
- # layout nil
- # end
- #
- # == Conditional layouts
- #
- # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
- # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
- # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
- #
- # class WeblogController < ActionController::Base
- # layout "weblog_standard", :except => :rss
- #
- # # ...
- #
- # end
- #
- # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will
- # be rendered directly, without wrapping a layout around the rendered view.
- #
- # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
- # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
- #
- # == Using a different layout in the action render call
- #
- # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
- # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller.
- # You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example:
- #
- # class WeblogController < ActionController::Base
- # layout "weblog_standard"
- #
- # def help
- # render :action => "help", :layout => "help"
- # end
- # end
- #
- # This will override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead.
- module Layouts
- extend ActiveSupport::Concern
-
- include Rendering
-
- included do
- class_attribute :_layout, :_layout_conditions, :instance_accessor => false
- self._layout = nil
- self._layout_conditions = {}
- _write_layout_method
- end
-
- delegate :_layout_conditions, :to => "self.class"
-
- module ClassMethods
- def inherited(klass)
- super
- klass._write_layout_method
- end
-
- # This module is mixed in if layout conditions are provided. This means
- # that if no layout conditions are used, this method is not used
- module LayoutConditions
- # Determines whether the current action has a layout by checking the
- # action name against the :only and :except conditions set on the
- # layout.
- #
- # ==== Returns
- # * <tt> Boolean</tt> - True if the action has a layout, false otherwise.
- def conditional_layout?
- return unless super
-
- conditions = _layout_conditions
-
- if only = conditions[:only]
- only.include?(action_name)
- elsif except = conditions[:except]
- !except.include?(action_name)
- else
- true
- end
- end
- end
-
- # Specify the layout to use for this class.
- #
- # If the specified layout is a:
- # String:: the String is the template name
- # Symbol:: call the method specified by the symbol, which will return the template name
- # false:: There is no layout
- # true:: raise an ArgumentError
- # nil:: Force default layout behavior with inheritance
- #
- # ==== Parameters
- # * <tt>layout</tt> - The layout to use.
- #
- # ==== Options (conditions)
- # * :only - A list of actions to apply this layout to.
- # * :except - Apply this layout to all actions but this one.
- def layout(layout, conditions = {})
- include LayoutConditions unless conditions.empty?
-
- conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
- self._layout_conditions = conditions
-
- self._layout = layout
- _write_layout_method
- end
-
- # If no layout is supplied, look for a template named the return
- # value of this method.
- #
- # ==== Returns
- # * <tt>String</tt> - A template name
- def _implied_layout_name
- controller_path
- end
-
- # Creates a _layout method to be called by _default_layout .
- #
- # If a layout is not explicitly mentioned then look for a layout with the controller's name.
- # if nothing is found then try same procedure to find super class's layout.
- def _write_layout_method
- remove_possible_method(:_layout)
-
- prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
- name_clause = if name
- <<-RUBY
- lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first || super
- RUBY
- else
- <<-RUBY
- super
- RUBY
- end
-
- layout_definition = case _layout
- when String
- _layout.inspect
- when Symbol
- <<-RUBY
- #{_layout}.tap do |layout|
- unless layout.is_a?(String) || !layout
- raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \
- "should have returned a String, false, or nil"
- end
- end
- RUBY
- when Proc
- 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, Proc, false, or nil"
- when nil
- name_clause
- end
-
- self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def _layout
- if conditional_layout?
- #{layout_definition}
- else
- #{name_clause}
- end
- end
- private :_layout
- RUBY
- end
- end
-
- def _normalize_options(options)
- super
-
- if _include_layout?(options)
- layout = options.delete(:layout) { :default }
- options[:layout] = _layout_for_option(layout)
- end
- end
-
- attr_internal_writer :action_has_layout
-
- def initialize(*)
- @_action_has_layout = true
- super
- end
-
- def action_has_layout?
- @_action_has_layout
- end
-
- def conditional_layout?
- true
- end
-
- private
-
- # This will be overwritten by _write_layout_method
- def _layout; end
-
- # Determine the layout for a given name, taking into account the name type.
- #
- # ==== Parameters
- # * <tt>name</tt> - The name of the template
- def _layout_for_option(name)
- case name
- when String then _normalize_layout(name)
- when Proc then name
- when true then Proc.new { _default_layout(true) }
- when :default then Proc.new { _default_layout(false) }
- when false, nil then nil
- else
- raise ArgumentError,
- "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}"
- end
- end
-
- def _normalize_layout(value)
- value.is_a?(String) && value !~ /\blayouts/ ? "layouts/#{value}" : value
- end
-
- # Returns the default layout for this controller.
- # Optionally raises an exception if the layout could not be found.
- #
- # ==== Parameters
- # * <tt>require_layout</tt> - If set to true and layout is not found,
- # an ArgumentError exception is raised (defaults to false)
- #
- # ==== Returns
- # * <tt>template</tt> - The template object for the default layout (or nil)
- def _default_layout(require_layout = false)
- begin
- value = _layout if action_has_layout?
- rescue NameError => e
- raise e, "Could not render layout: #{e.message}"
- end
-
- if require_layout && action_has_layout? && !value
- raise ArgumentError,
- "There was no default layout for #{self.class} in #{view_paths.inspect}"
- end
-
- _normalize_layout(value)
- end
-
- def _include_layout?(options)
- (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
- end
- end
-end
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 3da2834af0..6f6079d3c5 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -1,5 +1,5 @@
-require "abstract_controller/base"
-require "action_view"
+require 'active_support/concern'
+require 'active_support/core_ext/class/attribute'
module AbstractController
class DoubleRenderError < Error
@@ -10,119 +10,55 @@ module AbstractController
end
end
- # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
- # it will trigger the lookup_context and consequently expire the cache.
- class I18nProxy < ::I18n::Config #:nodoc:
- attr_reader :original_config, :lookup_context
-
- def initialize(original_config, lookup_context)
- original_config = original_config.original_config if original_config.respond_to?(:original_config)
- @original_config, @lookup_context = original_config, lookup_context
- end
-
- def locale
- @original_config.locale
- end
-
- def locale=(value)
- @lookup_context.locale = value
- end
- end
-
module Rendering
extend ActiveSupport::Concern
- include AbstractController::ViewPaths
included do
class_attribute :protected_instance_variables
self.protected_instance_variables = []
end
- # Overwrite process to setup I18n proxy.
- def process(*) #:nodoc:
- old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
- super
- ensure
- I18n.config = old_config
- end
-
- module ClassMethods
- def view_context_class
- @view_context_class ||= begin
- routes = respond_to?(:_routes) && _routes
- helpers = respond_to?(:_helpers) && _helpers
-
- Class.new(ActionView::Base) do
- if routes
- include routes.url_helpers
- include routes.mounted_helpers
- end
-
- if helpers
- include helpers
- end
- end
- end
- end
- end
-
- attr_internal_writer :view_context_class
-
- def view_context_class
- @_view_context_class ||= self.class.view_context_class
- end
-
- # An instance of a view class. The default view class is ActionView::Base
- #
- # The view class must have the following methods:
- # View.new[lookup_context, assigns, controller]
- # Create a new ActionView instance for a controller
- # View#render[options]
- # Returns String with the rendered template
- #
- # Override this method in a module to change the default behavior.
- def view_context
- view_context_class.new(view_renderer, view_assigns, self)
- end
-
- # Returns an object that is able to render templates.
- def view_renderer
- @_view_renderer ||= ActionView::Renderer.new(lookup_context)
- end
-
# Normalize arguments, options and then delegates render_to_body and
# sticks the result in self.response_body.
+ # :api: public
def render(*args, &block)
options = _normalize_render(*args, &block)
self.response_body = render_to_body(options)
+ _process_format(rendered_format)
+ self.response_body
end
- # Raw rendering of a template to a string. Just convert the results of
- # render_response into a String.
+ # Raw rendering of a template to a string.
+ #
+ # It is similar to render, except that it does not
+ # set the response_body and it should be guaranteed
+ # to always return a string.
+ #
+ # If a component extends the semantics of response_body
+ # (as Action Controller extends it to be anything that
+ # responds to the method each), this method needs to be
+ # overridden in order to still return a string.
# :api: plugin
def render_to_string(*args, &block)
options = _normalize_render(*args, &block)
render_to_body(options)
end
- # Raw rendering of a template to a Rack-compatible body.
- # :api: plugin
+ # Performs the actual template rendering.
+ # :api: public
def render_to_body(options = {})
- _process_options(options)
- _render_template(options)
end
- # 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)
+ # Return Content-Type of rendered content
+ # :api: public
+ def rendered_format
+ Mime::TEXT
end
- DEFAULT_PROTECTED_INSTANCE_VARIABLES = [
- :@_action_name, :@_response_body, :@_formats, :@_prefixes, :@_config,
- :@_view_context_class, :@_view_renderer, :@_lookup_context
- ]
+ DEFAULT_PROTECTED_INSTANCE_VARIABLES = %w(
+ @_action_name @_response_body @_formats @_prefixes @_config
+ @_view_context_class @_view_renderer @_lookup_context
+ )
# This method should return a hash with assigns.
# You can overwrite this configuration per controller.
@@ -136,53 +72,40 @@ module AbstractController
hash
end
- private
-
- # Normalize args and options.
- # :api: private
- def _normalize_render(*args, &block)
- options = _normalize_args(*args, &block)
- _normalize_options(options)
- options
- end
-
# Normalize args by converting render "foo" to render :action => "foo" and
# render "foo/bar" to render :file => "foo/bar".
# :api: plugin
def _normalize_args(action=nil, options={})
- case action
- when NilClass
- when Hash
- options = action
- when String, Symbol
- action = action.to_s
- key = action.include?(?/) ? :file : :action
- options[key] = action
+ if action.is_a? Hash
+ action
else
- options[:partial] = action
+ options
end
-
- options
end
# Normalize options.
# :api: plugin
def _normalize_options(options)
- if options[:partial] == true
- options[:partial] = action_name
- end
-
- if (options.keys & [:partial, :file, :template]).empty?
- options[:prefixes] ||= _prefixes
- end
-
- options[:template] ||= (options[:action] || action_name).to_s
options
end
# Process extra options.
# :api: plugin
def _process_options(options)
+ options
+ end
+
+ # Process the rendered format.
+ # :api: private
+ def _process_format(format)
+ end
+
+ # Normalize args and options.
+ # :api: private
+ def _normalize_render(*args, &block)
+ options = _normalize_args(*args, &block)
+ _normalize_options(options)
+ options
end
end
end
diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb
index b6c484d188..02028d8e05 100644
--- a/actionpack/lib/abstract_controller/translation.rb
+++ b/actionpack/lib/abstract_controller/translation.rb
@@ -1,9 +1,17 @@
module AbstractController
module Translation
+ # Delegates to <tt>I18n.translate</tt>. Also aliased as <tt>t</tt>.
+ #
+ # When the given key starts with a period, it will be scoped by the current
+ # controller and action. So if you call <tt>translate(".foo")</tt> from
+ # <tt>PeopleController#index</tt>, it will convert the call to
+ # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
+ # to translate many keys within the same controller / action and gives you a
+ # simple framework for scoping them consistently.
def translate(*args)
key = args.first
if key.is_a?(String) && (key[0] == '.')
- key = "#{ controller_path.gsub('/', '.') }.#{ action_name }#{ key }"
+ key = "#{ controller_path.tr('/', '.') }.#{ action_name }#{ key }"
args[0] = key
end
@@ -11,6 +19,7 @@ module AbstractController
end
alias :t :translate
+ # Delegates to <tt>I18n.localize</tt>. Also aliased as <tt>l</tt>.
def localize(*args)
I18n.localize(*args)
end
diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb
deleted file mode 100644
index c08b3a0e2a..0000000000
--- a/actionpack/lib/abstract_controller/view_paths.rb
+++ /dev/null
@@ -1,96 +0,0 @@
-require 'action_view/base'
-
-module AbstractController
- module ViewPaths
- extend ActiveSupport::Concern
-
- included do
- class_attribute :_view_paths
- self._view_paths = ActionView::PathSet.new
- self._view_paths.freeze
- end
-
- delegate :template_exists?, :view_paths, :formats, :formats=,
- :locale, :locale=, :to => :lookup_context
-
- module ClassMethods
- def parent_prefixes
- @parent_prefixes ||= begin
- parent_controller = superclass
- prefixes = []
-
- until parent_controller.abstract?
- prefixes << parent_controller.controller_path
- parent_controller = parent_controller.superclass
- end
-
- prefixes
- end
- end
- end
-
- # The prefixes used in render "foo" shortcuts.
- def _prefixes
- @_prefixes ||= begin
- parent_prefixes = self.class.parent_prefixes
- parent_prefixes.dup.unshift(controller_path)
- end
- end
-
- # LookupContext is the object responsible to hold all information required to lookup
- # templates, i.e. view paths and details. Check ActionView::LookupContext for more
- # information.
- def lookup_context
- @_lookup_context ||=
- ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
- end
-
- def details_for_lookup
- { }
- end
-
- def append_view_path(path)
- lookup_context.view_paths.push(*path)
- end
-
- def prepend_view_path(path)
- lookup_context.view_paths.unshift(*path)
- end
-
- module ClassMethods
- # Append a path to the list of view paths for this controller.
- #
- # ==== Parameters
- # * <tt>path</tt> - If a String is provided, it gets converted into
- # the default view path. You may also provide a custom view path
- # (see ActionView::PathSet for more information)
- def append_view_path(path)
- self._view_paths = view_paths + Array(path)
- end
-
- # Prepend a path to the list of view paths for this controller.
- #
- # ==== Parameters
- # * <tt>path</tt> - If a String is provided, it gets converted into
- # the default view path. You may also provide a custom view path
- # (see ActionView::PathSet for more information)
- def prepend_view_path(path)
- self._view_paths = ActionView::PathSet.new(Array(path) + view_paths)
- end
-
- # A list of all of the default view paths for this controller.
- def view_paths
- _view_paths
- end
-
- # Set the view paths.
- #
- # ==== Parameters
- # * <tt>paths</tt> - If a PathSet is provided, use that;
- # otherwise, process the parameter into a PathSet.
- def view_paths=(paths)
- self._view_paths = ActionView::PathSet.new(Array(paths))
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 1a13d7af29..417d2efec2 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -40,32 +40,15 @@ module ActionController
autoload :UrlFor
end
- autoload :Integration, 'action_controller/deprecated/integration_test'
- autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
- autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
- autoload :Routing, 'action_controller/deprecated'
autoload :TestCase, 'action_controller/test_case'
autoload :TemplateAssertions, 'action_controller/test_case'
- eager_autoload do
- autoload :RecordIdentifier
- end
-
def self.eager_load!
super
ActionController::Caching.eager_load!
- HTML.eager_load!
end
end
-# All of these simply register additional autoloads
-require 'action_view'
-require 'action_view/vendor/html-scanner'
-
-ActiveSupport.on_load(:action_view) do
- ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
-end
-
# Common Active Support usage in Action Controller
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/load_error'
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 9b3bf99fc3..3b0d094f4f 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -1,8 +1,23 @@
require "action_controller/log_subscriber"
+require "action_controller/metal/params_wrapper"
module ActionController
+ # The <tt>metal</tt> anonymous class was introduced to solve issue with including modules in <tt>ActionController::Base</tt>.
+ # Modules needs to be included in particluar order. First we need to have <tt>AbstractController::Rendering</tt> included,
+ # next we should include actuall implementation which would be for example <tt>ActionView::Rendering</tt> and after that
+ # <tt>ActionController::Rendering</tt>. This order must be preserved and as we want to have middle module included dynamicaly
+ # <tt>metal</tt> class was introduced. It has <tt>AbstractController::Rendering</tt> included and is parent class of
+ # <tt>ActionController::Base</tt> which includes <tt>ActionController::Rendering</tt>. If we include <tt>ActionView::Rendering</tt>
+ # beetween them to perserve the required order, we can simply do this by:
+ #
+ # ActionController::Base.superclass.send(:include, ActionView::Rendering)
+ #
+ metal = Class.new(Metal) do
+ include AbstractController::Rendering
+ end
+
# Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed
- # on request and then either render a template or redirect to another action. An action is defined as a public method
+ # on request and then either it renders a template or redirects to another action. An action is defined as a public method
# on the controller, which will automatically be made accessible to the web-server through \Rails Routes.
#
# By default, only the ApplicationController in a \Rails application inherits from <tt>ActionController::Base</tt>. All other
@@ -58,7 +73,7 @@ module ActionController
# <input type="text" name="post[address]" value="hyacintvej">
#
# A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
- # If the address input had been named "post[address][street]", the params would have included
+ # If the address input had been named <tt>post[address][street]</tt>, the params would have included
# <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
#
# == Sessions
@@ -159,7 +174,7 @@ module ActionController
# render action: "overthere" # won't be called if monkeys is nil
# end
#
- class Base < Metal
+ class Base < metal
abstract!
# We document the request and response methods here because albeit they are
@@ -199,7 +214,6 @@ module ActionController
end
MODULES = [
- AbstractController::Layouts,
AbstractController::Translation,
AbstractController::AssetPaths,
@@ -222,7 +236,6 @@ module ActionController
ForceSSL,
Streaming,
DataStreaming,
- RecordIdentifier,
HttpAuthentication::Basic::ControllerMethods,
HttpAuthentication::Digest::ControllerMethods,
HttpAuthentication::Token::ControllerMethods,
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 462f147371..12d798d0c1 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -6,11 +6,10 @@ module ActionController
# \Caching is a cheap way of speeding up slow applications by keeping the result of
# calculations, renderings, and database calls around for subsequent requests.
#
- # You can read more about each approach and the sweeping assistance by clicking the
- # modules below.
+ # You can read more about each approach by clicking the modules below.
#
- # Note: To turn off all caching and sweeping, set
- # config.action_controller.perform_caching = false.
+ # Note: To turn off all caching, set
+ # config.action_controller.perform_caching = false
#
# == \Caching stores
#
@@ -30,8 +29,6 @@ module ActionController
eager_autoload do
autoload :Fragments
- autoload :Sweeper, 'action_controller/caching/sweeping'
- autoload :Sweeping, 'action_controller/caching/sweeping'
end
module ConfigMethods
@@ -54,7 +51,6 @@ module ActionController
include ConfigMethods
include Fragments
- include Sweeping if defined?(ActiveRecord)
included do
extend ConfigMethods
@@ -62,22 +58,22 @@ module ActionController
config_accessor :default_static_extension
self.default_static_extension ||= '.html'
- def self.page_cache_extension=(extension)
- ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension)
- self.default_static_extension = extension
- end
-
- def self.page_cache_extension
- ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension)
- default_static_extension
- end
-
config_accessor :perform_caching
self.perform_caching = true if perform_caching.nil?
+
+ class_attribute :_view_cache_dependencies
+ self._view_cache_dependencies = []
+ helper_method :view_cache_dependencies if respond_to?(:helper_method)
+ end
+
+ module ClassMethods
+ def view_cache_dependency(&dependency)
+ self._view_cache_dependencies += [dependency]
+ end
end
- def caching_allowed?
- request.get? && response.status == 200
+ def view_cache_dependencies
+ self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact
end
protected
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
deleted file mode 100644
index 317ac74b40..0000000000
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-module ActionController
- module Caching
- # Sweepers are the terminators of the caching world and responsible for expiring
- # caches when Active Record objects change. They do this by being half-observers,
- # half-filters and implementing callbacks for both roles.
- #
- # class ListSweeper < ActionController::Caching::Sweeper
- # observe List, Item
- #
- # def after_save(record)
- # list = record.is_a?(List) ? record : record.list
- # expire_page(controller: 'lists', action: %w( show public feed ), id: list.id)
- # expire_action(controller: 'lists', action: 'all')
- # list.shares.each { |share| expire_page(controller: 'lists', action: 'show', id: share.url_key) }
- # end
- # end
- #
- # The sweeper is assigned in the controllers that wish to have its job performed using
- # the +cache_sweeper+ class method:
- #
- # class ListsController < ApplicationController
- # caches_action :index, :show, :public, :feed
- # cache_sweeper :list_sweeper, only: [ :edit, :destroy, :share ]
- # end
- #
- # In the example above, four actions are cached and three actions are responsible for expiring those caches.
- #
- # You can also name an explicit class in the declaration of a sweeper, which is needed
- # if the sweeper is in a module:
- #
- # class ListsController < ApplicationController
- # caches_action :index, :show, :public, :feed
- # cache_sweeper OpenBar::Sweeper, only: [ :edit, :destroy, :share ]
- # end
- module Sweeping
- extend ActiveSupport::Concern
-
- module ClassMethods # :nodoc:
- def cache_sweeper(*sweepers)
- configuration = sweepers.extract_options!
-
- sweepers.each do |sweeper|
- ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
- sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(sweeper.to_s.classify) : sweeper).instance
-
- if sweeper_instance.is_a?(Sweeper)
- around_filter(sweeper_instance, :only => configuration[:only])
- else
- after_filter(sweeper_instance, :only => configuration[:only])
- end
- end
- end
- end
- end
-
- if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
- 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
- true # before method from sweeper should always return true
- end
-
- def after(controller)
- self.controller = controller
- callback(:after) if controller.perform_caching
- end
-
- def around(controller)
- before(controller)
- yield
- after(controller)
- ensure
- clean_up
- end
-
- protected
- # gets the action cache path for the given options.
- def action_path_for(options)
- Actions::ActionCachePath.new(controller, options).path
- end
-
- # Retrieve instance variables set in the controller.
- def assigns(key)
- controller.instance_variable_get("@#{key}")
- end
-
- private
- def clean_up
- # Clean up, so that the controller can be collected after this request
- self.controller = nil
- end
-
- def callback(timing)
- controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
- action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
-
- __send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
- __send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
- end
-
- def method_missing(method, *arguments, &block)
- return super unless @controller
- @controller.__send__(method, *arguments, &block)
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb
deleted file mode 100644
index 2405bebb97..0000000000
--- a/actionpack/lib/action_controller/deprecated.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
-ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
-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
deleted file mode 100644
index 54eae48f47..0000000000
--- a/actionpack/lib/action_controller/deprecated/integration_test.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-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
deleted file mode 100644
index c7ba5a2fe7..0000000000
--- a/actionpack/lib/action_controller/deprecated/performance_test.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-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/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 3d274e7dd7..9279d8bcea 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -33,7 +33,7 @@ module ActionController
end
def halted_callback(event)
- info("Filter chain halted as #{event.payload[:filter]} rendered or redirected")
+ info("Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected")
end
def send_file(event)
@@ -48,6 +48,11 @@ module ActionController
info("Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)")
end
+ def unpermitted_parameters(event)
+ unpermitted_keys = event.payload[:keys]
+ debug("Unpermitted parameters: #{unpermitted_keys.join(", ")}")
+ end
+
%w(write_fragment read_fragment exist_fragment?
expire_fragment expire_page write_page).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index f5ab1e2350..b84c9e78c3 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/array/extract_options'
require 'action_dispatch/middleware/stack'
module ActionController
@@ -5,7 +6,7 @@ module ActionController
# allowing the following syntax in controllers:
#
# class PostsController < ApplicationController
- # use AuthenticationMiddleware, :except => [:index, :show]
+ # use AuthenticationMiddleware, except: [:index, :show]
# end
#
class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc:
@@ -35,8 +36,7 @@ module ActionController
raise "MiddlewareStack#build requires an app" unless app
middlewares.reverse.inject(app) do |a, middleware|
- middleware.valid?(action) ?
- middleware.build(a) : a
+ middleware.valid?(action) ? middleware.build(a) : a
end
end
end
@@ -56,7 +56,7 @@ module ActionController
# And then to route requests to your metal controller, you would add
# something like this to <tt>config/routes.rb</tt>:
#
- # match 'hello', :to => HelloController.action(:index)
+ # get 'hello', to: HelloController.action(:index)
#
# The +action+ method returns a valid Rack application for the \Rails
# router to dispatch to.
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 3f37a6a618..6e0cd51d8b 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/hash/keys'
module ActionController
module ConditionalGet
@@ -42,7 +42,7 @@ module ActionController
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
# +true+ if you want your application to be cachable by other devices (proxy caches).
#
- # === Example:
+ # === Example:
#
# def show
# @article = Article.find(params[:id])
@@ -64,7 +64,7 @@ module ActionController
#
# def show
# @article = Article.find(params[:id])
- # fresh_when(@article, :public => true)
+ # fresh_when(@article, public: true)
# end
def fresh_when(record_or_options, additional_options = {})
if record_or_options.is_a? Hash
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 5422cb93c4..75c4d3ef99 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -47,11 +47,11 @@ module ActionController #:nodoc:
#
# Show a JPEG in the browser:
#
- # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
+ # send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline'
#
# Show a 404 page in the browser:
#
- # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404
+ # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', status: 404
#
# Read about the other Content-* HTTP headers if you'd like to
# provide the user with more information (such as Content-Description) in
@@ -96,7 +96,7 @@ module ActionController #:nodoc:
end
# Sends the given binary data to the browser. This method is similar to
- # <tt>render :text => data</tt>, but also allows you to specify whether
+ # <tt>render text: data</tt>, but also allows you to specify whether
# the browser should display the response as a file attachment (i.e. in a
# download dialog) or as inline data. You may also set the content type,
# the apparent file name, and other things.
@@ -117,11 +117,11 @@ module ActionController #:nodoc:
#
# Download a dynamically-generated tarball:
#
- # send_data generate_tgz('dir'), :filename => 'dir.tgz'
+ # send_data generate_tgz('dir'), filename: 'dir.tgz'
#
# Display an image Active Record in the browser:
#
- # send_data image.data, :type => image.content_type, :disposition => 'inline'
+ # send_data image.data, type: image.content_type, disposition: 'inline'
#
# See +send_file+ for more information on HTTP Content-* headers and caching.
def send_data(data, options = {}) #:doc:
@@ -150,6 +150,7 @@ module ActionController #:nodoc:
disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
unless disposition.nil?
+ disposition = disposition.to_s
disposition += %(; filename="#{options[:filename]}") if options[:filename]
headers['Content-Disposition'] = disposition
end
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index 3c9d0c86a7..3844dbf2a6 100644
--- a/actionpack/lib/action_controller/metal/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -3,6 +3,15 @@ module ActionController
end
class BadRequest < ActionControllerError #:nodoc:
+ attr_reader :original_exception
+
+ def initialize(type = nil, e = nil)
+ return super() unless type && e
+
+ super("Invalid #{type} parameters: #{e.message}")
+ @original_exception = e
+ set_backtrace e.backtrace
+ end
end
class RenderError < ActionControllerError #:nodoc:
diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index b078beb675..65351284b9 100644
--- a/actionpack/lib/action_controller/metal/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -11,6 +11,23 @@ module ActionController #:nodoc:
end
module ClassMethods
+ # Creates new flash types. You can pass as many types as you want to create
+ # flash types other than the default <tt>alert</tt> and <tt>notice</tt> in
+ # your controllers and views. For instance:
+ #
+ # # in application_controller.rb
+ # class ApplicationController < ActionController::Base
+ # add_flash_types :warning
+ # end
+ #
+ # # in your controller
+ # redirect_to user_path(@user), warning: "Incomplete profile"
+ #
+ # # in your view
+ # <%= warning %>
+ #
+ # This method will automatically define a new method for each of the given
+ # names, and it will be available in your views.
def add_flash_types(*types)
types.each do |type|
next if _flash_types.include?(type)
@@ -20,7 +37,7 @@ module ActionController #:nodoc:
end
helper_method type
- _flash_types << type
+ self._flash_types += [type]
end
end
end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index e905a3cf1d..a2cb6d1e66 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -1,3 +1,6 @@
+require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/hash/slice'
+
module ActionController
# This module provides a method which will redirect browser to use HTTPS
# protocol. This will ensure that user's sensitive information will be
@@ -14,6 +17,10 @@ module ActionController
extend ActiveSupport::Concern
include AbstractController::Callbacks
+ ACTION_OPTIONS = [:only, :except, :if, :unless]
+ URL_OPTIONS = [:protocol, :host, :domain, :subdomain, :port, :path]
+ REDIRECT_OPTIONS = [:status, :flash, :alert, :notice]
+
module ClassMethods
# Force the request to this particular controller or specified actions to be
# under HTTPS protocol.
@@ -22,25 +29,41 @@ module ActionController
# an +:if+ or +:unless+ condition.
#
# class AccountsController < ApplicationController
- # force_ssl :if => :ssl_configured?
+ # 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.
+ # ==== URL Options
+ # You can pass any of the following options to affect the redirect url
+ # * <tt>host</tt> - Redirect to a different host name
+ # * <tt>subdomain</tt> - Redirect to a different subdomain
+ # * <tt>domain</tt> - Redirect to a different domain
+ # * <tt>port</tt> - Redirect to a non-standard port
+ # * <tt>path</tt> - Redirect to a different path
+ #
+ # ==== Redirect Options
+ # You can pass any of the following options to affect the redirect status and response
+ # * <tt>status</tt> - Redirect with a custom status (default is 301 Moved Permanently)
+ # * <tt>flash</tt> - Set a flash message when redirecting
+ # * <tt>alert</tt> - Set an alert message when redirecting
+ # * <tt>notice</tt> - Set a notice message when redirecting
+ #
+ # ==== Action Options
+ # You can pass any of the following options to affect the before_action callback
+ # * <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
- force_ssl_redirect(host)
+ action_options = options.slice(*ACTION_OPTIONS)
+ redirect_options = options.except(*ACTION_OPTIONS)
+ before_action(action_options) do
+ force_ssl_redirect(redirect_options)
end
end
end
@@ -48,14 +71,26 @@ module ActionController
# Redirect the existing request to use the HTTPS protocol.
#
# ==== Parameters
- # * <tt>host</tt> - Redirect to a different host name
- def force_ssl_redirect(host = nil)
+ # * <tt>host_or_options</tt> - Either a host name or any of the url & redirect options
+ # available to the <tt>force_ssl</tt> method.
+ def force_ssl_redirect(host_or_options = nil)
unless request.ssl?
- redirect_options = {:protocol => 'https://', :status => :moved_permanently}
- redirect_options.merge!(:host => host) if host
- redirect_options.merge!(:params => request.query_parameters)
+ options = {
+ :protocol => 'https://',
+ :host => request.host,
+ :path => request.fullpath,
+ :status => :moved_permanently
+ }
+
+ if host_or_options.is_a?(Hash)
+ options.merge!(host_or_options)
+ elsif host_or_options
+ options.merge!(:host => host_or_options)
+ end
+
+ secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS))
flash.keep if respond_to?(:flash)
- redirect_to redirect_options
+ redirect_to secure_url, options.slice(*REDIRECT_OPTIONS)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 747e1273be..424473801d 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -1,15 +1,13 @@
module ActionController
module Head
- extend ActiveSupport::Concern
-
# Return a response that has no content (merely headers). The options
# argument is interpreted to be a hash of header names and values.
# This allows you to easily return a response that consists only of
# significant headers:
#
- # head :created, :location => person_path(@person)
+ # head :created, location: person_path(@person)
#
- # head :created, :location => @person
+ # head :created, location: @person
#
# It can also be used to return exceptional conditions:
#
@@ -31,6 +29,7 @@ module ActionController
if include_content?(self.status)
self.content_type = content_type || (Mime[formats.first] if formats)
+ self.response.charset = false if self.response
self.response_body = " "
else
headers.delete('Content-Type')
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index d2cbbd3330..a9c3e438fb 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -1,4 +1,3 @@
-
module ActionController
# The \Rails framework provides a large number of helpers for working with assets, dates, forms,
# numbers and model objects, to name a few. These helpers are available to all templates
@@ -6,7 +5,7 @@ module ActionController
#
# In addition to using the standard template helpers provided, creating custom helpers to
# extract complicated logic or reusable functionality is strongly encouraged. By default, each controller
- # will include all helpers.
+ # will include all helpers. These helpers are only accessible on the controller through <tt>.helpers</tt>
#
# In previous versions of \Rails the controller will include a helper whose
# name matches that of the controller, e.g., <tt>MyController</tt> will automatically
@@ -74,7 +73,11 @@ module ActionController
# Provides a proxy to access helpers methods from outside the view.
def helpers
- @helper_proxy ||= ActionView::Base.new.extend(_helpers)
+ @helper_proxy ||= begin
+ proxy = ActionView::Base.new
+ proxy.config = config.inheritable_copy
+ proxy.extend(_helpers)
+ end
end
# Overwrite modules_for_helpers to accept :all as argument, which loads
@@ -91,11 +94,10 @@ module ActionController
end
def all_helpers_from_path(path)
- helpers = []
- Array(path).each do |_path|
- extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
+ helpers = Array(path).flat_map do |_path|
+ extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
- helpers += names.sort
+ names.sort!
end
helpers.uniq!
helpers
diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb
index 420b22cf56..af36ffa240 100644
--- a/actionpack/lib/action_controller/metal/hide_actions.rb
+++ b/actionpack/lib/action_controller/metal/hide_actions.rb
@@ -26,20 +26,14 @@ module ActionController
self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze
end
- def inherited(klass)
- klass.class_eval { @visible_actions = {} }
- super
- end
-
def visible_action?(action_name)
- return @visible_actions[action_name] if @visible_actions.key?(action_name)
- @visible_actions[action_name] = !hidden_actions.include?(action_name)
+ not hidden_actions.include?(action_name)
end
# Overrides AbstractController::Base#action_methods to remove any methods
# that are listed as hidden methods.
def action_methods
- @action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) })
+ @action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) }).freeze
end
end
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 03b8d8db1a..158d552ec7 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -8,14 +8,14 @@ module ActionController
# === Simple \Basic example
#
# class PostsController < ApplicationController
- # http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index
+ # http_basic_authenticate_with name: "dhh", password: "secret", except: :index
#
# def index
- # render :text => "Everyone can see me!"
+ # render text: "Everyone can see me!"
# end
#
# def edit
- # render :text => "I'm only accessible if you know the password"
+ # render text: "I'm only accessible if you know the password"
# end
# end
#
@@ -25,11 +25,11 @@ module ActionController
# the regular HTML interface is protected by a session approach:
#
# class ApplicationController < ActionController::Base
- # before_filter :set_account, :authenticate
+ # before_action :set_account, :authenticate
#
# protected
# def set_account
- # @account = Account.find_by_url_name(request.subdomains.first)
+ # @account = Account.find_by(url_name: request.subdomains.first)
# end
#
# def authenticate
@@ -68,7 +68,7 @@ module ActionController
module ClassMethods
def http_basic_authenticate_with(options = {})
- before_filter(options.except(:name, :password, :realm)) do
+ before_action(options.except(:name, :password, :realm)) do
authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
name == options[:name] && password == options[:password]
end
@@ -124,14 +124,14 @@ module ActionController
# USERS = {"dhh" => "secret", #plain text password
# "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
#
- # before_filter :authenticate, :except => [:index]
+ # before_action :authenticate, except: [:index]
#
# def index
- # render :text => "Everyone can see me!"
+ # render text: "Everyone can see me!"
# end
#
# def edit
- # render :text => "I'm only accessible if you know the password"
+ # render text: "I'm only accessible if you know the password"
# end
#
# private
@@ -228,7 +228,7 @@ module ActionController
end
def decode_credentials(header)
- HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair|
+ ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, '').split(',').map do |pair|
key, value = pair.split('=', 2)
[key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')]
end]
@@ -249,9 +249,9 @@ module ActionController
end
def secret_token(request)
- secret = request.env["action_dispatch.secret_token"]
- raise "You must set config.secret_token in your app's config" if secret.blank?
- secret
+ key_generator = request.env["action_dispatch.key_generator"]
+ http_auth_salt = request.env["action_dispatch.http_auth_salt"]
+ key_generator.generate_key(http_auth_salt)
end
# Uses an MD5 digest based on time to generate a value to be used only once.
@@ -299,6 +299,7 @@ module ActionController
# allow a user to use new nonce without prompting user again for their
# username and password.
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
+ return false if value.nil?
t = ::Base64.decode64(value).split(":").first.to_i
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
end
@@ -317,14 +318,14 @@ module ActionController
# class PostsController < ApplicationController
# TOKEN = "secret"
#
- # before_filter :authenticate, :except => [ :index ]
+ # before_action :authenticate, except: [ :index ]
#
# def index
- # render :text => "Everyone can see me!"
+ # render text: "Everyone can see me!"
# end
#
# def edit
- # render :text => "I'm only accessible if you know the password"
+ # render text: "I'm only accessible if you know the password"
# end
#
# private
@@ -340,11 +341,11 @@ module ActionController
# the regular HTML interface is protected by a session approach:
#
# class ApplicationController < ActionController::Base
- # before_filter :set_account, :authenticate
+ # before_action :set_account, :authenticate
#
# protected
# def set_account
- # @account = Account.find_by_url_name(request.subdomains.first)
+ # @account = Account.find_by(url_name: request.subdomains.first)
# end
#
# def authenticate
@@ -384,6 +385,8 @@ module ActionController
#
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
+ TOKEN_REGEX = /^Token /
+ AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
extend self
module ControllerMethods
@@ -424,27 +427,41 @@ module ActionController
# Parses the token and options out of the token authorization header. If
# the header looks like this:
# Authorization: Token token="abc", nonce="def"
- # Then the returned token is "abc", and the options is {:nonce => "def"}
+ # Then the returned token is "abc", and the options is {nonce: "def"}
#
# request - ActionDispatch::Request instance with the current headers.
#
# Returns an Array of [String, Hash] if a token is present.
# Returns nil if no token is found.
def token_and_options(request)
- if request.authorization.to_s[/^Token (.*)/]
- values = Hash[$1.split(',').map do |value|
- value.strip! # remove any spaces between commas and values
- key, value = value.split(/\=\"?/) # split key=value pairs
- if value
- value.chomp!('"') # chomp trailing " in value
- value.gsub!(/\\\"/, '"') # unescape remaining quotes
- [key, value]
- end
- end.compact]
- [values.delete("token"), values.with_indifferent_access]
+ authorization_request = request.authorization.to_s
+ if authorization_request[TOKEN_REGEX]
+ params = token_params_from authorization_request
+ [params.shift.last, Hash[params].with_indifferent_access]
end
end
+ def token_params_from(auth)
+ rewrite_param_values params_array_from raw_params auth
+ end
+
+ # Takes raw_params and turns it into an array of parameters
+ def params_array_from(raw_params)
+ raw_params.map { |param| param.split %r/=(.+)?/ }
+ end
+
+ # This removes the `"` characters wrapping the value.
+ def rewrite_param_values(array_params)
+ array_params.each { |param| param.last.gsub! %r/^"|"$/, '' }
+ end
+
+ # This method takes an authorization body and splits up the key-value
+ # pairs by the standardized `:`, `;`, or `\t` delimiters defined in
+ # `AUTHN_PAIR_DELIMITERS`.
+ def raw_params(auth)
+ auth.sub(TOKEN_REGEX, '').split(/"\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
+ end
+
# Encodes the given token and options into an Authorization header value.
#
# token - String token.
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index ca4ae532ca..d3aa8f90c5 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -60,7 +60,7 @@ module ActionController
ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
result = super
payload[:status] = response.status
- payload[:location] = response.location
+ payload[:location] = response.filtered_location
result
end
end
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 32e5afa335..0dd788645b 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -1,5 +1,6 @@
require 'action_dispatch/http/response'
require 'delegate'
+require 'active_support/json'
module ActionController
# Mix this module in to your controller, and all actions in that controller
@@ -14,6 +15,7 @@ module ActionController
# response.stream.write "hello world\n"
# sleep 1
# }
+ # ensure
# response.stream.close
# end
# end
@@ -31,8 +33,82 @@ module ActionController
# the main thread. Make sure your actions are thread safe, and this shouldn't
# be a problem (don't share state across threads, etc).
module Live
+ # This class provides the ability to write an SSE (Server Sent Event)
+ # to an IO stream. The class is initialized with a stream and can be used
+ # to either write a JSON string or an object which can be converted to JSON.
+ #
+ # Writing an object will convert it into standard SSE format with whatever
+ # options you have configured. You may choose to set the following options:
+ #
+ # 1) Event. If specified, an event with this name will be dispatched on
+ # the browser.
+ # 2) Retry. The reconnection time in milliseconds used when attempting
+ # to send the event.
+ # 3) Id. If the connection dies while sending an SSE to the browser, then
+ # the server will receive a +Last-Event-ID+ header with value equal to +id+.
+ #
+ # After setting an option in the constructor of the SSE object, all future
+ # SSEs sent accross the stream will use those options unless overridden.
+ #
+ # Example Usage:
+ #
+ # class MyController < ActionController::Base
+ # include ActionController::Live
+ #
+ # def index
+ # response.headers['Content-Type'] = 'text/event-stream'
+ # sse = SSE.new(response.stream, retry: 300, event: "event-name")
+ # sse.write({ name: 'John'})
+ # sse.write({ name: 'John'}, id: 10)
+ # sse.write({ name: 'John'}, id: 10, event: "other-event")
+ # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500)
+ # ensure
+ # sse.close
+ # end
+ # end
+ #
+ # Note: SSEs are not currently supported by IE. However, they are supported
+ # by Chrome, Firefox, Opera, and Safari.
+ class SSE
+
+ WHITELISTED_OPTIONS = %w( retry event id )
+
+ def initialize(stream, options = {})
+ @stream = stream
+ @options = options
+ end
+
+ def close
+ @stream.close
+ end
+
+ def write(object, options = {})
+ case object
+ when String
+ perform_write(object, options)
+ else
+ perform_write(ActiveSupport::JSON.encode(object), options)
+ end
+ end
+
+ private
+
+ def perform_write(json, options)
+ current_options = @options.merge(options).stringify_keys
+
+ WHITELISTED_OPTIONS.each do |option_name|
+ if (option_value = current_options[option_name])
+ @stream.write "#{option_name}: #{option_value}\n"
+ end
+ end
+
+ @stream.write "data: #{json}\n\n"
+ end
+ end
+
class Buffer < ActionDispatch::Response::Buffer #:nodoc:
def initialize(response)
+ @error_callback = nil
super(response, SizedQueue.new(10))
end
@@ -55,6 +131,14 @@ module ActionController
super
@buf.push nil
end
+
+ def on_error(&block)
+ @error_callback = block
+ end
+
+ def call_on_error
+ @error_callback.call
+ end
end
class Response < ActionDispatch::Response #:nodoc: all
@@ -97,6 +181,10 @@ module ActionController
def merge_default_headers(original, default)
Header.new self, super
end
+
+ def handle_conditional_get!
+ super unless committed?
+ end
end
def process(name)
@@ -116,6 +204,16 @@ module ActionController
begin
super(name)
+ rescue => e
+ begin
+ @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
+ @_response.stream.call_on_error
+ rescue => exception
+ log_error(exception)
+ ensure
+ log_error(e)
+ @_response.stream.close
+ end
ensure
@_response.commit!
end
@@ -124,6 +222,16 @@ module ActionController
@_response.await_commit
end
+ def log_error(exception)
+ logger = ActionController::Base.logger
+ return unless logger
+
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << exception.backtrace.join("\n ")
+ logger.fatal("#{message}\n\n")
+ end
+
def response_body=(body)
super
response.stream.close if response
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 18ae2c3bfc..a072fce1a1 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/array/extract_options'
require 'abstract_controller/collector'
module ActionController #:nodoc:
@@ -23,13 +24,13 @@ module ActionController #:nodoc:
# <tt>:except</tt> with an array of actions or a single action:
#
# respond_to :html
- # respond_to :xml, :json, :except => [ :edit ]
+ # respond_to :xml, :json, except: [ :edit ]
#
# This specifies that all actions respond to <tt>:html</tt>
# and all actions except <tt>:edit</tt> respond to <tt>:xml</tt> and
# <tt>:json</tt>.
#
- # respond_to :json, :only => :create
+ # respond_to :json, only: :create
#
# This specifies that the <tt>:create</tt> action and no other responds
# to <tt>:json</tt>.
@@ -70,7 +71,7 @@ module ActionController #:nodoc:
#
# respond_to do |format|
# format.html
- # format.xml { render :xml => @people }
+ # format.xml { render xml: @people }
# end
# end
#
@@ -82,7 +83,7 @@ module ActionController #:nodoc:
# (by name) if it does not already exist, without web-services, it might look like this:
#
# def create
- # @company = Company.find_or_create_by_name(params[:company][:name])
+ # @company = Company.find_or_create_by(name: params[:company][:name])
# @person = @company.people.create(params[:person])
#
# redirect_to(person_list_url)
@@ -92,13 +93,13 @@ module ActionController #:nodoc:
#
# def create
# company = params[:person].delete(:company)
- # @company = Company.find_or_create_by_name(company[:name])
+ # @company = Company.find_or_create_by(name: company[:name])
# @person = @company.people.create(params[:person])
#
# respond_to do |format|
# format.html { redirect_to(person_list_url) }
# format.js
- # format.xml { render :xml => @person.to_xml(:include => @company) }
+ # format.xml { render xml: @person.to_xml(include: @company) }
# end
# end
#
@@ -120,7 +121,7 @@ module ActionController #:nodoc:
# Note, however, the extra bit at the top of that action:
#
# company = params[:person].delete(:company)
- # @company = Company.find_or_create_by_name(company[:name])
+ # @company = Company.find_or_create_by(name: company[:name])
#
# This is because the incoming XML document (if a web-service request is in process) can only contain a
# single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
@@ -162,11 +163,11 @@ module ActionController #:nodoc:
#
# In the example above, if the format is xml, it will render:
#
- # render :xml => @people
+ # render xml: @people
#
# Or if the format is json:
#
- # render :json => @people
+ # render json: @people
#
# Since this is a common pattern, you can use the class method respond_to
# with the respond_with method to have the same results:
@@ -227,7 +228,7 @@ module ActionController #:nodoc:
# 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+.
+ # +post+ request or <tt>:edit</tt> for +patch+ or +put+.
# Thus an example like this -
#
# respond_to :html, :xml
@@ -246,10 +247,10 @@ module ActionController #:nodoc:
# if @user.save
# flash[:notice] = 'User was successfully created.'
# format.html { redirect_to(@user) }
- # format.xml { render :xml => @user }
+ # format.xml { render xml: @user }
# else
- # format.html { render :action => "new" }
- # format.xml { render :xml => @user }
+ # format.html { render action: "new" }
+ # format.xml { render xml: @user }
# end
# end
# end
@@ -260,7 +261,7 @@ module ActionController #:nodoc:
# 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>.
+ # <code>render xml: resource</code>.
#
# === Nested resources
#
@@ -309,7 +310,7 @@ module ActionController #:nodoc:
# 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
+ # 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.
@@ -320,11 +321,12 @@ module ActionController #:nodoc:
# 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 " <<
+ 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 collector = retrieve_collector_from_mimes(&block)
options = resources.size == 1 ? {} : resources.extract_options!
+ options = options.clone
options[:default_response] = collector.response
(options.delete(:responder) || self.class.responder).call(self, resources, options)
end
@@ -363,9 +365,7 @@ module ActionController #:nodoc:
format = collector.negotiate_format(request)
if format
- self.content_type ||= format.to_s
- lookup_context.formats = [format.to_sym]
- lookup_context.rendered_format = lookup_context.formats.first
+ _process_format(format)
collector
else
raise ActionController::UnknownFormat
@@ -381,7 +381,7 @@ module ActionController #:nodoc:
#
# respond_to do |format|
# format.html
- # format.xml { render :xml => @people }
+ # format.xml { render xml: @people }
# end
#
# In this usage, the argument passed to the block (+format+ above) is an
@@ -419,7 +419,7 @@ module ActionController #:nodoc:
end
def response
- @responses[format] || @responses[Mime::ALL]
+ @responses.fetch(format, @responses[Mime::ALL])
end
def negotiate_format(request)
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 88b9e78da7..c9f1d8dcb4 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -1,7 +1,8 @@
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/anonymous'
-require 'action_dispatch/http/mime_types'
+require 'active_support/core_ext/struct'
+require 'action_dispatch/http/mime_type'
module ActionController
# Wraps the parameters hash into a nested hash. This will allow clients to submit
@@ -72,17 +73,104 @@ module ActionController
EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8)
+ require 'mutex_m'
+
+ class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc:
+ include Mutex_m
+
+ def self.from_hash(hash)
+ name = hash[:name]
+ format = Array(hash[:format])
+ include = hash[:include] && Array(hash[:include]).collect(&:to_s)
+ exclude = hash[:exclude] && Array(hash[:exclude]).collect(&:to_s)
+ new name, format, include, exclude, nil, nil
+ end
+
+ def initialize(name, format, include, exclude, klass, model) # nodoc
+ super
+ @include_set = include
+ @name_set = name
+ end
+
+ def model
+ super || synchronize { super || self.model = _default_wrap_model }
+ end
+
+ def include
+ return super if @include_set
+
+ m = model
+ synchronize do
+ return super if @include_set
+
+ @include_set = true
+
+ unless super || exclude
+ if m.respond_to?(:attribute_names) && m.attribute_names.any?
+ self.include = m.attribute_names
+ end
+ end
+ end
+ end
+
+ def name
+ return super if @name_set
+
+ m = model
+ synchronize do
+ return super if @name_set
+
+ @name_set = true
+
+ unless super || klass.anonymous?
+ self.name = m ? m.to_s.demodulize.underscore :
+ klass.controller_name.singularize
+ end
+ end
+ end
+
+ private
+ # Determine the wrapper model from the controller's name. By convention,
+ # this could be done by trying to find the defined model that has the
+ # same singularize name as the controller. For example, +UsersController+
+ # will try to find if the +User+ model exists.
+ #
+ # This method also does namespace lookup. Foo::Bar::UsersController will
+ # try to find Foo::Bar::User, Foo::User and finally User.
+ def _default_wrap_model #:nodoc:
+ return nil if klass.anonymous?
+ model_name = klass.name.sub(/Controller$/, '').classify
+
+ begin
+ if model_klass = model_name.safe_constantize
+ model_klass
+ else
+ namespaces = model_name.split("::")
+ namespaces.delete_at(-2)
+ break if namespaces.last == model_name
+ model_name = namespaces.join("::")
+ end
+ end until model_klass
+
+ model_klass
+ end
+ end
+
included do
class_attribute :_wrapper_options
- self._wrapper_options = { :format => [] }
+ self._wrapper_options = Options.from_hash(format: [])
end
module ClassMethods
+ def _set_wrapper_options(options)
+ self._wrapper_options = Options.from_hash(options)
+ end
+
# Sets the name of the wrapper key, or the model which +ParamsWrapper+
# would use to determine the attribute names from.
#
# ==== Examples
- # wrap_parameters :format => :xml
+ # wrap_parameters format: :xml
# # enables the parameter wrapper for XML format
#
# wrap_parameters :person
@@ -92,7 +180,7 @@ module ActionController
# # wraps parameters by determining the wrapper key from Person class
# (+person+, in this case) and the list of attribute names
#
- # wrap_parameters :include => [:username, :title]
+ # wrap_parameters include: [:username, :title]
# # wraps only +:username+ and +:title+ attributes from parameters.
#
# wrap_parameters false
@@ -119,68 +207,24 @@ module ActionController
model = name_or_model_or_options
end
- _set_wrapper_defaults(_wrapper_options.slice(:format).merge(options), model)
+ opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options)
+ opts.model = model
+ opts.klass = self
+
+ self._wrapper_options = opts
end
# Sets the default wrapper key or model which will be used to determine
# wrapper key and attribute names. Will be called automatically when the
# module is inherited.
def inherited(klass)
- if klass._wrapper_options[:format].present?
- klass._set_wrapper_defaults(klass._wrapper_options.slice(:format))
+ if klass._wrapper_options.format.any?
+ params = klass._wrapper_options.dup
+ params.klass = klass
+ klass._wrapper_options = params
end
super
end
-
- protected
-
- # Determine the wrapper model from the controller's name. By convention,
- # this could be done by trying to find the defined model that has the
- # same singularize name as the controller. For example, +UsersController+
- # will try to find if the +User+ model exists.
- #
- # This method also does namespace lookup. Foo::Bar::UsersController will
- # try to find Foo::Bar::User, Foo::User and finally User.
- def _default_wrap_model #:nodoc:
- return nil if self.anonymous?
- model_name = self.name.sub(/Controller$/, '').classify
-
- begin
- if model_klass = model_name.safe_constantize
- model_klass
- else
- namespaces = model_name.split("::")
- namespaces.delete_at(-2)
- break if namespaces.last == model_name
- model_name = namespaces.join("::")
- end
- end until model_klass
-
- model_klass
- end
-
- def _set_wrapper_defaults(options, model=nil)
- options = options.dup
-
- unless options[:include] || options[:exclude]
- model ||= _default_wrap_model
- if model.respond_to?(:attribute_names) && model.attribute_names.present?
- options[:include] = model.attribute_names
- end
- end
-
- unless options[:name] || self.anonymous?
- model ||= _default_wrap_model
- options[:name] = model ? model.to_s.demodulize.underscore :
- controller_name.singularize
- end
-
- options[:include] = Array(options[:include]).collect(&:to_s) if options[:include]
- options[:exclude] = Array(options[:exclude]).collect(&:to_s) if options[:exclude]
- options[:format] = Array(options[:format])
-
- self._wrapper_options = options
- end
end
# Performs parameters wrapping upon the request. Will be called automatically
@@ -205,20 +249,20 @@ module ActionController
# Returns the wrapper key which will use to stored wrapped parameters.
def _wrapper_key
- _wrapper_options[:name]
+ _wrapper_options.name
end
# Returns the list of enabled formats.
def _wrapper_formats
- _wrapper_options[:format]
+ _wrapper_options.format
end
# Returns the list of parameters which will be selected for wrapped.
def _wrap_parameters(parameters)
- value = if include_only = _wrapper_options[:include]
+ value = if include_only = _wrapper_options.include
parameters.slice(*include_only)
else
- exclude = _wrapper_options[:exclude] || []
+ exclude = _wrapper_options.exclude || []
parameters.except(*(exclude + EXCLUDE_PARAMETERS))
end
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index ee0e69d87c..ab14a61b97 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -24,7 +24,7 @@ module ActionController
# * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
# Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
#
- # redirect_to :action => "show", :id => 5
+ # redirect_to action: "show", id: 5
# redirect_to post
# redirect_to "http://www.rubyonrails.org"
# redirect_to "/images/screenshot.jpg"
@@ -32,12 +32,12 @@ module ActionController
# redirect_to :back
# redirect_to proc { edit_post_url(@post) }
#
- # The redirection happens as a "302 Moved" header unless otherwise specified.
+ # The redirection happens as a "302 Found" header unless otherwise specified.
#
- # redirect_to post_url(@post), :status => :found
- # redirect_to :action=>'atom', :status => :moved_permanently
- # redirect_to post_url(@post), :status => 301
- # redirect_to :action=>'atom', :status => 302
+ # redirect_to post_url(@post), status: :found
+ # redirect_to action: 'atom', status: :moved_permanently
+ # redirect_to post_url(@post), status: 301
+ # redirect_to action: 'atom', status: 302
#
# The status code can either be a standard {HTTP Status code}[http://www.iana.org/assignments/http-status-codes] as an
# integer, or a symbol representing the downcased, underscored and symbolized description.
@@ -49,32 +49,51 @@ module ActionController
# around this you can return a <tt>303 See Other</tt> status code which will be
# followed using a GET request.
#
- # redirect_to posts_url, :status => :see_other
- # redirect_to :action => 'index', :status => 303
+ # 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.
#
- # redirect_to post_url(@post), :alert => "Watch it, mister!"
- # redirect_to post_url(@post), :status=> :found, :notice => "Pay attention to the road"
- # redirect_to post_url(@post), :status => 301, :flash => { :updated_post_id => @post.id }
- # redirect_to { :action=>'atom' }, :alert => "Something serious happened"
+ # redirect_to post_url(@post), alert: "Watch it, mister!"
+ # redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
+ # redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
+ # redirect_to { action: 'atom' }, alert: "Something serious happened"
#
# When using <tt>redirect_to :back</tt>, if there is no referrer, ActionController::RedirectBackError will be raised. You may specify some fallback
# behavior for this case by rescuing ActionController::RedirectBackError.
def redirect_to(options = {}, response_status = {}) #:doc:
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
- logger.debug { "Redirected by #{caller(1).first rescue "unknown"}" } if logger
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(options)
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>"
end
+ def _compute_redirect_to_location(options) #:nodoc:
+ case options
+ # The scheme name consist of a letter followed by any combination of
+ # letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
+ # characters; and is terminated by a colon (":").
+ # See http://tools.ietf.org/html/rfc3986#section-3.1
+ # The protocol relative scheme starts with a double slash "//".
+ when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i
+ options
+ when String
+ request.protocol + request.host_with_port + options
+ when :back
+ request.headers["Referer"] or raise RedirectBackError
+ when Proc
+ _compute_redirect_to_location options.call
+ else
+ url_for(options)
+ end.delete("\0\r\n")
+ end
+
private
def _extract_redirect_to_status(options, response_status)
- status = if options.is_a?(Hash) && options.key?(:status)
+ if options.is_a?(Hash) && options.key?(:status)
Rack::Utils.status_code(options.delete(:status))
elsif response_status.key?(:status)
Rack::Utils.status_code(response_status[:status])
@@ -82,25 +101,5 @@ module ActionController
302
end
end
-
- def _compute_redirect_to_location(options)
- case options
- # The scheme name consist of a letter followed by any combination of
- # letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
- # characters; and is terminated by a colon (":").
- # The protocol relative scheme starts with a double slash "//"
- when %r{^(\w[\w+.-]*:|//).*}
- options
- when String
- request.protocol + request.host_with_port + options
- when :back
- raise RedirectBackError unless refer = request.headers["Referer"]
- refer
- when Proc
- _compute_redirect_to_location options.call
- else
- url_for(options)
- 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 78aeeef2bf..62a3844b04 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -6,6 +6,12 @@ module ActionController
Renderers.add(key, &block)
end
+ class MissingRenderer < LoadError
+ def initialize(format)
+ super "No renderer defined for format: #{format}"
+ end
+ end
+
module Renderers
extend ActiveSupport::Concern
@@ -52,8 +58,8 @@ module ActionController
# ActionController::Renderers.add :csv do |obj, options|
# filename = options[:filename] || 'data'
# str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
- # send_data str, :type => Mime::CSV,
- # :disposition => "attachment; filename=#{filename}.csv"
+ # send_data str, type: Mime::CSV,
+ # disposition: "attachment; filename=#{filename}.csv"
# end
#
# Note that we used Mime::CSV for the csv mime type as it comes with Rails.
@@ -66,7 +72,7 @@ module ActionController
# @csvable = Csvable.find(params[:id])
# respond_to do |format|
# format.html
- # format.csv { render :csv => @csvable, :filename => @csvable.name }
+ # format.csv { render csv: @csvable, filename: @csvable.name }
# }
# end
# To use renderers and their mime types in more concise ways, see
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index c5e7d4e357..90f0ef0b1c 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -2,39 +2,45 @@ module ActionController
module Rendering
extend ActiveSupport::Concern
- include AbstractController::Rendering
-
# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
- self.formats = request.formats.map { |x| x.ref }
+ self.formats = request.formats.map(&:ref).compact
super
end
# Check for double render errors and set the content_type after rendering.
def render(*args) #:nodoc:
- raise ::AbstractController::DoubleRenderError if response_body
+ raise ::AbstractController::DoubleRenderError if self.response_body
super
- self.content_type ||= Mime[lookup_context.rendered_format].to_s
- response_body
end
# Overwrite render_to_string because body can now be set to a rack body.
def render_to_string(*)
- if self.response_body = super
+ result = super
+ if result.respond_to?(:each)
string = ""
- response_body.each { |r| string << r }
+ result.each { |r| string << r }
string
+ else
+ result
end
- ensure
- self.response_body = nil
end
- def render_to_body(*)
- super || " "
+ def render_to_body(options = {})
+ super || if options[:text].present?
+ options[:text]
+ else
+ " "
+ end
end
private
+ def _process_format(format)
+ super
+ self.content_type ||= format.to_s
+ end
+
# Normalize arguments by catching blocks and setting them on :update.
def _normalize_args(action=nil, options={}, &blk) #:nodoc:
options = super
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 17d4a793ac..bd64b1f812 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -19,7 +19,7 @@ module ActionController #:nodoc:
#
# class ApplicationController < ActionController::Base
# protect_from_forgery
- # skip_before_filter :verify_authenticity_token, :if => :json_request?
+ # skip_before_action :verify_authenticity_token, if: :json_request?
#
# protected
#
@@ -50,6 +50,10 @@ module ActionController #:nodoc:
config_accessor :request_forgery_protection_token
self.request_forgery_protection_token ||= :authenticity_token
+ # Holds the class which implements the request forgery protection.
+ config_accessor :forgery_protection_strategy
+ self.forgery_protection_strategy = nil
+
# 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?
@@ -62,19 +66,19 @@ module ActionController #:nodoc:
# Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
#
# class FooController < ApplicationController
- # protect_from_forgery :except => :index
+ # protect_from_forgery except: :index
#
# You can disable csrf protection on controller-by-controller basis:
#
- # skip_before_filter :verify_authenticity_token
+ # skip_before_action :verify_authenticity_token
#
# It can also be disabled for specific controller actions:
#
- # skip_before_filter :verify_authenticity_token, :except => [:create]
+ # skip_before_action :verify_authenticity_token, except: [:create]
#
# Valid Options:
#
- # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
+ # * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified.
# * <tt>:with</tt> - Set the method to handle unverified request.
#
# Valid unverified request handling methods are:
@@ -82,14 +86,14 @@ module ActionController #:nodoc:
# * <tt>:reset_session</tt> - Resets the session.
# * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
def protect_from_forgery(options = {})
- include protection_method_module(options[:with] || :null_session)
+ self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
self.request_forgery_protection_token ||= :authenticity_token
- prepend_before_filter :verify_authenticity_token, options
+ prepend_before_action :verify_authenticity_token, options
end
private
- def protection_method_module(name)
+ def protection_method_class(name)
ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
rescue NameError
raise ArgumentError, 'Invalid request forgery protection method, use :null_session, :exception, or :reset_session'
@@ -97,23 +101,32 @@ module ActionController #:nodoc:
end
module ProtectionMethods
- module NullSession
- protected
+ class NullSession
+ def initialize(controller)
+ @controller = controller
+ end
# This is the method that defines the application behavior when a request is found to be unverified.
def handle_unverified_request
- request.session = NullSessionHash.new
+ request = @controller.request
+ request.session = NullSessionHash.new(request.env)
request.env['action_dispatch.request.flash_hash'] = nil
request.env['rack.session.options'] = { skip: true }
request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
end
+ protected
+
class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
- def initialize
- super(nil, nil)
+ def initialize(env)
+ super(nil, env)
+ @data = {}
@loaded = true
end
+ # no-op
+ def destroy; end
+
def exists?
true
end
@@ -121,11 +134,11 @@ module ActionController #:nodoc:
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
def self.build(request)
- secret = request.env[ActionDispatch::Cookies::TOKEN_KEY]
- host = request.host
- secure = request.ssl?
+ key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
+ host = request.host
+ secure = request.ssl?
- new(secret, host, secure)
+ new(key_generator, host, secure, options_for_env({}))
end
def write(*)
@@ -134,16 +147,20 @@ module ActionController #:nodoc:
end
end
- module ResetSession
- protected
+ class ResetSession
+ def initialize(controller)
+ @controller = controller
+ end
def handle_unverified_request
- reset_session
+ @controller.reset_session
end
end
- module Exception
- protected
+ class Exception
+ def initialize(controller)
+ @controller = controller
+ end
def handle_unverified_request
raise ActionController::InvalidAuthenticityToken
@@ -152,7 +169,11 @@ module ActionController #:nodoc:
end
protected
- # The actual before_filter that is used. Modify this to change how you handle unverified requests.
+ def handle_unverified_request
+ forgery_protection_strategy.new(self).handle_unverified_request
+ end
+
+ # The actual before_action that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
unless verified_request?
logger.warn "Can't verify CSRF token authenticity" if logger
@@ -162,11 +183,11 @@ module ActionController #:nodoc:
# Returns true or false if a request is verified. Checks:
#
- # * is it a GET request? Gets should be safe and idempotent
+ # * is it a GET or HEAD request? Gets should be safe and idempotent
# * Does the form_authenticity_token match the given token value from the params?
# * Does the X-CSRF-Token header match the form_authenticity_token
def verified_request?
- !protect_against_forgery? || request.get? ||
+ !protect_against_forgery? || request.get? || request.head? ||
form_authenticity_token == params[request_forgery_protection_token] ||
form_authenticity_token == request.headers['X-CSRF-Token']
end
@@ -181,6 +202,7 @@ module ActionController #:nodoc:
params[request_forgery_protection_token]
end
+ # Checks if the controller allows forgery protection.
def protect_against_forgery?
allow_forgery_protection
end
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index 42a0959a58..b4ba169e8f 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -45,10 +45,10 @@ module ActionController #:nodoc:
# if @user.save
# flash[:notice] = 'User was successfully created.'
# format.html { redirect_to(@user) }
- # format.xml { render :xml => @user, :status => :created, :location => @user }
+ # format.xml { render xml: @user, status: :created, location: @user }
# else
- # format.html { render :action => "new" }
- # format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
+ # format.html { render action: "new" }
+ # format.xml { render xml: @user.errors, status: :unprocessable_entity }
# end
# end
# end
@@ -92,18 +92,22 @@ module ActionController #:nodoc:
# @project = Project.find(params[:project_id])
# @task = @project.tasks.build(params[:task])
# flash[:notice] = 'Task was successfully created.' if @task.save
- # respond_with(@project, @task, :status => 201)
+ # respond_with(@project, @task, status: 201)
# end
#
# This will return status 201 if the task was saved successfully. If not,
# it will simply ignore the given options and return status 422 and the
- # resource errors. To customize the failure scenario, you can pass a
- # a block to <code>respond_with</code>:
+ # resource errors. You can also override the location to redirect to:
+ #
+ # respond_with(@project, location: root_path)
+ #
+ # To customize the failure scenario, you can pass a block to
+ # <code>respond_with</code>:
#
# def create
# @project = Project.find(params[:project_id])
# @task = @project.tasks.build(params[:task])
- # respond_with(@project, @task, :status => 201) do |format|
+ # respond_with(@project, @task, status: 201) do |format|
# if @task.save
# flash[:notice] = 'Task was successfully created.'
# else
@@ -140,7 +144,7 @@ module ActionController #:nodoc:
undef_method(:to_json) if method_defined?(:to_json)
undef_method(:to_yaml) if method_defined?(:to_yaml)
- # Initializes a new responder an invoke the proper format. If the format is
+ # Initializes a new responder and invokes the proper format. If the format is
# not defined, call to_format.
#
def self.call(*args)
@@ -198,6 +202,7 @@ module ActionController #:nodoc:
# This is the common behavior for formats associated with APIs, such as :xml and :json.
def api_behavior(error)
raise error unless resourceful?
+ raise MissingRenderer.new(format) unless has_renderer?
if get?
display resource
@@ -236,20 +241,20 @@ module ActionController #:nodoc:
# Display is just a shortcut to render a resource with the current format.
#
- # display @user, :status => :ok
+ # display @user, status: :ok
#
# For XML requests it's equivalent to:
#
- # render :xml => @user, :status => :ok
+ # render xml: @user, status: :ok
#
# Options sent by the user are also used:
#
- # respond_with(@user, :status => :created)
- # display(@user, :status => :ok)
+ # respond_with(@user, status: :created)
+ # display(@user, status: :ok)
#
# Results in:
#
- # render :xml => @user, :status => :created
+ # render xml: @user, status: :created
#
def display(resource, given_options={})
controller.render given_options.merge!(options).merge!(format => resource)
@@ -265,6 +270,11 @@ module ActionController #:nodoc:
resource.respond_to?(:errors) && !resource.errors.empty?
end
+ # Check whether the neceessary Renderer is available
+ def has_renderer?
+ Renderers::RENDERERS.include?(format)
+ end
+
# By default, render the <code>:edit</code> action for HTML requests with errors, unless
# the verb was POST.
#
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 9f3c997024..62d5931b45 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -21,15 +21,13 @@ module ActionController #:nodoc:
# supports fibers (fibers are supported since version 1.9.2 of the main
# Ruby implementation).
#
- # == Examples
- #
# Streaming can be added to a given template easily, all you need to do is
# to pass the :stream option.
#
# class PostsController
# def index
- # @posts = Post.scoped
- # render :stream => true
+ # @posts = Post.all
+ # render stream: true
# end
# end
#
@@ -53,10 +51,10 @@ module ActionController #:nodoc:
#
# def dashboard
# # Allow lazy execution of the queries
- # @posts = Post.scoped
- # @pages = Page.scoped
- # @articles = Article.scoped
- # render :stream => true
+ # @posts = Post.all
+ # @pages = Page.all
+ # @articles = Article.all
+ # render stream: true
# end
#
# Notice that :stream only works with templates. Rendering :json
@@ -176,7 +174,7 @@ module ActionController #:nodoc:
# need to create a config file as follow:
#
# # unicorn.config.rb
- # listen 3000, :tcp_nopush => false
+ # listen 3000, tcp_nopush: false
#
# And use it on initialization:
#
@@ -195,31 +193,29 @@ module ActionController #:nodoc:
module Streaming
extend ActiveSupport::Concern
- include AbstractController::Rendering
-
protected
- # Set proper cache control and transfer encoding when streaming
- def _process_options(options) #:nodoc:
- super
- if options[:stream]
- if env["HTTP_VERSION"] == "HTTP/1.0"
- options.delete(:stream)
- else
- headers["Cache-Control"] ||= "no-cache"
- headers["Transfer-Encoding"] = "chunked"
- headers.delete("Content-Length")
+ # Set proper cache control and transfer encoding when streaming
+ def _process_options(options) #:nodoc:
+ super
+ if options[:stream]
+ if env["HTTP_VERSION"] == "HTTP/1.0"
+ options.delete(:stream)
+ else
+ headers["Cache-Control"] ||= "no-cache"
+ headers["Transfer-Encoding"] = "chunked"
+ headers.delete("Content-Length")
+ end
end
end
- end
- # 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)
- else
- super
+ # 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)
+ else
+ super
+ end
end
- end
end
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 6f46954266..8ae7e474a3 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -1,6 +1,8 @@
-require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/array/wrap'
require 'active_support/rescuable'
+require 'action_dispatch/http/upload'
+require 'stringio'
module ActionController
# Raised when a required parameter is missing.
@@ -8,8 +10,6 @@ module ActionController
# params = ActionController::Parameters.new(a: {})
# params.fetch(:b)
# # => ActionController::ParameterMissing: param not found: b
- # params.require(:a)
- # # => ActionController::ParameterMissing: param not found: a
class ParameterMissing < KeyError
attr_reader :param # :nodoc:
@@ -19,13 +19,41 @@ module ActionController
end
end
- # == Action Controller Parameters
+ # Raised when a required parameter value is empty.
+ #
+ # params = ActionController::Parameters.new(a: {})
+ # params.require(:a)
+ # # => ActionController::EmptyParameter: value is empty for required key: a
+ class EmptyParameter < IndexError
+ attr_reader :param
+
+ def initialize(param)
+ @param = param
+ super("value is empty for required key: #{param}")
+ end
+ end
+
+ # Raised when a supplied parameter is not expected.
+ #
+ # params = ActionController::Parameters.new(a: "123", b: "456")
+ # params.permit(:c)
+ # # => ActionController::UnpermittedParameters: found unexpected keys: a, b
+ class UnpermittedParameters < IndexError
+ attr_reader :params # :nodoc:
+
+ def initialize(params) # :nodoc:
+ @params = params
+ super("found unpermitted parameters: #{params.join(", ")}")
+ end
+ end
+
+ # == Action Controller \Parameters
#
# Allows to choose which attributes should be whitelisted for mass updating
# and thus prevent accidentally exposing that which shouldn’t be exposed.
# Provides two methods for this purpose: #require and #permit. The former is
# used to mark parameters as required. The latter is used to set the parameter
- # as permitted and limit which attributes should be allowed for mass updating.
+ # as permitted and limit which attributes should be allowed for mass updating.
#
# params = ActionController::Parameters.new({
# person: {
@@ -40,13 +68,20 @@ module ActionController
# permitted.class # => ActionController::Parameters
# permitted.permitted? # => true
#
- # Person.first.update_attributes!(permitted)
+ # Person.first.update!(permitted)
# # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
#
- # It provides a +permit_all_parameters+ option that controls the top-level
- # behaviour of new instances. If it's +true+, all the parameters will be
- # permitted by default. The default value for +permit_all_parameters+
- # option is +false+.
+ # It provides two options that controls the top-level behavior of new instances:
+ #
+ # * +permit_all_parameters+ - If it's +true+, all the parameters will be
+ # permitted by default. The default is +false+.
+ # * +action_on_unpermitted_parameters+ - Allow to control the behavior when parameters
+ # that are not explicitly permitted are found. The values can be <tt>:log</tt> to
+ # write a message on the logger or <tt>:raise</tt> to raise
+ # ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt>
+ # in test and development environments, +false+ otherwise.
+ #
+ # Examples:
#
# params = ActionController::Parameters.new
# params.permitted? # => false
@@ -56,6 +91,16 @@ module ActionController
# params = ActionController::Parameters.new
# params.permitted? # => true
#
+ # params = ActionController::Parameters.new(a: "123", b: "456")
+ # params.permit(:c)
+ # # => {}
+ #
+ # ActionController::Parameters.action_on_unpermitted_parameters = :raise
+ #
+ # params = ActionController::Parameters.new(a: "123", b: "456")
+ # params.permit(:c)
+ # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b
+ #
# <tt>ActionController::Parameters</tt> is inherited from
# <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means
# that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>.
@@ -65,24 +110,27 @@ module ActionController
# params["key"] # => "value"
class Parameters < ActiveSupport::HashWithIndifferentAccess
cattr_accessor :permit_all_parameters, instance_accessor: false
- attr_accessor :permitted # :nodoc:
+ cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
+
+ # Never raise an UnpermittedParameters exception because of these params
+ # are present. They are added by Rails and it's of no concern.
+ NEVER_UNPERMITTED_PARAMS = %w( controller action )
# Returns a new instance of <tt>ActionController::Parameters</tt>.
# Also, sets the +permitted+ attribute to the default value of
# <tt>ActionController::Parameters.permit_all_parameters</tt>.
#
- # class Person
- # include ActiveRecord::Base
+ # class Person < ActiveRecord::Base
# end
#
# params = ActionController::Parameters.new(name: 'Francesco')
# params.permitted? # => false
- # Person.new(params) # => ActiveModel::ForbiddenAttributesError
+ # Person.new(params) # => ActiveModel::ForbiddenAttributesError
#
# ActionController::Parameters.permit_all_parameters = true
#
# params = ActionController::Parameters.new(name: 'Francesco')
- # params.permitted? # => true
+ # params.permitted? # => true
# Person.new(params) # => #<Person id: nil, name: "Francesco">
def initialize(attributes = nil)
super(attributes)
@@ -106,7 +154,7 @@ module ActionController
# end
#
# params = ActionController::Parameters.new(name: 'Francesco')
- # params.permitted? # => false
+ # params.permitted? # => false
# Person.new(params) # => ActiveModel::ForbiddenAttributesError
# params.permit!
# params.permitted? # => true
@@ -121,41 +169,59 @@ module ActionController
self
end
- # Ensures that a parameter is present. If it's present, returns
- # the parameter at the given +key+, otherwise raises an
- # <tt>ActionController::ParameterMissing</tt> error.
+ # Ensures that a parameter is present. If it's present and not empty,
+ # returns the parameter at the given +key+, if it's empty raises
+ # an <tt>ActionController::EmptyParameter</tt> error, otherwise
+ # raises an <tt>ActionController::ParameterMissing</tt> error.
#
- # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
- # # => {"name"=>"Francesco"}
- #
- # ActionController::Parameters.new(person: nil).require(:person)
- # # => ActionController::ParameterMissing: param not found: person
+ # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
+ # # => {"name"=>"Francesco"}
#
# ActionController::Parameters.new(person: {}).require(:person)
+ # # => ActionController::EmptyParameter: value is empty for required key: person
+ #
+ # ActionController::Parameters.new(name: {}).require(:person)
# # => ActionController::ParameterMissing: param not found: person
def require(key)
- self[key].presence || raise(ParameterMissing.new(key))
+ raise(ActionController::ParameterMissing.new(key)) unless self.key?(key)
+ self[key].presence || raise(ActionController::EmptyParameter.new(key))
end
# Alias of #require.
alias :required :require
# Returns a new <tt>ActionController::Parameters</tt> instance that
- # includes only the given +filters+ and sets the +permitted+ for the
- # object to +true+. This is useful for limiting which attributes
+ # includes only the given +filters+ and sets the +permitted+ attribute
+ # for the object to +true+. This is useful for limiting which attributes
# should be allowed for mass updating.
#
# params = ActionController::Parameters.new(user: { name: 'Francesco', age: 22, role: 'admin' })
# permitted = params.require(:user).permit(:name, :age)
- # permitted.permitted? # => true
+ # permitted.permitted? # => true
# permitted.has_key?(:name) # => true
# permitted.has_key?(:age) # => true
# permitted.has_key?(:role) # => false
#
+ # Only permitted scalars pass the filter. For example, given
+ #
+ # params.permit(:name)
+ #
+ # +:name+ passes it is a key of +params+ whose associated value is of type
+ # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+,
+ # +Date+, +Time+, +DateTime+, +StringIO+, +IO+,
+ # +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+.
+ # Otherwise, the key +:name+ is filtered out.
+ #
+ # You may declare that the parameter should be an array of permitted scalars
+ # by mapping it to an empty array:
+ #
+ # params = ActionController::Parameters.new(tags: ['rails', 'parameters'])
+ # params.permit(tags: [])
+ #
# You can also use +permit+ on nested parameters, like:
#
# params = ActionController::Parameters.new({
- # person: {
+ # person: {
# name: 'Francesco',
# age: 22,
# pets: [{
@@ -165,10 +231,10 @@ module ActionController
# }
# })
#
- # permitted = params.permit(person: [ :name, { pets: :name } ])
+ # permitted = params.permit(person: [ :name, { pets: :name } ])
# permitted.permitted? # => true
# permitted[:person][:name] # => "Francesco"
- # permitted[:person][:age] # => nil
+ # permitted[:person][:age] # => nil
# permitted[:person][:pets][0][:name] # => "Purplish"
# permitted[:person][:pets][0][:category] # => nil
#
@@ -179,7 +245,7 @@ module ActionController
# params = ActionController::Parameters.new({
# person: {
# contact: {
- # email: 'none@test.com'
+ # email: 'none@test.com',
# phone: '555-1234'
# }
# }
@@ -189,47 +255,32 @@ module ActionController
# # => {}
#
# params.require(:person).permit(contact: :phone)
- # # => {"contact"=>{"phone"=>"555-1234"}}
+ # # => {"contact"=>{"phone"=>"555-1234"}}
#
# params.require(:person).permit(contact: [ :email, :phone ])
# # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}}
def permit(*filters)
params = self.class.new
- filters.each do |filter|
+ filters.flatten.each do |filter|
case filter
- when Symbol, String then
- if has_key?(filter)
- _value = self[filter]
- params[filter] = _value unless Hash === _value
- end
- keys.grep(/\A#{Regexp.escape(filter)}\(\di\)\z/) { |key| params[key] = self[key] }
+ when Symbol, String
+ permitted_scalar_filter(params, filter)
when Hash then
- self.slice(*filter.keys).each do |key, values|
- return unless values
-
- key = key.to_sym
-
- params[key] = each_element(values) do |value|
- # filters are a Hash, so we expect value to be a Hash too
- next if filter.is_a?(Hash) && !value.is_a?(Hash)
-
- value = self.class.new(value) if !value.respond_to?(:permit)
-
- value.permit(*Array.wrap(filter[key]))
- end
- end
+ hash_filter(params, filter)
end
end
+ unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
+
params.permit!
end
# Returns a parameter for the given +key+. If not found,
# returns +nil+.
#
- # params = ActionController::Parameters.new(person: { name: 'Francesco' })
- # params[:person] # => {"name"=>"Francesco"}
+ # params = ActionController::Parameters.new(person: { name: 'Francesco' })
+ # params[:person] # => {"name"=>"Francesco"}
# params[:none] # => nil
def [](key)
convert_hashes_to_parameters(key, super)
@@ -242,12 +293,19 @@ module ActionController
# is given, then that will be run and its result returned.
#
# params = ActionController::Parameters.new(person: { name: 'Francesco' })
- # params.fetch(:person) # => {"name"=>"Francesco"}
- # params.fetch(:none) # => ActionController::ParameterMissing: param not found: none
+ # params.fetch(:person) # => {"name"=>"Francesco"}
+ # params.fetch(:none) # => ActionController::ParameterMissing: param not found: none
# params.fetch(:none, 'Francesco') # => "Francesco"
- # params.fetch(:none) { 'Francesco' } # => "Francesco"
+ # params.fetch(:none) { 'Francesco' } # => "Francesco"
def fetch(key, *args)
- convert_hashes_to_parameters(key, super)
+ value = super
+ # Don't rely on +convert_hashes_to_parameters+
+ # so as to not mutate via a +fetch+
+ if value.is_a?(Hash)
+ value = self.class.new(value)
+ value.permit! if permitted?
+ end
+ value
rescue KeyError
raise ActionController::ParameterMissing.new(key)
end
@@ -260,7 +318,9 @@ module ActionController
# params.slice(:a, :b) # => {"a"=>1, "b"=>2}
# params.slice(:d) # => {}
def slice(*keys)
- self.class.new(super)
+ self.class.new(super).tap do |new_instance|
+ new_instance.permitted = @permitted
+ end
end
# Returns an exact copy of the <tt>ActionController::Parameters</tt>
@@ -273,10 +333,15 @@ module ActionController
# copy_params.permitted? # => true
def dup
super.tap do |duplicate|
- duplicate.instance_variable_set :@permitted, @permitted
+ duplicate.permitted = @permitted
end
end
+ protected
+ def permitted=(new_permitted)
+ @permitted = new_permitted
+ end
+
private
def convert_hashes_to_parameters(key, value)
if value.is_a?(Parameters) || !value.is_a?(Hash)
@@ -290,7 +355,7 @@ module ActionController
def each_element(object)
if object.is_a?(Array)
object.map { |el| yield el }.compact
- elsif object.is_a?(Hash) && object.keys.all? { |k| k =~ /\A-?\d+\z/ }
+ elsif fields_for_style?(object)
hash = object.class.new
object.each { |k,v| hash[k] = yield v }
hash
@@ -298,12 +363,111 @@ module ActionController
yield object
end
end
+
+ def fields_for_style?(object)
+ object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
+ end
+
+ def unpermitted_parameters!(params)
+ unpermitted_keys = unpermitted_keys(params)
+ if unpermitted_keys.any?
+ case self.class.action_on_unpermitted_parameters
+ when :log
+ name = "unpermitted_parameters.action_controller"
+ ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys)
+ when :raise
+ raise ActionController::UnpermittedParameters.new(unpermitted_keys)
+ end
+ end
+ end
+
+ def unpermitted_keys(params)
+ self.keys - params.keys - NEVER_UNPERMITTED_PARAMS
+ end
+
+ #
+ # --- Filtering ----------------------------------------------------------
+ #
+
+ # This is a white list of permitted scalar types that includes the ones
+ # supported in XML and JSON requests.
+ #
+ # This list is in particular used to filter ordinary requests, String goes
+ # as first element to quickly short-circuit the common case.
+ #
+ # If you modify this collection please update the API of +permit+ above.
+ PERMITTED_SCALAR_TYPES = [
+ String,
+ Symbol,
+ NilClass,
+ Numeric,
+ TrueClass,
+ FalseClass,
+ Date,
+ Time,
+ # DateTimes are Dates, we document the type but avoid the redundant check.
+ StringIO,
+ IO,
+ ActionDispatch::Http::UploadedFile,
+ Rack::Test::UploadedFile,
+ ]
+
+ def permitted_scalar?(value)
+ PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)}
+ end
+
+ def permitted_scalar_filter(params, key)
+ if has_key?(key) && permitted_scalar?(self[key])
+ params[key] = self[key]
+ end
+
+ keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k|
+ if permitted_scalar?(self[k])
+ params[k] = self[k]
+ end
+ end
+ end
+
+ def array_of_permitted_scalars?(value)
+ if value.is_a?(Array)
+ value.all? {|element| permitted_scalar?(element)}
+ end
+ end
+
+ def array_of_permitted_scalars_filter(params, key)
+ if has_key?(key) && array_of_permitted_scalars?(self[key])
+ params[key] = self[key]
+ end
+ end
+
+ EMPTY_ARRAY = []
+ def hash_filter(params, filter)
+ filter = filter.with_indifferent_access
+
+ # Slicing filters out non-declared keys.
+ slice(*filter.keys).each do |key, value|
+ next unless value
+
+ if filter[key] == EMPTY_ARRAY
+ # Declaration { comment_ids: [] }.
+ array_of_permitted_scalars_filter(params, key)
+ else
+ # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
+ params[key] = each_element(value) do |element|
+ if element.is_a?(Hash)
+ element = self.class.new(element) unless element.respond_to?(:permit)
+ element.permit(*Array.wrap(filter[key]))
+ end
+ end
+ end
+ end
+ end
end
# == Strong \Parameters
#
# It provides an interface for protecting attributes from end-user
- # assignment. This makes Action Controller parameters forbidden
+ # assignment. This makes Action Controller parameters forbidden
# to be used in Active Model mass assignment until they have been
# whitelisted.
#
@@ -326,13 +490,13 @@ module ActionController
# # into a 400 Bad Request reply.
# def update
# redirect_to current_account.people.find(params[:id]).tap { |person|
- # person.update_attributes!(person_params)
+ # person.update!(person_params)
# }
# end
#
# private
# # Using a private method to encapsulate the permissible parameters is
- # # just a good pattern since you'll be able to reuse the same permit
+ # # just a good pattern since you'll be able to reuse the same permit
# # list between create and update. Also, you can specialize this method
# # with per-user checking of permissible attributes.
# def person_params
@@ -340,18 +504,37 @@ module ActionController
# end
# end
#
+ # In order to use <tt>accepts_nested_attribute_for</tt> with Strong \Parameters, you
+ # will need to specify which nested attributes should be whitelisted.
+ #
+ # class Person
+ # has_many :pets
+ # accepts_nested_attributes_for :pets
+ # end
+ #
+ # class PeopleController < ActionController::Base
+ # def create
+ # Person.create(person_params)
+ # end
+ #
+ # ...
+ #
+ # private
+ #
+ # def person_params
+ # # It's mandatory to specify the nested attributes that should be whitelisted.
+ # # If you use `permit` with just the key that points to the nested attributes hash,
+ # # it will return an empty hash.
+ # params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ])
+ # end
+ # end
+ #
# See ActionController::Parameters.require and ActionController::Parameters.permit
# for more information.
module StrongParameters
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
- included do
- rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception|
- render text: "Required parameter missing: #{parameter_missing_exception.param}", status: :bad_request
- end
- end
-
# Returns a new ActionController::Parameters object that
# has been instantiated with the <tt>request.parameters</tt>.
def params
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 0cdd17bc2e..754249cbc8 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -10,7 +10,7 @@ module ActionController
# include ActionController::UrlFor
# include Rails.application.routes.url_helpers
#
- # delegate :env, :request, :to => :controller
+ # delegate :env, :request, to: :controller
#
# def initialize(controller)
# @controller = controller
@@ -32,7 +32,8 @@ module ActionController
if (same_origin = _routes.equal?(env["action_dispatch.routes"])) ||
(script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
- (original_script_name = env['SCRIPT_NAME'])
+ (original_script_name = env['ORIGINAL_SCRIPT_NAME'])
+
@_url_options.dup.tap do |options|
if original_script_name
options[:original_script_name] = original_script_name
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index ee0f053bad..0833e65d23 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -1,7 +1,6 @@
require "rails"
require "action_controller"
require "action_dispatch/railtie"
-require "action_view/railtie"
require "abstract_controller/railties/routes_helpers"
require "action_controller/railties/helpers"
@@ -20,23 +19,27 @@ module ActionController
end
initializer "action_controller.parameters_config" do |app|
- ActionController::Parameters.permit_all_parameters = app.config.action_controller.delete(:permit_all_parameters) { false }
+ options = app.config.action_controller
+
+ ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false }
+ ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do
+ (Rails.env.test? || Rails.env.development?) ? :log : false
+ end
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.logger ||= Rails.logger
+ options.cache_store ||= Rails.cache
- options.javascripts_dir ||= paths["public/javascripts"].first
- options.stylesheets_dir ||= paths["public/stylesheets"].first
+ options.javascripts_dir ||= paths["public/javascripts"].first
+ options.stylesheets_dir ||= paths["public/stylesheets"].first
# 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
+ options.asset_host ||= app.config.asset_host
+ options.relative_url_root ||= app.config.relative_url_root
ActiveSupport.on_load(:action_controller) do
include app.routes.mounted_helpers
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
deleted file mode 100644
index bffd2a02d0..0000000000
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'active_support/deprecation'
-require 'action_view/record_identifier'
-
-module ActionController
- module RecordIdentifier
- MESSAGE = 'method will no longer be included by default in controllers since Rails 4.1. ' +
- 'If you would like to use it in controllers, please include ' +
- 'ActionView::RecodIdentifier module.'
-
- def dom_id(record, prefix = nil)
- ActiveSupport::Deprecation.warn 'dom_id ' + MESSAGE
- ActionView::RecordIdentifier.dom_id(record, prefix)
- end
-
- def dom_class(record, prefix = nil)
- ActiveSupport::Deprecation.warn 'dom_class ' + MESSAGE
- ActionView::RecordIdentifier.dom_class(record, prefix)
- end
- end
-end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index d911d47a1d..5ed3d2ebc1 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -1,6 +1,7 @@
require 'rack/session/abstract/id'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/module/anonymous'
+require 'active_support/core_ext/hash/keys'
module ActionController
module TemplateAssertions
@@ -15,8 +16,9 @@ module ActionController
@_partials = Hash.new(0)
@_templates = Hash.new(0)
@_layouts = Hash.new(0)
+ @_files = Hash.new(0)
- ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
+ ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
path = payload[:layout]
if path
@_layouts[path] += 1
@@ -26,7 +28,7 @@ module ActionController
end
end
- ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
+ ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
path = payload[:virtual_path]
next unless path
partial = path =~ /^.*\/_[^\/]*$/
@@ -38,6 +40,16 @@ module ActionController
@_templates[path] += 1
end
+
+ ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
+ next if payload[:virtual_path] # files don't have virtual path
+
+ path = payload[:identifier]
+ if path
+ @_files[path] += 1
+ @_files[path.split("/").last] += 1
+ end
+ end
end
def teardown_subscriptions
@@ -61,28 +73,27 @@ module ActionController
# assert_template %r{\Aadmin/posts/new\Z}
#
# # assert that the layout 'admin' was rendered
- # assert_template :layout => 'admin'
- # assert_template :layout => 'layouts/admin'
- # assert_template :layout => :admin
+ # assert_template layout: 'admin'
+ # assert_template layout: 'layouts/admin'
+ # assert_template layout: :admin
#
# # assert that no layout was rendered
- # assert_template :layout => nil
- # assert_template :layout => false
+ # assert_template layout: nil
+ # assert_template layout: false
#
# # assert that the "_customer" partial was rendered twice
- # assert_template :partial => '_customer', :count => 2
+ # assert_template partial: '_customer', count: 2
#
# # assert that no partials were rendered
- # assert_template :partial => false
+ # assert_template partial: false
#
# In a view test case, you can also assert that specific locals are passed
# to partials:
#
# # assert that the "_customer" partial was rendered with a specific object
- # assert_template :partial => '_customer', :locals => { :customer => @customer }
+ # 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
+ # Force body to be read in case the template is being streamed.
response.body
case options
@@ -94,7 +105,7 @@ module ActionController
matches_template =
case options
when String
- rendered.any? do |t, num|
+ !options.empty? && rendered.any? do |t, num|
options_splited = options.split(File::SEPARATOR)
t_splited = t.split(File::SEPARATOR)
t_splited.last(options_splited.size) == options_splited
@@ -106,6 +117,8 @@ module ActionController
end
assert matches_template, msg
when Hash
+ options.assert_valid_keys(:layout, :partial, :locals, :count, :file)
+
if options.key?(:layout)
expected_layout = options[:layout]
msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
@@ -121,10 +134,18 @@ module ActionController
end
end
+ if options[:file]
+ assert_includes @_files.keys, options[:file]
+ end
+
if expected_partial = options[:partial]
if expected_locals = options[:locals]
if defined?(@_rendered_views)
- view = expected_partial.to_s.sub(/^_/,'')
+ view = expected_partial.to_s.sub(/^_/, '').sub(/\/_(?=[^\/]+\z)/, '/')
+
+ partial_was_not_rendered_msg = "expected %s to be rendered but it was not." % view
+ assert_includes @_rendered_views.rendered_views, view, partial_was_not_rendered_msg
+
msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial,
expected_locals,
@_rendered_views.locals_for(view)]
@@ -234,18 +255,39 @@ module ActionController
end
end
+ # Methods #destroy and #load! are overridden to avoid calling methods on the
+ # @store object, which does not exist for the TestSession class.
class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
def initialize(session = {})
super(nil, nil)
- replace(session.stringify_keys)
+ @id = SecureRandom.hex(16)
+ @data = stringify_keys(session)
@loaded = true
end
def exists?
true
end
+
+ def keys
+ @data.keys
+ end
+
+ def values
+ @data.values
+ end
+
+ def destroy
+ clear
+ end
+
+ private
+
+ def load!
+ @id
+ end
end
# Superclass for ActionController functional tests. Functional tests allow you to
@@ -267,21 +309,21 @@ module ActionController
# class BooksControllerTest < ActionController::TestCase
# def test_create
# # Simulate a POST response with the given HTTP parameters.
- # post(:create, :book => { :title => "Love Hina" })
+ # post(:create, book: { title: "Love Hina" })
#
# # Assert that the controller tried to redirect us to
# # the created book's URI.
# assert_response :found
#
# # Assert that the controller really put the book in the database.
- # assert_not_nil Book.find_by_title("Love Hina")
+ # assert_not_nil Book.find_by(title: "Love Hina")
# end
# end
#
# You can also send a real document in the simulated HTTP request.
#
# def test_create
- # json = {:book => { :title => "Love Hina" }}.to_json
+ # json = {book: { title: "Love Hina" }}.to_json
# post :create, json
# end
#
@@ -356,15 +398,8 @@ module ActionController
#
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
#
- # assert_redirected_to page_url(:title => 'foo')
+ # assert_redirected_to page_url(title: 'foo')
class TestCase < ActiveSupport::TestCase
-
- # Use AC::TestCase for the base class when describing a controller
- register_spec_type(self) do |desc|
- Class === desc && desc < ActionController::Metal
- end
- register_spec_type(/Controller( ?Test)?\z/i, self)
-
module Behavior
extend ActiveSupport::Concern
include ActionDispatch::TestProcess
@@ -416,41 +451,54 @@ module ActionController
end
- # Executes a request simulating GET HTTP method and set/volley the response
+ # Simulate a GET request with the given parameters.
+ #
+ # - +action+: The controller action to call.
+ # - +parameters+: The HTTP parameters that you want to pass. This may
+ # be +nil+, a hash, or a string that is appropriately encoded
+ # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
+ # - +session+: A hash of parameters to store in the session. This may be +nil+.
+ # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
+ #
+ # You can also simulate POST, PATCH, PUT, DELETE, HEAD, and OPTIONS requests with
+ # +post+, +patch+, +put+, +delete+, +head+, and +options+.
+ #
+ # Note that the request method is not verified. The different methods are
+ # available to make the tests more expressive.
def get(action, *args)
process(action, "GET", *args)
end
- # Executes a request simulating POST HTTP method and set/volley the response
+ # Simulate a POST request with the given parameters and set/volley the response.
+ # See +get+ for more details.
def post(action, *args)
process(action, "POST", *args)
end
- # Executes a request simulating PATCH HTTP method and set/volley the response
+ # Simulate a PATCH request with the given parameters and set/volley the response.
+ # See +get+ for more details.
def patch(action, *args)
process(action, "PATCH", *args)
end
- # Executes a request simulating PUT HTTP method and set/volley the response
+ # Simulate a PUT request with the given parameters and set/volley the response.
+ # See +get+ for more details.
def put(action, *args)
process(action, "PUT", *args)
end
- # Executes a request simulating DELETE HTTP method and set/volley the response
+ # Simulate a DELETE request with the given parameters and set/volley the response.
+ # See +get+ for more details.
def delete(action, *args)
process(action, "DELETE", *args)
end
- # Executes a request simulating HEAD HTTP method and set/volley the response
+ # Simulate a HEAD request with the given parameters and set/volley the response.
+ # See +get+ for more details.
def head(action, *args)
process(action, "HEAD", *args)
end
- # Executes a request simulating OPTIONS HTTP method and set/volley the response
- def options(action, *args)
- process(action, "OPTIONS", *args)
- end
-
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
@@ -476,7 +524,6 @@ module ActionController
def process(action, http_method = 'GET', *args)
check_required_ivars
- http_method, args = handle_old_process_api(http_method, args)
if args.first.is_a?(String) && http_method != 'HEAD'
@request.env['RAW_POST_DATA'] = args.shift
@@ -504,12 +551,12 @@ module ActionController
parameters ||= {}
controller_class_name = @controller.class.anonymous? ?
"anonymous" :
- @controller.class.name.underscore.sub(/_controller$/, '')
+ @controller.class.controller_path
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
@request.session.update(session) if session
- @request.session["flash"] = @request.flash.update(flash || {})
+ @request.flash.update(flash || {})
@controller.request = @request
@controller.response = @response
@@ -526,6 +573,7 @@ module ActionController
@response.prepare!
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
+ @request.session['flash'] = @request.flash.to_session_value
@request.session.delete('flash') if @request.session['flash'].blank?
@response
end
@@ -579,17 +627,6 @@ module ActionController
end
end
- def handle_old_process_api(http_method, args)
- # 4.0: Remove this method.
- if http_method.is_a?(Hash)
- ActiveSupport::Deprecation.warn("TestCase#process now expects the HTTP method as second argument: process(action, http_method, params, session, flash)")
- args.unshift(http_method)
- http_method = args.last.is_a?(String) ? args.last : "GET"
- end
-
- [http_method, args]
- end
-
def build_request_uri(action, parameters)
unless @request.env["PATH_INFO"]
options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters
diff --git a/actionpack/lib/action_controller/vendor/html-scanner.rb b/actionpack/lib/action_controller/vendor/html-scanner.rb
deleted file mode 100644
index 896208bc05..0000000000
--- a/actionpack/lib/action_controller/vendor/html-scanner.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'action_view/vendor/html-scanner'
-require 'active_support/deprecation'
-
-ActiveSupport::Deprecation.warn 'Vendored html-scanner was moved to action_view, please require "action_view/vendor/html-scanner" instead. ' +
- 'This file will be removed in Rails 4.1'
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 0ec355246e..24a3d4741e 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2012 David Heinemeier Hansson
+# Copyright (c) 2004-2013 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -47,7 +47,6 @@ module ActionDispatch
autoload_under 'middleware' do
autoload :RequestId
- autoload :BestStandardsSupport
autoload :Callbacks
autoload :Cookies
autoload :DebugExceptions
@@ -63,6 +62,7 @@ module ActionDispatch
autoload :Static
end
+ autoload :Journey
autoload :MiddlewareStack, 'action_dispatch/middleware/stack'
autoload :Routing
@@ -75,6 +75,7 @@ module ActionDispatch
autoload :Parameters
autoload :ParameterFilter
autoload :FilterParameters
+ autoload :FilterRedirect
autoload :Upload
autoload :UploadedFile, 'action_dispatch/http/upload'
autoload :URL
@@ -93,7 +94,6 @@ module ActionDispatch
autoload :Assertions
autoload :Integration
autoload :IntegrationTest, 'action_dispatch/testing/integration'
- autoload :PerformanceTest
autoload :TestProcess
autoload :TestRequest
autoload :TestResponse
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 0d6015d993..f9b278349e 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -92,7 +92,7 @@ module ActionDispatch
LAST_MODIFIED = "Last-Modified".freeze
ETAG = "ETag".freeze
CACHE_CONTROL = "Cache-Control".freeze
- SPESHUL_KEYS = %w[extras no-cache max-age public must-revalidate]
+ SPECIAL_KEYS = %w[extras no-cache max-age public must-revalidate]
def cache_control_segments
if cache_control = self[CACHE_CONTROL]
@@ -108,7 +108,7 @@ module ActionDispatch
cache_control_segments.each do |segment|
directive, argument = segment.split('=', 2)
- if SPESHUL_KEYS.include? directive
+ if SPECIAL_KEYS.include? directive
key = directive.tr('-', '_')
cache_control[key.to_sym] = argument || true
else
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 47cf41cfa3..289e204ac8 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/object/duplicable'
+require 'action_dispatch/http/parameter_filter'
module ActionDispatch
module Http
@@ -20,9 +21,16 @@ module ActionDispatch
# end
# => reverses the value to all keys matching /secret/i
module FilterParameters
- extend ActiveSupport::Concern
+ ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
+ NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
+ NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc:
- @@parameter_filter_for = {}
+ def initialize(env)
+ super
+ @filtered_parameters = nil
+ @filtered_env = nil
+ @filtered_path = nil
+ end
# Return a hash of parameters with all sensitive data replaced.
def filtered_parameters
@@ -42,15 +50,20 @@ module ActionDispatch
protected
def parameter_filter
- parameter_filter_for(@env["action_dispatch.parameter_filter"])
+ parameter_filter_for @env.fetch("action_dispatch.parameter_filter") {
+ return NULL_PARAM_FILTER
+ }
end
def env_filter
- parameter_filter_for(Array(@env["action_dispatch.parameter_filter"]) + [/RAW_POST_DATA/, "rack.request.form_vars"])
+ user_key = @env.fetch("action_dispatch.parameter_filter") {
+ return NULL_ENV_FILTER
+ }
+ parameter_filter_for(Array(user_key) + ENV_MATCH)
end
def parameter_filter_for(filters)
- @@parameter_filter_for[filters] ||= ParameterFilter.new(filters)
+ ParameterFilter.new(filters)
end
KV_RE = '[^&;=]+'
@@ -60,7 +73,6 @@ module ActionDispatch
parameter_filter.filter([[$1, $2]]).first.join("=")
end
end
-
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb
new file mode 100644
index 0000000000..900ce1c646
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb
@@ -0,0 +1,37 @@
+module ActionDispatch
+ module Http
+ module FilterRedirect
+
+ FILTERED = '[FILTERED]'.freeze # :nodoc:
+
+ def filtered_location
+ if !location_filter.empty? && location_filter_match?
+ FILTERED
+ else
+ location
+ end
+ end
+
+ private
+
+ def location_filter
+ if request.present?
+ request.env['action_dispatch.redirect_filter'] || []
+ else
+ []
+ end
+ end
+
+ def location_filter_match?
+ location_filter.any? do |filter|
+ if String === filter
+ location.include?(filter)
+ elsif Regexp === filter
+ location.match(filter)
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index a3bb25f75a..2666cd4b0a 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -1,32 +1,63 @@
module ActionDispatch
module Http
- class Headers < ::Hash
- @@env_cache = Hash.new { |h,k| h[k] = "HTTP_#{k.upcase.gsub(/-/, '_')}" }
+ class Headers
+ CGI_VARIABLES = %w(
+ CONTENT_TYPE CONTENT_LENGTH
+ HTTPS AUTH_TYPE GATEWAY_INTERFACE
+ PATH_INFO PATH_TRANSLATED QUERY_STRING
+ REMOTE_ADDR REMOTE_HOST REMOTE_IDENT REMOTE_USER
+ REQUEST_METHOD SCRIPT_NAME
+ SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE
+ )
+ HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
- def initialize(*args)
+ include Enumerable
+ attr_reader :env
- if args.size == 1 && args[0].is_a?(Hash)
- super()
- update(args[0])
- else
- super
- end
+ def initialize(env = {})
+ @env = env
+ end
+
+ def [](key)
+ @env[env_name(key)]
+ end
+
+ def []=(key, value)
+ @env[env_name(key)] = value
+ end
+
+ def key?(key); @env.key? key; end
+ alias :include? :key?
+
+ def fetch(key, *args, &block)
+ @env.fetch env_name(key), *args, &block
+ end
+
+ def each(&block)
+ @env.each(&block)
end
- def [](header_name)
- super env_name(header_name)
+ def merge(headers_or_env)
+ headers = Http::Headers.new(env.dup)
+ headers.merge!(headers_or_env)
+ headers
end
- def fetch(header_name, default=nil, &block)
- super env_name(header_name), default, &block
+ def merge!(headers_or_env)
+ headers_or_env.each do |key, value|
+ self[env_name(key)] = value
+ end
end
private
- # Converts a HTTP header name to an environment variable name if it is
- # not contained within the headers hash.
- def env_name(header_name)
- include?(header_name) ? header_name : @@env_cache[header_name]
+ def env_name(key)
+ key = key.to_s
+ if key =~ HTTP_HEADER
+ key = key.upcase.tr('-', '_')
+ key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
end
+ key
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 0f98e84788..40bb060d52 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -68,7 +68,7 @@ module ActionDispatch
# that are not controlled by the extension.
#
# class ApplicationController < ActionController::Base
- # before_filter :adjust_format_for_iphone
+ # before_action :adjust_format_for_iphone
#
# private
# def adjust_format_for_iphone
@@ -87,7 +87,7 @@ module ActionDispatch
# to the :html format.
#
# class ApplicationController < ActionController::Base
- # before_filter :adjust_format_for_iphone_with_html_fallback
+ # before_action :adjust_format_for_iphone_with_html_fallback
#
# private
# def adjust_format_for_iphone_with_html_fallback
@@ -121,8 +121,8 @@ module ActionDispatch
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
def valid_accept_header
- (xhr? && (accept || content_mime_type)) ||
- (accept && accept !~ BROWSER_LIKE_ACCEPTS)
+ (xhr? && (accept.present? || content_mime_type)) ||
+ (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS)
end
def use_accept_header
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 3d560518e1..ef144c3c76 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -27,7 +27,7 @@ module Mime
class << self
def [](type)
return type if type.is_a?(Type)
- Type.lookup_by_extension(type)
+ Type.lookup_by_extension(type) || NullType.new
end
def fetch(type)
@@ -44,8 +44,8 @@ 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 }
+ # format.ics { render text: post.to_ics, mime_type: Mime::Type["text/calendar"] }
+ # format.xml { render xml: @people }
# end
# end
# end
@@ -53,10 +53,6 @@ module Mime
@@html_types = Set.new [:html, :all]
cattr_reader :html_types
- # These are the content types which browsers can generate without using ajax, flash, etc
- # i.e. following a link, getting an image or posting a form. CSRF protection
- # only needs to protect against these types.
- @@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form, :text]
attr_reader :symbol
@register_callbacks = []
@@ -179,7 +175,7 @@ module Mime
def parse(accept_header)
if accept_header !~ /,/
accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
- parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)]
+ parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
else
list, index = AcceptList.new, 0
accept_header.split(',').each do |header|
@@ -223,8 +219,8 @@ module Mime
Mime.instance_eval { remove_const(symbol) }
SET.delete_if { |v| v.eql?(mime) }
- LOOKUP.delete_if { |k,v| v.eql?(mime) }
- EXTENSION_LOOKUP.delete_if { |k,v| v.eql?(mime) }
+ LOOKUP.delete_if { |_,v| v.eql?(mime) }
+ EXTENSION_LOOKUP.delete_if { |_,v| v.eql?(mime) }
end
end
@@ -272,34 +268,46 @@ module Mime
end
end
- # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
- # ActionController::RequestForgeryProtection.
- def verify_request?
- ActiveSupport::Deprecation.warn "Mime::Type#verify_request? is deprecated and will be removed in Rails 4.1"
- @@browser_generated_types.include?(to_sym)
- end
-
- def self.browser_generated_types
- ActiveSupport::Deprecation.warn "Mime::Type.browser_generated_types is deprecated and will be removed in Rails 4.1"
- @@browser_generated_types
- end
-
def html?
@@html_types.include?(to_sym) || @string =~ /html/
end
+
private
- def method_missing(method, *args)
- if method.to_s.ends_with? '?'
- method[0..-2].downcase.to_sym == to_sym
- else
- super
- end
- end
- def respond_to_missing?(method, include_private = false) #:nodoc:
- method.to_s.ends_with? '?'
+ def to_ary; end
+ def to_a; end
+
+ def method_missing(method, *args)
+ if method.to_s.ends_with? '?'
+ method[0..-2].downcase.to_sym == to_sym
+ else
+ super
end
+ end
+
+ def respond_to_missing?(method, include_private = false) #:nodoc:
+ method.to_s.ends_with? '?'
+ end
+ end
+
+ class NullType
+ def nil?
+ true
+ end
+
+ def ref
+ nil
+ end
+
+ def respond_to_missing?(method, include_private = false)
+ method.to_s.ends_with? '?'
+ end
+
+ private
+ def method_missing(method, *args)
+ false if method.to_s.ends_with? '?'
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
index 490b46c990..b655a54865 100644
--- a/actionpack/lib/action_dispatch/http/parameter_filter.rb
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -1,74 +1,72 @@
module ActionDispatch
module Http
class ParameterFilter
+ FILTERED = '[FILTERED]'.freeze # :nodoc:
- def initialize(filters)
+ def initialize(filters = [])
@filters = filters
end
def filter(params)
- if enabled?
- compiled_filter.call(params)
- else
- params.dup
- end
+ compiled_filter.call(params)
end
private
- def enabled?
- @filters.present?
+ def compiled_filter
+ @compiled_filter ||= CompiledFilter.compile(@filters)
end
- FILTERED = '[FILTERED]'.freeze
+ class CompiledFilter # :nodoc:
+ def self.compile(filters)
+ return lambda { |params| params.dup } if filters.empty?
- def compiled_filter
- @compiled_filter ||= begin
- regexps, blocks = compile_filter
+ strings, regexps, blocks = [], [], []
- lambda do |original_params|
- filtered_params = {}
+ filters.each do |item|
+ case item
+ when Proc
+ blocks << item
+ when Regexp
+ regexps << item
+ else
+ strings << item.to_s
+ end
+ end
- original_params.each do |key, value|
- if regexps.find { |r| key =~ r }
- value = FILTERED
- elsif value.is_a?(Hash)
- value = filter(value)
- elsif value.is_a?(Array)
- value = value.map { |v| v.is_a?(Hash) ? filter(v) : v }
- elsif blocks.present?
- key = key.dup
- value = value.dup if value.duplicable?
- blocks.each { |b| b.call(key, value) }
- end
+ regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
+ new regexps, blocks
+ end
- filtered_params[key] = value
- end
+ attr_reader :regexps, :blocks
- filtered_params
- end
+ def initialize(regexps, blocks)
+ @regexps = regexps
+ @blocks = blocks
end
- end
- def compile_filter
- strings, regexps, blocks = [], [], []
+ def call(original_params)
+ filtered_params = {}
+
+ original_params.each do |key, value|
+ if regexps.any? { |r| key =~ r }
+ value = FILTERED
+ elsif value.is_a?(Hash)
+ value = call(value)
+ elsif value.is_a?(Array)
+ value = value.map { |v| v.is_a?(Hash) ? call(v) : v }
+ elsif blocks.any?
+ key = key.dup
+ value = value.dup if value.duplicable?
+ blocks.each { |b| b.call(key, value) }
+ end
- @filters.each do |item|
- case item
- when NilClass
- when Proc
- blocks << item
- when Regexp
- regexps << item
- else
- strings << item.to_s
+ filtered_params[key] = value
end
- end
- regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
- [regexps, blocks]
+ filtered_params
+ end
end
-
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 9a7b5bc8c7..dcb299ed03 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -12,9 +12,13 @@ module ActionDispatch
# Returns both GET and POST \parameters in a single hash.
def parameters
@env["action_dispatch.request.parameters"] ||= begin
- params = request_parameters.merge(query_parameters)
+ params = begin
+ request_parameters.merge(query_parameters)
+ rescue EOFError
+ query_parameters.dup
+ end
params.merge!(path_parameters)
- encode_params(params).with_indifferent_access
+ params.with_indifferent_access
end
end
alias :params :parameters
@@ -46,39 +50,31 @@ module ActionDispatch
private
+ # Convert nested Hash to HashWithIndifferentAccess
+ # and UTF-8 encode both keys and values in nested Hash.
+ #
# TODO: Validate that the characters are UTF-8. If they aren't,
# you'll get a weird error down the road, but our form handling
# should really prevent that from happening
- def encode_params(params)
- if params.is_a?(String)
- return params.force_encoding("UTF-8").encode!
- elsif !params.is_a?(Hash)
- return params
- end
-
- params.each do |k, v|
- case v
- when Hash
- encode_params(v)
- when Array
- v.map! {|el| encode_params(el) }
+ def normalize_encode_params(params)
+ case params
+ when String
+ params.force_encoding(Encoding::UTF_8).encode!
+ when Hash
+ if params.has_key?(:tempfile)
+ UploadedFile.new(params)
else
- encode_params(v)
+ params.each_with_object({}) do |(key, val), new_hash|
+ new_key = key.is_a?(String) ? key.dup.force_encoding(Encoding::UTF_8).encode! : key
+ new_hash[new_key] = if val.is_a?(Array)
+ val.map! { |el| normalize_encode_params(el) }
+ else
+ normalize_encode_params(val)
+ end
+ end.with_indifferent_access
end
- end
- end
-
- # Convert nested Hash to HashWithIndifferentAccess
- def normalize_parameters(value)
- case value
- when Hash
- h = {}
- value.each { |k, v| h[k] = normalize_parameters(v) }
- h.with_indifferent_access
- when Array
- value.map { |e| normalize_parameters(e) }
else
- value
+ params
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index b8ebeb408f..aba8f66118 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -1,12 +1,16 @@
-require 'tempfile'
require 'stringio'
-require 'strscan'
-require 'active_support/core_ext/hash/indifferent_access'
-require 'active_support/core_ext/string/access'
require 'active_support/inflector'
require 'action_dispatch/http/headers'
require 'action_controller/metal/exceptions'
+require 'rack/request'
+require 'action_dispatch/http/cache'
+require 'action_dispatch/http/mime_negotiation'
+require 'action_dispatch/http/parameters'
+require 'action_dispatch/http/filter_parameters'
+require 'action_dispatch/http/upload'
+require 'action_dispatch/http/url'
+require 'active_support/core_ext/array/conversions'
module ActionDispatch
class Request < Rack::Request
@@ -14,10 +18,10 @@ module ActionDispatch
include ActionDispatch::Http::MimeNegotiation
include ActionDispatch::Http::Parameters
include ActionDispatch::Http::FilterParameters
- include ActionDispatch::Http::Upload
include ActionDispatch::Http::URL
autoload :Session, 'action_dispatch/request/session'
+ autoload :Utils, 'action_dispatch/request/utils'
LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
@@ -70,7 +74,13 @@ module ActionDispatch
RFC5789 = %w(PATCH)
HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789
- HTTP_METHOD_LOOKUP = Hash.new { |h, m| h[m] = m.underscore.to_sym if HTTP_METHODS.include?(m) }
+
+ HTTP_METHOD_LOOKUP = {}
+
+ # Populate the HTTP method lookup cache
+ HTTP_METHODS.each { |method|
+ HTTP_METHOD_LOOKUP[method] = method.underscore.to_sym
+ }
# Returns the HTTP \method that the application should see.
# In the case where the \method was overridden by a middleware
@@ -146,14 +156,29 @@ module ActionDispatch
@original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath)
end
+ # Returns the +String+ full path including params of the last URL requested.
+ #
+ # # get "/articles"
+ # request.fullpath # => "/articles"
+ #
+ # # get "/articles?page=2"
+ # request.fullpath # => "/articles?page=2"
def fullpath
@fullpath ||= super
end
+ # Returns the original request URL as a +String+.
+ #
+ # # get "/articles?page=2"
+ # request.original_url # => "http://www.example.com/articles?page=2"
def original_url
base_url + original_fullpath
end
+ # The +String+ MIME type of the request.
+ #
+ # # get "/articles"
+ # request.media_type # => "application/x-www-form-urlencoded"
def media_type
content_mime_type.to_s
end
@@ -199,8 +224,9 @@ module ActionDispatch
# work with raw requests directly.
def raw_post
unless @env.include? 'RAW_POST_DATA'
- @env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i)
- body.rewind if body.respond_to?(:rewind)
+ raw_post_body = body
+ @env['RAW_POST_DATA'] = raw_post_body.read(content_length)
+ raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
end
@env['RAW_POST_DATA']
end
@@ -245,21 +271,17 @@ module ActionDispatch
# Override Rack's GET method to support indifferent access
def GET
- begin
- @env["action_dispatch.request.query_parameters"] ||= (normalize_parameters(super) || {})
- rescue TypeError => e
- raise ActionController::BadRequest, "Invalid query parameters: #{e.message}"
- end
+ @env["action_dispatch.request.query_parameters"] ||= (normalize_encode_params(super) || {})
+ rescue TypeError => e
+ raise ActionController::BadRequest.new(:query, e)
end
alias :query_parameters :GET
# Override Rack's POST method to support indifferent access
def POST
- begin
- @env["action_dispatch.request.request_parameters"] ||= (normalize_parameters(super) || {})
- rescue TypeError => e
- raise ActionController::BadRequest, "Invalid request parameters: #{e.message}"
- end
+ @env["action_dispatch.request.request_parameters"] ||= (normalize_encode_params(super) || {})
+ rescue TypeError => e
+ raise ActionController::BadRequest.new(:request, e)
end
alias :request_parameters :POST
@@ -279,23 +301,8 @@ module ActionDispatch
protected
- # Remove nils from the params hash
- def deep_munge(hash)
- hash.each_value do |v|
- case v
- when Array
- v.grep(Hash) { |x| deep_munge(x) }
- v.compact!
- when Hash
- deep_munge(v)
- end
- end
-
- hash
- end
-
def parse_query(qs)
- deep_munge(super)
+ Utils.deep_munge(super)
end
private
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 11b7534ea4..5247e61a23 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,4 +1,3 @@
-require 'digest/md5'
require 'active_support/core_ext/class/attribute_accessors'
require 'monitor'
@@ -32,10 +31,17 @@ module ActionDispatch # :nodoc:
# end
# end
class Response
- attr_accessor :request, :header
+ # The request that the response is responding to.
+ attr_accessor :request
+
+ # The HTTP status code.
attr_reader :status
+
attr_writer :sending_file
+ # Get and set headers for this response.
+ attr_accessor :header
+
alias_method :headers=, :header=
alias_method :headers, :header
@@ -50,17 +56,22 @@ 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
attr_reader :content_type
+ # The charset of the response. HTML wants to know the encoding of the
+ # content you're giving them, so we need to send that along.
+ attr_accessor :charset
+
CONTENT_TYPE = "Content-Type".freeze
SET_COOKIE = "Set-Cookie".freeze
LOCATION = "Location".freeze
+ NO_CONTENT_CODES = [204, 304]
cattr_accessor(:default_charset) { "utf-8" }
cattr_accessor(:default_headers)
include Rack::Response::Helpers
+ include ActionDispatch::Http::FilterRedirect
include ActionDispatch::Http::Cache::Response
include MonitorMixin
@@ -92,6 +103,7 @@ module ActionDispatch # :nodoc:
end
end
+ # The underlying body, as a streamable object.
attr_reader :stream
def initialize(status = 200, header = {}, body = [])
@@ -136,32 +148,42 @@ module ActionDispatch # :nodoc:
@committed
end
+ # Sets the HTTP status code.
def status=(status)
@status = Rack::Utils.status_code(status)
end
+ # Sets the HTTP content type.
def content_type=(content_type)
@content_type = content_type.to_s
end
- # The response code of the request
+ # The response code of the request.
def response_code
@status
end
- # Returns a String to ensure compatibility with Net::HTTPResponse
+ # Returns a string to ensure compatibility with <tt>Net::HTTPResponse</tt>.
def code
@status.to_s
end
+ # Returns the corresponding message for the current HTTP status code:
+ #
+ # response.status = 200
+ # response.message # => "OK"
+ #
+ # response.status = 404
+ # response.message # => "Not Found"
+ #
def message
Rack::Utils::HTTP_STATUS_CODES[@status]
end
alias_method :status_message, :message
- def respond_to?(method)
- if method.to_sym == :to_path
- stream.respond_to?(:to_path)
+ def respond_to?(method, include_private = false)
+ if method.to_s == 'to_path'
+ stream.respond_to?(method)
else
super
end
@@ -171,6 +193,8 @@ module ActionDispatch # :nodoc:
stream.to_path
end
+ # Returns the content of the response as a string. This contains the contents
+ # of any calls to <tt>render</tt>.
def body
strings = []
each { |part| strings << part.to_s }
@@ -179,13 +203,16 @@ module ActionDispatch # :nodoc:
EMPTY = " "
+ # Allows you to manually set or override the response body.
def body=(body)
@blank = true if body == EMPTY
if body.respond_to?(:to_path)
@stream = body
else
- @stream = build_buffer self, munge_body_object(body)
+ synchronize do
+ @stream = build_buffer self, munge_body_object(body)
+ end
end
end
@@ -203,11 +230,13 @@ module ActionDispatch # :nodoc:
::Rack::Utils.delete_cookie_header!(header, key, value)
end
+ # The location header we'll be responding with.
def location
headers[LOCATION]
end
alias_method :redirect_url, :location
+ # Sets the location header we'll be responding with.
def location=(url)
headers[LOCATION] = url
end
@@ -216,11 +245,13 @@ module ActionDispatch # :nodoc:
stream.close if stream.respond_to?(:close)
end
+ # Turns the Response into a Rack-compatible array of the status, headers,
+ # and body.
def to_a
rack_response @status, @header.to_hash
end
alias prepare! to_a
- alias to_ary to_a # For implicit splat on 1.9.2
+ alias to_ary to_a
# Returns the response cookies, converted to a Hash of (name => value) pairs
#
@@ -259,21 +290,25 @@ module ActionDispatch # :nodoc:
return if headers[CONTENT_TYPE].present?
@content_type ||= Mime::HTML
- @charset ||= self.class.default_charset
+ @charset ||= self.class.default_charset unless @charset == false
type = @content_type.to_s.dup
- type << "; charset=#{@charset}" unless @sending_file
+ type << "; charset=#{@charset}" if append_charset?
headers[CONTENT_TYPE] = type
end
+ def append_charset?
+ !@sending_file && @charset != false
+ end
+
def rack_response(status, header)
assign_default_content_type_and_charset!(header)
handle_conditional_get!
header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)
- if [204, 304].include?(@status)
+ if NO_CONTENT_CODES.include?(@status)
header.delete CONTENT_TYPE
[status, header, []]
else
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 79437d6e85..a8d2dc3950 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -6,7 +6,7 @@ module ActionDispatch
# of its interface is available directly for convenience.
#
# Uploaded files are temporary files whose lifespan is one request. When
- # the object is finalized Ruby unlinks the file, so there is not need to
+ # the object is finalized Ruby unlinks the file, so there is no need to
# clean them with a separate maintenance task.
class UploadedFile
# The basename of the file in the client.
@@ -19,7 +19,7 @@ module ActionDispatch
# its interface is available directly.
attr_accessor :tempfile
- # TODO.
+ # A string with the headers of the multipart request.
attr_accessor :headers
def initialize(hash) # :nodoc:
@@ -70,21 +70,8 @@ module ActionDispatch
def encode_filename(filename)
# Encode the filename in the utf8 encoding, unless it is nil
- filename.force_encoding("UTF-8").encode! if filename
+ filename.force_encoding(Encoding::UTF_8).encode! if filename
end
end
-
- module Upload # :nodoc:
- # Convert nested Hash to HashWithIndifferentAccess and replace
- # file upload hash with UploadedFile objects
- def normalize_parameters(value)
- if Hash === value && value.has_key?(:tempfile)
- UploadedFile.new(value)
- else
- super
- end
- end
- private :normalize_parameters
- end
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 8aa02ec482..6f5a52c568 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -1,21 +1,28 @@
+require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/core_ext/hash/slice'
+
module ActionDispatch
module Http
module URL
- IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
+ IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
+ HOST_REGEXP = /(^.*:\/\/)?([^:]+)(?::(\d+$))?/
+ PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/
mattr_accessor :tld_length
self.tld_length = 1
class << self
def extract_domain(host, tld_length = @@tld_length)
- return nil unless named_host?(host)
- host.split('.').last(1 + tld_length).join('.')
+ host.split('.').last(1 + tld_length).join('.') if named_host?(host)
end
def extract_subdomains(host, tld_length = @@tld_length)
- return [] unless named_host?(host)
- parts = host.split('.')
- parts[0..-(tld_length+2)]
+ if named_host?(host)
+ parts = host.split('.')
+ parts[0..-(tld_length + 2)]
+ else
+ []
+ end
end
def extract_subdomain(host, tld_length = @@tld_length)
@@ -23,16 +30,23 @@ module ActionDispatch
end
def url_for(options = {})
- path = ""
- path << options.delete(:script_name).to_s.chomp("/")
+ options = options.dup
+ 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? }
+ params = options[:params].is_a?(Hash) ? options[:params] : options.slice(:params)
+ params.reject! { |_,v| v.to_param.nil? }
result = build_host_url(options)
-
- result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
+ if options[:trailing_slash]
+ if path.include?('?')
+ result << path.sub(/\?/, '/\&')
+ else
+ result << path.sub(/[^\/]\z|\A\z/, '\&/')
+ end
+ else
+ result << path
+ end
result << "?#{params.to_query}" unless params.empty?
result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
result
@@ -48,14 +62,20 @@ module ActionDispatch
result = ""
unless options[:only_path]
- unless options[:protocol] == false
- result << (options[:protocol] || "http")
- result << ":" unless result.match(%r{:|//})
+ if match = options[:host].match(HOST_REGEXP)
+ options[:protocol] ||= match[1] unless options[:protocol] == false
+ options[:host] = match[2]
+ options[:port] = match[3] unless options.key?(:port)
end
- result << "//" unless result.match("//")
+
+ options[:protocol] = normalize_protocol(options)
+ options[:host] = normalize_host(options)
+ options[:port] = normalize_port(options)
+
+ result << options[:protocol]
result << rewrite_authentication(options)
- result << host_or_subdomain_and_domain(options)
- result << ":#{options.delete(:port)}" if options[:port]
+ result << options[:host]
+ result << ":#{options[:port]}" if options[:port]
end
result
end
@@ -64,6 +84,10 @@ module ActionDispatch
host && IP_HOST_REGEXP !~ host
end
+ def same_host?(options)
+ (options[:subdomain] == true || !options.key?(:subdomain)) && options[:domain].nil?
+ end
+
def rewrite_authentication(options)
if options[:user] && options[:password]
"#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
@@ -72,19 +96,47 @@ module ActionDispatch
end
end
- def host_or_subdomain_and_domain(options)
- return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?)
+ def normalize_protocol(options)
+ case options[:protocol]
+ when nil
+ "http://"
+ when false, "//"
+ "//"
+ when PROTOCOL_REGEXP
+ "#{$1}://"
+ else
+ raise ArgumentError, "Invalid :protocol option: #{options[:protocol].inspect}"
+ end
+ end
+
+ def normalize_host(options)
+ return options[:host] if !named_host?(options[:host]) || same_host?(options)
tld_length = options[:tld_length] || @@tld_length
host = ""
- unless options[:subdomain] == false
- host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)).to_param
- host << "."
+ if options[:subdomain] == true || !options.key?(:subdomain)
+ host << extract_subdomain(options[:host], tld_length).to_param
+ elsif options[:subdomain].present?
+ host << options[:subdomain].to_param
end
+ host << "." unless host.empty?
host << (options[:domain] || extract_domain(options[:host], tld_length))
host
end
+
+ def normalize_port(options)
+ return nil if options[:port].nil? || options[:port] == false
+
+ case options[:protocol]
+ when "//"
+ nil
+ when "https://"
+ options[:port].to_i == 443 ? nil : options[:port]
+ else
+ options[:port].to_i == 80 ? nil : options[:port]
+ end
+ end
end
def initialize(env)
diff --git a/actionpack/lib/action_dispatch/journey.rb b/actionpack/lib/action_dispatch/journey.rb
new file mode 100644
index 0000000000..ad42713482
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey.rb
@@ -0,0 +1,5 @@
+require 'action_dispatch/journey/router'
+require 'action_dispatch/journey/gtg/builder'
+require 'action_dispatch/journey/gtg/simulator'
+require 'action_dispatch/journey/nfa/builder'
+require 'action_dispatch/journey/nfa/simulator'
diff --git a/actionpack/lib/action_dispatch/journey/backwards.rb b/actionpack/lib/action_dispatch/journey/backwards.rb
new file mode 100644
index 0000000000..3bd20fdf81
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/backwards.rb
@@ -0,0 +1,5 @@
+module Rack # :nodoc:
+ Mount = ActionDispatch::Journey::Router
+ Mount::RouteSet = ActionDispatch::Journey::Router
+ Mount::RegexpWithNamedGroups = ActionDispatch::Journey::Path::Pattern
+end
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
new file mode 100644
index 0000000000..7764763791
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -0,0 +1,150 @@
+require 'action_controller/metal/exceptions'
+
+module ActionDispatch
+ module Journey
+ # The Formatter class is used for formatting URLs. For example, parameters
+ # passed to +url_for+ in Rails will eventually call Formatter#generate.
+ class Formatter # :nodoc:
+ attr_reader :routes
+
+ def initialize(routes)
+ @routes = routes
+ @cache = nil
+ end
+
+ def generate(type, name, options, recall = {}, parameterize = nil)
+ constraints = recall.merge(options)
+ missing_keys = []
+
+ match_route(name, constraints) do |route|
+ parameterized_parts = extract_parameterized_parts(route, options, recall, parameterize)
+
+ # Skip this route unless a name has been provided or it is a
+ # standard Rails route since we can't determine whether an options
+ # hash passed to url_for matches a Rack application or a redirect.
+ next unless name || route.dispatcher?
+
+ missing_keys = missing_keys(route, parameterized_parts)
+ next unless missing_keys.empty?
+ params = options.dup.delete_if do |key, _|
+ parameterized_parts.key?(key) || route.defaults.key?(key)
+ end
+
+ return [route.format(parameterized_parts), params]
+ end
+
+ message = "No route matches #{constraints.inspect}"
+ message << " missing required keys: #{missing_keys.inspect}" if name
+
+ raise ActionController::UrlGenerationError, message
+ end
+
+ def clear
+ @cache = nil
+ end
+
+ private
+
+ def extract_parameterized_parts(route, options, recall, parameterize = nil)
+ parameterized_parts = recall.merge(options)
+
+ keys_to_keep = route.parts.reverse.drop_while { |part|
+ !options.key?(part) || (options[part] || recall[part]).nil?
+ } | route.required_parts
+
+ (parameterized_parts.keys - keys_to_keep).each do |bad_key|
+ parameterized_parts.delete(bad_key)
+ end
+
+ if parameterize
+ parameterized_parts.each do |k, v|
+ parameterized_parts[k] = parameterize.call(k, v)
+ end
+ end
+
+ parameterized_parts.keep_if { |_, v| v }
+ parameterized_parts
+ end
+
+ def named_routes
+ routes.named_routes
+ end
+
+ def match_route(name, options)
+ if named_routes.key?(name)
+ yield named_routes[name]
+ else
+ routes = non_recursive(cache, options.to_a)
+
+ hash = routes.group_by { |_, r| r.score(options) }
+
+ hash.keys.sort.reverse_each do |score|
+ next if score < 0
+
+ hash[score].sort_by { |i, _| i }.each do |_, route|
+ yield route
+ end
+ end
+ end
+ end
+
+ def non_recursive(cache, options)
+ routes = []
+ stack = [cache]
+
+ while stack.any?
+ c = stack.shift
+ routes.concat(c[:___routes]) if c.key?(:___routes)
+
+ options.each do |pair|
+ stack << c[pair] if c.key?(pair)
+ end
+ end
+
+ routes
+ end
+
+ # Returns an array populated with missing keys if any are present.
+ def missing_keys(route, parts)
+ missing_keys = []
+ tests = route.path.requirements
+ route.required_parts.each { |key|
+ if tests.key?(key)
+ missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key]
+ else
+ missing_keys << key unless parts[key]
+ end
+ }
+ missing_keys
+ end
+
+ def possibles(cache, options, depth = 0)
+ cache.fetch(:___routes) { [] } + options.find_all { |pair|
+ cache.key?(pair)
+ }.map { |pair|
+ possibles(cache[pair], options, depth + 1)
+ }.flatten(1)
+ end
+
+ # Returns +true+ if no missing keys are present, otherwise +false+.
+ def verify_required_parts!(route, parts)
+ missing_keys(route, parts).empty?
+ end
+
+ def build_cache
+ root = { ___routes: [] }
+ routes.each_with_index do |route, i|
+ leaf = route.required_defaults.inject(root) do |h, tuple|
+ h[tuple] ||= {}
+ end
+ (leaf[:___routes] ||= []) << [i, route]
+ end
+ root
+ end
+
+ def cache
+ @cache ||= build_cache
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/gtg/builder.rb b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
new file mode 100644
index 0000000000..7d2791714b
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
@@ -0,0 +1,162 @@
+require 'action_dispatch/journey/gtg/transition_table'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module GTG # :nodoc:
+ class Builder # :nodoc:
+ DUMMY = Nodes::Dummy.new
+
+ attr_reader :root, :ast, :endpoints
+
+ def initialize(root)
+ @root = root
+ @ast = Nodes::Cat.new root, DUMMY
+ @followpos = nil
+ end
+
+ def transition_table
+ dtrans = TransitionTable.new
+ marked = {}
+ state_id = Hash.new { |h,k| h[k] = h.length }
+
+ start = firstpos(root)
+ dstates = [start]
+ until dstates.empty?
+ s = dstates.shift
+ next if marked[s]
+ marked[s] = true # mark s
+
+ s.group_by { |state| symbol(state) }.each do |sym, ps|
+ u = ps.map { |l| followpos(l) }.flatten
+ next if u.empty?
+
+ if u.uniq == [DUMMY]
+ from = state_id[s]
+ to = state_id[Object.new]
+ dtrans[from, to] = sym
+
+ dtrans.add_accepting(to)
+ ps.each { |state| dtrans.add_memo(to, state.memo) }
+ else
+ dtrans[state_id[s], state_id[u]] = sym
+
+ if u.include?(DUMMY)
+ to = state_id[u]
+
+ accepting = ps.find_all { |l| followpos(l).include?(DUMMY) }
+
+ accepting.each { |accepting_state|
+ dtrans.add_memo(to, accepting_state.memo)
+ }
+
+ dtrans.add_accepting(state_id[u])
+ end
+ end
+
+ dstates << u
+ end
+ end
+
+ dtrans
+ end
+
+ def nullable?(node)
+ case node
+ when Nodes::Group
+ true
+ when Nodes::Star
+ true
+ when Nodes::Or
+ node.children.any? { |c| nullable?(c) }
+ when Nodes::Cat
+ nullable?(node.left) && nullable?(node.right)
+ when Nodes::Terminal
+ !node.left
+ when Nodes::Unary
+ nullable?(node.left)
+ else
+ raise ArgumentError, 'unknown nullable: %s' % node.class.name
+ end
+ end
+
+ def firstpos(node)
+ case node
+ when Nodes::Star
+ firstpos(node.left)
+ when Nodes::Cat
+ if nullable?(node.left)
+ firstpos(node.left) | firstpos(node.right)
+ else
+ firstpos(node.left)
+ end
+ when Nodes::Or
+ node.children.map { |c| firstpos(c) }.flatten.uniq
+ when Nodes::Unary
+ firstpos(node.left)
+ when Nodes::Terminal
+ nullable?(node) ? [] : [node]
+ else
+ raise ArgumentError, 'unknown firstpos: %s' % node.class.name
+ end
+ end
+
+ def lastpos(node)
+ case node
+ when Nodes::Star
+ firstpos(node.left)
+ when Nodes::Or
+ node.children.map { |c| lastpos(c) }.flatten.uniq
+ when Nodes::Cat
+ if nullable?(node.right)
+ lastpos(node.left) | lastpos(node.right)
+ else
+ lastpos(node.right)
+ end
+ when Nodes::Terminal
+ nullable?(node) ? [] : [node]
+ when Nodes::Unary
+ lastpos(node.left)
+ else
+ raise ArgumentError, 'unknown lastpos: %s' % node.class.name
+ end
+ end
+
+ def followpos(node)
+ followpos_table[node]
+ end
+
+ private
+
+ def followpos_table
+ @followpos ||= build_followpos
+ end
+
+ def build_followpos
+ table = Hash.new { |h, k| h[k] = [] }
+ @ast.each do |n|
+ case n
+ when Nodes::Cat
+ lastpos(n.left).each do |i|
+ table[i] += firstpos(n.right)
+ end
+ when Nodes::Star
+ lastpos(n).each do |i|
+ table[i] += firstpos(n)
+ end
+ end
+ end
+ table
+ end
+
+ def symbol(edge)
+ case edge
+ when Journey::Nodes::Symbol
+ edge.regexp
+ else
+ edge.left
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
new file mode 100644
index 0000000000..58ad803841
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
@@ -0,0 +1,44 @@
+require 'strscan'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module GTG # :nodoc:
+ class MatchData # :nodoc:
+ attr_reader :memos
+
+ def initialize(memos)
+ @memos = memos
+ end
+ end
+
+ class Simulator # :nodoc:
+ attr_reader :tt
+
+ def initialize(transition_table)
+ @tt = transition_table
+ end
+
+ def simulate(string)
+ input = StringScanner.new(string)
+ state = [0]
+ while sym = input.scan(%r([/.?]|[^/.?]+))
+ state = tt.move(state, sym)
+ end
+
+ acceptance_states = state.find_all { |s|
+ tt.accepting? s
+ }
+
+ return if acceptance_states.empty?
+
+ memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact
+
+ MatchData.new(memos)
+ end
+
+ alias :=~ :simulate
+ alias :match :simulate
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
new file mode 100644
index 0000000000..971cb3447f
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -0,0 +1,167 @@
+require 'action_dispatch/journey/nfa/dot'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module GTG # :nodoc:
+ class TransitionTable # :nodoc:
+ include Journey::NFA::Dot
+
+ attr_reader :memos
+
+ def initialize
+ @regexp_states = {}
+ @string_states = {}
+ @accepting = {}
+ @memos = Hash.new { |h,k| h[k] = [] }
+ end
+
+ def add_accepting(state)
+ @accepting[state] = true
+ end
+
+ def accepting_states
+ @accepting.keys
+ end
+
+ def accepting?(state)
+ @accepting[state]
+ end
+
+ def add_memo(idx, memo)
+ @memos[idx] << memo
+ end
+
+ def memo(idx)
+ @memos[idx]
+ end
+
+ def eclosure(t)
+ Array(t)
+ end
+
+ def move(t, a)
+ move_string(t, a).concat(move_regexp(t, a))
+ end
+
+ def to_json
+ require 'json'
+
+ simple_regexp = Hash.new { |h,k| h[k] = {} }
+
+ @regexp_states.each do |from, hash|
+ hash.each do |re, to|
+ simple_regexp[from][re.source] = to
+ end
+ end
+
+ JSON.dump({
+ regexp_states: simple_regexp,
+ string_states: @string_states,
+ accepting: @accepting
+ })
+ end
+
+ def to_svg
+ svg = IO.popen('dot -Tsvg', 'w+') { |f|
+ f.write(to_dot)
+ f.close_write
+ f.readlines
+ }
+ 3.times { svg.shift }
+ svg.join.sub(/width="[^"]*"/, '').sub(/height="[^"]*"/, '')
+ end
+
+ def visualizer(paths, title = 'FSM')
+ viz_dir = File.join File.dirname(__FILE__), '..', 'visualizer'
+ fsm_js = File.read File.join(viz_dir, 'fsm.js')
+ fsm_css = File.read File.join(viz_dir, 'fsm.css')
+ erb = File.read File.join(viz_dir, 'index.html.erb')
+ states = "function tt() { return #{to_json}; }"
+
+ fun_routes = paths.shuffle.first(3).map do |ast|
+ ast.map { |n|
+ case n
+ when Nodes::Symbol
+ case n.left
+ when ':id' then rand(100).to_s
+ when ':format' then %w{ xml json }.shuffle.first
+ else
+ 'omg'
+ end
+ when Nodes::Terminal then n.symbol
+ else
+ nil
+ end
+ }.compact.join
+ end
+
+ stylesheets = [fsm_css]
+ svg = to_svg
+ javascripts = [states, fsm_js]
+
+ # Annoying hack for 1.9 warnings
+ fun_routes = fun_routes
+ stylesheets = stylesheets
+ svg = svg
+ javascripts = javascripts
+
+ require 'erb'
+ template = ERB.new erb
+ template.result(binding)
+ end
+
+ def []=(from, to, sym)
+ to_mappings = states_hash_for(sym)[from] ||= {}
+ to_mappings[sym] = to
+ end
+
+ def states
+ ss = @string_states.keys + @string_states.values.map(&:values).flatten
+ rs = @regexp_states.keys + @regexp_states.values.map(&:values).flatten
+ (ss + rs).uniq
+ end
+
+ def transitions
+ @string_states.map { |from, hash|
+ hash.map { |s, to| [from, s, to] }
+ }.flatten(1) + @regexp_states.map { |from, hash|
+ hash.map { |s, to| [from, s, to] }
+ }.flatten(1)
+ end
+
+ private
+
+ def states_hash_for(sym)
+ case sym
+ when String
+ @string_states
+ when Regexp
+ @regexp_states
+ else
+ raise ArgumentError, 'unknown symbol: %s' % sym.class
+ end
+ end
+
+ def move_regexp(t, a)
+ return [] if t.empty?
+
+ t.map { |s|
+ if states = @regexp_states[s]
+ states.map { |re, v| re === a ? v : nil }
+ end
+ }.flatten.compact.uniq
+ end
+
+ def move_string(t, a)
+ return [] if t.empty?
+
+ t.map do |s|
+ if states = @string_states[s]
+ states[a]
+ end
+ end.compact
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/nfa/builder.rb b/actionpack/lib/action_dispatch/journey/nfa/builder.rb
new file mode 100644
index 0000000000..ee6494c3e4
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/nfa/builder.rb
@@ -0,0 +1,76 @@
+require 'action_dispatch/journey/nfa/transition_table'
+require 'action_dispatch/journey/gtg/transition_table'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module NFA # :nodoc:
+ class Visitor < Visitors::Visitor # :nodoc:
+ def initialize(tt)
+ @tt = tt
+ @i = -1
+ end
+
+ def visit_CAT(node)
+ left = visit(node.left)
+ right = visit(node.right)
+
+ @tt.merge(left.last, right.first)
+
+ [left.first, right.last]
+ end
+
+ def visit_GROUP(node)
+ from = @i += 1
+ left = visit(node.left)
+ to = @i += 1
+
+ @tt.accepting = to
+
+ @tt[from, left.first] = nil
+ @tt[left.last, to] = nil
+ @tt[from, to] = nil
+
+ [from, to]
+ end
+
+ def visit_OR(node)
+ from = @i += 1
+ children = node.children.map { |c| visit(c) }
+ to = @i += 1
+
+ children.each do |child|
+ @tt[from, child.first] = nil
+ @tt[child.last, to] = nil
+ end
+
+ @tt.accepting = to
+
+ [from, to]
+ end
+
+ def terminal(node)
+ from_i = @i += 1 # new state
+ to_i = @i += 1 # new state
+
+ @tt[from_i, to_i] = node
+ @tt.accepting = to_i
+ @tt.add_memo(to_i, node.memo)
+
+ [from_i, to_i]
+ end
+ end
+
+ class Builder # :nodoc:
+ def initialize(ast)
+ @ast = ast
+ end
+
+ def transition_table
+ tt = TransitionTable.new
+ Visitor.new(tt).accept(@ast)
+ tt
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
new file mode 100644
index 0000000000..5c33a872e5
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
@@ -0,0 +1,36 @@
+# encoding: utf-8
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module NFA # :nodoc:
+ module Dot # :nodoc:
+ def to_dot
+ edges = transitions.map { |from, sym, to|
+ " #{from} -> #{to} [label=\"#{sym || 'ε'}\"];"
+ }
+
+ #memo_nodes = memos.values.flatten.map { |n|
+ # label = n
+ # if Journey::Route === n
+ # label = "#{n.verb.source} #{n.path.spec}"
+ # end
+ # " #{n.object_id} [label=\"#{label}\", shape=box];"
+ #}
+ #memo_edges = memos.map { |k, memos|
+ # (memos || []).map { |v| " #{k} -> #{v.object_id};" }
+ #}.flatten.uniq
+
+ <<-eodot
+digraph nfa {
+ rankdir=LR;
+ node [shape = doublecircle];
+ #{accepting_states.join ' '};
+ node [shape = circle];
+#{edges.join "\n"}
+}
+ eodot
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb
new file mode 100644
index 0000000000..5b40da6569
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb
@@ -0,0 +1,47 @@
+require 'strscan'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module NFA # :nodoc:
+ class MatchData # :nodoc:
+ attr_reader :memos
+
+ def initialize(memos)
+ @memos = memos
+ end
+ end
+
+ class Simulator # :nodoc:
+ attr_reader :tt
+
+ def initialize(transition_table)
+ @tt = transition_table
+ end
+
+ def simulate(string)
+ input = StringScanner.new(string)
+ state = tt.eclosure(0)
+ until input.eos?
+ sym = input.scan(%r([/.?]|[^/.?]+))
+
+ # FIXME: tt.eclosure is not needed for the GTG
+ state = tt.eclosure(tt.move(state, sym))
+ end
+
+ acceptance_states = state.find_all { |s|
+ tt.accepting?(tt.eclosure(s).sort.last)
+ }
+
+ return if acceptance_states.empty?
+
+ memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact
+
+ MatchData.new(memos)
+ end
+
+ alias :=~ :simulate
+ alias :match :simulate
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
new file mode 100644
index 0000000000..a3017aeea1
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
@@ -0,0 +1,163 @@
+require 'action_dispatch/journey/nfa/dot'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module NFA # :nodoc:
+ class TransitionTable # :nodoc:
+ include Journey::NFA::Dot
+
+ attr_accessor :accepting
+ attr_reader :memos
+
+ def initialize
+ @table = Hash.new { |h,f| h[f] = {} }
+ @memos = {}
+ @accepting = nil
+ @inverted = nil
+ end
+
+ def accepting?(state)
+ accepting == state
+ end
+
+ def accepting_states
+ [accepting]
+ end
+
+ def add_memo(idx, memo)
+ @memos[idx] = memo
+ end
+
+ def memo(idx)
+ @memos[idx]
+ end
+
+ def []=(i, f, s)
+ @table[f][i] = s
+ end
+
+ def merge(left, right)
+ @memos[right] = @memos.delete(left)
+ @table[right] = @table.delete(left)
+ end
+
+ def states
+ (@table.keys + @table.values.map(&:keys).flatten).uniq
+ end
+
+ # Returns a generalized transition graph with reduced states. The states
+ # are reduced like a DFA, but the table must be simulated like an NFA.
+ #
+ # Edges of the GTG are regular expressions.
+ def generalized_table
+ gt = GTG::TransitionTable.new
+ marked = {}
+ state_id = Hash.new { |h,k| h[k] = h.length }
+ alphabet = self.alphabet
+
+ stack = [eclosure(0)]
+
+ until stack.empty?
+ state = stack.pop
+ next if marked[state] || state.empty?
+
+ marked[state] = true
+
+ alphabet.each do |alpha|
+ next_state = eclosure(following_states(state, alpha))
+ next if next_state.empty?
+
+ gt[state_id[state], state_id[next_state]] = alpha
+ stack << next_state
+ end
+ end
+
+ final_groups = state_id.keys.find_all { |s|
+ s.sort.last == accepting
+ }
+
+ final_groups.each do |states|
+ id = state_id[states]
+
+ gt.add_accepting(id)
+ save = states.find { |s|
+ @memos.key?(s) && eclosure(s).sort.last == accepting
+ }
+
+ gt.add_memo(id, memo(save))
+ end
+
+ gt
+ end
+
+ # Returns set of NFA states to which there is a transition on ast symbol
+ # +a+ from some state +s+ in +t+.
+ def following_states(t, a)
+ Array(t).map { |s| inverted[s][a] }.flatten.uniq
+ end
+
+ # Returns set of NFA states to which there is a transition on ast symbol
+ # +a+ from some state +s+ in +t+.
+ def move(t, a)
+ Array(t).map { |s|
+ inverted[s].keys.compact.find_all { |sym|
+ sym === a
+ }.map { |sym| inverted[s][sym] }
+ }.flatten.uniq
+ end
+
+ def alphabet
+ inverted.values.map(&:keys).flatten.compact.uniq.sort_by { |x| x.to_s }
+ end
+
+ # Returns a set of NFA states reachable from some NFA state +s+ in set
+ # +t+ on nil-transitions alone.
+ def eclosure(t)
+ stack = Array(t)
+ seen = {}
+ children = []
+
+ until stack.empty?
+ s = stack.pop
+ next if seen[s]
+
+ seen[s] = true
+ children << s
+
+ stack.concat(inverted[s][nil])
+ end
+
+ children.uniq
+ end
+
+ def transitions
+ @table.map { |to, hash|
+ hash.map { |from, sym| [from, sym, to] }
+ }.flatten(1)
+ end
+
+ private
+
+ def inverted
+ return @inverted if @inverted
+
+ @inverted = Hash.new { |h, from|
+ h[from] = Hash.new { |j, s| j[s] = [] }
+ }
+
+ @table.each { |to, hash|
+ hash.each { |from, sym|
+ if sym
+ sym = Nodes::Symbol === sym ? sym.regexp : sym.left
+ end
+
+ @inverted[from][sym] << to
+ }
+ }
+
+ @inverted
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
new file mode 100644
index 0000000000..935442ef66
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -0,0 +1,124 @@
+require 'action_dispatch/journey/visitors'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module Nodes # :nodoc:
+ class Node # :nodoc:
+ include Enumerable
+
+ attr_accessor :left, :memo
+
+ def initialize(left)
+ @left = left
+ @memo = nil
+ end
+
+ def each(&block)
+ Visitors::Each.new(block).accept(self)
+ end
+
+ def to_s
+ Visitors::String.new.accept(self)
+ end
+
+ def to_dot
+ Visitors::Dot.new.accept(self)
+ end
+
+ def to_sym
+ name.to_sym
+ end
+
+ def name
+ left.tr '*:', ''
+ end
+
+ def type
+ raise NotImplementedError
+ end
+
+ def symbol?; false; end
+ def literal?; false; end
+ end
+
+ class Terminal < Node # :nodoc:
+ alias :symbol :left
+ end
+
+ class Literal < Terminal # :nodoc:
+ def literal?; true; end
+ def type; :LITERAL; end
+ end
+
+ class Dummy < Literal # :nodoc:
+ def initialize(x = Object.new)
+ super
+ end
+
+ def literal?; false; end
+ end
+
+ %w{ Symbol Slash Dot }.each do |t|
+ class_eval <<-eoruby, __FILE__, __LINE__ + 1
+ class #{t} < Terminal;
+ def type; :#{t.upcase}; end
+ end
+ eoruby
+ end
+
+ class Symbol < Terminal # :nodoc:
+ attr_accessor :regexp
+ alias :symbol :regexp
+
+ DEFAULT_EXP = /[^\.\/\?]+/
+ def initialize(left)
+ super
+ @regexp = DEFAULT_EXP
+ end
+
+ def default_regexp?
+ regexp == DEFAULT_EXP
+ end
+
+ def symbol?; true; end
+ end
+
+ class Unary < Node # :nodoc:
+ def children; [left] end
+ end
+
+ class Group < Unary # :nodoc:
+ def type; :GROUP; end
+ end
+
+ class Star < Unary # :nodoc:
+ def type; :STAR; end
+ end
+
+ class Binary < Node # :nodoc:
+ attr_accessor :right
+
+ def initialize(left, right)
+ super(left)
+ @right = right
+ end
+
+ def children; [left, right] end
+ end
+
+ class Cat < Binary # :nodoc:
+ def type; :CAT; end
+ end
+
+ class Or < Node # :nodoc:
+ attr_reader :children
+
+ def initialize(children)
+ @children = children
+ end
+
+ def type; :OR; end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb
new file mode 100644
index 0000000000..bb4cbb00e2
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/parser.rb
@@ -0,0 +1,206 @@
+#
+# DO NOT MODIFY!!!!
+# This file is automatically generated by Racc 1.4.9
+# from Racc grammer file "".
+#
+
+require 'racc/parser.rb'
+
+
+require 'action_dispatch/journey/parser_extras'
+module ActionDispatch
+ module Journey # :nodoc:
+ class Parser < Racc::Parser # :nodoc:
+##### State transition tables begin ###
+
+racc_action_table = [
+ 17, 21, 13, 15, 14, 7, nil, 16, 8, 19,
+ 13, 15, 14, 7, 23, 16, 8, 19, 13, 15,
+ 14, 7, nil, 16, 8, 13, 15, 14, 7, nil,
+ 16, 8, 13, 15, 14, 7, nil, 16, 8 ]
+
+racc_action_check = [
+ 1, 17, 1, 1, 1, 1, nil, 1, 1, 1,
+ 20, 20, 20, 20, 20, 20, 20, 20, 7, 7,
+ 7, 7, nil, 7, 7, 19, 19, 19, 19, nil,
+ 19, 19, 0, 0, 0, 0, nil, 0, 0 ]
+
+racc_action_pointer = [
+ 30, 0, nil, nil, nil, nil, nil, 16, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, 1, nil, 23,
+ 8, nil, nil, nil ]
+
+racc_action_default = [
+ -18, -18, -2, -3, -4, -5, -6, -18, -9, -10,
+ -11, -12, -13, -14, -15, -16, -17, -18, -1, -18,
+ -18, 24, -8, -7 ]
+
+racc_goto_table = [
+ 18, 1, nil, nil, nil, nil, nil, nil, 20, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, 22, 18 ]
+
+racc_goto_check = [
+ 2, 1, nil, nil, nil, nil, nil, nil, 1, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, 2, 2 ]
+
+racc_goto_pointer = [
+ nil, 1, -1, nil, nil, nil, nil, nil, nil, nil,
+ nil ]
+
+racc_goto_default = [
+ nil, nil, 2, 3, 4, 5, 6, 9, 10, 11,
+ 12 ]
+
+racc_reduce_table = [
+ 0, 0, :racc_error,
+ 2, 11, :_reduce_1,
+ 1, 11, :_reduce_2,
+ 1, 11, :_reduce_none,
+ 1, 12, :_reduce_none,
+ 1, 12, :_reduce_none,
+ 1, 12, :_reduce_none,
+ 3, 15, :_reduce_7,
+ 3, 13, :_reduce_8,
+ 1, 16, :_reduce_9,
+ 1, 14, :_reduce_none,
+ 1, 14, :_reduce_none,
+ 1, 14, :_reduce_none,
+ 1, 14, :_reduce_none,
+ 1, 19, :_reduce_14,
+ 1, 17, :_reduce_15,
+ 1, 18, :_reduce_16,
+ 1, 20, :_reduce_17 ]
+
+racc_reduce_n = 18
+
+racc_shift_n = 24
+
+racc_token_table = {
+ false => 0,
+ :error => 1,
+ :SLASH => 2,
+ :LITERAL => 3,
+ :SYMBOL => 4,
+ :LPAREN => 5,
+ :RPAREN => 6,
+ :DOT => 7,
+ :STAR => 8,
+ :OR => 9 }
+
+racc_nt_base = 10
+
+racc_use_result_var = true
+
+Racc_arg = [
+ racc_action_table,
+ racc_action_check,
+ racc_action_default,
+ racc_action_pointer,
+ racc_goto_table,
+ racc_goto_check,
+ racc_goto_default,
+ racc_goto_pointer,
+ racc_nt_base,
+ racc_reduce_table,
+ racc_token_table,
+ racc_shift_n,
+ racc_reduce_n,
+ racc_use_result_var ]
+
+Racc_token_to_s_table = [
+ "$end",
+ "error",
+ "SLASH",
+ "LITERAL",
+ "SYMBOL",
+ "LPAREN",
+ "RPAREN",
+ "DOT",
+ "STAR",
+ "OR",
+ "$start",
+ "expressions",
+ "expression",
+ "or",
+ "terminal",
+ "group",
+ "star",
+ "symbol",
+ "literal",
+ "slash",
+ "dot" ]
+
+Racc_debug_parser = false
+
+##### State transition tables end #####
+
+# reduce 0 omitted
+
+def _reduce_1(val, _values, result)
+ result = Cat.new(val.first, val.last)
+ result
+end
+
+def _reduce_2(val, _values, result)
+ result = val.first
+ result
+end
+
+# reduce 3 omitted
+
+# reduce 4 omitted
+
+# reduce 5 omitted
+
+# reduce 6 omitted
+
+def _reduce_7(val, _values, result)
+ result = Group.new(val[1])
+ result
+end
+
+def _reduce_8(val, _values, result)
+ result = Or.new([val.first, val.last])
+ result
+end
+
+def _reduce_9(val, _values, result)
+ result = Star.new(Symbol.new(val.last))
+ result
+end
+
+# reduce 10 omitted
+
+# reduce 11 omitted
+
+# reduce 12 omitted
+
+# reduce 13 omitted
+
+def _reduce_14(val, _values, result)
+ result = Slash.new('/')
+ result
+end
+
+def _reduce_15(val, _values, result)
+ result = Symbol.new(val.first)
+ result
+end
+
+def _reduce_16(val, _values, result)
+ result = Literal.new(val.first)
+ result
+end
+
+def _reduce_17(val, _values, result)
+ result = Dot.new(val.first)
+ result
+end
+
+def _reduce_none(val, _values, result)
+ val[0]
+end
+
+ end # class Parser
+ end # module Journey
+ end # module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/journey/parser.y b/actionpack/lib/action_dispatch/journey/parser.y
new file mode 100644
index 0000000000..040f8d5922
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/parser.y
@@ -0,0 +1,48 @@
+class ActionDispatch::Journey::Parser
+
+token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR
+
+rule
+ expressions
+ : expressions expression { result = Cat.new(val.first, val.last) }
+ | expression { result = val.first }
+ | or
+ ;
+ expression
+ : terminal
+ | group
+ | star
+ ;
+ group
+ : LPAREN expressions RPAREN { result = Group.new(val[1]) }
+ ;
+ or
+ : expressions OR expression { result = Or.new([val.first, val.last]) }
+ ;
+ star
+ : STAR { result = Star.new(Symbol.new(val.last)) }
+ ;
+ terminal
+ : symbol
+ | literal
+ | slash
+ | dot
+ ;
+ slash
+ : SLASH { result = Slash.new('/') }
+ ;
+ symbol
+ : SYMBOL { result = Symbol.new(val.first) }
+ ;
+ literal
+ : LITERAL { result = Literal.new(val.first) }
+ ;
+ dot
+ : DOT { result = Dot.new(val.first) }
+ ;
+
+end
+
+---- header
+
+require 'action_dispatch/journey/parser_extras'
diff --git a/actionpack/lib/action_dispatch/journey/parser_extras.rb b/actionpack/lib/action_dispatch/journey/parser_extras.rb
new file mode 100644
index 0000000000..14892f4321
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/parser_extras.rb
@@ -0,0 +1,23 @@
+require 'action_dispatch/journey/scanner'
+require 'action_dispatch/journey/nodes/node'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ class Parser < Racc::Parser # :nodoc:
+ include Journey::Nodes
+
+ def initialize
+ @scanner = Scanner.new
+ end
+
+ def parse(string)
+ @scanner.scan_setup(string)
+ do_parse
+ end
+
+ def next_token
+ @scanner.next_token
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
new file mode 100644
index 0000000000..d37aa1fbe5
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -0,0 +1,196 @@
+module ActionDispatch
+ module Journey # :nodoc:
+ module Path # :nodoc:
+ class Pattern # :nodoc:
+ attr_reader :spec, :requirements, :anchored
+
+ def initialize(strexp)
+ parser = Journey::Parser.new
+
+ @anchored = true
+
+ case strexp
+ when String
+ @spec = parser.parse(strexp)
+ @requirements = {}
+ @separators = "/.?"
+ when Router::Strexp
+ @spec = parser.parse(strexp.path)
+ @requirements = strexp.requirements
+ @separators = strexp.separators.join
+ @anchored = strexp.anchor
+ else
+ raise ArgumentError, "Bad expression: #{strexp}"
+ end
+
+ @names = nil
+ @optional_names = nil
+ @required_names = nil
+ @re = nil
+ @offsets = nil
+ end
+
+ def ast
+ @spec.grep(Nodes::Symbol).each do |node|
+ re = @requirements[node.to_sym]
+ node.regexp = re if re
+ end
+
+ @spec.grep(Nodes::Star).each do |node|
+ node = node.left
+ node.regexp = @requirements[node.to_sym] || /(.+)/
+ end
+
+ @spec
+ end
+
+ def names
+ @names ||= spec.grep(Nodes::Symbol).map { |n| n.name }
+ end
+
+ def required_names
+ @required_names ||= names - optional_names
+ end
+
+ def optional_names
+ @optional_names ||= spec.grep(Nodes::Group).map { |group|
+ group.grep(Nodes::Symbol)
+ }.flatten.map { |n| n.name }.uniq
+ end
+
+ class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
+ attr_reader :offsets
+
+ def initialize(matchers)
+ @matchers = matchers
+ @capture_count = [0]
+ end
+
+ def visit(node)
+ super
+ @capture_count
+ end
+
+ def visit_SYMBOL(node)
+ node = node.to_sym
+
+ if @matchers.key?(node)
+ re = /#{@matchers[node]}|/
+ @capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
+ else
+ @capture_count << (@capture_count.last || 0)
+ end
+ end
+ end
+
+ class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
+ def initialize(separator, matchers)
+ @separator = separator
+ @matchers = matchers
+ @separator_re = "([^#{separator}]+)"
+ super()
+ end
+
+ def accept(node)
+ %r{\A#{visit node}\Z}
+ end
+
+ def visit_CAT(node)
+ [visit(node.left), visit(node.right)].join
+ end
+
+ def visit_SYMBOL(node)
+ node = node.to_sym
+
+ return @separator_re unless @matchers.key?(node)
+
+ re = @matchers[node]
+ "(#{re})"
+ end
+
+ def visit_GROUP(node)
+ "(?:#{visit node.left})?"
+ end
+
+ def visit_LITERAL(node)
+ Regexp.escape(node.left)
+ end
+ alias :visit_DOT :visit_LITERAL
+
+ def visit_SLASH(node)
+ node.left
+ end
+
+ def visit_STAR(node)
+ re = @matchers[node.left.to_sym] || '.+'
+ "(#{re})"
+ end
+ end
+
+ class UnanchoredRegexp < AnchoredRegexp # :nodoc:
+ def accept(node)
+ %r{\A#{visit node}}
+ end
+ end
+
+ class MatchData # :nodoc:
+ attr_reader :names
+
+ def initialize(names, offsets, match)
+ @names = names
+ @offsets = offsets
+ @match = match
+ end
+
+ def captures
+ (length - 1).times.map { |i| self[i + 1] }
+ end
+
+ def [](x)
+ idx = @offsets[x - 1] + x
+ @match[idx]
+ end
+
+ def length
+ @offsets.length
+ end
+
+ def post_match
+ @match.post_match
+ end
+
+ def to_s
+ @match.to_s
+ end
+ end
+
+ def match(other)
+ return unless match = to_regexp.match(other)
+ MatchData.new(names, offsets, match)
+ end
+ alias :=~ :match
+
+ def source
+ to_regexp.source
+ end
+
+ def to_regexp
+ @re ||= regexp_visitor.new(@separators, @requirements).accept spec
+ end
+
+ private
+
+ def regexp_visitor
+ @anchored ? AnchoredRegexp : UnanchoredRegexp
+ end
+
+ def offsets
+ return @offsets if @offsets
+
+ viz = RegexpOffsets.new(@requirements)
+ @offsets = viz.accept(spec)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
new file mode 100644
index 0000000000..c8eb0f6f2d
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -0,0 +1,136 @@
+module ActionDispatch
+ module Journey # :nodoc:
+ class Route # :nodoc:
+ attr_reader :app, :path, :defaults, :name
+
+ attr_reader :constraints
+ alias :conditions :constraints
+
+ attr_accessor :precedence
+
+ ##
+ # +path+ is a path constraint.
+ # +constraints+ is a hash of constraints to be applied to this route.
+ def initialize(name, app, path, constraints, defaults = {})
+ @name = name
+ @app = app
+ @path = path
+
+ # Unwrap any constraints so we can see what's inside for route generation.
+ # This allows the formatter to skip over any mounted applications or redirects
+ # that shouldn't be matched when using a url_for without a route name.
+ while app.is_a?(Routing::Mapper::Constraints) do
+ app = app.app
+ end
+ @dispatcher = app.is_a?(Routing::RouteSet::Dispatcher)
+
+ @constraints = constraints
+ @defaults = defaults
+ @required_defaults = nil
+ @required_parts = nil
+ @parts = nil
+ @decorated_ast = nil
+ @precedence = 0
+ end
+
+ def ast
+ @decorated_ast ||= begin
+ decorated_ast = path.ast
+ decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self }
+ decorated_ast
+ end
+ end
+
+ def requirements # :nodoc:
+ # needed for rails `rake routes`
+ path.requirements.merge(@defaults).delete_if { |_,v|
+ /.+?/ == v
+ }
+ end
+
+ def segments
+ path.names
+ end
+
+ def required_keys
+ required_parts + required_defaults.keys
+ end
+
+ def score(constraints)
+ required_keys = path.required_names
+ supplied_keys = constraints.map { |k,v| v && k.to_s }.compact
+
+ return -1 unless (required_keys - supplied_keys).empty?
+
+ score = (supplied_keys & path.names).length
+ score + (required_defaults.length * 2)
+ end
+
+ def parts
+ @parts ||= segments.map { |n| n.to_sym }
+ end
+ alias :segment_keys :parts
+
+ def format(path_options)
+ path_options.delete_if do |key, value|
+ value.to_s == defaults[key].to_s && !required_parts.include?(key)
+ end
+
+ Visitors::Formatter.new(path_options).accept(path.spec)
+ end
+
+ def optimized_path
+ Visitors::OptimizedPath.new.accept(path.spec)
+ end
+
+ def optional_parts
+ path.optional_names.map { |n| n.to_sym }
+ end
+
+ def required_parts
+ @required_parts ||= path.required_names.map { |n| n.to_sym }
+ end
+
+ def required_default?(key)
+ (constraints[:required_defaults] || []).include?(key)
+ end
+
+ def required_defaults
+ @required_defaults ||= @defaults.dup.delete_if do |k,_|
+ parts.include?(k) || !required_default?(k)
+ end
+ end
+
+ def dispatcher?
+ @dispatcher
+ end
+
+ def matches?(request)
+ constraints.all? do |method, value|
+ next true unless request.respond_to?(method)
+
+ case value
+ when Regexp, String
+ value === request.send(method).to_s
+ when Array
+ value.include?(request.send(method))
+ when TrueClass
+ request.send(method).present?
+ when FalseClass
+ request.send(method).blank?
+ else
+ value === request.send(method)
+ end
+ end
+ end
+
+ def ip
+ constraints[:ip] || //
+ end
+
+ def verb
+ constraints[:request_method] || //
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
new file mode 100644
index 0000000000..da32f1bfe7
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -0,0 +1,172 @@
+require 'action_dispatch/journey/router/utils'
+require 'action_dispatch/journey/router/strexp'
+require 'action_dispatch/journey/routes'
+require 'action_dispatch/journey/formatter'
+
+before = $-w
+$-w = false
+require 'action_dispatch/journey/parser'
+$-w = before
+
+require 'action_dispatch/journey/route'
+require 'action_dispatch/journey/path/pattern'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ class Router # :nodoc:
+ class RoutingError < ::StandardError # :nodoc:
+ end
+
+ # :nodoc:
+ VERSION = '2.0.0'
+
+ class NullReq # :nodoc:
+ attr_reader :env
+ def initialize(env)
+ @env = env
+ end
+
+ def request_method
+ env['REQUEST_METHOD']
+ end
+
+ def path_info
+ env['PATH_INFO']
+ end
+
+ def ip
+ env['REMOTE_ADDR']
+ end
+
+ def [](k)
+ env[k]
+ end
+ end
+
+ attr_reader :request_class, :formatter
+ attr_accessor :routes
+
+ def initialize(routes, options)
+ @options = options
+ @params_key = options[:parameters_key]
+ @request_class = options[:request_class] || NullReq
+ @routes = routes
+ end
+
+ def call(env)
+ env['PATH_INFO'] = normalize_path(env['PATH_INFO'])
+
+ find_routes(env).each do |match, parameters, route|
+ script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
+ 'PATH_INFO',
+ @params_key)
+
+ unless route.path.anchored
+ env['SCRIPT_NAME'] = (script_name.to_s + match.to_s).chomp('/')
+ env['PATH_INFO'] = match.post_match
+ end
+
+ env[@params_key] = (set_params || {}).merge parameters
+
+ status, headers, body = route.app.call(env)
+
+ if 'pass' == headers['X-Cascade']
+ env['SCRIPT_NAME'] = script_name
+ env['PATH_INFO'] = path_info
+ env[@params_key] = set_params
+ next
+ end
+
+ return [status, headers, body]
+ end
+
+ return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
+ end
+
+ def recognize(req)
+ find_routes(req.env).each do |match, parameters, route|
+ unless route.path.anchored
+ req.env['SCRIPT_NAME'] = match.to_s
+ req.env['PATH_INFO'] = match.post_match.sub(/^([^\/])/, '/\1')
+ end
+
+ yield(route, nil, parameters)
+ end
+ end
+
+ def visualizer
+ tt = GTG::Builder.new(ast).transition_table
+ groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s }
+ asts = groups.values.map { |v| v.first }
+ tt.visualizer(asts)
+ end
+
+ private
+
+ def normalize_path(path)
+ path = "/#{path}"
+ path.squeeze!('/')
+ path
+ end
+
+ def partitioned_routes
+ routes.partitioned_routes
+ end
+
+ def ast
+ routes.ast
+ end
+
+ def simulator
+ routes.simulator
+ end
+
+ def custom_routes
+ partitioned_routes.last
+ end
+
+ def filter_routes(path)
+ return [] unless ast
+ data = simulator.match(path)
+ data ? data.memos : []
+ end
+
+ def find_routes env
+ req = request_class.new(env)
+
+ routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
+ r.path.match(req.path_info)
+ }
+ routes.concat get_routes_as_head(routes)
+
+ routes.sort_by!(&:precedence).select! { |r| r.matches?(req) }
+
+ routes.map! { |r|
+ match_data = r.path.match(req.path_info)
+ match_names = match_data.names.map { |n| n.to_sym }
+ match_values = match_data.captures.map { |v| v && Utils.unescape_uri(v) }
+ info = Hash[match_names.zip(match_values).find_all { |_, y| y }]
+
+ [match_data, r.defaults.merge(info), r]
+ }
+ end
+
+ def get_routes_as_head(routes)
+ precedence = (routes.map(&:precedence).max || 0) + 1
+ routes = routes.select { |r|
+ r.verb === "GET" && !(r.verb === "HEAD")
+ }.map! { |r|
+ Route.new(r.name,
+ r.app,
+ r.path,
+ r.conditions.merge(request_method: "HEAD"),
+ r.defaults).tap do |route|
+ route.precedence = r.precedence + precedence
+ end
+ }
+ routes.flatten!
+ routes
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/router/strexp.rb b/actionpack/lib/action_dispatch/journey/router/strexp.rb
new file mode 100644
index 0000000000..f97f1a223e
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/router/strexp.rb
@@ -0,0 +1,24 @@
+module ActionDispatch
+ module Journey # :nodoc:
+ class Router # :nodoc:
+ class Strexp # :nodoc:
+ class << self
+ alias :compile :new
+ end
+
+ attr_reader :path, :requirements, :separators, :anchor
+
+ def initialize(path, requirements, separators, anchor = true)
+ @path = path
+ @requirements = requirements
+ @separators = separators
+ @anchor = anchor
+ end
+
+ def names
+ @path.scan(/:\w+/).map { |s| s.tr(':', '') }
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
new file mode 100644
index 0000000000..d1a004af50
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -0,0 +1,57 @@
+require 'uri'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ class Router # :nodoc:
+ class Utils # :nodoc:
+ # Normalizes URI path.
+ #
+ # Strips off trailing slash and ensures there is a leading slash.
+ # Also converts downcase url encoded string to uppercase.
+ #
+ # normalize_path("/foo") # => "/foo"
+ # normalize_path("/foo/") # => "/foo"
+ # normalize_path("foo") # => "/foo"
+ # normalize_path("") # => "/"
+ # normalize_path("/%ab") # => "/%AB"
+ def self.normalize_path(path)
+ path = "/#{path}"
+ path.squeeze!('/')
+ path.sub!(%r{/+\Z}, '')
+ path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
+ path = '/' if path == ''
+ path
+ end
+
+ # URI path and fragment escaping
+ # http://tools.ietf.org/html/rfc3986
+ module UriEscape # :nodoc:
+ # Symbol captures can generate multiple path segments, so include /.
+ reserved_segment = '/'
+ reserved_fragment = '/?'
+ reserved_pchar = ':@&=+$,;%'
+
+ safe_pchar = "#{URI::REGEXP::PATTERN::UNRESERVED}#{reserved_pchar}"
+ safe_segment = "#{safe_pchar}#{reserved_segment}"
+ safe_fragment = "#{safe_pchar}#{reserved_fragment}"
+ UNSAFE_SEGMENT = Regexp.new("[^#{safe_segment}]", false).freeze
+ UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze
+ end
+
+ Parser = URI::Parser.new
+
+ def self.escape_path(path)
+ Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT)
+ end
+
+ def self.escape_fragment(fragment)
+ Parser.escape(fragment.to_s, UriEscape::UNSAFE_FRAGMENT)
+ end
+
+ def self.unescape_uri(uri)
+ Parser.unescape(uri)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb
new file mode 100644
index 0000000000..80e3818ccd
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/routes.rb
@@ -0,0 +1,76 @@
+module ActionDispatch
+ module Journey # :nodoc:
+ # The Routing table. Contains all routes for a system. Routes can be
+ # added to the table by calling Routes#add_route.
+ class Routes # :nodoc:
+ include Enumerable
+
+ attr_reader :routes, :named_routes
+
+ def initialize
+ @routes = []
+ @named_routes = {}
+ @ast = nil
+ @partitioned_routes = nil
+ @simulator = nil
+ end
+
+ def length
+ routes.length
+ end
+ alias :size :length
+
+ def last
+ routes.last
+ end
+
+ def each(&block)
+ routes.each(&block)
+ end
+
+ def clear
+ routes.clear
+ named_routes.clear
+ end
+
+ def partitioned_routes
+ @partitioned_routes ||= routes.partition do |r|
+ r.path.anchored && r.ast.grep(Nodes::Symbol).all?(&:default_regexp?)
+ end
+ end
+
+ def ast
+ @ast ||= begin
+ asts = partitioned_routes.first.map(&:ast)
+ Nodes::Or.new(asts) unless asts.empty?
+ end
+ end
+
+ def simulator
+ @simulator ||= begin
+ gtg = GTG::Builder.new(ast).transition_table
+ GTG::Simulator.new(gtg)
+ end
+ end
+
+ # Add a route to the routing table.
+ def add_route(app, path, conditions, defaults, name = nil)
+ route = Route.new(name, app, path, conditions, defaults)
+
+ route.precedence = routes.length
+ routes << route
+ named_routes[name] = route if name && !named_routes[name]
+ clear_cache!
+ route
+ end
+
+ private
+
+ def clear_cache!
+ @ast = nil
+ @partitioned_routes = nil
+ @simulator = nil
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb
new file mode 100644
index 0000000000..633be11a2d
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/scanner.rb
@@ -0,0 +1,61 @@
+require 'strscan'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ class Scanner # :nodoc:
+ def initialize
+ @ss = nil
+ end
+
+ def scan_setup(str)
+ @ss = StringScanner.new(str)
+ end
+
+ def eos?
+ @ss.eos?
+ end
+
+ def pos
+ @ss.pos
+ end
+
+ def pre_match
+ @ss.pre_match
+ end
+
+ def next_token
+ return if @ss.eos?
+
+ until token = scan || @ss.eos?; end
+ token
+ end
+
+ private
+
+ def scan
+ case
+ # /
+ when text = @ss.scan(/\//)
+ [:SLASH, text]
+ when text = @ss.scan(/\*\w+/)
+ [:STAR, text]
+ when text = @ss.scan(/\(/)
+ [:LPAREN, text]
+ when text = @ss.scan(/\)/)
+ [:RPAREN, text]
+ when text = @ss.scan(/\|/)
+ [:OR, text]
+ when text = @ss.scan(/\./)
+ [:DOT, text]
+ when text = @ss.scan(/:\w+/)
+ [:SYMBOL, text]
+ when text = @ss.scan(/[\w%\-~]+/)
+ [:LITERAL, text]
+ # any char
+ when text = @ss.scan(/./)
+ [:LITERAL, text]
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
new file mode 100644
index 0000000000..9e66cab052
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -0,0 +1,199 @@
+# encoding: utf-8
+
+require 'thread_safe'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module Visitors # :nodoc:
+ class Visitor # :nodoc:
+ DISPATCH_CACHE = ThreadSafe::Cache.new { |h,k|
+ h[k] = :"visit_#{k}"
+ }
+
+ def accept(node)
+ visit(node)
+ end
+
+ private
+
+ def visit node
+ send(DISPATCH_CACHE[node.type], node)
+ end
+
+ def binary(node)
+ visit(node.left)
+ visit(node.right)
+ end
+ def visit_CAT(n); binary(n); end
+
+ def nary(node)
+ node.children.each { |c| visit(c) }
+ end
+ def visit_OR(n); nary(n); end
+
+ def unary(node)
+ visit(node.left)
+ end
+ def visit_GROUP(n); unary(n); end
+ def visit_STAR(n); unary(n); end
+
+ def terminal(node); end
+ %w{ LITERAL SYMBOL SLASH DOT }.each do |t|
+ class_eval %{ def visit_#{t}(n); terminal(n); end }, __FILE__, __LINE__
+ end
+ end
+
+ # Loop through the requirements AST
+ class Each < Visitor # :nodoc:
+ attr_reader :block
+
+ def initialize(block)
+ @block = block
+ end
+
+ def visit(node)
+ super
+ block.call(node)
+ end
+ end
+
+ class String < Visitor # :nodoc:
+ private
+
+ def binary(node)
+ [visit(node.left), visit(node.right)].join
+ end
+
+ def nary(node)
+ node.children.map { |c| visit(c) }.join '|'
+ end
+
+ def terminal(node)
+ node.left
+ end
+
+ def visit_GROUP(node)
+ "(#{visit(node.left)})"
+ end
+ end
+
+ class OptimizedPath < String # :nodoc:
+ private
+
+ def visit_GROUP(node)
+ ""
+ end
+ end
+
+ # Used for formatting urls (url_for)
+ class Formatter < Visitor # :nodoc:
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ private
+
+ def visit(node, optional = false)
+ case node.type
+ when :LITERAL, :SLASH, :DOT
+ node.left
+ when :STAR
+ visit(node.left)
+ when :GROUP
+ visit(node.left, true)
+ when :CAT
+ visit_CAT(node, optional)
+ when :SYMBOL
+ visit_SYMBOL(node)
+ end
+ end
+
+ def visit_CAT(node, optional)
+ left = visit(node.left, optional)
+ right = visit(node.right, optional)
+
+ if optional && !(right && left)
+ ""
+ else
+ [left, right].join
+ end
+ end
+
+ def visit_SYMBOL(node)
+ if value = options[node.to_sym]
+ Router::Utils.escape_path(value)
+ end
+ end
+ end
+
+ class Dot < Visitor # :nodoc:
+ def initialize
+ @nodes = []
+ @edges = []
+ end
+
+ def accept(node)
+ super
+ <<-eodot
+ digraph parse_tree {
+ size="8,5"
+ node [shape = none];
+ edge [dir = none];
+ #{@nodes.join "\n"}
+ #{@edges.join("\n")}
+ }
+ eodot
+ end
+
+ private
+
+ def binary(node)
+ node.children.each do |c|
+ @edges << "#{node.object_id} -> #{c.object_id};"
+ end
+ super
+ end
+
+ def nary(node)
+ node.children.each do |c|
+ @edges << "#{node.object_id} -> #{c.object_id};"
+ end
+ super
+ end
+
+ def unary(node)
+ @edges << "#{node.object_id} -> #{node.left.object_id};"
+ super
+ end
+
+ def visit_GROUP(node)
+ @nodes << "#{node.object_id} [label=\"()\"];"
+ super
+ end
+
+ def visit_CAT(node)
+ @nodes << "#{node.object_id} [label=\"○\"];"
+ super
+ end
+
+ def visit_STAR(node)
+ @nodes << "#{node.object_id} [label=\"*\"];"
+ super
+ end
+
+ def visit_OR(node)
+ @nodes << "#{node.object_id} [label=\"|\"];"
+ super
+ end
+
+ def terminal(node)
+ value = node.left
+
+ @nodes << "#{node.object_id} [label=\"#{value}\"];"
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/visualizer/fsm.css b/actionpack/lib/action_dispatch/journey/visualizer/fsm.css
new file mode 100644
index 0000000000..50caebaa18
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/visualizer/fsm.css
@@ -0,0 +1,34 @@
+body {
+ font-family: "Helvetica Neue", Helvetica, Arial, Sans-Serif;
+ margin: 0;
+}
+
+h1 {
+ font-size: 2.0em; font-weight: bold; text-align: center;
+ color: white; background-color: black;
+ padding: 5px 0;
+ margin: 0 0 20px;
+}
+
+h2 {
+ text-align: center;
+ display: none;
+ font-size: 0.5em;
+}
+
+div#chart-2 {
+ height: 350px;
+}
+
+.clearfix {display: inline-block; }
+.input { overflow: show;}
+.instruction { color: #666; padding: 0 30px 20px; font-size: 0.9em}
+.instruction p { padding: 0 0 5px; }
+.instruction li { padding: 0 10px 5px; }
+
+.form { background: #EEE; padding: 20px 30px; border-radius: 5px; margin-left: auto; margin-right: auto; width: 500px; margin-bottom: 20px}
+.form p, .form form { text-align: center }
+.form form {padding: 0 10px 5px; }
+.form .fun_routes { font-size: 0.9em;}
+.form .fun_routes a { margin: 0 5px 0 0; }
+
diff --git a/actionpack/lib/action_dispatch/journey/visualizer/fsm.js b/actionpack/lib/action_dispatch/journey/visualizer/fsm.js
new file mode 100644
index 0000000000..d9bcaef928
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/visualizer/fsm.js
@@ -0,0 +1,134 @@
+function tokenize(input, callback) {
+ while(input.length > 0) {
+ callback(input.match(/^[\/\.\?]|[^\/\.\?]+/)[0]);
+ input = input.replace(/^[\/\.\?]|[^\/\.\?]+/, '');
+ }
+}
+
+var graph = d3.select("#chart-2 svg");
+var svg_edges = {};
+var svg_nodes = {};
+
+graph.selectAll("g.edge").each(function() {
+ var node = d3.select(this);
+ var index = node.select("title").text().split("->");
+ var left = parseInt(index[0]);
+ var right = parseInt(index[1]);
+
+ if(!svg_edges[left]) { svg_edges[left] = {} }
+ svg_edges[left][right] = node;
+});
+
+graph.selectAll("g.node").each(function() {
+ var node = d3.select(this);
+ var index = parseInt(node.select("title").text());
+ svg_nodes[index] = node;
+});
+
+function reset_graph() {
+ for(var key in svg_edges) {
+ for(var mkey in svg_edges[key]) {
+ var node = svg_edges[key][mkey];
+ var path = node.select("path");
+ var arrow = node.select("polygon");
+ path.style("stroke", "black");
+ arrow.style("stroke", "black").style("fill", "black");
+ }
+ }
+
+ for(var key in svg_nodes) {
+ var node = svg_nodes[key];
+ node.select('ellipse').style("fill", "white");
+ node.select('polygon').style("fill", "white");
+ }
+ return false;
+}
+
+function highlight_edge(from, to) {
+ var node = svg_edges[from][to];
+ var path = node.select("path");
+ var arrow = node.select("polygon");
+
+ path
+ .transition().duration(500)
+ .style("stroke", "green");
+
+ arrow
+ .transition().duration(500)
+ .style("stroke", "green").style("fill", "green");
+}
+
+function highlight_state(index, color) {
+ if(!color) { color = "green"; }
+
+ svg_nodes[index].select('ellipse')
+ .style("fill", "white")
+ .transition().duration(500)
+ .style("fill", color);
+}
+
+function highlight_finish(index) {
+ svg_nodes[index].select('polygon')
+ .style("fill", "while")
+ .transition().duration(500)
+ .style("fill", "blue");
+}
+
+function match(input) {
+ reset_graph();
+ var table = tt();
+ var states = [0];
+ var regexp_states = table['regexp_states'];
+ var string_states = table['string_states'];
+ var accepting = table['accepting'];
+
+ highlight_state(0);
+
+ tokenize(input, function(token) {
+ var new_states = [];
+ for(var key in states) {
+ var state = states[key];
+
+ if(string_states[state] && string_states[state][token]) {
+ var new_state = string_states[state][token];
+ highlight_edge(state, new_state);
+ highlight_state(new_state);
+ new_states.push(new_state);
+ }
+
+ if(regexp_states[state]) {
+ for(var key in regexp_states[state]) {
+ var re = new RegExp("^" + key + "$");
+ if(re.test(token)) {
+ var new_state = regexp_states[state][key];
+ highlight_edge(state, new_state);
+ highlight_state(new_state);
+ new_states.push(new_state);
+ }
+ }
+ }
+ }
+
+ if(new_states.length == 0) {
+ return;
+ }
+ states = new_states;
+ });
+
+ for(var key in states) {
+ var state = states[key];
+ if(accepting[state]) {
+ for(var mkey in svg_edges[state]) {
+ if(!regexp_states[mkey] && !string_states[mkey]) {
+ highlight_edge(state, mkey);
+ highlight_finish(mkey);
+ }
+ }
+ } else {
+ highlight_state(state, "red");
+ }
+ }
+
+ return false;
+}
+
diff --git a/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb b/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb
new file mode 100644
index 0000000000..6aff10956a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title><%= title %></title>
+ <link rel="stylesheet" href="https://raw.github.com/gist/1706081/af944401f75ea20515a02ddb3fb43d23ecb8c662/reset.css" type="text/css">
+ <style>
+ <% stylesheets.each do |style| %>
+ <%= style %>
+ <% end %>
+ </style>
+ <script src="https://raw.github.com/gist/1706081/df464722a05c3c2bec450b7b5c8240d9c31fa52d/d3.min.js" type="text/javascript"></script>
+ </head>
+ <body>
+ <div id="wrapper">
+ <h1>Routes FSM with NFA simulation</h1>
+ <div class="instruction form">
+ <p>
+ Type a route in to the box and click "simulate".
+ </p>
+ <form onsubmit="return match(this.route.value);">
+ <input type="text" size="30" name="route" value="/articles/new" />
+ <button>simulate</button>
+ <input type="reset" value="reset" onclick="return reset_graph();"/>
+ </form>
+ <p class="fun_routes">
+ Some fun routes to try:
+ <% fun_routes.each do |path| %>
+ <a href="#" onclick="document.forms[0].elements[0].value=this.text.replace(/^\s+|\s+$/g,''); return match(this.text.replace(/^\s+|\s+$/g,''));">
+ <%= path %>
+ </a>
+ <% end %>
+ </p>
+ </div>
+ <div class='chart' id='chart-2'>
+ <%= svg %>
+ </div>
+ <div class="instruction">
+ <p>
+ This is a FSM for a system that has the following routes:
+ </p>
+ <ul>
+ <% paths.each do |route| %>
+ <li><%= route %></li>
+ <% end %>
+ </ul>
+ </div>
+ </div>
+ <% javascripts.each do |js| %>
+ <script><%= js %></script>
+ <% end %>
+ </body>
+</html>
diff --git a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb
deleted file mode 100644
index 69adcc419f..0000000000
--- a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-module ActionDispatch
- class BestStandardsSupport
- def initialize(app, type = true)
- @app = app
-
- @header = case type
- when true
- "IE=Edge,chrome=1"
- when :builtin
- "IE=Edge"
- when false
- nil
- end
- end
-
- def call(env)
- status, headers, body = @app.call(env)
- headers["X-UA-Compatible"] = @header
- [status, headers, body]
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index ba5d332d49..3ccd0c9ee8 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -1,5 +1,8 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/core_ext/object/blank'
+require 'active_support/key_generator'
+require 'active_support/message_verifier'
module ActionDispatch
class Request < Rack::Request
@@ -14,7 +17,7 @@ module ActionDispatch
# being written will be sent out with the response. Reading a cookie does not get
# the cookie object itself back, just the value it holds.
#
- # Examples for writing:
+ # Examples of writing:
#
# # Sets a simple session cookie.
# # This cookie will be deleted when the user's browser is closed.
@@ -24,11 +27,11 @@ module ActionDispatch
# cookies[:lat_lon] = [47.68, -122.37]
#
# # Sets a cookie that expires in 1 hour.
- # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
+ # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
#
# # Sets a signed cookie, which prevents users from tampering with its value.
- # # The cookie is signed by your app's <tt>config.secret_token</tt> value.
- # # It can be read using the signed method <tt>cookies.signed[:key]</tt>
+ # # The cookie is signed by your app's <tt>config.secret_key_base</tt> value.
+ # # It can be read using the signed method <tt>cookies.signed[:name]</tt>
# cookies.signed[:user_id] = current_user.id
#
# # Sets a "permanent" cookie (which expires in 20 years from now).
@@ -37,7 +40,7 @@ module ActionDispatch
# # You can also chain these methods:
# cookies.permanent.signed[:login] = "XJ-122"
#
- # Examples for reading:
+ # Examples of reading:
#
# cookies[:user_name] # => "david"
# cookies.size # => 2
@@ -50,13 +53,13 @@ module ActionDispatch
#
# Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
#
- # cookies[:key] = {
- # :value => 'a yummy cookie',
- # :expires => 1.year.from_now,
- # :domain => 'domain.com'
+ # cookies[:name] = {
+ # value: 'a yummy cookie',
+ # expires: 1.year.from_now,
+ # domain: 'domain.com'
# }
#
- # cookies.delete(:key, :domain => 'domain.com')
+ # cookies.delete(:name, domain: 'domain.com')
#
# The option symbols for setting cookies are:
#
@@ -67,26 +70,125 @@ module ActionDispatch
# restrict to the domain level. If you use a schema like www.example.com
# and want to share session with user.example.com set <tt>:domain</tt>
# to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
- # <tt>:all</tt> again when deleting keys.
+ # <tt>:all</tt> again when deleting cookies.
#
- # :domain => nil # Does not sets cookie domain. (default)
- # :domain => :all # Allow the cookie for the top most level
+ # domain: nil # Does not sets cookie domain. (default)
+ # domain: :all # Allow the cookie for the top most level
# domain and subdomains.
#
# * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
- # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
+ # * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
# Default is +false+.
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
# only HTTP. Defaults to +false+.
class Cookies
- HTTP_HEADER = "Set-Cookie".freeze
- TOKEN_KEY = "action_dispatch.secret_token".freeze
+ HTTP_HEADER = "Set-Cookie".freeze
+ GENERATOR_KEY = "action_dispatch.key_generator".freeze
+ SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
+ ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
+ ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
+ SECRET_TOKEN = "action_dispatch.secret_token".freeze
+ SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
+
+ # Cookies can typically store 4096 bytes.
+ MAX_COOKIE_SIZE = 4096
# Raised when storing more than 4K of session data.
CookieOverflow = Class.new StandardError
+ # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed
+ module ChainedCookieJars
+ # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
+ #
+ # cookies.permanent[:prefers_open_id] = true
+ # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
+ #
+ # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
+ #
+ # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
+ #
+ # cookies.permanent.signed[:remember_me] = current_user.id
+ # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
+ def permanent
+ @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
+ end
+
+ # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
+ # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
+ # cookie was tampered with by the user (or a 3rd party), nil will be returned.
+ #
+ # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # legacy cookies signed with the old key generator will be transparently upgraded.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ #
+ # Example:
+ #
+ # cookies.signed[:discount] = 45
+ # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
+ #
+ # cookies.signed[:discount] # => 45
+ def signed
+ @signed ||=
+ if @options[:upgrade_legacy_signed_cookies]
+ UpgradeLegacySignedCookieJar.new(self, @key_generator, @options)
+ else
+ SignedCookieJar.new(self, @key_generator, @options)
+ end
+ end
+
+ # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
+ # If the cookie was tampered with by the user (or a 3rd party), nil will be returned.
+ #
+ # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # legacy cookies signed with the old key generator will be transparently upgraded.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ #
+ # Example:
+ #
+ # cookies.encrypted[:discount] = 45
+ # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
+ #
+ # cookies.encrypted[:discount] # => 45
+ def encrypted
+ @encrypted ||=
+ if @options[:upgrade_legacy_signed_cookies]
+ UpgradeLegacyEncryptedCookieJar.new(self, @key_generator, @options)
+ else
+ EncryptedCookieJar.new(self, @key_generator, @options)
+ end
+ end
+
+ # Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set.
+ # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
+ def signed_or_encrypted
+ @signed_or_encrypted ||=
+ if @options[:secret_key_base].present?
+ encrypted
+ else
+ signed
+ end
+ end
+ end
+
+ module VerifyAndUpgradeLegacySignedMessage
+ def initialize(*args)
+ super
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token])
+ end
+
+ def verify_and_upgrade_legacy_signed_message(name, signed_message)
+ @legacy_verifier.verify(signed_message).tap do |value|
+ self[name] = value
+ end
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ nil
+ end
+ end
+
class CookieJar #:nodoc:
- include Enumerable
+ include Enumerable, ChainedCookieJars
# This regular expression is used to split the levels of a domain.
# The top level domain can be any string without a period or
@@ -102,22 +204,36 @@ module ActionDispatch
# $& => example.local
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
+ def self.options_for_env(env) #:nodoc:
+ { signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '',
+ encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '',
+ encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
+ secret_token: env[SECRET_TOKEN],
+ secret_key_base: env[SECRET_KEY_BASE],
+ upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?
+ }
+ end
+
def self.build(request)
- secret = request.env[TOKEN_KEY]
+ env = request.env
+ key_generator = env[GENERATOR_KEY]
+ options = options_for_env env
+
host = request.host
secure = request.ssl?
- new(secret, host, secure).tap do |hash|
+ new(key_generator, host, secure, options).tap do |hash|
hash.update(request.cookies)
end
end
- def initialize(secret = nil, host = nil, secure = false)
- @secret = secret
+ def initialize(key_generator, host = nil, secure = false, options = {})
+ @key_generator = key_generator
@set_cookies = {}
@delete_cookies = {}
@host = host
@secure = secure
+ @options = options
@cookies = {}
end
@@ -130,6 +246,10 @@ module ActionDispatch
@cookies[name.to_s]
end
+ def fetch(name, *args, &block)
+ @cookies.fetch(name.to_s, *args, &block)
+ end
+
def key?(name)
@cookies.key?(name.to_s)
end
@@ -160,7 +280,7 @@ module ActionDispatch
# Sets the cookie named +name+. The second argument may be the very cookie
# value, or a hash of options as documented above.
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
value = options[:value]
@@ -171,36 +291,36 @@ module ActionDispatch
handle_options(options)
- 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)
+ if @cookies[name.to_s] != value or options[:expires]
+ @cookies[name.to_s] = value
+ @set_cookies[name.to_s] = options
+ @delete_cookies.delete(name.to_s)
end
value
end
# Removes the cookie on the client machine by setting the value to an empty string
- # and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
+ # and the expiration date in 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 = {})
- return unless @cookies.has_key? key.to_s
+ def delete(name, options = {})
+ return unless @cookies.has_key? name.to_s
options.symbolize_keys!
handle_options(options)
- value = @cookies.delete(key.to_s)
- @delete_cookies[key.to_s] = options
+ value = @cookies.delete(name.to_s)
+ @delete_cookies[name.to_s] = options
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 = {})
+ def deleted?(name, options = {})
options.symbolize_keys!
handle_options(options)
- @delete_cookies[key.to_s] == options
+ @delete_cookies[name.to_s] == options
end
# Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
@@ -208,38 +328,6 @@ module ActionDispatch
@cookies.each_key{ |k| delete(k, options) }
end
- # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
- #
- # cookies.permanent[:prefers_open_id] = true
- # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
- #
- # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
- #
- # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
- #
- # cookies.permanent.signed[:remember_me] = current_user.id
- # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @secret)
- end
-
- # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
- # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
- # 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+.
- #
- # Example:
- #
- # cookies.signed[:discount] = 45
- # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
- #
- # cookies.signed[:discount] # => 45
- def signed
- @signed ||= SignedCookieJar.new(self, @secret)
- end
-
def write(headers)
@set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) }
@delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
@@ -254,18 +342,25 @@ module ActionDispatch
self.always_write_cookie = false
private
-
def write_cookie?(cookie)
@secure || !cookie[:secure] || always_write_cookie
end
end
- class PermanentCookieJar < CookieJar #:nodoc:
- def initialize(parent_jar, secret)
- @parent_jar, @secret = parent_jar, secret
+ class PermanentCookieJar #:nodoc:
+ include ChainedCookieJars
+
+ def initialize(parent_jar, key_generator, options = {})
+ @parent_jar = parent_jar
+ @key_generator = key_generator
+ @options = options
+ end
+
+ def [](name)
+ @parent_jar[name.to_s]
end
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
else
@@ -273,33 +368,27 @@ module ActionDispatch
end
options[:expires] = 20.years.from_now
- @parent_jar[key] = options
- end
-
- def method_missing(method, *arguments, &block)
- @parent_jar.send(method, *arguments, &block)
+ @parent_jar[name] = options
end
end
- class SignedCookieJar < CookieJar #:nodoc:
- MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes.
- SECRET_MIN_LENGTH = 30 # Characters
+ class SignedCookieJar #:nodoc:
+ include ChainedCookieJars
- def initialize(parent_jar, secret)
- ensure_secret_secure(secret)
+ def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
+ @options = options
+ secret = key_generator.generate_key(@options[:signed_cookie_salt])
@verifier = ActiveSupport::MessageVerifier.new(secret)
end
def [](name)
if signed_message = @parent_jar[name]
- @verifier.verify(signed_message)
+ verify(signed_message)
end
- rescue ActiveSupport::MessageVerifier::InvalidSignature
- nil
end
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
options[:value] = @verifier.generate(options[:value])
@@ -308,31 +397,83 @@ module ActionDispatch
end
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
- @parent_jar[key] = options
+ @parent_jar[name] = options
end
- def method_missing(method, *arguments, &block)
- @parent_jar.send(method, *arguments, &block)
+ private
+ def verify(signed_message)
+ @verifier.verify(signed_message)
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ nil
+ end
+ end
+
+ # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
+ # config.secret_token and config.secret_key_base are both set. It reads
+ # legacy cookies signed with the old dummy key generator and re-saves
+ # them using the new key generator to provide a smooth upgrade path.
+ class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
+ include VerifyAndUpgradeLegacySignedMessage
+
+ def [](name)
+ if signed_message = @parent_jar[name]
+ verify(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message)
+ end
end
+ end
+
+ class EncryptedCookieJar #:nodoc:
+ include ChainedCookieJars
+
+ def initialize(parent_jar, key_generator, options = {})
+ if ActiveSupport::LegacyKeyGenerator === key_generator
+ raise "You didn't set config.secret_key_base, which is required for this cookie jar. " +
+ "Read the upgrade documentation to learn more about this new config option."
+ end
- protected
+ @parent_jar = parent_jar
+ @options = options
+ secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
+ sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+ end
- # To prevent users from using something insecure like "Password" we make sure that the
- # secret they've provided is at least 30 characters in length.
- def ensure_secret_secure(secret)
- if secret.blank?
- raise ArgumentError, "A secret is required to generate an " +
- "integrity hash for cookie session data. Use " +
- "config.secret_token = \"some secret phrase of at " +
- "least #{SECRET_MIN_LENGTH} characters\"" +
- "in config/initializers/secret_token.rb"
+ def [](name)
+ if encrypted_message = @parent_jar[name]
+ decrypt_and_verify(encrypted_message)
end
+ end
- if secret.length < SECRET_MIN_LENGTH
- raise ArgumentError, "Secret should be something secure, " +
- "like \"#{SecureRandom.hex(16)}\". The value you " +
- "provided, \"#{secret}\", is shorter than the minimum length " +
- "of #{SECRET_MIN_LENGTH} characters"
+ def []=(name, options)
+ if options.is_a?(Hash)
+ options.symbolize_keys!
+ else
+ options = { :value => options }
+ end
+ options[:value] = @encryptor.encrypt_and_sign(options[:value])
+
+ raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
+ @parent_jar[name] = options
+ end
+
+ private
+ def decrypt_and_verify(encrypted_message)
+ @encryptor.decrypt_and_verify(encrypted_message)
+ rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
+ nil
+ end
+ end
+
+ # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
+ # instead of EncryptedCookieJar if config.secret_token and config.secret_key_base
+ # are both set. It reads legacy cookies signed with the old dummy key generator and
+ # encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
+ class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
+ include VerifyAndUpgradeLegacySignedMessage
+
+ def [](name)
+ if encrypted_or_signed_message = @parent_jar[name]
+ decrypt_and_verify(encrypted_or_signed_message) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 0f0589a844..0ca1a87645 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -2,12 +2,11 @@ require 'action_dispatch/http/request'
require 'action_dispatch/middleware/exception_wrapper'
require 'action_dispatch/routing/inspector'
-
module ActionDispatch
# This middleware is responsible for logging exceptions and
# showing a debugging page in case the request is local.
class DebugExceptions
- RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
+ RESCUES_TEMPLATE_PATH = File.expand_path('../templates', __FILE__)
def initialize(app, routes_app = nil)
@app = app
@@ -15,19 +14,17 @@ module ActionDispatch
end
def call(env)
- begin
- response = @app.call(env)
+ _, headers, body = response = @app.call(env)
- if response[1]['X-Cascade'] == 'pass'
- body = response[2]
- body.close if body.respond_to?(:close)
- raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
- end
- rescue Exception => exception
- raise exception if env['action_dispatch.show_exceptions'] == false
+ if headers['X-Cascade'] == 'pass'
+ body.close if body.respond_to?(:close)
+ raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
end
- exception ? render_exception(env, exception) : response
+ response
+ rescue Exception => exception
+ raise exception if env['action_dispatch.show_exceptions'] == false
+ render_exception(env, exception)
end
private
@@ -37,25 +34,35 @@ module ActionDispatch
log_error(env, wrapper)
if env['action_dispatch.show_detailed_exceptions']
+ request = Request.new(env)
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
- :request => Request.new(env),
- :exception => wrapper.exception,
- :application_trace => wrapper.application_trace,
- :framework_trace => wrapper.framework_trace,
- :full_trace => wrapper.full_trace,
- :routes => formatted_routes(exception)
+ request: request,
+ exception: wrapper.exception,
+ application_trace: wrapper.application_trace,
+ framework_trace: wrapper.framework_trace,
+ full_trace: wrapper.full_trace,
+ routes_inspector: routes_inspector(exception),
+ source_extract: wrapper.source_extract,
+ line_number: wrapper.line_number,
+ file: wrapper.file
)
-
file = "rescues/#{wrapper.rescue_template}"
- body = template.render(:template => file, :layout => 'rescues/layout')
- render(wrapper.status_code, body)
+
+ if request.xhr?
+ body = template.render(template: file, layout: false, formats: [:text])
+ format = "text/plain"
+ else
+ body = template.render(template: file, layout: 'rescues/layout')
+ format = "text/html"
+ end
+ render(wrapper.status_code, body, format)
else
raise exception
end
end
- def render(status, body)
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ def render(status, body, format)
+ [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
end
def log_error(env, wrapper)
@@ -83,11 +90,9 @@ module ActionDispatch
@stderr_logger ||= ActiveSupport::Logger.new($stderr)
end
- def formatted_routes(exception)
- return false unless @routes_app.respond_to?(:routes)
- if exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error)
- inspector = ActionDispatch::Routing::RoutesInspector.new
- inspector.format(@routes_app.routes.routes).join("\n")
+ def routes_inspector(exception)
+ if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error))
+ ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index ae38c56a67..daddaccadd 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -1,5 +1,4 @@
require 'action_controller/metal/exceptions'
-require 'active_support/core_ext/exception'
require 'active_support/core_ext/class/attribute_accessors'
module ActionDispatch
@@ -10,10 +9,14 @@ module ActionDispatch
'ActionController::RoutingError' => :not_found,
'AbstractController::ActionNotFound' => :not_found,
'ActionController::MethodNotAllowed' => :method_not_allowed,
+ 'ActionController::UnknownHttpMethod' => :method_not_allowed,
'ActionController::NotImplemented' => :not_implemented,
'ActionController::UnknownFormat' => :not_acceptable,
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
- 'ActionController::BadRequest' => :bad_request
+ 'ActionDispatch::ParamsParser::ParseError' => :bad_request,
+ 'ActionController::BadRequest' => :bad_request,
+ 'ActionController::ParameterMissing' => :bad_request,
+ 'ActionController::EmptyParameter' => :bad_request
)
cattr_accessor :rescue_templates
@@ -25,7 +28,7 @@ module ActionDispatch
'ActionView::Template::Error' => 'template_error'
)
- attr_reader :env, :exception
+ attr_reader :env, :exception, :line_number, :file
def initialize(env, exception)
@env = env
@@ -56,6 +59,15 @@ module ActionDispatch
Rack::Utils.status_code(@@rescue_responses[class_name])
end
+ def source_extract
+ if application_trace && trace = application_trace.first
+ file, line, _ = trace.split(":")
+ @file = file
+ @line_number = line.to_i
+ source_fragment(@file, @line_number)
+ end
+ end
+
private
def original_exception(exception)
@@ -81,5 +93,17 @@ module ActionDispatch
def backtrace_cleaner
@backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
end
+
+ def source_fragment(path, line)
+ return unless Rails.respond_to?(:root) && Rails.root
+ full_path = Rails.root.join(path)
+ if File.exist?(full_path)
+ File.open(full_path, "r") do |file|
+ start = [line - 3, 0].max
+ lines = file.each_line.drop(start).take(6)
+ Hash[*(start+1..(lines.count+start)).zip(lines).flatten]
+ end
+ end
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 9928b7cc3a..89003e7a5e 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).tap(&:sweep)
+ @env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"])
end
end
@@ -59,27 +59,41 @@ module ActionDispatch
@flash[k]
end
- # Convenience accessor for flash.now[:alert]=
+ # Convenience accessor for <tt>flash.now[:alert]=</tt>.
def alert=(message)
self[:alert] = message
end
- # Convenience accessor for flash.now[:notice]=
+ # Convenience accessor for <tt>flash.now[:notice]=</tt>.
def notice=(message)
self[:notice] = message
end
end
- # Implementation detail: please do not change the signature of the
- # FlashHash class. Doing that will likely affect all Rails apps in
- # production as the FlashHash currently stored in their sessions will
- # become invalid.
class FlashHash
include Enumerable
- def initialize #:nodoc:
- @discard = Set.new
- @flashes = {}
+ def self.from_session_value(value)
+ flash = case value
+ when FlashHash # Rails 3.1, 3.2
+ new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used))
+ when Hash # Rails 4.0
+ new(value['flashes'], value['discard'])
+ else
+ new
+ end
+
+ flash.tap(&:sweep)
+ end
+
+ def to_session_value
+ return nil if empty?
+ {'discard' => @discard.to_a, 'flashes' => @flashes}
+ end
+
+ def initialize(flashes = {}, discard = []) #:nodoc:
+ @discard = Set.new(discard)
+ @flashes = flashes
@now = nil
end
@@ -91,7 +105,7 @@ module ActionDispatch
super
end
- def []=(k, v) #:nodoc:
+ def []=(k, v)
@discard.delete k
@flashes[k] = v
end
@@ -155,6 +169,14 @@ module ActionDispatch
# vanish when the current action is done.
#
# Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
+ #
+ # Also, brings two convenience accessors:
+ #
+ # flash.now.alert = "Beware now!"
+ # # Equivalent to flash.now[:alert] = "Beware now!"
+ #
+ # flash.now.notice = "Good luck now!"
+ # # Equivalent to flash.now[:notice] = "Good luck now!"
def now
@now ||= FlashNow.new(self)
end
@@ -185,22 +207,22 @@ module ActionDispatch
@discard.replace @flashes.keys
end
- # Convenience accessor for flash[:alert]
+ # Convenience accessor for <tt>flash[:alert]</tt>.
def alert
self[:alert]
end
- # Convenience accessor for flash[:alert]=
+ # Convenience accessor for <tt>flash[:alert]=</tt>.
def alert=(message)
self[:alert] = message
end
- # Convenience accessor for flash[:notice]
+ # Convenience accessor for <tt>flash[:notice]</tt>.
def notice
self[:notice]
end
- # Convenience accessor for flash[:notice]=
+ # Convenience accessor for <tt>flash[:notice]=</tt>.
def notice=(message)
self[:notice] = message
end
@@ -221,19 +243,13 @@ module ActionDispatch
session = Request::Session.find(env) || {}
flash_hash = env[KEY]
- if flash_hash
- if !flash_hash.empty? || session.key?('flash')
- session["flash"] = flash_hash
- new_hash = flash_hash.dup
- else
- new_hash = flash_hash
- end
-
- env[KEY] = new_hash
+ if flash_hash && (flash_hash.present? || session.key?('flash'))
+ session["flash"] = flash_hash.to_session_value
+ env[KEY] = flash_hash.dup
end
if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
- session.key?('flash') && session['flash'].empty?
+ session.key?('flash') && session['flash'].nil?
session.delete('flash')
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index 2c98ca03a8..b426183488 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -13,10 +13,7 @@ module ActionDispatch
end
end
- DEFAULT_PARSERS = {
- Mime::XML => :xml_simple,
- Mime::JSON => :json
- }
+ DEFAULT_PARSERS = { Mime::JSON => :json }
def initialize(app, parsers = {})
@app, @parsers = app, DEFAULT_PARSERS.merge(parsers)
@@ -36,45 +33,26 @@ module ActionDispatch
return false if request.content_length.zero?
- mime_type = content_type_from_legacy_post_data_format_header(env) ||
- request.content_mime_type
-
- strategy = @parsers[mime_type]
+ strategy = @parsers[request.content_mime_type]
return false unless strategy
case strategy
when Proc
strategy.call(request.raw_post)
- when :xml_simple, :xml_node
- data = Hash.from_xml(request.raw_post) || {}
- data.with_indifferent_access
- when :yaml
- YAML.load(request.raw_post)
when :json
data = ActiveSupport::JSON.decode(request.raw_post)
data = {:_json => data} unless data.is_a?(Hash)
- data.with_indifferent_access
+ Request::Utils.deep_munge(data).with_indifferent_access
else
false
end
- rescue Exception => e # YAML, XML or Ruby code block errors
+ rescue Exception => e # JSON or Ruby code block errors
logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
raise ParseError.new(e.message, e)
end
- def content_type_from_legacy_post_data_format_header(env)
- if x_post_format = env['HTTP_X_POST_DATA_FORMAT']
- case x_post_format.to_s.downcase
- when 'yaml' then return Mime::YAML
- when 'xml' then return Mime::XML
- end
- end
-
- nil
- end
-
def logger(env)
env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr)
end
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index 53bedaa40a..cbb2d475b1 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -7,11 +7,10 @@ module ActionDispatch
end
def call(env)
- exception = env["action_dispatch.exception"]
status = env["PATH_INFO"][1..-1]
request = ActionDispatch::Request.new(env)
content_type = request.formats.first
- body = { :status => status, :error => exception.message }
+ body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status.to_i, Rack::Utils::HTTP_STATUS_CODES[500]) }
render(status, content_type, body)
end
@@ -19,7 +18,7 @@ module ActionDispatch
private
def render(status, content_type, body)
- format = content_type && "to_#{content_type.to_sym}"
+ format = "to_#{content_type.to_sym}" if content_type
if format && body.respond_to?(format)
render_format(status, content_type, body.public_send(format))
else
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index ec15a2a715..57bc6d5cd0 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -1,23 +1,59 @@
module ActionDispatch
+ # This middleware calculates the IP address of the remote client that is
+ # making the request. It does this by checking various headers that could
+ # contain the address, and then picking the last-set address that is not
+ # on the list of trusted IPs. This follows the precedent set by e.g.
+ # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
+ # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
+ # by @gingerlime. A more detailed explanation of the algorithm is given
+ # at GetIp#calculate_ip.
+ #
+ # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
+ # requires. Some Rack servers simply drop preceding headers, and only report
+ # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
+ # If you are behind multiple proxy servers (like Nginx to HAProxy to Unicorn)
+ # then you should test your Rack server to make sure your data is good.
+ #
+ # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
+ # This middleware assumes that there is at least one proxy sitting around
+ # and setting headers with the client's remote IP address. If you don't use
+ # a proxy, because you are hosted on e.g. Heroku without SSL, any client can
+ # claim to have any IP address by setting the X-Forwarded-For header. If you
+ # care about that, then you need to explicitly drop or ignore those headers
+ # sometime before this middleware runs.
class RemoteIp
- class IpSpoofAttackError < StandardError ; end
+ class IpSpoofAttackError < StandardError; end
- # 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.
+ # The default trusted IPs list simply includes IP addresses that are
+ # guaranteed by the IP specification to be private addresses. Those will
+ # not be the ultimate client IP in production, and so are discarded. See
+ # http://en.wikipedia.org/wiki/Private_network for details.
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
- fc00:: # private IP fc00
- )\.
+ ^127\.0\.0\.1$ | # localhost IPv4
+ ^::1$ | # localhost IPv6
+ ^fc00: | # private IPv6 range fc00
+ ^10\. | # private IPv4 range 10.x.x.x
+ ^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255
+ ^192\.168\. # private IPv4 range 192.168.x.x
}x
attr_reader :check_ip, :proxies
+ # Create a new +RemoteIp+ middleware instance.
+ #
+ # The +check_ip_spoofing+ option is on by default. When on, an exception
+ # is raised if it looks like the client is trying to lie about its own IP
+ # address. It makes sense to turn off this check on sites aimed at non-IP
+ # clients (like WAP devices), or behind proxies that set headers in an
+ # incorrect or confusing way (like AWS ELB).
+ #
+ # The +custom_trusted+ argument can take a regex, which will be used
+ # instead of +TRUSTED_PROXIES+, or a string, which will be used in addition
+ # to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the
+ # middle (or at the beginning) of the X-Forwarded-For list, with your proxy
+ # servers after it. If your proxies aren't removed, pass them in via the
+ # +custom_trusted+ parameter. That way, the middleware will ignore those
+ # IP addresses, and return the one that you want.
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
@app = app
@check_ip = check_ip_spoofing
@@ -31,15 +67,23 @@ module ActionDispatch
end
end
+ # Since the IP address may not be needed, we store the object here
+ # without calculating the IP to keep from slowing down the majority of
+ # requests. For those requests that do need to know the IP, the
+ # GetIp#calculate_ip method will calculate the memoized client IP address.
def call(env)
env["action_dispatch.remote_ip"] = GetIp.new(env, self)
@app.call(env)
end
+ # The GetIp class exists as a way to defer processing of the request data
+ # into an actual IP address. If the ActionDispatch::Request#remote_ip method
+ # is called, this class will calculate the value and then memoize it.
class GetIp
- # IP v4 and v6 (with compression) validation regexp
- # https://gist.github.com/1289635
+ # This constant contains a regular expression that validates every known
+ # form of IP v4 and v6 address, with or without abbreviations, adapted
+ # from {this gist}[https://gist.github.com/gazay/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
(^(
@@ -57,70 +101,84 @@ module ActionDispatch
(([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}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the beginning
(([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
)$)
}x
def initialize(env, middleware)
- @env = env
- @middleware = middleware
- @calculated_ip = false
+ @env = env
+ @check_ip = middleware.check_ip
+ @proxies = middleware.proxies
end
- # Determines originating IP address. REMOTE_ADDR is the standard
- # 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 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
+ # Sort through the various IP address headers, looking for the IP most
+ # likely to be the address of the actual remote client making this
+ # request.
+ #
+ # REMOTE_ADDR will be correct if the request is made directly against the
+ # Ruby process, on e.g. Heroku. When the request is proxied by another
+ # server like HAProxy or Nginx, the IP address that made the original
+ # request will be put in an X-Forwarded-For header. If there are multiple
+ # proxies, that header may contain a list of IPs. Other proxy services
+ # set the Client-Ip header instead, so we check that too.
+ #
+ # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
+ # while the first IP in the list is likely to be the "originating" IP,
+ # it could also have been set by the client maliciously.
+ #
+ # In order to find the first address that is (probably) accurate, we
+ # take the list of IPs, remove known and trusted proxies, and then take
+ # the last address left, which was presumably set by one of those proxies.
def calculate_ip
- 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_ip != client_ip
+ # Set by the Rack web server, this is a single value.
+ remote_addr = ips_from('REMOTE_ADDR').last
+
+ # Could be a CSV list and/or repeated headers that were concatenated.
+ client_ips = ips_from('HTTP_CLIENT_IP').reverse
+ forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse
+
+ # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
+ # If they are both set, it means that this request passed through two
+ # proxies with incompatible IP header conventions, and there is no way
+ # for us to determine which header is the right one after the fact.
+ # Since we have no idea, we give up and explode.
+ should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
+ if should_check_ip && !forwarded_ips.include?(client_ips.last)
# 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}" \
+ raise IpSpoofAttackError, "IP spoofing attack?! " +
+ "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " +
"HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
end
- 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
+ # We assume these things about the IP headers:
+ #
+ # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
+ # - Client-Ip is propagated from the outermost proxy, or is blank
+ # - REMOTE_ADDR will be the IP that made the request to Rack
+ ips = [forwarded_ips, client_ips, remote_addr].flatten.compact
+
+ # If every single IP option is in the trusted list, just return REMOTE_ADDR
+ filter_proxies(ips).first || remote_addr
end
+ # Memoizes the value returned by #calculate_ip and returns it for
+ # ActionDispatch::Request to use.
def to_s
- return @ip if @calculated_ip
- @calculated_ip = true
- @ip = calculate_ip
+ @ip ||= calculate_ip
end
- private
+ protected
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
+ # Split the comma-separated list into an array of strings
+ ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
+ # Only return IPs that are valid according to the regex
+ ips.select{ |ip| ip =~ VALID_IP }
end
- def remove_proxies(ips)
- ips.select { |ip| valid_ip?(ip) && not_a_proxy?(ip) }
+ def filter_proxies(ips)
+ ips.reject { |ip| ip =~ @proxies }
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index 44290445d4..5d1740d0d4 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -18,7 +18,7 @@ module ActionDispatch
def call(env)
env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
- @app.call(env).tap { |status, headers, body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
+ @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 7c12590c49..84df55fd5a 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -26,7 +26,7 @@ module ActionDispatch
def generate_sid
sid = SecureRandom.hex(16)
- sid.encode!('UTF-8')
+ sid.encode!(Encoding::UTF_8)
sid
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 019849ef95..b9eb8036e9 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -4,61 +4,89 @@ require 'rack/session/cookie'
module ActionDispatch
module Session
- # This cookie-based session store is the Rails default. Sessions typically
- # contain at most a user_id and flash message; both fit within the 4K cookie
- # size limit. Cookie-based sessions are dramatically faster than the
- # alternatives.
+ # This cookie-based session store is the Rails default. It is
+ # dramatically faster than the alternatives.
#
- # If you have more than 4K of session data or don't want your data to be
- # visible to the user, pick another session store.
+ # Sessions typically contain at most a user_id and flash message; both fit
+ # within the 4K cookie size limit. A CookieOverflow exception is raised if
+ # you attempt to store more than 4K of data.
#
- # CookieOverflow is raised if you attempt to store more than 4K of data.
+ # The cookie jar used for storage is automatically configured to be the
+ # best possible option given your application's configuration.
#
- # A message digest is included with the cookie to ensure data integrity:
- # a user cannot alter his +user_id+ without knowing the secret key
- # included in the hash. New apps are generated with a pregenerated secret
- # in config/environment.rb. Set your own for old apps you're upgrading.
+ # If you only have secret_token set, your cookies will be signed, but
+ # not encrypted. This means a user cannot alter his +user_id+ without
+ # knowing your app's secret key, but can easily read his +user_id+. This
+ # was the default for Rails 3 apps.
#
- # Session options:
+ # If you have secret_key_base set, your cookies will be encrypted. This
+ # goes a step further than signed cookies in that encrypted cookies cannot
+ # be altered or read by users. This is the default starting in Rails 4.
#
- # * <tt>:secret</tt>: An application-wide key string or block returning a
- # string called per generated digest. The block is called with the
- # CGI::Session instance as an argument. It's important that the secret
- # is not vulnerable to a dictionary attack. Therefore, you should choose
- # a secret consisting of random numbers and letters and more than 30
- # characters.
+ # If you have both secret_token and secret_key base set, your cookies will
+ # be encrypted, and signed cookies generated by Rails 3 will be
+ # transparently read and encrypted to provide a smooth upgrade path.
#
- # :secret => '449fe2e7daee471bffae2fd8dc02313d'
- # :secret => Proc.new { User.current_user.secret_key }
+ # Configure your session store in config/initializers/session_store.rb:
#
- # * <tt>:digest</tt>: The message digest algorithm used to verify session
- # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
- # such as 'MD5', 'RIPEMD160', 'SHA256', etc.
+ # Myapp::Application.config.session_store :cookie_store, key: '_your_app_session'
#
- # To generate a secret key for an existing application, run
- # "rake secret" and set the key in config/initializers/secret_token.rb.
+ # Configure your secret key in config/initializers/secret_token.rb:
+ #
+ # Myapp::Application.config.secret_key_base 'secret key'
+ #
+ # To generate a secret key for an existing application, run `rake secret`.
+ #
+ # If you are upgrading an existing Rails 3 app, you should leave your
+ # existing secret_token in place and simply add the new secret_key_base.
+ # Note that you should wait to set secret_key_base until you have 100% of
+ # your userbase on Rails 4 and are reasonably sure you will not need to
+ # rollback to Rails 3. This is because cookies signed based on the new
+ # secret_key_base in Rails 4 are not backwards compatible with Rails 3.
+ # You are free to leave your existing secret_token in place, not set the
+ # new secret_key_base, and ignore the deprecation warnings until you are
+ # reasonably sure that your upgrade is otherwise complete. Additionally,
+ # you should take care to make sure you are not relying on the ability to
+ # decode signed cookies generated by your app in external applications or
+ # Javascript before upgrading.
#
# Note that changing digest or secret invalidates all existing sessions!
- class CookieStore < Rack::Session::Cookie
+ class CookieStore < Rack::Session::Abstract::ID
include Compatibility
include StaleSessionCheck
include SessionObject
- # Override rack's method
+ def initialize(app, options={})
+ super(app, options.merge!(:cookie_only => true))
+ end
+
def destroy_session(env, session_id, options)
- new_sid = super
+ new_sid = generate_sid unless options[:drop]
# Reset hash and Assign the new session id
env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {}
new_sid
end
+ def load_session(env)
+ stale_session_check! do
+ data = unpacked_cookie_data(env)
+ data = persistent_session_id!(data)
+ [data["session_id"], data]
+ end
+ end
+
private
+ def extract_session_id(env)
+ stale_session_check! do
+ unpacked_cookie_data(env)["session_id"]
+ end
+ end
+
def unpacked_cookie_data(env)
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
stale_session_check! do
- request = ActionDispatch::Request.new(env)
- if data = request.cookie_jar.signed[@key]
+ if data = get_cookie(env)
data.stringify_keys!
end
data || {}
@@ -66,14 +94,28 @@ module ActionDispatch
end
end
+ def persistent_session_id!(data, sid=nil)
+ data ||= {}
+ data["session_id"] ||= sid || generate_sid
+ data
+ end
+
def set_session(env, sid, session_data, options)
session_data["session_id"] = sid
session_data
end
def set_cookie(env, session_id, cookie)
+ cookie_jar(env)[@key] = cookie
+ end
+
+ def get_cookie(env)
+ cookie_jar(env)[@key]
+ end
+
+ def cookie_jar(env)
request = ActionDispatch::Request.new(env)
- request.cookie_jar.signed[@key] = cookie
+ request.cookie_jar.signed_or_encrypted
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 0de10695e0..1d4f0f89a6 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -16,9 +16,9 @@ module ActionDispatch
# catches the exceptions and returns a FAILSAFE_RESPONSE.
class ShowExceptions
FAILSAFE_RESPONSE = [500, { 'Content-Type' => 'text/plain' },
- ["500 Internal Server Error\n" <<
- "If you are the administrator of this website, then please read this web " <<
- "application's log file and/or the web server's log file to find out what " <<
+ ["500 Internal Server Error\n" \
+ "If you are the administrator of this website, then please read this web " \
+ "application's log file and/or the web server's log file to find out what " \
"went wrong."]]
def initialize(app, exceptions_app)
@@ -27,13 +27,13 @@ module ActionDispatch
end
def call(env)
- begin
- response = @app.call(env)
- rescue Exception => exception
- raise exception if env['action_dispatch.show_exceptions'] == false
+ @app.call(env)
+ rescue Exception => exception
+ if env['action_dispatch.show_exceptions'] == false
+ raise exception
+ else
+ render_exception(env, exception)
end
-
- response || render_exception(env, exception)
end
private
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 9098f4e170..999c022535 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -36,8 +36,7 @@ module ActionDispatch
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)
+ headers = { 'Content-Type' => 'text/html', 'Location' => url.to_s }
[301, headers, []]
end
@@ -45,7 +44,7 @@ module ActionDispatch
# http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02
def hsts_headers
if @hsts
- value = "max-age=#{@hsts[:expires]}"
+ value = "max-age=#{@hsts[:expires].to_i}"
value += "; includeSubDomains" if @hsts[:subdomains]
{ 'Strict-Transport-Security' => value }
else
@@ -58,7 +57,7 @@ module ActionDispatch
cookies = cookies.split("\n")
headers['Set-Cookie'] = cookies.map { |cookie|
- if cookie !~ /;\s+secure(;|$)/
+ if cookie !~ /;\s*secure\s*(;|$)/i
"#{cookie}; secure"
else
cookie
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index e3b15b43b9..c6a7d9c415 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -6,7 +6,8 @@ module ActionDispatch
def initialize(root, cache_control)
@root = root.chomp('/')
@compiled_root = /^#{Regexp.escape(root)}/
- @file_server = ::Rack::File.new(@root, cache_control)
+ headers = cache_control && { 'Cache-Control' => cache_control }
+ @file_server = ::Rack::File.new(@root, headers)
end
def match?(path)
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
deleted file mode 100644
index 823f5d25b6..0000000000
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
+++ /dev/null
@@ -1,31 +0,0 @@
-<% unless @exception.blamed_files.blank? %>
- <% if (hide = @exception.blamed_files.length > 8) %>
- <a href="#" onclick="document.getElementById('blame_trace').style.display='block'; return false;">Show blamed files</a>
- <% end %>
- <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%=h @exception.describe_blame %></code></pre>
-<% end %>
-
-<%
- clean_params = @request.filtered_parameters.clone
- clean_params.delete("action")
- clean_params.delete("controller")
-
- request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
-
- def debug_hash(object)
- object.to_hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
- end unless self.class.method_defined?(:debug_hash)
-%>
-
-<h2 style="margin-top: 30px">Request</h2>
-<p><b>Parameters</b>: <pre><%=h request_dump %></pre></p>
-
-<p><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></p>
-<div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div>
-
-<p><a href="#" onclick="document.getElementById('env_dump').style.display='block'; return false;">Show env dump</a></p>
-<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div>
-
-
-<h2 style="margin-top: 30px">Response</h2>
-<p><b>Headers</b>: <pre><%=h defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
new file mode 100644
index 0000000000..db219c8fa9
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
@@ -0,0 +1,34 @@
+<% unless @exception.blamed_files.blank? %>
+ <% if (hide = @exception.blamed_files.length > 8) %>
+ <a href="#" onclick="return toggleTrace()">Toggle blamed files</a>
+ <% end %>
+ <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%= @exception.describe_blame %></code></pre>
+<% end %>
+
+<%
+ clean_params = @request.filtered_parameters.clone
+ clean_params.delete("action")
+ clean_params.delete("controller")
+
+ request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
+
+ def debug_hash(object)
+ object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
+ end unless self.class.method_defined?(:debug_hash)
+%>
+
+<h2 style="margin-top: 30px">Request</h2>
+<p><b>Parameters</b>:</p> <pre><%= request_dump %></pre>
+
+<div class="details">
+ <div class="summary"><a href="#" onclick="return toggleSessionDump()">Toggle session dump</a></div>
+ <div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div>
+</div>
+
+<div class="details">
+ <div class="summary"><a href="#" onclick="return toggleEnvDump()">Toggle env dump</a></div>
+ <div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div>
+</div>
+
+<h2 style="margin-top: 30px">Response</h2>
+<p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb
new file mode 100644
index 0000000000..396768ecee
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb
@@ -0,0 +1,23 @@
+<%
+ clean_params = @request.filtered_parameters.clone
+ clean_params.delete("action")
+ clean_params.delete("controller")
+
+ request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
+
+ def debug_hash(object)
+ object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
+ end unless self.class.method_defined?(:debug_hash)
+%>
+
+Request parameters
+<%= request_dump %>
+
+Session dump
+<%= debug_hash @request.session %>
+
+Env dump
+<%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %>
+
+Response headers
+<%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb
new file mode 100644
index 0000000000..38429cb78e
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb
@@ -0,0 +1,25 @@
+<% if @source_extract %>
+<div class="source">
+<div class="info">
+ Extracted source (around line <strong>#<%= @line_number %></strong>):
+</div>
+<div class="data">
+ <table cellpadding="0" cellspacing="0" class="lines">
+ <tr>
+ <td>
+ <pre class="line_numbers">
+ <% @source_extract.keys.each do |line_number| %>
+<span><%= line_number -%></span>
+ <% end %>
+ </pre>
+ </td>
+<td width="100%">
+<pre>
+<% @source_extract.each do |line, source| -%><div class="line<%= " active" if line == @line_number -%>"><%= source -%></div><% end -%>
+</pre>
+</td>
+ </tr>
+ </table>
+</div>
+</div>
+<% end %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
index 8771b5fd6d..b181909bff 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
@@ -1,10 +1,8 @@
<%
- traces = [
- ["Application Trace", @application_trace],
- ["Framework Trace", @framework_trace],
- ["Full Trace", @full_trace]
- ]
- names = traces.collect {|name, trace| name}
+ traces = { "Application Trace" => @application_trace,
+ "Framework Trace" => @framework_trace,
+ "Full Trace" => @full_trace }
+ names = traces.keys
%>
<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
@@ -12,15 +10,15 @@
<div id="traces">
<% names.each do |name| %>
<%
- show = "document.getElementById('#{name.gsub(/\s/, '-')}').style.display='block';"
- hide = (names - [name]).collect {|hide_name| "document.getElementById('#{hide_name.gsub(/\s/, '-')}').style.display='none';"}
+ show = "show('#{name.gsub(/\s/, '-')}');"
+ hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"}
%>
<a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
<% end %>
<% traces.each do |name, trace| %>
<div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == "Application Trace") ? 'block' : 'none' %>;">
- <pre><code><%=h trace.join "\n" %></code></pre>
+ <pre><code><%= trace.join "\n" %></code></pre>
</div>
<% end %>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb
new file mode 100644
index 0000000000..d4af5c9b06
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb
@@ -0,0 +1,15 @@
+<%
+ traces = { "Application Trace" => @application_trace,
+ "Framework Trace" => @framework_trace,
+ "Full Trace" => @full_trace }
+%>
+
+Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %>
+
+<% traces.each do |name, trace| %>
+<% if trace.any? %>
+<%= name %>
+<%= trace.join("\n") %>
+
+<% end %>
+<% end %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
index 4b9d3141d5..f154021ae6 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
@@ -1,10 +1,16 @@
-<h1>
- <%=h @exception.class.to_s %>
- <% if @request.parameters['controller'] %>
- in <%=h @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
- <% end %>
-</h1>
-<pre><%=h @exception.message %></pre>
+<header>
+ <h1>
+ <%= @exception.class.to_s %>
+ <% if @request.parameters['controller'] %>
+ in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
+ <% end %>
+ </h1>
+</header>
-<%= render :template => "rescues/_trace" %>
-<%= render :template => "rescues/_request_and_response" %>
+<div id="container">
+ <h2><%= h @exception.message %></h2>
+
+ <%= render template: "rescues/_source" %>
+ <%= render template: "rescues/_trace" %>
+ <%= render template: "rescues/_request_and_response" %>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
index 1a308707d1..bc5d03dc10 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
@@ -4,7 +4,11 @@
<meta charset="utf-8" />
<title>Action Controller: Exception caught</title>
<style>
- body { background-color: #fff; color: #333; }
+ body {
+ background-color: #FAFAFA;
+ color: #333;
+ margin: 0px;
+ }
body, p, ol, ul, td {
font-family: helvetica, verdana, arial, sans-serif;
@@ -13,16 +17,134 @@
}
pre {
- background-color: #eee;
- padding: 10px;
font-size: 11px;
white-space: pre-wrap;
}
- a { color: #000; }
+ pre.box {
+ border: 1px solid #EEE;
+ padding: 10px;
+ margin: 0px;
+ width: 958px;
+ }
+
+ header {
+ color: #F0F0F0;
+ background: #C52F24;
+ padding: 0.5em 1.5em;
+ }
+
+ h1 {
+ margin: 0.2em 0;
+ line-height: 1.1em;
+ font-size: 2em;
+ }
+
+ h2 {
+ color: #C52F24;
+ line-height: 25px;
+ }
+
+ .details {
+ border: 1px solid #D0D0D0;
+ border-radius: 4px;
+ margin: 1em 0px;
+ display: block;
+ width: 978px;
+ }
+
+ .summary {
+ padding: 8px 15px;
+ border-bottom: 1px solid #D0D0D0;
+ display: block;
+ }
+
+ .details pre {
+ margin: 5px;
+ border: none;
+ }
+
+ #container {
+ box-sizing: border-box;
+ width: 100%;
+ padding: 0 1.5em;
+ }
+
+ .source * {
+ margin: 0px;
+ padding: 0px;
+ }
+
+ .source {
+ border: 1px solid #D9D9D9;
+ background: #ECECEC;
+ width: 978px;
+ }
+
+ .source pre {
+ padding: 10px 0px;
+ border: none;
+ }
+
+ .source .data {
+ font-size: 80%;
+ overflow: auto;
+ background-color: #FFF;
+ }
+
+ .info {
+ padding: 0.5em;
+ }
+
+ .source .data .line_numbers {
+ background-color: #ECECEC;
+ color: #AAA;
+ padding: 1em .5em;
+ border-right: 1px solid #DDD;
+ text-align: right;
+ }
+
+ .line {
+ padding-left: 10px;
+ }
+
+ .line:hover {
+ background-color: #F6F6F6;
+ }
+
+ .line.active {
+ background-color: #FFCCCC;
+ }
+
+ a { color: #980905; }
a:visited { color: #666; }
- a:hover { color: #fff; background-color:#000; }
+ a:hover { color: #C52F24; }
+
+ <%= yield :style %>
</style>
+
+ <script>
+ var toggle = function(id) {
+ var s = document.getElementById(id).style;
+ s.display = s.display == 'none' ? 'block' : 'none';
+ return false;
+ }
+ var show = function(id) {
+ document.getElementById(id).style.display = 'block';
+ }
+ var hide = function(id) {
+ document.getElementById(id).style.display = 'none';
+ }
+ var toggleTrace = function() {
+ return toggle('blame_trace');
+ }
+ var toggleSessionDump = function() {
+ return toggle('session_dump');
+ }
+ var toggleEnvDump = function() {
+ return toggle('env_dump');
+ }
+ </script>
</head>
<body>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb
deleted file mode 100644
index dbfdf76947..0000000000
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<h1>Template is missing</h1>
-<p><%=h @exception.message %></p>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
new file mode 100644
index 0000000000..5c016e544e
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
@@ -0,0 +1,7 @@
+<header>
+ <h1>Template is missing</h1>
+</header>
+
+<div id="container">
+ <h2><%= h @exception.message %></h2>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb
new file mode 100644
index 0000000000..ae62d9eb02
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb
@@ -0,0 +1,3 @@
+Template is missing
+
+<%= @exception.message %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
deleted file mode 100644
index 8c594c1523..0000000000
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
+++ /dev/null
@@ -1,23 +0,0 @@
-<h1>Routing Error</h1>
-<p><pre><%=h @exception.message %></pre></p>
-<% unless @exception.failures.empty? %>
- <p>
- <h2>Failure reasons:</h2>
- <ol>
- <% @exception.failures.each do |route, reason| %>
- <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
- <% end %>
- </ol>
- </p>
-<% end %>
-<%= render :template => "rescues/_trace" %>
-
-<h2>
- Routes
-</h2>
-
-<p>
- Routes match in priority from top to bottom
-</p>
-
-<p><pre><%= @routes %></pre></p>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
new file mode 100644
index 0000000000..7e9cedb95e
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
@@ -0,0 +1,30 @@
+<header>
+ <h1>Routing Error</h1>
+</header>
+<div id="container">
+ <h2><%= h @exception.message %></h2>
+ <% unless @exception.failures.empty? %>
+ <p>
+ <h2>Failure reasons:</h2>
+ <ol>
+ <% @exception.failures.each do |route, reason| %>
+ <li><code><%= route.inspect.delete('\\') %></code> failed because <%= reason.downcase %></li>
+ <% end %>
+ </ol>
+ </p>
+ <% end %>
+
+ <%= render template: "rescues/_trace" %>
+
+ <% if @routes_inspector %>
+ <h2>
+ Routes
+ </h2>
+
+ <p>
+ Routes match in priority from top to bottom
+ </p>
+
+ <%= @routes_inspector.format(ActionDispatch::Routing::HtmlTableFormatter.new(self)) %>
+ <% end %>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb
new file mode 100644
index 0000000000..f6e4dac1f3
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb
@@ -0,0 +1,11 @@
+Routing Error
+
+<%= @exception.message %>
+<% unless @exception.failures.empty? %>
+Failure reasons:
+<% @exception.failures.each do |route, reason| %>
+ - <%= route.inspect.delete('\\') %></code> failed because <%= reason.downcase %>
+<% end %>
+<% end %>
+
+<%= render template: "rescues/_trace", format: :text %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
deleted file mode 100644
index c658559be9..0000000000
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
+++ /dev/null
@@ -1,17 +0,0 @@
-<h1>
- <%=h @exception.original_exception.class.to_s %> in
- <%=h @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%=h @request.parameters["action"] %>
-</h1>
-
-<p>
- Showing <i><%=h @exception.file_name %></i> where line <b>#<%=h @exception.line_number %></b> raised:
- <pre><code><%=h @exception.message %></code></pre>
-</p>
-
-<p>Extracted source (around line <b>#<%=h @exception.line_number %></b>):
-<pre><code><%=h @exception.source_extract %></code></pre></p>
-
-<p><%=h @exception.sub_template_message %></p>
-
-<%= render :template => "rescues/_trace" %>
-<%= render :template => "rescues/_request_and_response" %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
new file mode 100644
index 0000000000..027a0f5b3e
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
@@ -0,0 +1,43 @@
+<% @source_extract = @exception.source_extract(0, :html) %>
+<header>
+ <h1>
+ <%= @exception.original_exception.class.to_s %> in
+ <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
+ </h1>
+</header>
+
+<div id="container">
+ <p>
+ Showing <i><%= @exception.file_name %></i> where line <b>#<%= @exception.line_number %></b> raised:
+ </p>
+ <pre><code><%= h @exception.message %></code></pre>
+
+ <div class="source">
+ <div class="info">
+ <p>Extracted source (around line <strong>#<%= @exception.line_number %></strong>):</p>
+ </div>
+ <div class="data">
+ <table cellpadding="0" cellspacing="0" class="lines">
+ <tr>
+ <td>
+ <pre class="line_numbers">
+ <% @source_extract.keys.each do |line_number| %>
+<span><%= line_number -%></span>
+ <% end %>
+ </pre>
+ </td>
+<td width="100%">
+<pre>
+<% @source_extract.each do |line, source| -%><div class="line<%= " active" if line == @exception.line_number -%>"><%= source -%></div><% end -%>
+</pre>
+</td>
+ </tr>
+ </table>
+</div>
+</div>
+
+ <p><%= @exception.sub_template_message %></p>
+
+ <%= render template: "rescues/_trace" %>
+ <%= render template: "rescues/_request_and_response" %>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb
new file mode 100644
index 0000000000..5da21d9784
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb
@@ -0,0 +1,8 @@
+<% @source_extract = @exception.source_extract(0, :html) %>
+<%= @exception.original_exception.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
+
+Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised:
+<%= @exception.message %>
+<%= @exception.sub_template_message %>
+<%= render template: "rescues/_trace", format: :text %>
+<%= render template: "rescues/_request_and_response", format: :text %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb
deleted file mode 100644
index 683379da10..0000000000
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<h1>Unknown action</h1>
-<p><%=h @exception.message %></p>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb
new file mode 100644
index 0000000000..259fb2bb3b
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb
@@ -0,0 +1,6 @@
+<header>
+ <h1>Unknown action</h1>
+</header>
+<div id="container">
+ <h2><%= h @exception.message %></h2>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb
new file mode 100644
index 0000000000..83973addcb
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb
@@ -0,0 +1,3 @@
+Unknown action
+
+<%= @exception.message %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb
new file mode 100644
index 0000000000..24e44f31ac
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb
@@ -0,0 +1,16 @@
+<tr class='route_row' data-helper='path'>
+ <td data-route-name='<%= route[:name] %>'>
+ <% if route[:name].present? %>
+ <%= route[:name] %><span class='helper'>_path</span>
+ <% end %>
+ </td>
+ <td data-route-verb='<%= route[:verb] %>'>
+ <%= route[:verb] %>
+ </td>
+ <td data-route-path='<%= route[:path] %>' data-regexp='<%= route[:regexp] %>'>
+ <%= route[:path] %>
+ </td>
+ <td data-route-reqs='<%= route[:reqs] %>'>
+ <%= route[:reqs] %>
+ </td>
+</tr>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
new file mode 100644
index 0000000000..95461fa693
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -0,0 +1,144 @@
+<% content_for :style do %>
+ #route_table {
+ margin: 0 auto 0;
+ border-collapse: collapse;
+ }
+
+ #route_table td {
+ padding: 0 30px;
+ }
+
+ #route_table tr.bottom th {
+ padding-bottom: 10px;
+ line-height: 15px;
+ }
+
+ #route_table .matched_paths {
+ background-color: LightGoldenRodYellow;
+ }
+
+ #route_table .matched_paths {
+ border-bottom: solid 3px SlateGrey;
+ }
+
+ #path_search {
+ width: 80%;
+ font-size: inherit;
+ }
+<% end %>
+
+<table id='route_table' class='route_table'>
+ <thead>
+ <tr>
+ <th>Helper</th>
+ <th>HTTP Verb</th>
+ <th>Path</th>
+ <th>Controller#Action</th>
+ </tr>
+ <tr class='bottom'>
+ <th><%# Helper %>
+ <%= link_to "Path", "#", 'data-route-helper' => '_path',
+ title: "Returns a relative path (without the http or domain)" %> /
+ <%= link_to "Url", "#", 'data-route-helper' => '_url',
+ title: "Returns an absolute url (with the http and domain)" %>
+ </th>
+ <th><%# HTTP Verb %>
+ </th>
+ <th><%# Path %>
+ <%= search_field(:path, nil, id: 'path_search', placeholder: "Path Match") %>
+ </th>
+ <th><%# Controller#action %>
+ </th>
+ </tr>
+ </thead>
+ <tbody class='matched_paths' id='matched_paths'>
+ </tbody>
+ <tbody>
+ <%= yield %>
+ </tbody>
+</table>
+
+<script type='text/javascript'>
+ function each(elems, func) {
+ if (!elems instanceof Array) { elems = [elems]; }
+ for (var i = 0, len = elems.length; i < len; i++) {
+ func(elems[i]);
+ }
+ }
+
+ function setValOn(elems, val) {
+ each(elems, function(elem) {
+ elem.innerHTML = val;
+ });
+ }
+
+ function onClick(elems, func) {
+ each(elems, function(elem) {
+ elem.onclick = func;
+ });
+ }
+
+ // Enables functionality to toggle between `_path` and `_url` helper suffixes
+ function setupRouteToggleHelperLinks() {
+ var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
+ onClick(toggleLinks, function(){
+ var helperTxt = this.getAttribute("data-route-helper"),
+ helperElems = document.querySelectorAll('[data-route-name] span.helper');
+ setValOn(helperElems, helperTxt);
+ });
+ }
+
+ // takes an array of elements with a data-regexp attribute and
+ // passes their their parent <tr> into the callback function
+ // if the regexp matchs a given path
+ function eachElemsForPath(elems, path, func) {
+ each(elems, function(e){
+ var reg = e.getAttribute("data-regexp");
+ if (path.match(RegExp(reg))) {
+ func(e.parentNode.cloneNode(true));
+ }
+ })
+ }
+
+ // Ensure path always starts with a slash "/" and remove params or fragments
+ function sanitizePath(path) {
+ var path = path.charAt(0) == '/' ? path : "/" + path;
+ return path.replace(/\#.*|\?.*/, '');
+ }
+
+ // Enables path search functionality
+ function setupMatchPaths() {
+ var regexpElems = document.querySelectorAll('#route_table [data-regexp]'),
+ pathElem = document.querySelector('#path_search'),
+ selectedSection = document.querySelector('#matched_paths'),
+ noMatchText = '<tr><th colspan="4">None</th></tr>';
+
+
+ // Remove matches if no path is present
+ pathElem.onblur = function(e) {
+ if (pathElem.value === "") selectedSection.innerHTML = "";
+ }
+
+ // On key press perform a search for matching paths
+ pathElem.onkeyup = function(e){
+ var path = sanitizePath(pathElem.value),
+ defaultText = '<tr><th colspan="4">Paths Matching (' + path + '):</th></tr>';
+
+ // Clear out results section
+ selectedSection.innerHTML= defaultText;
+
+ // Display matches if they exist
+ eachElemsForPath(regexpElems, path, function(e){
+ selectedSection.appendChild(e);
+ });
+
+ // If no match present, tell the user
+ if (selectedSection.innerHTML === defaultText) {
+ selectedSection.innerHTML = selectedSection.innerHTML + noMatchText;
+ }
+ }
+ }
+
+ setupMatchPaths();
+ setupRouteToggleHelperLinks();
+</script>
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 284dd180db..2dfaab3587 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -1,18 +1,21 @@
require "action_dispatch"
module ActionDispatch
- class Railtie < Rails::Railtie
+ class Railtie < Rails::Railtie # :nodoc:
config.action_dispatch = ActiveSupport::OrderedOptions.new
config.action_dispatch.x_sendfile_header = nil
config.action_dispatch.ip_spoofing_check = true
config.action_dispatch.show_exceptions = true
- config.action_dispatch.best_standards_support = true
config.action_dispatch.tld_length = 1
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 = false
+ config.action_dispatch.http_auth_salt = 'http authentication'
+ config.action_dispatch.signed_cookie_salt = 'signed cookie'
+ config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie'
+ config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie'
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index a05a23d953..6d911a75f1 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -63,6 +63,10 @@ module ActionDispatch
@exists = nil # we haven't checked yet
end
+ def id
+ options[:id]
+ end
+
def options
Options.find @env
end
@@ -123,6 +127,18 @@ module ActionDispatch
@delegate.delete key.to_s
end
+ def fetch(key, default=nil)
+ if self.key?(key)
+ self[key]
+ elsif default
+ self[key] = default
+ elsif block_given?
+ self[key] = yield(key)
+ else
+ raise KeyError
+ end
+ end
+
def inspect
if loaded?
super
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
new file mode 100644
index 0000000000..8b43cdada8
--- /dev/null
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -0,0 +1,24 @@
+module ActionDispatch
+ class Request < Rack::Request
+ class Utils # :nodoc:
+ class << self
+ # Remove nils from the params hash
+ def deep_munge(hash)
+ hash.each do |k, v|
+ case v
+ when Array
+ v.grep(Hash) { |x| deep_munge(x) }
+ v.compact!
+ hash[k] = nil if v.empty?
+ when Hash
+ deep_munge(v)
+ end
+ end
+
+ hash
+ end
+ end
+ end
+ end
+end
+
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 29090882a5..a9ac2bce1d 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -61,7 +61,7 @@ module ActionDispatch
# directory by using +scope+. +scope+ takes additional options which
# apply to all enclosed routes.
#
- # scope :path => "/cpanel", :as => 'admin' do
+ # scope path: "/cpanel", as: 'admin' do
# resources :posts, :comments
# end
#
@@ -69,6 +69,22 @@ module ActionDispatch
# <tt>Routing::Mapper::Scoping#namespace</tt>, and
# <tt>Routing::Mapper::Scoping#scope</tt>.
#
+ # == Non-resourceful routes
+ #
+ # For routes that don't fit the <tt>resources</tt> mold, you can use the HTTP helper
+ # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
+ #
+ # get 'post/:id' => 'posts#show'
+ # post 'post/:id' => 'posts#create_comment'
+ #
+ # If your route needs to respond to more than one HTTP method (or all methods) then using the
+ # <tt>:via</tt> option on <tt>match</tt> is preferable.
+ #
+ # match 'post/:id' => 'posts#show', via: [:get, :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.
+ #
# == Named routes
#
# Routes can be named by passing an <tt>:as</tt> option,
@@ -78,22 +94,22 @@ module ActionDispatch
# Example:
#
# # In routes.rb
- # match '/login' => 'accounts#login', :as => 'login'
+ # get '/login' => 'accounts#login', as: 'login'
#
# # With render, redirect_to, tests, etc.
# redirect_to login_url
#
# Arguments can be passed as well.
#
- # redirect_to show_item_path(:id => 25)
+ # redirect_to show_item_path(id: 25)
#
# Use <tt>root</tt> as a shorthand to name a route for the root path "/".
#
# # In routes.rb
- # root :to => 'blogs#index'
+ # root to: 'blogs#index'
#
# # would recognize http://www.example.com/ as
- # params = { :controller => 'blogs', :action => 'index' }
+ # params = { controller: 'blogs', action: 'index' }
#
# # and provide these named routes
# root_url # => 'http://www.example.com/'
@@ -104,126 +120,70 @@ module ActionDispatch
#
# # In routes.rb
# controller :blog do
- # match 'blog/show' => :list
- # match 'blog/delete' => :delete
- # match 'blog/edit/:id' => :edit
+ # get 'blog/show' => :list
+ # get 'blog/delete' => :delete
+ # get 'blog/edit/:id' => :edit
# end
#
# # provides named routes for show, delete, and edit
- # link_to @article.title, show_path(:id => @article.id)
+ # link_to @article.title, show_path(id: @article.id)
#
# == Pretty URLs
#
# Routes can generate pretty URLs. For example:
#
- # match '/articles/:year/:month/:day' => 'articles#find_by_id', :constraints => {
- # :year => /\d{4}/,
- # :month => /\d{1,2}/,
- # :day => /\d{1,2}/
+ # get '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
+ # year: /\d{4}/,
+ # month: /\d{1,2}/,
+ # day: /\d{1,2}/
# }
#
# Using the route above, the URL "http://localhost:3000/articles/2005/11/06"
# maps to
#
- # params = {:year => '2005', :month => '11', :day => '06'}
+ # params = {year: '2005', month: '11', day: '06'}
#
# == Regular Expressions and parameters
# You can specify a regular expression to define a format for a parameter.
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show, :constraints => {
- # :postalcode => /\d{5}(-\d{4})?/
+ # get 'geocode/:postalcode' => :show, constraints: {
+ # postalcode: /\d{5}(-\d{4})?/
# }
#
# Constraints can include the 'ignorecase' and 'extended syntax' regular
# expression modifiers:
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show, :constraints => {
- # :postalcode => /hx\d\d\s\d[a-z]{2}/i
+ # get 'geocode/:postalcode' => :show, constraints: {
+ # postalcode: /hx\d\d\s\d[a-z]{2}/i
# }
# end
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show, :constraints => {
- # :postalcode => /# Postcode format
+ # get 'geocode/:postalcode' => :show, constraints: {
+ # postalcode: /# Postcode format
# \d{5} #Prefix
# (-\d{4})? #Suffix
# /x
# }
# end
#
- # Using the multiline match modifier will raise an +ArgumentError+.
+ # Using the multiline modifier will raise an +ArgumentError+.
# Encoding regular expression modifiers are silently ignored. The
# match will always use the default encoding or ASCII.
#
- # == Default route
- #
- # Consider the following route, which you will find commented out at the
- # bottom of your generated <tt>config/routes.rb</tt>:
- #
- # match ':controller(/:action(/:id))(.:format)'
- #
- # This route states that it expects requests to consist of a
- # <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in
- # turn is followed optionally by an <tt>:id</tt>, which in turn is followed
- # optionally by a <tt>:format</tt>.
- #
- # Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end
- # up with:
- #
- # params = { :controller => 'blog',
- # :action => 'edit',
- # :id => '22'
- # }
- #
- # By not relying on default routes, you improve the security of your
- # application since not all controller actions, which includes actions you
- # might add at a later time, are exposed by default.
- #
- # == 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
- #
- # 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.
- #
- # === 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>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
- #
- # Examples:
- #
- # get 'post/:id' => 'posts#show'
- # 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
- # <tt>:via</tt> option on <tt>match</tt> is preferable.
- #
# == External redirects
#
# You can redirect any path to another path using the redirect helper in your router:
#
- # match "/stories" => redirect("/posts")
+ # get "/stories" => redirect("/posts")
#
# == Unicode character routes
#
# You can specify unicode character routes in your router:
#
- # match "こんにちは" => "welcome#index"
+ # get "こんにちは" => "welcome#index"
#
# == Routing to Rack Applications
#
@@ -231,7 +191,7 @@ module ActionDispatch
# index action in the PostsController, you can specify any Rack application
# as the endpoint for a matcher:
#
- # match "/application.js" => Sprockets
+ # get "/application.js" => Sprockets
#
# == Reloading routes
#
@@ -249,7 +209,7 @@ module ActionDispatch
# === +assert_routing+
#
# def test_movie_route_properly_splits
- # opts = {:controller => "plugin", :action => "checkout", :id => "2"}
+ # opts = {controller: "plugin", action: "checkout", id: "2"}
# assert_routing "plugin/checkout/2", opts
# end
#
@@ -258,7 +218,7 @@ module ActionDispatch
# === +assert_recognizes+
#
# def test_route_has_options
- # opts = {:controller => "plugin", :action => "show", :id => "12"}
+ # opts = {controller: "plugin", action: "show", id: "12"}
# assert_recognizes opts, "/plugins/show/12"
# end
#
@@ -286,11 +246,13 @@ module ActionDispatch
# Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>.
#
module Routing
- autoload :Mapper, 'action_dispatch/routing/mapper'
- autoload :RouteSet, 'action_dispatch/routing/route_set'
- autoload :RoutesProxy, 'action_dispatch/routing/routes_proxy'
- autoload :UrlFor, 'action_dispatch/routing/url_for'
- autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
+ extend ActiveSupport::Autoload
+
+ autoload :Mapper
+ autoload :RouteSet
+ autoload :RoutesProxy
+ autoload :UrlFor
+ autoload :PolymorphicRoutes
SEPARATORS = %w( / . ? ) #:nodoc:
HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index c18dc94d4f..cffb814e1e 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -1,4 +1,5 @@
require 'delegate'
+require 'active_support/core_ext/string/strip'
module ActionDispatch
module Routing
@@ -34,6 +35,23 @@ module ActionDispatch
super.to_s
end
+ def regexp
+ __getobj__.path.to_regexp
+ end
+
+ def json_regexp
+ str = regexp.inspect.
+ sub('\\A' , '^').
+ sub('\\Z' , '$').
+ sub('\\z' , '$').
+ sub(/^\// , '').
+ sub(/\/[a-z]*$/ , '').
+ gsub(/\(\?#.+\)/ , '').
+ gsub(/\(\?-\w+:/ , '(').
+ gsub(/\s/ , '')
+ Regexp.new(str).source
+ end
+
def reqs
@reqs ||= begin
reqs = endpoint
@@ -51,7 +69,7 @@ module ActionDispatch
end
def internal?
- path =~ %r{/rails/info.*|^#{Rails.application.config.assets.prefix}}
+ controller.to_s =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}}
end
def engine?
@@ -61,32 +79,58 @@ module ActionDispatch
##
# This class is just used for displaying route information when someone
- # executes `rake routes`. People should not use this class.
+ # executes `rake routes` or looks at the RoutingError page.
+ # People should not use this class.
class RoutesInspector # :nodoc:
- def initialize
- @engines = Hash.new
+ def initialize(routes)
+ @engines = {}
+ @routes = routes
end
- def format(all_routes, filter = nil)
- if filter
- all_routes = all_routes.select{ |route| route.defaults[:controller] == filter }
+ def format(formatter, filter = nil)
+ routes_to_display = filter_routes(filter)
+
+ routes = collect_routes(routes_to_display)
+
+ if routes.none?
+ formatter.no_routes
+ return formatter.result
+ end
+
+ formatter.header routes
+ formatter.section routes
+
+ @engines.each do |name, engine_routes|
+ formatter.section_title "Routes for #{name}"
+ formatter.section engine_routes
end
- routes = collect_routes(all_routes)
+ formatter.result
+ end
+
+ private
- formatted_routes(routes) +
- formatted_routes_for_engines
+ def filter_routes(filter)
+ if filter
+ @routes.select { |route| route.defaults[:controller] == filter }
+ else
+ @routes
+ end
end
def collect_routes(routes)
- routes = routes.collect do |route|
+ routes.collect do |route|
RouteWrapper.new(route)
end.reject do |route|
route.internal?
end.collect do |route|
collect_engine_routes(route)
- {:name => route.name, :verb => route.verb, :path => route.path, :reqs => route.reqs }
+ { name: route.name,
+ verb: route.verb,
+ path: route.path,
+ reqs: route.reqs,
+ regexp: route.json_regexp }
end
end
@@ -100,21 +144,96 @@ module ActionDispatch
@engines[name] = collect_routes(routes.routes)
end
end
+ end
+
+ class ConsoleFormatter
+ def initialize
+ @buffer = []
+ end
+
+ def result
+ @buffer.join("\n")
+ end
- def formatted_routes_for_engines
- @engines.map do |name, routes|
- ["\nRoutes for #{name}:"] + formatted_routes(routes)
- end.flatten
+ def section_title(title)
+ @buffer << "\n#{title}:"
end
- def formatted_routes(routes)
- name_width = routes.map{ |r| r[:name].length }.max
- verb_width = routes.map{ |r| r[:verb].length }.max
- path_width = routes.map{ |r| r[:path].length }.max
+ def section(routes)
+ @buffer << draw_section(routes)
+ end
+
+ def header(routes)
+ @buffer << draw_header(routes)
+ end
- routes.map do |r|
- "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
+ def no_routes
+ @buffer << <<-MESSAGE.strip_heredoc
+ You don't have any routes defined!
+
+ Please add some routes in config/routes.rb.
+
+ For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html.
+ MESSAGE
+ end
+
+ private
+ def draw_section(routes)
+ name_width, verb_width, path_width = widths(routes)
+
+ routes.map do |r|
+ "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
+ end
end
+
+ def draw_header(routes)
+ name_width, verb_width, path_width = widths(routes)
+
+ "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action"
+ end
+
+ def widths(routes)
+ [routes.map { |r| r[:name].length }.max,
+ routes.map { |r| r[:verb].length }.max,
+ routes.map { |r| r[:path].length }.max]
+ end
+ end
+
+ class HtmlTableFormatter
+ def initialize(view)
+ @view = view
+ @buffer = []
+ end
+
+ def section_title(title)
+ @buffer << %(<tr><th colspan="4">#{title}</th></tr>)
+ end
+
+ def section(routes)
+ @buffer << @view.render(partial: "routes/route", collection: routes)
+ end
+
+ # the header is part of the HTML page, so we don't construct it here.
+ def header(routes)
+ end
+
+ def no_routes
+ @buffer << <<-MESSAGE.strip_heredoc
+ <p>You don't have any routes defined!</p>
+ <ul>
+ <li>Please add some routes in <tt>config/routes.rb</tt>.</li>
+ <li>
+ For more information about routes, please see the Rails guide
+ <a href="http://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>.
+ </li>
+ </ul>
+ MESSAGE
+ end
+
+ def result
+ @view.raw @view.render(layout: "routes/table") {
+ @view.raw @buffer.join("\n")
+ }
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index c5cf413c8f..db9c993590 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -2,12 +2,18 @@ require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/enumerable'
+require 'active_support/core_ext/array/extract_options'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
module ActionDispatch
module Routing
class Mapper
+ URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
+ SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
+ :controller, :action, :path_names, :constraints,
+ :shallow, :blocks, :defaults, :options]
+
class Constraints #:nodoc:
def self.new(app, constraints, request = Rack::Request)
if constraints.any?
@@ -26,15 +32,10 @@ module ActionDispatch
def matches?(env)
req = @request.new(env)
- @constraints.each { |constraint|
- if constraint.respond_to?(:matches?) && !constraint.matches?(req)
- return false
- elsif constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req))
- return false
- end
- }
-
- return true
+ @constraints.all? do |constraint|
+ (constraint.respond_to?(:matches?) && constraint.matches?(req)) ||
+ (constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))
+ end
ensure
req.reset_parameters
end
@@ -50,126 +51,157 @@ module ActionDispatch
end
class Mapping #:nodoc:
- IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
+ IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format]
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- SHORTHAND_REGEX = %r{/[\w/]+$}
WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
- def initialize(set, scope, path, options)
- @set, @scope = set, scope
- @segment_keys = nil
- @options = (@scope[:options] || {}).merge(options)
- @path = normalize_path(path)
- normalize_options!
+ attr_reader :scope, :path, :options, :requirements, :conditions, :defaults
- via_all = @options.delete(:via) if @options[:via] == :all
+ def initialize(set, scope, path, options)
+ @set, @scope, @path, @options = set, scope, path, options
+ @requirements, @conditions, @defaults = {}, {}, {}
- 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
+ normalize_options!
+ normalize_path!
+ normalize_requirements!
+ normalize_conditions!
+ normalize_defaults!
end
def to_route
- [ app, conditions, requirements, defaults, @options[:as], @options[:anchor] ]
+ [ app, conditions, requirements, defaults, options[:as], options[:anchor] ]
end
private
- def normalize_options!
- path_without_format = @path.sub(/\(\.:format\)$/, '')
+ def normalize_path!
+ raise ArgumentError, "path is required" if @path.blank?
+ @path = Mapper.normalize_path(@path)
- if using_match_shorthand?(path_without_format, @options)
- to_shorthand = @options[:to].blank?
- @options[:to] ||= path_without_format.gsub(/\(.*\)/, "")[1..-1].sub(%r{/([^/]*)$}, '#\1')
- end
-
- @options.merge!(default_controller_and_action(to_shorthand))
-
- requirements.each do |name, requirement|
- # segment_keys.include?(k.to_s) || k == :controller
- next unless Regexp === requirement && !constraints[name]
-
- if requirement.source =~ ANCHOR_CHARACTERS_REGEX
- raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
- end
- if requirement.multiline?
- raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
- end
+ if required_format?
+ @path = "#{@path}.:format"
+ elsif optional_format?
+ @path = "#{@path}(.:format)"
end
+ end
- if @options[:constraints].is_a?(Hash)
- (@options[:defaults] ||= {}).reverse_merge!(defaults_from_constraints(@options[:constraints]))
- end
+ def required_format?
+ options[:format] == true
end
- # match "account/overview"
- def using_match_shorthand?(path, options)
- path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX
+ def optional_format?
+ options[:format] != false && !path.include?(':format') && !path.end_with?('/')
end
- def normalize_path(path)
- raise ArgumentError, "path is required" if path.blank?
- path = Mapper.normalize_path(path)
+ def normalize_options!
+ @options.reverse_merge!(scope[:options]) if scope[:options]
+ path_without_format = path.sub(/\(\.:format\)$/, '')
- if path.match(':controller')
- raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module]
+ # Add a constraint for wildcard route to make it non-greedy and match the
+ # optional format part of the route by default
+ if path_without_format.match(WILDCARD_PATH) && @options[:format] != false
+ @options[$1.to_sym] ||= /.+?/
+ end
+
+ if path_without_format.match(':controller')
+ raise ArgumentError, ":controller segment is not allowed within a namespace block" if scope[:module]
# Add a default constraint for :controller path segments that matches namespaced
# controllers with default routes like :controller/:action/:id(.:format), e.g:
# GET /admin/products/show/1
- # => { :controller => 'admin/products', :action => 'show', :id => '1' }
+ # => { controller: 'admin/products', action: 'show', id: '1' }
@options[:controller] ||= /.+?/
end
- # Add a constraint for wildcard route to make it non-greedy and match the
- # optional format part of the route by default
- if path.match(WILDCARD_PATH) && @options[:format] != false
- @options[$1.to_sym] ||= /.+?/
+ @options.merge!(default_controller_and_action)
+ end
+
+ def normalize_requirements!
+ constraints.each do |key, requirement|
+ next unless segment_keys.include?(key) || key == :controller
+ verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
+ @requirements[key] = requirement
end
- if @options[:format] == false
- @options.delete(:format)
- path
- elsif path.include?(":format") || path.end_with?('/')
- path
- elsif @options[:format] == true
- "#{path}.:format"
- else
- "#{path}(.:format)"
+ if options[:format] == true
+ @requirements[:format] ||= /.+/
+ elsif Regexp === options[:format]
+ @requirements[:format] = options[:format]
+ elsif String === options[:format]
+ @requirements[:format] = Regexp.compile(options[:format])
end
end
- def app
- Constraints.new(
- to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults),
- blocks,
- @set.request_class
- )
- end
+ def verify_regexp_requirement(requirement)
+ if requirement.source =~ ANCHOR_CHARACTERS_REGEX
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
+ end
- def conditions
- { :path_info => @path }.merge(constraints).merge(request_method_condition)
+ if requirement.multiline?
+ raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
+ end
end
- def requirements
- @requirements ||= (@options[:constraints].is_a?(Hash) ? @options[:constraints] : {}).tap do |requirements|
- requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
- @options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) }
+ def normalize_defaults!
+ @defaults.merge!(scope[:defaults]) if scope[:defaults]
+ @defaults.merge!(options[:defaults]) if options[:defaults]
+
+ options.each do |key, default|
+ next if Regexp === default || IGNORE_OPTIONS.include?(key)
+ @defaults[key] = default
+ end
+
+ if options[:constraints].is_a?(Hash)
+ options[:constraints].each do |key, default|
+ next unless URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
+ @defaults[key] ||= default
+ end
+ end
+
+ if Regexp === options[:format]
+ @defaults[:format] = nil
+ elsif String === options[:format]
+ @defaults[:format] = options[:format]
end
end
- def defaults
- @defaults ||= (@options[:defaults] || {}).tap do |defaults|
- defaults.reverse_merge!(@scope[:defaults]) if @scope[:defaults]
- @options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) }
+ def normalize_conditions!
+ @conditions.merge!(:path_info => path)
+
+ constraints.each do |key, condition|
+ next if segment_keys.include?(key) || key == :controller
+ @conditions[key] = condition
+ end
+
+ @conditions[:required_defaults] = []
+ options.each do |key, required_default|
+ next if segment_keys.include?(key) || IGNORE_OPTIONS.include?(key)
+ next if Regexp === required_default
+ @conditions[:required_defaults] << key
+ end
+
+ via_all = options.delete(:via) if options[:via] == :all
+
+ if !via_all && options[:via].blank?
+ 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 both GET and POST, add `via: [:get, :post]` option.\n" \
+ "If you want to expose your action to GET, use `get` in the router:\n" \
+ " Instead of: match \"controller#action\"\n" \
+ " Do: get \"controller#action\""
+ raise msg
+ end
+
+ if via = options[:via]
+ list = Array(via).map { |m| m.to_s.dasherize.upcase }
+ @conditions.merge!(:request_method => list)
end
end
- def default_controller_and_action(to_shorthand=nil)
+ def app
+ Constraints.new(endpoint, blocks, @set.request_class)
+ end
+
+ def default_controller_and_action
if to.respond_to?(:call)
{ }
else
@@ -193,14 +225,20 @@ module ActionDispatch
controller = controller.to_s unless controller.is_a?(Regexp)
action = action.to_s unless action.is_a?(Regexp)
- if controller.blank? && segment_keys.exclude?("controller")
+ if controller.blank? && segment_keys.exclude?(:controller)
raise ArgumentError, "missing :controller"
end
- if action.blank? && segment_keys.exclude?("action")
+ if action.blank? && segment_keys.exclude?(:action)
raise ArgumentError, "missing :action"
end
+ if controller.is_a?(String) && controller !~ /\A[a-z_0-9\/]*\z/
+ message = "'#{controller}' is not a supported controller name. This can lead to potential routing problems."
+ message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
+ raise ArgumentError, message
+ end
+
hash = {}
hash[:controller] = controller unless controller.blank?
hash[:action] = action unless action.blank?
@@ -209,54 +247,59 @@ module ActionDispatch
end
def blocks
- constraints = @options[:constraints]
- if constraints.present? && !constraints.is_a?(Hash)
- [constraints]
+ if options[:constraints].present? && !options[:constraints].is_a?(Hash)
+ [options[:constraints]]
else
- @scope[:blocks] || []
+ scope[:blocks] || []
end
end
def constraints
- @constraints ||= requirements.reject { |k, v| segment_keys.include?(k.to_s) || k == :controller }
- end
+ @constraints ||= {}.tap do |constraints|
+ constraints.merge!(scope[:constraints]) if scope[:constraints]
- def request_method_condition
- if via = @options[:via]
- list = Array(via).map { |m| m.to_s.dasherize.upcase }
- { :request_method => list }
- else
- { }
+ options.except(*IGNORE_OPTIONS).each do |key, option|
+ constraints[key] = option if Regexp === option
+ end
+
+ constraints.merge!(options[:constraints]) if options[:constraints].is_a?(Hash)
end
end
def segment_keys
- return @segment_keys if @segment_keys
+ @segment_keys ||= path_pattern.names.map{ |s| s.to_sym }
+ end
- @segment_keys = Journey::Path::Pattern.new(
- Journey::Router::Strexp.compile(@path, requirements, SEPARATORS)
- ).names
+ def path_pattern
+ Journey::Path::Pattern.new(strexp)
+ end
+
+ def strexp
+ Journey::Router::Strexp.compile(path, requirements, SEPARATORS)
+ end
+
+ def endpoint
+ to.respond_to?(:call) ? to : dispatcher
+ end
+
+ def dispatcher
+ Routing::RouteSet::Dispatcher.new(:defaults => defaults)
end
def to
- @options[:to]
+ options[:to]
end
def default_controller
- @options[:controller] || @scope[:controller]
+ options[:controller] || scope[:controller]
end
def default_action
- @options[:action] || @scope[:action]
- end
-
- def defaults_from_constraints(constraints)
- url_keys = [:protocol, :subdomain, :domain, :host, :port]
- constraints.slice(*url_keys).select{ |k, v| v.is_a?(String) || v.is_a?(Fixnum) }
+ options[:action] || scope[:action]
end
end
- # Invokes Rack::Mount::Utils.normalize path and ensure that
+ # Invokes Journey::Router::Utils.normalize_path and ensure that
# (:locale) becomes (/:locale) instead of /(:locale). Except
# for root cases, where the latter is the correct one.
def self.normalize_path(path)
@@ -272,7 +315,7 @@ module ActionDispatch
module Base
# You can specify what Rails should route "/" to with the root method:
#
- # root :to => 'pages#main'
+ # root to: 'pages#main'
#
# For options, see +match+, as +root+ uses it internally.
#
@@ -284,8 +327,7 @@ module ActionDispatch
# 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 = {})
- options = { :to => options } if options.is_a?(String)
- match '/', { :as => :root, :via => :get }.merge(options)
+ match '/', { :as => :root, :via => :get }.merge!(options)
end
# Matches a url pattern to one or more routes. Any symbols in a pattern
@@ -299,7 +341,7 @@ module ActionDispatch
# and +:action+ to the controller's action. A pattern can also map
# wildcard segments (globs) to params:
#
- # match 'songs/*category/:title' => 'songs#show'
+ # match 'songs/*category/:title', to: 'songs#show'
#
# # 'songs/rock/classic/stairway-to-heaven' sets
# # params[:category] = 'rock/classic'
@@ -309,16 +351,21 @@ module ActionDispatch
# +:controller+ should be set in options or hash shorthand. Examples:
#
# match 'photos/:id' => 'photos#show'
- # match 'photos/:id', :to => 'photos#show'
- # match 'photos/:id', :controller => 'photos', :action => 'show'
+ # match 'photos/:id', to: 'photos#show'
+ # match 'photos/:id', controller: 'photos', action: 'show'
#
# A pattern can also point to a +Rack+ endpoint i.e. anything that
# responds to +call+:
#
- # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon"] }
- # match 'photos/:id' => PhotoRackApp
+ # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }
+ # match 'photos/:id', to: PhotoRackApp
# # Yes, controller actions are just rack endpoints
- # match 'photos/:id' => PhotosController.action(:show)
+ # match 'photos/:id', to: PhotosController.action(:show)
+ #
+ # Because requesting various HTTP verbs with a single action has security
+ # implications, you must either specify the actions in
+ # the via options or use one of the HtttpHelpers[rdoc-ref:HttpHelpers]
+ # instead +match+
#
# === Options
#
@@ -336,7 +383,7 @@ module ActionDispatch
# [:module]
# The namespace for :controller.
#
- # match 'path' => 'c#a', :module => 'sekret', :controller => 'posts'
+ # match 'path', to: 'c#a', module: 'sekret', controller: 'posts'
# #=> Sekret::PostsController
#
# See <tt>Scoping#namespace</tt> for its scope equivalent.
@@ -347,16 +394,17 @@ module ActionDispatch
# [:via]
# Allowed HTTP verb(s) for route.
#
- # match 'path' => 'c#a', :via => :get
- # match 'path' => 'c#a', :via => [:get, :post]
+ # match 'path', to: 'c#a', via: :get
+ # match 'path', to: 'c#a', via: [:get, :post]
+ # match 'path', to: 'c#a', via: :all
#
# [:to]
# Points to a +Rack+ endpoint. Can be an object that responds to
# +call+ or a string representing a controller's action.
#
- # match 'path', :to => 'controller#action'
- # match 'path', :to => lambda { |env| [200, {}, "Success!"] }
- # match 'path', :to => RackApp
+ # match 'path', to: 'controller#action'
+ # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }
+ # match 'path', to: RackApp
#
# [:on]
# Shorthand for wrapping routes in a specific RESTful context. Valid
@@ -364,27 +412,31 @@ module ActionDispatch
# <tt>resource(s)</tt> block. For example:
#
# resource :bar do
- # match 'foo' => 'c#a', :on => :member, :via => [:get, :post]
+ # match 'foo', to: 'c#a', on: :member, via: [:get, :post]
# end
#
# Is equivalent to:
#
# resource :bar do
# member do
- # match 'foo' => 'c#a', :via => [:get, :post]
+ # match 'foo', to: 'c#a', via: [:get, :post]
# end
# end
#
# [:constraints]
- # Constrains parameters with a hash of regular expressions or an
- # object that responds to <tt>matches?</tt>
+ # Constrains parameters with a hash of regular expressions
+ # or an object that responds to <tt>matches?</tt>. In addition, constraints
+ # other than path can also be specified with any object
+ # that responds to <tt>===</tt> (eg. String, Array, Range, etc.).
#
- # match 'path/:id', :constraints => { :id => /[A-Z]\d{5}/ }
+ # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }
#
- # class Blacklist
+ # match 'json_only', constraints: { format: 'json' }
+ #
+ # class Whitelist
# def matches?(request) request.remote_ip == '1.2.3.4' end
# end
- # match 'path' => 'c#a', :constraints => Blacklist.new
+ # match 'path', to: 'c#a', constraints: Whitelist.new
#
# See <tt>Scoping#constraints</tt> for more examples with its scope
# equivalent.
@@ -393,7 +445,7 @@ module ActionDispatch
# Sets defaults for parameters
#
# # Sets params[:format] to 'jpg' by default
- # match 'path' => 'c#a', :defaults => { :format => 'jpg' }
+ # match 'path', to: 'c#a', defaults: { format: 'jpg' }
#
# See <tt>Scoping#defaults</tt> for its scope equivalent.
#
@@ -402,7 +454,7 @@ module ActionDispatch
# false, the pattern matches any request prefixed with the given path.
#
# # Matches any request starting with 'path'
- # match 'path' => 'c#a', :anchor => false
+ # match 'path', to: 'c#a', anchor: false
#
# [:format]
# Allows you to specify the default value for optional +format+
@@ -412,7 +464,7 @@ module ActionDispatch
# Mount a Rack-based application to be used within the application.
#
- # mount SomeRackApp, :at => "some_route"
+ # mount SomeRackApp, at: "some_route"
#
# Alternatively:
#
@@ -425,7 +477,7 @@ module ActionDispatch
# the helper is either +some_rack_app_path+ or +some_rack_app_url+.
# To customize this helper's name, use the +:as+ option:
#
- # mount(SomeRackApp => "some_route", :as => "exciting")
+ # mount(SomeRackApp => "some_route", as: "exciting")
#
# This will generate the +exciting_path+ and +exciting_url+ helpers
# which can be used to navigate to this mounted app.
@@ -438,7 +490,7 @@ module ActionDispatch
end
options = app
- app, path = options.find { |k, v| k.respond_to?(:call) }
+ app, path = options.find { |k, _| k.respond_to?(:call) }
options.delete(app) if app
end
@@ -464,6 +516,11 @@ module ActionDispatch
end
end
+ # Query if the following named route was already defined.
+ def has_named_route?(name)
+ @set.named_routes.routes[name.to_sym]
+ end
+
private
def app_name(app)
return unless app.respond_to?(:routes)
@@ -491,9 +548,7 @@ module ActionDispatch
prefix_options = options.slice(*_route.segment_keys)
# we must actually delete prefix segment keys to avoid passing them to next url_for
_route.segment_keys.each { |k| options.delete(k) }
- prefix = _routes.url_helpers.send("#{name}_path", prefix_options)
- prefix = '' if prefix == '/'
- prefix
+ _routes.url_helpers.send("#{name}_path", prefix_options)
end
end
end
@@ -501,41 +556,41 @@ module ActionDispatch
module HttpHelpers
# Define a route that only recognizes HTTP GET.
- # For supported arguments, see <tt>Base#match</tt>.
+ # For supported arguments, see match[rdoc-ref:Base#match]
#
- # get 'bacon', :to => 'food#bacon'
+ # get 'bacon', to: 'food#bacon'
def get(*args, &block)
map_method(:get, args, &block)
end
# Define a route that only recognizes HTTP POST.
- # For supported arguments, see <tt>Base#match</tt>.
+ # For supported arguments, see match[rdoc-ref:Base#match]
#
- # post 'bacon', :to => 'food#bacon'
+ # post 'bacon', to: 'food#bacon'
def 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>.
+ # For supported arguments, see match[rdoc-ref:Base#match]
#
- # patch 'bacon', :to => 'food#bacon'
+ # patch 'bacon', to: 'food#bacon'
def patch(*args, &block)
map_method(:patch, args, &block)
end
# Define a route that only recognizes HTTP PUT.
- # For supported arguments, see <tt>Base#match</tt>.
+ # For supported arguments, see match[rdoc-ref:Base#match]
#
- # put 'bacon', :to => 'food#bacon'
+ # put 'bacon', to: 'food#bacon'
def put(*args, &block)
map_method(:put, args, &block)
end
# Define a route that only recognizes HTTP DELETE.
- # For supported arguments, see <tt>Base#match</tt>.
+ # For supported arguments, see match[rdoc-ref:Base#match]
#
- # delete 'broccoli', :to => 'food#broccoli'
+ # delete 'broccoli', to: 'food#broccoli'
def delete(*args, &block)
map_method(:delete, args, &block)
end
@@ -543,8 +598,7 @@ module ActionDispatch
private
def map_method(method, args, &block)
options = args.extract_options!
- options[:via] = method
- options[:path] ||= args.first if args.first.is_a?(String)
+ options[:via] = method
match(*args, options, &block)
self
end
@@ -574,13 +628,13 @@ module ActionDispatch
# If you want to route /posts (without the prefix /admin) to
# <tt>Admin::PostsController</tt>, you could use
#
- # scope :module => "admin" do
+ # scope module: "admin" do
# resources :posts
# end
#
# or, for a single case
#
- # resources :posts, :module => "admin"
+ # resources :posts, module: "admin"
#
# If you want to route /admin/posts to +PostsController+
# (without the Admin:: module prefix), you could use
@@ -591,7 +645,7 @@ module ActionDispatch
#
# or, for a single case
#
- # resources :posts, :path => "/admin/posts"
+ # resources :posts, path: "/admin/posts"
#
# In each of these cases, the named routes remain the same as if you did
# not use scope. In the last case, the following paths map to
@@ -609,7 +663,7 @@ module ActionDispatch
#
# Take the following route definition as an example:
#
- # scope :path => ":account_id", :as => "account" do
+ # scope path: ":account_id", as: "account" do
# resources :projects
# end
#
@@ -621,66 +675,62 @@ module ActionDispatch
#
# Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
#
- # === Examples
- #
# # route /posts (without the prefix /admin) to <tt>Admin::PostsController</tt>
- # scope :module => "admin" do
+ # scope module: "admin" do
# resources :posts
# end
#
# # prefix the posts resource's requests with '/admin'
- # scope :path => "/admin" do
+ # scope path: "/admin" do
# resources :posts
# end
#
# # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
- # scope :as => "sekret" do
+ # scope as: "sekret" do
# resources :posts
# end
def scope(*args)
- options = args.extract_options!
- options = options.dup
-
- options[:path] = args.first if args.first.is_a?(String)
+ options = args.extract_options!.dup
recover = {}
+ options[:path] = args.flatten.join('/') if args.any?
options[:constraints] ||= {}
- unless options[:constraints].is_a?(Hash)
- block, options[:constraints] = options[:constraints], {}
- end
if options[:constraints].is_a?(Hash)
- (options[:defaults] ||= {}).reverse_merge!(defaults_from_constraints(options[:constraints]))
+ defaults = options[:constraints].select do
+ |k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
+ end
+
+ (options[:defaults] ||= {}).reverse_merge!(defaults)
+ else
+ block, options[:constraints] = options[:constraints], {}
end
- scope_options.each do |option|
- if value = options.delete(option)
+ SCOPE_OPTIONS.each do |option|
+ if option == :blocks
+ value = block
+ elsif option == :options
+ value = options
+ else
+ value = options.delete(option)
+ end
+
+ if value
recover[option] = @scope[option]
@scope[option] = send("merge_#{option}_scope", @scope[option], value)
end
end
- recover[:block] = @scope[:blocks]
- @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
-
- recover[:options] = @scope[:options]
- @scope[:options] = merge_options_scope(@scope[:options], options)
-
yield
self
ensure
- scope_options.each do |option|
- @scope[option] = recover[option] if recover.has_key?(option)
- end
-
- @scope[:options] = recover[:options]
- @scope[:blocks] = recover[:block]
+ @scope.merge!(recover)
end
# Scopes routes to a specific controller
#
# controller "food" do
- # match "bacon", :action => "bacon"
+ # match "bacon", action: "bacon"
# end
def controller(controller, options={})
options[:controller] = controller
@@ -711,20 +761,18 @@ module ActionDispatch
# For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see
# <tt>Resources#resources</tt>.
#
- # === Examples
- #
# # accessible through /sekret/posts rather than /admin/posts
- # namespace :admin, :path => "sekret" do
+ # namespace :admin, path: "sekret" do
# resources :posts
# end
#
# # maps to <tt>Sekret::PostsController</tt> rather than <tt>Admin::PostsController</tt>
- # namespace :admin, :module => "sekret" do
+ # namespace :admin, module: "sekret" do
# resources :posts
# end
#
# # generates +sekret_posts_path+ rather than +admin_posts_path+
- # namespace :admin, :as => "sekret" do
+ # namespace :admin, as: "sekret" do
# resources :posts
# end
def namespace(path, options = {})
@@ -738,7 +786,7 @@ module ActionDispatch
# Allows you to constrain the nested routes based on a set of rules.
# For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
#
- # constraints(:id => /\d+\.\d+/) do
+ # constraints(id: /\d+\.\d+/) do
# resources :posts
# end
#
@@ -748,7 +796,7 @@ module ActionDispatch
# You may use this to also restrict other parameters:
#
# resources :posts do
- # constraints(:post_id => /\d+\.\d+/) do
+ # constraints(post_id: /\d+\.\d+/) do
# resources :comments
# end
# end
@@ -757,7 +805,7 @@ module ActionDispatch
#
# Routes can also be constrained to an IP or a certain range of IP addresses:
#
- # constraints(:ip => /192\.168\.\d+\.\d+/) do
+ # constraints(ip: /192\.168\.\d+\.\d+/) do
# resources :posts
# end
#
@@ -794,8 +842,8 @@ module ActionDispatch
end
# Allows you to set default parameters for a route, such as this:
- # defaults :id => 'home' do
- # match 'scoped_pages/(:id)', :to => 'pages#show'
+ # defaults id: 'home' do
+ # match 'scoped_pages/(:id)', to: 'pages#show'
# end
# Using this, the +:id+ parameter here will default to 'home'.
def defaults(defaults = {})
@@ -803,10 +851,6 @@ module ActionDispatch
end
private
- def scope_options #:nodoc:
- @scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
- end
-
def merge_path_scope(parent, child) #:nodoc:
Mapper.normalize_path("#{parent}/#{child}")
end
@@ -831,6 +875,10 @@ module ActionDispatch
child
end
+ def merge_action_scope(parent, child) #:nodoc:
+ child
+ end
+
def merge_path_names_scope(parent, child) #:nodoc:
merge_options_scope(parent, child)
end
@@ -850,7 +898,7 @@ module ActionDispatch
end
def merge_options_scope(parent, child) #:nodoc:
- (parent || {}).except(*override_keys(child)).merge(child)
+ (parent || {}).except(*override_keys(child)).merge!(child)
end
def merge_shallow_scope(parent, child) #:nodoc:
@@ -860,11 +908,6 @@ module ActionDispatch
def override_keys(child) #:nodoc:
child.key?(:only) || child.key?(:except) ? [:only, :except] : []
end
-
- def defaults_from_constraints(constraints)
- url_keys = [:protocol, :subdomain, :domain, :host, :port]
- constraints.slice(*url_keys).select{ |k, v| v.is_a?(String) || v.is_a?(Fixnum) }
- end
end
# Resource routing allows you to quickly declare all of the common routes
@@ -902,7 +945,7 @@ module ActionDispatch
# use dots as part of the +:id+ parameter add a constraint which
# overrides this restriction, e.g:
#
- # resources :articles, :id => /[^\/]+/
+ # resources :articles, id: /[^\/]+/
#
# This allows any character other than a slash as part of your +:id+.
#
@@ -912,6 +955,8 @@ module ActionDispatch
VALID_ON_OPTIONS = [:new, :collection, :member]
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
CANONICAL_ACTIONS = %w(index create new show update destroy)
+ RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
+ RESOURCE_SCOPES = [:resource, :resources]
class Resource #:nodoc:
attr_reader :controller, :path, :options, :param
@@ -1022,18 +1067,18 @@ module ActionDispatch
# a singular resource to map /profile (rather than /profile/:id) to
# the show action:
#
- # resource :geocoder
+ # resource :profile
#
# creates six different routes in your application, all mapping to
- # the +GeoCoders+ controller (note that the controller is named after
+ # the +Profiles+ controller (note that the controller is named after
# the plural):
#
- # GET /geocoder/new
- # POST /geocoder
- # GET /geocoder
- # GET /geocoder/edit
- # PATCH/PUT /geocoder
- # DELETE /geocoder
+ # GET /profile/new
+ # POST /profile
+ # GET /profile
+ # GET /profile/edit
+ # PATCH/PUT /profile
+ # DELETE /profile
#
# === Options
# Takes same options as +resources+.
@@ -1057,15 +1102,7 @@ module ActionDispatch
get :new
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)
- if parent_resource.actions.include?(:update)
- patch :update
- put :update
- end
- delete :destroy if parent_resource.actions.include?(:destroy)
- end
+ set_member_mappings_for_resource
end
self
@@ -1112,43 +1149,43 @@ module ActionDispatch
# Allows you to change the segment component of the +edit+ and +new+ actions.
# Actions not specified are not changed.
#
- # resources :posts, :path_names => { :new => "brand_new" }
+ # resources :posts, path_names: { new: "brand_new" }
#
# The above example will now change /posts/new to /posts/brand_new
#
# [:path]
# Allows you to change the path prefix for the resource.
#
- # resources :posts, :path => 'postings'
+ # resources :posts, path: 'postings'
#
# The resource and all segments will now route to /postings instead of /posts
#
# [:only]
# Only generate routes for the given actions.
#
- # resources :cows, :only => :show
- # resources :cows, :only => [:show, :index]
+ # resources :cows, only: :show
+ # resources :cows, only: [:show, :index]
#
# [:except]
# Generate all routes except for the given actions.
#
- # resources :cows, :except => :show
- # resources :cows, :except => [:show, :index]
+ # resources :cows, except: :show
+ # resources :cows, except: [:show, :index]
#
# [:shallow]
# Generates shallow routes for nested resource(s). When placed on a parent resource,
# generates shallow routes for all nested resources.
#
- # resources :posts, :shallow => true do
+ # resources :posts, shallow: true do
# resources :comments
# end
#
# Is the same as:
#
# resources :posts do
- # resources :comments, :except => [:show, :edit, :update, :destroy]
+ # resources :comments, except: [:show, :edit, :update, :destroy]
# end
- # resources :comments, :only => [:show, :edit, :update, :destroy]
+ # resources :comments, only: [:show, :edit, :update, :destroy]
#
# This allows URLs for resources that otherwise would be deeply nested such
# as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
@@ -1157,9 +1194,9 @@ module ActionDispatch
# [:shallow_path]
# Prefixes nested shallow routes with the specified path.
#
- # scope :shallow_path => "sekret" do
+ # scope shallow_path: "sekret" do
# resources :posts do
- # resources :comments, :shallow => true
+ # resources :comments, shallow: true
# end
# end
#
@@ -1176,9 +1213,9 @@ module ActionDispatch
# [:shallow_prefix]
# Prefixes nested shallow route names with specified prefix.
#
- # scope :shallow_prefix => "sekret" do
+ # scope shallow_prefix: "sekret" do
# resources :posts do
- # resources :comments, :shallow => true
+ # resources :comments, shallow: true
# end
# end
#
@@ -1199,10 +1236,10 @@ module ActionDispatch
# === Examples
#
# # routes call <tt>Admin::PostsController</tt>
- # resources :posts, :module => "admin"
+ # resources :posts, module: "admin"
#
# # resource actions are at /admin/posts.
- # resources :posts, :path => "admin/posts"
+ # resources :posts, path: "admin/posts"
def resources(*resources, &block)
options = resources.extract_options!.dup
@@ -1224,15 +1261,7 @@ module ActionDispatch
get :new
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)
- if parent_resource.actions.include?(:update)
- patch :update
- put :update
- end
- delete :destroy if parent_resource.actions.include?(:destroy)
- end
+ set_member_mappings_for_resource
end
self
@@ -1344,7 +1373,7 @@ module ActionDispatch
def match(path, *rest)
if rest.empty? && Hash === path
options = path
- path, to = options.find { |name, value| name.is_a?(String) }
+ path, to = options.find { |name, _value| name.is_a?(String) }
options[:to] = to
options.delete(path)
paths = [path]
@@ -1359,10 +1388,28 @@ module ActionDispatch
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
end
- paths.each { |_path| decomposed_match(_path, options.dup) }
+ if @scope[:controller] && @scope[:action]
+ options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
+ end
+
+ paths.each do |_path|
+ route_options = options.dup
+ route_options[:path] ||= _path if _path.is_a?(String)
+
+ path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
+ if using_match_shorthand?(path_without_format, route_options)
+ route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
+ end
+
+ decomposed_match(_path, route_options)
+ end
self
end
+ def using_match_shorthand?(path, options)
+ path && (options[:to] || options[:action]).nil? && path =~ %r{/[\w/]+$}
+ end
+
def decomposed_match(path, options) # :nodoc:
if on = options.delete(:on)
send(on) { decomposed_match(path, options) }
@@ -1380,9 +1427,10 @@ module ActionDispatch
def add_route(action, options) # :nodoc:
path = path_for_action(action, options.delete(:path))
+ action = action.to_s.dup
- if action.to_s =~ /^[\w\/]+$/
- options[:action] ||= action unless action.to_s.include?("/")
+ if action =~ /^[\w\/]+$/
+ options[:action] ||= action unless action.include?("/")
else
action = nil
end
@@ -1398,7 +1446,15 @@ module ActionDispatch
@set.add_route(app, conditions, requirements, defaults, as, anchor)
end
- def root(options={})
+ def root(path, options={})
+ if path.is_a?(String)
+ options[:to] = path
+ elsif path.is_a?(Hash) and options.empty?
+ options = path
+ else
+ raise ArgumentError, "must be called with a path and/or options"
+ end
+
if @scope[:scope_level] == :resources
with_scope_level(:root) do
scope(parent_resource.path) do
@@ -1459,11 +1515,11 @@ module ActionDispatch
end
def resource_scope? #:nodoc:
- [:resource, :resources].include? @scope[:scope_level]
+ RESOURCE_SCOPES.include? @scope[:scope_level]
end
def resource_method_scope? #:nodoc:
- [:collection, :member, :new].include? @scope[:scope_level]
+ RESOURCE_METHOD_SCOPES.include? @scope[:scope_level]
end
def with_exclusive_scope
@@ -1583,6 +1639,18 @@ module ActionDispatch
end
end
end
+
+ def set_member_mappings_for_resource
+ member do
+ 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
end
# Routing Concerns allow you to declare common routes that can be reused
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 3d7b8878b8..2fb03f2712 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -34,7 +34,7 @@ module ActionDispatch
# == Prefixed polymorphic helpers
#
# In addition to <tt>polymorphic_url</tt> and <tt>polymorphic_path</tt> methods, a
- # number of prefixed helpers are available as a shorthand to <tt>:action => "..."</tt>
+ # number of prefixed helpers are available as a shorthand to <tt>action: "..."</tt>
# in options. Those are:
#
# * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
@@ -43,7 +43,7 @@ module ActionDispatch
# Example usage:
#
# edit_polymorphic_path(@post) # => "/posts/1/edit"
- # polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
+ # polymorphic_path(@post, format: :pdf) # => "/posts/1.pdf"
#
# == Usage with mounted engines
#
@@ -74,7 +74,18 @@ module ActionDispatch
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
# Default is <tt>:url</tt>.
#
- # ==== Examples
+ # Also includes all the options from <tt>url_for</tt>. These include such
+ # things as <tt>:anchor</tt> or <tt>:trailing_slash</tt>. Example usage
+ # is given below:
+ #
+ # polymorphic_url([blog, post], anchor: 'my_anchor')
+ # # => "http://example.com/blogs/1/posts/1#my_anchor"
+ # polymorphic_url([blog, post], anchor: 'my_anchor', script_name: "/my_app")
+ # # => "http://example.com/my_app/blogs/1/posts/1#my_anchor"
+ #
+ # For all of these options, see the documentation for <tt>url_for</tt>.
+ #
+ # ==== Functionality
#
# # an Article record
# polymorphic_url(record) # same as article_url(record)
@@ -132,7 +143,7 @@ module ActionDispatch
end
# Returns the path component of a URL for the given record. It uses
- # <tt>polymorphic_url</tt> with <tt>:routing_type => :path</tt>.
+ # <tt>polymorphic_url</tt> with <tt>routing_type: :path</tt>.
def polymorphic_path(record_or_hash_or_array, options = {})
polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
end
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 205ff44b1c..3e54c7e71c 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -17,7 +17,7 @@ module ActionDispatch
def call(env)
req = Request.new(env)
- # If any of the path parameters has a invalid encoding then
+ # If any of the path parameters has an invalid encoding then
# raise since it's likely to trigger errors further on.
req.symbolized_path_parameters.each do |key, value|
unless value.valid_encoding?
@@ -30,6 +30,10 @@ module ActionDispatch
uri.host ||= req.host
uri.port ||= req.port unless req.standard_port?
+ if relative_path?(uri.path)
+ uri.path = "#{req.script_name}/#{uri.path}"
+ end
+
body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
headers = {
@@ -48,6 +52,11 @@ module ActionDispatch
def inspect
"redirect(#{status})"
end
+
+ private
+ def relative_path?(path)
+ path && !path.empty? && path[0] != '/'
+ end
end
class PathRedirect < Redirect
@@ -75,12 +84,17 @@ module ActionDispatch
:port => request.optional_port,
:path => request.path,
:params => request.query_parameters
- }.merge options
+ }.merge! options
if !params.empty? && url_options[:path].match(/%\{\w*\}/)
url_options[:path] = (url_options[:path] % escape_path(params))
end
+ if relative_path?(url_options[:path])
+ url_options[:path] = "/#{url_options[:path]}"
+ url_options[:script_name] = request.script_name
+ end
+
ActionDispatch::Http::URL.url_for url_options
end
@@ -98,11 +112,15 @@ module ActionDispatch
# Redirect any path to another path:
#
- # match "/stories" => redirect("/posts")
+ # get "/stories" => redirect("/posts")
#
# You can also use interpolation in the supplied redirect argument:
#
- # match 'docs/:article', :to => redirect('/wiki/%{article}')
+ # get 'docs/:article', to: redirect('/wiki/%{article}')
+ #
+ # Note that if you return a path without a leading slash then the url is prefixed with the
+ # current SCRIPT_NAME environment variable. This is typically '/' but may be different in
+ # a mounted engine or where the application is deployed to a subdirectory of a website.
#
# Alternatively you can use one of the other syntaxes:
#
@@ -111,25 +129,25 @@ module ActionDispatch
# params, depending of how many arguments your block accepts. A string is required as a
# return value.
#
- # match 'jokes/:number', :to => redirect { |params, request|
+ # get 'jokes/:number', to: redirect { |params, request|
# path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp")
# "http://#{request.host_with_port}/#{path}"
# }
#
# 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 block to +get+ 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.
#
- # match 'stores/:name', :to => redirect(:subdomain => 'stores', :path => '/%{name}')
- # match 'stores/:name(*all)', :to => redirect(:subdomain => 'stores', :path => '/%{name}%{all}')
+ # get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}')
+ # get 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}')
#
# Finally, an object which responds to call can be supplied to redirect, allowing you to reuse
# common redirect routes. The call method must accept two arguments, params and request, and return
# a string.
#
- # match 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
+ # get 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
#
def redirect(*args, &block)
options = args.extract_options!
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 060d0bfa2f..b8abdabca5 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,8 +1,10 @@
-require 'journey'
+require 'action_dispatch/journey'
require 'forwardable'
+require 'thread_safe'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/module/remove_method'
+require 'active_support/core_ext/array/extract_options'
require 'action_controller/metal/exceptions'
module ActionDispatch
@@ -20,15 +22,17 @@ module ActionDispatch
def initialize(options={})
@defaults = options[:defaults]
@glob_param = options.delete(:glob)
- @controllers = {}
+ @controller_class_names = ThreadSafe::Cache.new
end
def call(env)
params = env[PARAMETERS_KEY]
- # If any of the path parameters has a invalid encoding then
+ # If any of the path parameters has an invalid encoding then
# raise since it's likely to trigger errors further on.
params.each do |key, value|
+ next unless value.respond_to?(:valid_encoding?)
+
unless value.valid_encoding?
raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}"
end
@@ -68,13 +72,8 @@ module ActionDispatch
private
def controller_reference(controller_param)
- controller_name = "#{controller_param.camelize}Controller"
-
- unless controller = @controllers[controller_param]
- controller = @controllers[controller_param] =
- ActiveSupport::Dependencies.reference(controller_name)
- end
- controller.get(controller_name)
+ const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller"
+ ActiveSupport::Dependencies.constantize(const_name)
end
def dispatch(controller, action, env)
@@ -104,32 +103,18 @@ module ActionDispatch
def initialize
@routes = {}
@helpers = []
- @module = Module.new do
- protected
-
- def handle_positional_args(args, options, segment_keys)
- inner_options = args.extract_options!
- result = options.dup
-
- if args.size > 0
- 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
+ @module = Module.new
end
def helper_names
- self.module.instance_methods.map(&:to_s)
+ @helpers.map(&:to_s)
end
def clear!
+ @helpers.each do |helper|
+ @module.remove_possible_method helper
+ end
+
@routes.clear
@helpers.clear
end
@@ -160,68 +145,145 @@ module ActionDispatch
routes.length
end
- private
+ class UrlHelper # :nodoc:
+ def self.create(route, options)
+ if optimize_helper?(route)
+ OptimizedUrlHelper.new(route, options)
+ else
+ new route, options
+ end
+ end
- def define_named_route_methods(name, route)
- define_url_helper route, :"#{name}_path",
- route.defaults.merge(:use_route => name, :only_path => true)
- define_url_helper route, :"#{name}_url",
- route.defaults.merge(:use_route => name, :only_path => false)
+ def self.optimize_helper?(route)
+ route.requirements.except(:controller, :action).empty?
end
- # Create a url helper allowing ordered parameters to be associated
- # with corresponding dynamic segments, so you can do:
- #
- # foo_url(bar, baz, bang)
- #
- # Instead of:
- #
- # foo_url(:bar => bar, :baz => baz, :bang => bang)
- #
- # Also allow options hash, so you can do:
- #
- # foo_url(bar, baz, bang, :sort_by => 'baz')
- #
- def define_url_helper(route, name, options)
- @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
- remove_possible_method :#{name}
- def #{name}(*args)
- if #{optimize_helper?(route)} && args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation?
- options = #{options.inspect}
- options.merge!(url_options) if respond_to?(: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}))
+ class OptimizedUrlHelper < UrlHelper # :nodoc:
+ attr_reader :arg_size
+
+ def initialize(route, options)
+ super
+ @path_parts = @route.required_parts
+ @arg_size = @path_parts.size
+ @string_route = @route.optimized_path
+ end
+
+ def call(t, args)
+ if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t)
+ options = @options.dup
+ options.merge!(t.url_options) if t.respond_to?(:url_options)
+ options[:path] = optimized_helper(args)
+ ActionDispatch::Http::URL.url_for(options)
+ else
+ super
+ end
+ end
+
+ private
+
+ def optimized_helper(args)
+ path = @string_route.dup
+ klass = Journey::Router::Utils
+
+ @path_parts.zip(args) do |part, arg|
+ parameterized_arg = arg.to_param
+
+ if parameterized_arg.nil? || parameterized_arg.empty?
+ raise_generation_error(args)
end
+
+ # Replace each route parameter
+ # e.g. :id for regular parameter or *path for globbing
+ # with ruby string interpolation code
+ path.gsub!(/(\*|:)#{part}/, klass.escape_fragment(parameterized_arg))
end
- END_EVAL
+ path
+ end
+
+ def optimize_routes_generation?(t)
+ t.send(:optimize_routes_generation?)
+ end
- helpers << name
+ def raise_generation_error(args)
+ parts, missing_keys = [], []
+
+ @path_parts.zip(args) do |part, arg|
+ parameterized_arg = arg.to_param
+
+ if parameterized_arg.nil? || parameterized_arg.empty?
+ missing_keys << part
+ end
+
+ parts << [part, arg]
+ end
+
+ message = "No route matches #{Hash[parts].inspect}"
+ message << " missing required keys: #{missing_keys.inspect}"
+
+ raise ActionController::UrlGenerationError, message
+ end
end
- # Clause check about when we need to generate an optimized helper.
- def optimize_helper?(route) #:nodoc:
- route.requirements.except(:controller, :action).empty?
+ def initialize(route, options)
+ @options = options
+ @segment_keys = route.segment_keys
+ @route = route
end
- # Generates the interpolation to be used in the optimized helper.
- def optimized_helper(route)
- string_route = route.ast.to_s
+ def call(t, args)
+ t.url_for(handle_positional_args(t, args, @options, @segment_keys))
+ end
- while string_route.gsub!(/\([^\)]*\)/, "")
- true
- end
+ def handle_positional_args(t, args, options, keys)
+ inner_options = args.extract_options!
+ result = options.dup
- 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)}")
+ if args.size > 0
+ if args.size < keys.size - 1 # take format into account
+ keys -= t.url_options.keys if t.respond_to?(:url_options)
+ keys -= options.keys
+ end
+ keys -= inner_options.keys
+ result.merge!(Hash[keys.zip(args)])
end
- string_route
+ result.merge!(inner_options)
end
+ end
+
+ private
+ # Create a url helper allowing ordered parameters to be associated
+ # with corresponding dynamic segments, so you can do:
+ #
+ # foo_url(bar, baz, bang)
+ #
+ # Instead of:
+ #
+ # foo_url(bar: bar, baz: baz, bang: bang)
+ #
+ # Also allow options hash, so you can do:
+ #
+ # foo_url(bar, baz, bang, sort_by: 'baz')
+ #
+ def define_url_helper(route, name, options)
+ helper = UrlHelper.create(route, options.dup)
+
+ @module.remove_possible_method name
+ @module.module_eval do
+ define_method(name) do |*args|
+ helper.call self, args
+ end
+ end
+
+ helpers << name
+ end
+
+ def define_named_route_methods(name, route)
+ define_url_helper route, :"#{name}_path",
+ route.defaults.merge(:use_route => name, :only_path => true)
+ define_url_helper route, :"#{name}_url",
+ route.defaults.merge(:use_route => name, :only_path => false)
+ end
end
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
@@ -368,11 +430,19 @@ module ActionDispatch
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
+ if name && named_routes[name]
+ raise ArgumentError, "Invalid route name, already in use: '#{name}' \n" \
+ "You may have defined two routes with the same name using the `:as` option, or " \
+ "you may be overriding a route already defined by a resource with the same naming. " \
+ "For the latter, you can restrict the routes created with `resources` as explained here: \n" \
+ "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
+ end
+
path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
route = @set.add_route(app, path, conditions, defaults, name)
- named_routes[name] = route if name && !named_routes[name]
+ named_routes[name] = route if name
route
end
@@ -419,7 +489,7 @@ module ActionDispatch
end
conditions.keep_if do |k, _|
- k == :action || k == :controller ||
+ k == :action || k == :controller || k == :required_defaults ||
@request_class.public_method_defined?(k) || path_values.include?(k)
end
end
@@ -444,11 +514,12 @@ module ActionDispatch
@recall = recall.dup
@set = set
+ normalize_recall!
normalize_options!
normalize_controller_action_id!
use_relative_controller!
normalize_controller!
- handle_nil_action!
+ normalize_action!
end
def controller
@@ -467,11 +538,16 @@ module ActionDispatch
end
end
+ # Set 'index' as default action for recall
+ def normalize_recall!
+ @recall[:action] ||= 'index'
+ end
+
def normalize_options!
# If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like
#
- # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
+ # generate({controller: 'content'}, {controller: 'content', action: 'show'})
#
# (the above is from the unit tests). In the above case, because the
# controller was explicitly given, but no action, the action is implied to
@@ -482,8 +558,8 @@ module ActionDispatch
options[:controller] = options[:controller].to_s
end
- if options[:action]
- options[:action] = options[:action].to_s
+ if options.key?(:action)
+ options[:action] = (options[:action] || 'index').to_s
end
end
@@ -493,14 +569,12 @@ module ActionDispatch
# :controller, :action or :id is not found, don't pull any
# more keys from the recall.
def normalize_controller_action_id!
- @recall[:action] ||= 'index' if current_controller
-
use_recall_for(:controller) or return
use_recall_for(:action) or return
use_recall_for(:id)
end
- # if the current controller is "foo/bar/baz" and :controller => "baz/bat"
+ # 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? && !controller.start_with?("/")
@@ -516,21 +590,17 @@ module ActionDispatch
@options[:controller] = controller.sub(%r{^/}, '') if controller
end
- # This handles the case of :action => nil being explicitly passed.
- # It is identical to :action => "index"
- def handle_nil_action!
- if options.has_key?(:action) && options[:action].nil?
- options[:action] = 'index'
+ # Move 'index' action from options to recall
+ def normalize_action!
+ if @options[:action] == 'index'
+ @recall[:action] = @options.delete(:action)
end
- recall[:action] = options.delete(:action) if options[:action] == 'index'
end
- # Generates a path from routes, returns [path, params]
- # if no path is returned the formatter will raise Journey::Router::RoutingError
+ # Generates a path from routes, returns [path, params].
+ # If no route is generated the formatter will raise ActionController::UrlGenerationError
def generate
@set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE)
- rescue Journey::Router::RoutingError => e
- raise ActionController::UrlGenerationError, "No route matches #{options.inspect} #{e.message}"
end
def different_controller?
@@ -624,7 +694,7 @@ module ActionDispatch
end
req = @request_class.new(env)
- @router.recognize(req) do |route, matches, params|
+ @router.recognize(req) do |route, _matches, params|
params.merge!(extras)
params.each do |key, value|
if value.is_a?(String)
diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
index 73af5920ed..e2393d3799 100644
--- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb
+++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/array/extract_options'
+
module ActionDispatch
module Routing
class RoutesProxy #:nodoc:
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index d4cd537048..bcebe532bf 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -18,9 +18,9 @@ module ActionDispatch
# of parameters. For example, you've probably had the chance to write code
# like this in one of your views:
#
- # <%= link_to('Click here', :controller => 'users',
- # :action => 'new', :message => 'Welcome!') %>
- # # => "/users/new?message=Welcome%21"
+ # <%= link_to('Click here', controller: 'users',
+ # action: 'new', message: 'Welcome!') %>
+ # # => <a href="/users/new?message=Welcome%21">Click here</a>
#
# link_to, and all other functions that require URL generation functionality,
# actually use ActionController::UrlFor under the hood. And in particular,
@@ -28,22 +28,22 @@ module ActionDispatch
# the same path as the above example by using the following code:
#
# include UrlFor
- # url_for(:controller => 'users',
- # :action => 'new',
- # :message => 'Welcome!',
- # :only_path => true)
+ # url_for(controller: 'users',
+ # action: 'new',
+ # message: 'Welcome!',
+ # only_path: true)
# # => "/users/new?message=Welcome%21"
#
- # Notice the <tt>:only_path => true</tt> part. This is because UrlFor has no
+ # Notice the <tt>only_path: true</tt> part. This is because UrlFor has no
# information about the website hostname that your Rails app is serving. So if you
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
# argument:
#
# include UrlFor
- # url_for(:controller => 'users',
- # :action => 'new',
- # :message => 'Welcome!',
- # :host => 'www.example.com')
+ # url_for(controller: 'users',
+ # action: 'new',
+ # message: 'Welcome!',
+ # host: 'www.example.com')
# # => "http://www.example.com/users/new?message=Welcome%21"
#
# By default, all controllers and views have access to a special version of url_for,
@@ -130,18 +130,23 @@ module ActionDispatch
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
+ # * <tt>:script_name</tt> - Specifies application path relative to domain root. If provided, prepends application path.
#
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
# +url_for+ is forwarded to the Routes module.
#
- # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :port => '8080'
+ # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', port: '8080'
# # => 'http://somehost.org:8080/tasks/testing'
- # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :anchor => 'ok', :only_path => true
+ # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', anchor: 'ok', only_path: true
# # => '/tasks/testing#ok'
- # url_for :controller => 'tasks', :action => 'testing', :trailing_slash => true
+ # url_for controller: 'tasks', action: 'testing', trailing_slash: true
# # => 'http://somehost.org/tasks/testing/'
- # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :number => '33'
+ # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', number: '33'
# # => 'http://somehost.org/tasks/testing?number=33'
+ # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp"
+ # # => 'http://somehost.org/myapp/tasks/testing'
+ # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp", only_path: true
+ # # => '/myapp/tasks/testing'
def url_for(options = nil)
case options
when nil
diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
index 6c61d4e61a..241a39393a 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
@@ -7,20 +7,20 @@ module ActionDispatch
#
# # assert that the referenced method generates the appropriate HTML string
# assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
- def assert_dom_equal(expected, actual, message = "")
+ def assert_dom_equal(expected, actual, message = nil)
expected_dom = HTML::Document.new(expected).root
actual_dom = HTML::Document.new(actual).root
- assert_equal expected_dom, actual_dom
+ assert_equal expected_dom, actual_dom, message
end
# The negated form of +assert_dom_equivalent+.
#
# # assert that the referenced method does not generate the specified HTML string
# assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
- def assert_dom_not_equal(expected, actual, message = "")
+ def assert_dom_not_equal(expected, actual, message = nil)
expected_dom = HTML::Document.new(expected).root
actual_dom = HTML::Document.new(actual).root
- refute_equal expected_dom, actual_dom
+ assert_not_equal expected_dom, actual_dom, message
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index b15e0446de..93f9fab9c2 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -35,11 +35,11 @@ module ActionDispatch
end
# Assert that the redirection options passed in match those of the redirect called in the latest action.
- # This match can be partial, such that <tt>assert_redirected_to(:controller => "weblog")</tt> will also
- # match the redirection of <tt>redirect_to(:controller => "weblog", :action => "show")</tt> and so on.
+ # This match can be partial, such that <tt>assert_redirected_to(controller: "weblog")</tt> will also
+ # match the redirection of <tt>redirect_to(controller: "weblog", action: "show")</tt> and so on.
#
# # assert that the redirection was to the "index" action on the WeblogController
- # assert_redirected_to :controller => "weblog", :action => "index"
+ # assert_redirected_to controller: "weblog", action: "index"
#
# # assert that the redirection was to the named route login_url
# assert_redirected_to login_url
@@ -67,21 +67,11 @@ module ActionDispatch
end
def normalize_argument_to_redirection(fragment)
- 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
+ if Regexp === fragment
+ fragment
+ else
+ @controller._compute_redirect_to_location(fragment)
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 9de545b3c5..496682e8bd 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -1,6 +1,6 @@
require 'uri'
-require 'active_support/core_ext/hash/diff'
require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/string/access'
require 'action_controller/metal/exceptions'
module ActionDispatch
@@ -15,39 +15,41 @@ module ActionDispatch
# and a :method containing the required HTTP verb.
#
# # assert that POSTing to /items will call the create action on ItemsController
- # assert_recognizes({:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post})
+ # assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post})
#
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
# to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
# extras argument, appending the query string on the path directly will not work. For example:
#
# # assert that a path of '/items/list/1?view=print' returns the correct options
- # assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
+ # assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" })
#
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
#
# # Check the default route (i.e., the index action)
- # assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
+ # assert_recognizes({controller: 'items', action: 'index'}, 'items')
#
# # Test a specific action
- # assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list')
+ # assert_recognizes({controller: 'items', action: 'list'}, 'items/list')
#
# # Test an action with a parameter
- # assert_recognizes({:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1')
+ # assert_recognizes({controller: 'items', action: 'destroy', id: '1'}, 'items/destroy/1')
#
# # Test a custom route
- # assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1')
- def assert_recognizes(expected_options, path, extras={}, message=nil)
+ # assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1')
+ def assert_recognizes(expected_options, path, extras={}, msg=nil)
request = recognized_request_for(path, extras)
expected_options = expected_options.clone
expected_options.stringify_keys!
- # FIXME: minitest does object diffs, do we need to have our own?
- message ||= sprintf("The recognized options <%s> did not match <%s>, difference: <%s>",
- request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
- assert_equal(expected_options, request.path_parameters, message)
+ msg = message(msg, "") {
+ sprintf("The recognized options <%s> did not match <%s>, difference:",
+ request.path_parameters, expected_options)
+ }
+
+ assert_equal(expected_options, request.path_parameters, msg)
end
# Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
@@ -57,16 +59,16 @@ module ActionDispatch
# The +defaults+ parameter is unused.
#
# # Asserts that the default action is generated for a route with no action
- # assert_generates "/items", :controller => "items", :action => "index"
+ # assert_generates "/items", controller: "items", action: "index"
#
# # Tests that the list action is properly routed
- # assert_generates "/items/list", :controller => "items", :action => "list"
+ # assert_generates "/items/list", controller: "items", action: "list"
#
# # Tests the generation of a route with a parameter
- # assert_generates "/items/list/1", { :controller => "items", :action => "list", :id => "1" }
+ # assert_generates "/items/list/1", { controller: "items", action: "list", id: "1" }
#
# # Asserts that the generated route gives us our custom route
- # assert_generates "changesets/12", { :controller => 'scm', :action => 'show_diff', :revision => "12" }
+ # assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" }
def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
if expected_path =~ %r{://}
fail_on(URI::InvalidURIError) do
@@ -79,7 +81,7 @@ module ActionDispatch
# Load routes.rb if it hasn't been loaded.
generated_path, extra_keys = @routes.generate_extras(options, defaults)
- found_extras = options.reject {|k, v| ! extra_keys.include? k}
+ found_extras = options.reject { |k, _| ! extra_keys.include? k }
msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
assert_equal(extras, found_extras, msg)
@@ -97,19 +99,19 @@ module ActionDispatch
# +message+ parameter allows you to specify a custom error message to display upon failure.
#
# # Assert a basic route: a controller with the default action (index)
- # assert_routing '/home', :controller => 'home', :action => 'index'
+ # assert_routing '/home', controller: 'home', action: 'index'
#
# # Test a route generated with a specific controller, action, and parameter (id)
- # assert_routing '/entries/show/23', :controller => 'entries', :action => 'show', :id => 23
+ # assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23
#
# # Assert a basic route (controller + default action), with an error message if it fails
- # assert_routing '/store', { :controller => 'store', :action => 'index' }, {}, {}, 'Route for store index not generated properly'
+ # assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly'
#
# # Tests a route, providing a defaults hash
- # assert_routing 'controller/action/9', {:id => "9", :item => "square"}, {:controller => "controller", :action => "action"}, {}, {:item => "square"}
+ # assert_routing 'controller/action/9', {id: "9", item: "square"}, {controller: "controller", action: "action"}, {}, {item: "square"}
#
# # Tests a route with a HTTP method
- # assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" })
+ # assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" })
def assert_routing(path, options, defaults={}, extras={}, message=nil)
assert_recognizes(options, path, extras, message)
@@ -118,7 +120,7 @@ module ActionDispatch
options[:controller] = "/#{controller}"
end
- generate_options = options.dup.delete_if{ |k,v| defaults.key?(k) }
+ generate_options = options.dup.delete_if{ |k, _| defaults.key?(k) }
assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
end
@@ -207,11 +209,9 @@ module ActionDispatch
end
def fail_on(exception_class)
- begin
- yield
- rescue exception_class => e
- raise MiniTest::Assertion, e.message
- end
+ yield
+ rescue exception_class => e
+ raise MiniTest::Assertion, e.message
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 9388d44eef..3253a3d424 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -155,8 +155,6 @@ module ActionDispatch
# If the method is called with a block, once all equality tests are
# evaluated the block is called with an array of all matched elements.
#
- # ==== Examples
- #
# # At least one form element
# assert_select "form"
#
@@ -167,7 +165,7 @@ module ActionDispatch
# assert_select "title", "Welcome"
#
# # Page title is "Welcome" and there is only one title element
- # assert_select "title", {:count => 1, :text => "Welcome"},
+ # assert_select "title", {count: 1, text: "Welcome"},
# "Wrong title or more than one title element"
#
# # Page contains no forms
@@ -379,8 +377,8 @@ module ActionDispatch
node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { Rack::Utils.escapeHTML($1) }
end
- selected = elements.map do |_element|
- text = _element.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join
+ selected = elements.map do |elem|
+ text = elem.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join
root = HTML::Document.new(CGI.unescapeHTML("<encoded>#{text}</encoded>")).root
css_select(root, "encoded:root", &block)[0]
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/tag.rb b/actionpack/lib/action_dispatch/testing/assertions/tag.rb
index 2e38266aba..e5fe30ba82 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/tag.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/tag.rb
@@ -49,44 +49,44 @@ module ActionDispatch
# * if the condition is +false+ or +nil+, the value must be +nil+.
#
# # Assert that there is a "span" tag
- # assert_tag :tag => "span"
+ # assert_tag tag: "span"
#
# # Assert that there is a "span" tag with id="x"
- # assert_tag :tag => "span", :attributes => { :id => "x" }
+ # assert_tag tag: "span", attributes: { id: "x" }
#
# # Assert that there is a "span" tag using the short-hand
# assert_tag :span
#
# # Assert that there is a "span" tag with id="x" using the short-hand
- # assert_tag :span, :attributes => { :id => "x" }
+ # assert_tag :span, attributes: { id: "x" }
#
# # Assert that there is a "span" inside of a "div"
- # assert_tag :tag => "span", :parent => { :tag => "div" }
+ # assert_tag tag: "span", parent: { tag: "div" }
#
# # Assert that there is a "span" somewhere inside a table
- # assert_tag :tag => "span", :ancestor => { :tag => "table" }
+ # assert_tag tag: "span", ancestor: { tag: "table" }
#
# # Assert that there is a "span" with at least one "em" child
- # assert_tag :tag => "span", :child => { :tag => "em" }
+ # assert_tag tag: "span", child: { tag: "em" }
#
# # Assert that there is a "span" containing a (possibly nested)
# # "strong" tag.
- # assert_tag :tag => "span", :descendant => { :tag => "strong" }
+ # assert_tag tag: "span", descendant: { tag: "strong" }
#
# # Assert that there is a "span" containing between 2 and 4 "em" tags
# # as immediate children
- # assert_tag :tag => "span",
- # :children => { :count => 2..4, :only => { :tag => "em" } }
+ # assert_tag tag: "span",
+ # children: { count: 2..4, only: { tag: "em" } }
#
# # Get funky: assert that there is a "div", with an "ul" ancestor
# # and an "li" parent (with "class" = "enum"), and containing a
# # "span" descendant that contains text matching /hello world/
- # assert_tag :tag => "div",
- # :ancestor => { :tag => "ul" },
- # :parent => { :tag => "li",
- # :attributes => { :class => "enum" } },
- # :descendant => { :tag => "span",
- # :child => /hello world/ }
+ # assert_tag tag: "div",
+ # ancestor: { tag: "ul" },
+ # parent: { tag: "li",
+ # attributes: { class: "enum" } },
+ # descendant: { tag: "span",
+ # child: /hello world/ }
#
# <b>Please note</b>: +assert_tag+ and +assert_no_tag+ only work
# with well-formed XHTML. They recognize a few tags as implicitly self-closing
@@ -103,15 +103,15 @@ module ActionDispatch
# exist. (See +assert_tag+ for a full discussion of the syntax.)
#
# # Assert that there is not a "div" containing a "p"
- # assert_no_tag :tag => "div", :descendant => { :tag => "p" }
+ # assert_no_tag tag: "div", descendant: { tag: "p" }
#
# # Assert that an unordered list is empty
- # assert_no_tag :tag => "ul", :descendant => { :tag => "li" }
+ # assert_no_tag tag: "ul", descendant: { tag: "li" }
#
# # Assert that there is not a "p" tag with between 1 to 3 "img" tags
# # as immediate children
- # assert_no_tag :tag => "p",
- # :children => { :count => 1..3, :only => { :tag => "img" } }
+ # assert_no_tag tag: "p",
+ # children: { count: 1..3, only: { tag: "img" } }
def assert_no_tag(*opts)
opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
tag = find_tag(opts)
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 4bd7b69642..9beb30307b 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -3,6 +3,7 @@ require 'uri'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/object/try'
require 'rack/test'
+require 'minitest'
module ActionDispatch
module Integration #:nodoc:
@@ -16,7 +17,7 @@ module ActionDispatch
# a Hash, or a String that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or
# <tt>multipart/form-data</tt>).
- # - +headers+: Additional headers to pass, as a Hash. The headers will be
+ # - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be
# merged into the Rack env hash.
#
# This method returns a Response object, which one can use to
@@ -27,44 +28,38 @@ module ActionDispatch
#
# 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
+ def get(path, parameters = nil, headers_or_env = nil)
+ process :get, path, parameters, headers_or_env
end
# Performs a POST request with the given parameters. See +#get+ for more
# details.
- def post(path, parameters = nil, headers = nil)
- process :post, path, parameters, headers
+ def post(path, parameters = nil, headers_or_env = nil)
+ process :post, path, parameters, headers_or_env
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
+ def patch(path, parameters = nil, headers_or_env = nil)
+ process :patch, path, parameters, headers_or_env
end
# Performs a PUT request with the given parameters. See +#get+ for more
# details.
- def put(path, parameters = nil, headers = nil)
- process :put, path, parameters, headers
+ def put(path, parameters = nil, headers_or_env = nil)
+ process :put, path, parameters, headers_or_env
end
# Performs a DELETE request with the given parameters. See +#get+ for
# more details.
- def delete(path, parameters = nil, headers = nil)
- process :delete, path, parameters, headers
+ def delete(path, parameters = nil, headers_or_env = nil)
+ process :delete, path, parameters, headers_or_env
end
# Performs a HEAD request with the given parameters. See +#get+ for more
# details.
- def head(path, parameters = nil, headers = nil)
- 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
+ def head(path, parameters = nil, headers_or_env = nil)
+ process :head, path, parameters, headers_or_env
end
# Performs an XMLHttpRequest request with the given parameters, mirroring
@@ -73,11 +68,11 @@ module ActionDispatch
# 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.
- def xml_http_request(request_method, path, parameters = nil, headers = nil)
- headers ||= {}
- headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
- process(request_method, path, parameters, headers)
+ def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil)
+ headers_or_env ||= {}
+ headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ process(request_method, path, parameters, headers_or_env)
end
alias xhr :xml_http_request
@@ -94,40 +89,40 @@ module ActionDispatch
# redirect. Note that the redirects are followed until the response is
# not a redirect--this means you may run into an infinite loop if your
# redirect loops back to itself.
- def request_via_redirect(http_method, path, parameters = nil, headers = nil)
- process(http_method, path, parameters, headers)
+ def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil)
+ process(http_method, path, parameters, headers_or_env)
follow_redirect! while redirect?
status
end
# Performs a GET request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def get_via_redirect(path, parameters = nil, headers = nil)
- request_via_redirect(:get, path, parameters, headers)
+ def get_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:get, path, parameters, headers_or_env)
end
# Performs a POST request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def post_via_redirect(path, parameters = nil, headers = nil)
- request_via_redirect(:post, path, parameters, headers)
+ def post_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:post, path, parameters, headers_or_env)
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)
+ def patch_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:patch, path, parameters, headers_or_env)
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)
- request_via_redirect(:put, path, parameters, headers)
+ def put_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:put, path, parameters, headers_or_env)
end
# Performs a DELETE request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def delete_via_redirect(path, parameters = nil, headers = nil)
- request_via_redirect(:delete, path, parameters, headers)
+ def delete_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:delete, path, parameters, headers_or_env)
end
end
@@ -267,12 +262,11 @@ module ActionDispatch
end
# Performs the actual request.
- def process(method, path, parameters = nil, rack_env = nil)
- rack_env ||= {}
+ def process(method, path, parameters = nil, headers_or_env = nil)
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
- host! location.host if location.host
+ host! "#{location.host}:#{location.port}" if location.host
path = location.query ? "#{location.path}?#{location.query}" : location.path
end
@@ -299,11 +293,11 @@ module ActionDispatch
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
"HTTP_ACCEPT" => accept
}
+ # this modifies the passed env directly
+ Http::Headers.new(env).merge!(headers_or_env || {})
session = Rack::Test::Session.new(_mock_session)
- env.merge!(rack_env)
-
# NOTE: rack-test v0.5 doesn't build a default uri correctly
# Make sure requested path is always a full uri
uri = URI.parse('/')
@@ -340,7 +334,7 @@ module ActionDispatch
@integration_session = Integration::Session.new(app)
end
- %w(get post patch put head delete options cookies assigns
+ %w(get post patch put head delete cookies assigns
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless integration_session
@@ -429,8 +423,8 @@ module ActionDispatch
# assert_equal 200, status
#
# # post the login and follow through to the home page
- # post "/login", :username => people(:jamis).username,
- # :password => people(:jamis).password
+ # post "/login", username: people(:jamis).username,
+ # password: people(:jamis).password
# follow_redirect!
# assert_equal 200, status
# assert_equal "/home", path
@@ -463,13 +457,13 @@ module ActionDispatch
# module CustomAssertions
# def enter(room)
# # reference a named route, for maximum internal consistency!
- # get(room_url(:id => room.id))
+ # get(room_url(id: room.id))
# assert(...)
# ...
# end
#
# def speak(room, message)
- # xml_http_request "/say/#{room.id}", :message => message
+ # xml_http_request "/say/#{room.id}", message: message
# assert(...)
# ...
# end
@@ -479,8 +473,8 @@ module ActionDispatch
# open_session do |sess|
# sess.extend(CustomAssertions)
# who = people(who)
- # sess.post "/login", :username => who.username,
- # :password => who.password
+ # sess.post "/login", username: who.username,
+ # password: who.password
# assert(...)
# end
# end
@@ -490,17 +484,9 @@ module ActionDispatch
include ActionController::TemplateAssertions
include ActionDispatch::Routing::UrlFor
- # Use AD::IntegrationTest for acceptance tests
- register_spec_type(/(Acceptance|Integration) ?Test\z/i, self)
-
@@app = nil
def self.app
- if !@@app && !ActionDispatch.test_app
- ActiveSupport::Deprecation.warn "Rails application fallback is deprecated " \
- "and no longer works, please set ActionDispatch.test_app", caller
- end
-
@@app || ActionDispatch.test_app
end
diff --git a/actionpack/lib/action_dispatch/testing/performance_test.rb b/actionpack/lib/action_dispatch/testing/performance_test.rb
deleted file mode 100644
index 13fe693c32..0000000000
--- a/actionpack/lib/action_dispatch/testing/performance_test.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'active_support/testing/performance'
-
-module ActionDispatch
- # An integration test that runs a code profiler on your test methods.
- # Profiling output for combinations of each test method, measurement, and
- # output format are written to your tmp/performance directory.
- class PerformanceTest < ActionDispatch::IntegrationTest
- include ActiveSupport::Testing::Performance
- end
-end
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index 3a6d081721..630e6a9b78 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -6,7 +6,7 @@ module ActionDispatch
module TestProcess
def assigns(key = nil)
assigns = {}.with_indifferent_access
- @controller.view_assigns.each {|k, v| assigns.regular_writer(k, v)}
+ @controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) }
key.nil? ? assigns : assigns[key]
end
@@ -26,17 +26,19 @@ module ActionDispatch
@response.redirect_url
end
- # Shortcut for <tt>Rack::Test::UploadedFile.new(ActionController::TestCase.fixture_path + path, type)</tt>:
+ # Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionController::TestCase.fixture_path, path), type)</tt>:
#
- # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
+ # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png')
#
# To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
# This will not affect other platforms:
#
- # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
+ # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png', :binary)
def fixture_file_upload(path, mime_type = nil, binary = false)
- fixture_path = self.class.fixture_path if self.class.respond_to?(:fixture_path)
- Rack::Test::UploadedFile.new("#{fixture_path}#{path}", mime_type, binary)
+ if self.class.respond_to?(:fixture_path) && self.class.fixture_path
+ path = File.join(self.class.fixture_path, path)
+ end
+ Rack::Test::UploadedFile.new(path, mime_type, binary)
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index c63778f870..57c678843b 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -3,7 +3,11 @@ require 'rack/utils'
module ActionDispatch
class TestRequest < Request
- DEFAULT_ENV = Rack::MockRequest.env_for('/')
+ DEFAULT_ENV = Rack::MockRequest.env_for('/',
+ 'HTTP_HOST' => 'test.host',
+ 'REMOTE_ADDR' => '0.0.0.0',
+ 'HTTP_USER_AGENT' => 'Rails Testing'
+ )
def self.new(env = {})
super
@@ -12,10 +16,6 @@ module ActionDispatch
def initialize(env = {})
env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
super(default_env.merge(env))
-
- self.host = 'test.host'
- self.remote_addr = '0.0.0.0'
- self.user_agent = 'Rails Testing'
end
def request_method=(method)
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index 39c7faf740..ad5acd8080 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2012 David Heinemeier Hansson
+# Copyright (c) 2004-2013 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index 94cbc8e478..fd08f392aa 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,10 +1,11 @@
module ActionPack
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta"
+ # Returns the version of the currently loaded ActionPack as a Gem::Version
+ def self.version
+ Gem::Version.new "4.1.0.beta"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActionPack.version.segments
+ STRING = ActionPack.version.to_s
end
end
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
deleted file mode 100644
index 091b0d8cd2..0000000000
--- a/actionpack/lib/action_view.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-#--
-# Copyright (c) 2004-2012 David Heinemeier Hansson
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#++
-
-require 'active_support'
-require 'active_support/rails'
-require 'action_pack'
-
-module ActionView
- extend ActiveSupport::Autoload
-
- eager_autoload do
- autoload :AssetPaths
- autoload :Base
- autoload :Context
- autoload :CompiledTemplates, "action_view/context"
- autoload :Digestor
- autoload :Helpers
- autoload :LookupContext
- autoload :PathSet
- autoload :RecordIdentifier
- autoload :RoutingUrlFor
- autoload :Template
-
- autoload_under "renderer" do
- autoload :Renderer
- autoload :AbstractRenderer
- autoload :PartialRenderer
- autoload :TemplateRenderer
- autoload :StreamingTemplateRenderer
- end
-
- autoload_at "action_view/template/resolver" do
- autoload :Resolver
- autoload :PathResolver
- autoload :FileSystemResolver
- autoload :OptimizedFileSystemResolver
- autoload :FallbackFileSystemResolver
- end
-
- autoload_at "action_view/buffers" do
- autoload :OutputBuffer
- autoload :StreamingBuffer
- end
-
- autoload_at "action_view/flows" do
- autoload :OutputFlow
- autoload :StreamingFlow
- end
-
- autoload_at "action_view/template/error" do
- autoload :MissingTemplate
- autoload :ActionViewError
- autoload :EncodingError
- autoload :MissingRequestError
- autoload :TemplateError
- autoload :WrongEncodingError
- end
- end
-
- autoload :TestCase
-
- ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
-
- def self.eager_load!
- super
- ActionView::Template.eager_load!
- end
-end
-
-require 'active_support/core_ext/string/output_safety'
-
-ActiveSupport.on_load(:i18n) do
- I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"
-end
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
deleted file mode 100644
index 4bbb31b3ee..0000000000
--- a/actionpack/lib/action_view/asset_paths.rb
+++ /dev/null
@@ -1,143 +0,0 @@
-require 'zlib'
-require 'active_support/core_ext/file'
-
-module ActionView
- class AssetPaths #:nodoc:
- URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}
-
- attr_reader :config, :controller
-
- def initialize(config, controller = nil)
- @config = config
- @controller = controller
- end
-
- # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched.
- # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
- # roots. Rewrite the asset path for cache-busting asset ids. Include
- # asset host, if configured, with the correct request protocol.
- #
- # When :relative (default), the protocol will be determined by the client using current protocol
- # When :request, the protocol will be the request protocol
- # Otherwise, the protocol is used (E.g. :http, :https, etc)
- def compute_public_path(source, dir, options = {})
- source = source.to_s
- return source if is_uri?(source)
-
- source = rewrite_extension(source, dir, options[:ext]) if options[:ext]
- source = rewrite_asset_path(source, dir, options)
- source = rewrite_relative_url_root(source, relative_url_root)
- source = rewrite_host_and_protocol(source, options[:protocol])
- source
- end
-
- # Return the filesystem path for the source
- def compute_source_path(source, dir, ext)
- source = rewrite_extension(source, dir, ext) if ext
-
- sources = []
- sources << config.assets_dir
- sources << dir unless source[0] == ?/
- sources << source
-
- File.join(sources)
- end
-
- def is_uri?(path)
- path =~ URI_REGEXP
- end
-
- private
-
- def rewrite_extension(source, dir, ext)
- raise NotImplementedError
- end
-
- def rewrite_asset_path(source, path = nil)
- raise NotImplementedError
- end
-
- def rewrite_relative_url_root(source, relative_url_root)
- relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source
- end
-
- def has_request?
- controller.respond_to?(:request)
- end
-
- def rewrite_host_and_protocol(source, protocol = nil)
- host = compute_asset_host(source)
- if host && !is_uri?(host)
- if (protocol || default_protocol) == :request && !has_request?
- host = nil
- else
- host = "#{compute_protocol(protocol)}#{host}"
- end
- end
- host ? "#{host}#{source}" : source
- end
-
- def compute_protocol(protocol)
- protocol ||= default_protocol
- case protocol
- when :relative
- "//"
- when :request
- unless @controller
- invalid_asset_host!("The protocol requested was :request. Consider using :relative instead.")
- end
- @controller.request.protocol
- else
- "#{protocol}://"
- end
- end
-
- def default_protocol
- @config.default_asset_host_protocol || (has_request? ? :request : :relative)
- end
-
- def invalid_asset_host!(help_message)
- raise ActionView::MissingRequestError, "This asset host cannot be computed without a request in scope. #{help_message}"
- end
-
- # Pick an asset host for this source. Returns +nil+ if no host is set,
- # the host if no wildcard is set, the host interpolated with the
- # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
- # or the value returned from invoking call on an object responding to call
- # (proc or otherwise).
- def compute_asset_host(source)
- if host = asset_host_config
- if host.respond_to?(:call)
- args = [source]
- arity = arity_of(host)
- if (arity > 1 || arity < -2) && !has_request?
- invalid_asset_host!("Remove the second argument to your asset_host Proc if you do not need the request, or make it optional.")
- end
- args << current_request if (arity > 1 || arity < 0) && has_request?
- host.call(*args)
- else
- (host =~ /%d/) ? host % (Zlib.crc32(source) % 4) : host
- end
- end
- end
-
- def relative_url_root
- config.relative_url_root || current_request.try(:script_name)
- end
-
- def asset_host_config
- config.asset_host
- end
-
- # Returns the current request if one exists.
- def current_request
- controller.request if has_request?
- end
-
- # Returns the arity of a callable
- def arity_of(callable)
- callable.respond_to?(:arity) ? callable.arity : callable.method(:call).arity
- end
-
- end
-end
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
deleted file mode 100644
index 3464ec523e..0000000000
--- a/actionpack/lib/action_view/base.rb
+++ /dev/null
@@ -1,201 +0,0 @@
-require 'active_support/core_ext/module/attr_internal'
-require 'active_support/core_ext/class/attribute_accessors'
-require 'active_support/ordered_options'
-require 'action_view/log_subscriber'
-
-module ActionView #:nodoc:
- # = Action View Base
- #
- # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERb
- # (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> extension then Jim Weirich's Builder::XmlMarkup library is used.
- #
- # == ERB
- #
- # You trigger ERB by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
- # following loop for names:
- #
- # <b>Names of all the people</b>
- # <% @people.each do |person| %>
- # Name: <%= person.name %><br/>
- # <% end %>
- #
- # The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this
- # is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong:
- #
- # <%# WRONG %>
- # Hi, Mr. <% puts "Frodo" %>
- #
- # If you absolutely must write from within a function use +concat+.
- #
- # <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>.
- #
- # === Using sub templates
- #
- # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The
- # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts):
- #
- # <%= render "shared/header" %>
- # Something really specific and terrific
- # <%= render "shared/footer" %>
- #
- # As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the
- # result of the rendering. The output embedding writes it to the current template.
- #
- # But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance
- # variables defined using the regular embedding tags. Like this:
- #
- # <% @page_title = "A Wonderful Hello" %>
- # <%= render "shared/header" %>
- #
- # Now the header can pick up on the <tt>@page_title</tt> variable and use it for outputting a title tag:
- #
- # <title><%= @page_title %></title>
- #
- # === Passing local variables to sub templates
- #
- # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
- #
- # <%= render "shared/header", { :headline => "Welcome", :person => person } %>
- #
- # These can now be accessed in <tt>shared/header</tt> with:
- #
- # Headline: <%= headline %>
- # First name: <%= person.first_name %>
- #
- # If you need to find out whether a certain local variable has been assigned a value in a particular render call,
- # you need to use the following pattern:
- #
- # <% if local_assigns.has_key? :headline %>
- # Headline: <%= headline %>
- # <% end %>
- #
- # Testing using <tt>defined? headline</tt> will not work. This is an implementation restriction.
- #
- # === Template caching
- #
- # By default, Rails will compile each template to a method in order to render it. When you alter a template,
- # Rails will check the file's modification time and recompile it in development mode.
- #
- # == Builder
- #
- # Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object
- # named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension.
- #
- # Here are some basic examples:
- #
- # xml.em("emphasized") # => <em>emphasized</em>
- # xml.em { xml.b("emph & bold") } # => <em><b>emph &amp; bold</b></em>
- # xml.a("A Link", "href" => "http://onestepback.org") # => <a href="http://onestepback.org">A Link</a>
- # xml.target("name" => "compile", "option" => "fast") # => <target option="fast" name="compile"\>
- # # NOTE: order of attributes is not specified.
- #
- # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
- #
- # xml.div do
- # xml.h1(@person.name)
- # xml.p(@person.bio)
- # end
- #
- # would produce something like:
- #
- # <div>
- # <h1>David Heinemeier Hansson</h1>
- # <p>A product of Danish Design during the Winter of '79...</p>
- # </div>
- #
- # A full-length RSS example actually used on Basecamp:
- #
- # xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
- # xml.channel do
- # xml.title(@feed_title)
- # xml.link(@url)
- # xml.description "Basecamp: Recent items"
- # xml.language "en-us"
- # xml.ttl "40"
- #
- # @recent_items.each do |item|
- # xml.item do
- # xml.title(item_title(item))
- # xml.description(item_description(item)) if item_description(item)
- # xml.pubDate(item_pubDate(item))
- # xml.guid(@person.firm.account.url + @recent_items.url(item))
- # xml.link(@person.firm.account.url + @recent_items.url(item))
- #
- # xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
- # end
- # end
- # end
- # end
- #
- # More builder documentation can be found at http://builder.rubyforge.org.
- class Base
- include Helpers, ::ERB::Util, Context
-
- # Specify the proc used to decorate input tags that refer to attributes with errors.
- cattr_accessor :field_error_proc
- @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
-
- # 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>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
-
- # Specify default_formats that can be rendered.
- cattr_accessor :default_formats
-
- class_attribute :_routes
- class_attribute :logger
-
- class << self
- delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'
-
- def cache_template_loading
- ActionView::Resolver.caching?
- end
-
- def cache_template_loading=(value)
- ActionView::Resolver.caching = value
- end
-
- def xss_safe? #:nodoc:
- true
- end
- end
-
- attr_accessor :view_renderer
- attr_internal :config, :assigns
-
- delegate :lookup_context, :to => :view_renderer
- delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context
-
- def assign(new_assigns) # :nodoc:
- @_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
- end
-
- def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc:
- @_config = ActiveSupport::InheritableOptions.new
-
- if context.is_a?(ActionView::Renderer)
- @view_renderer = context
- else
- lookup_context = context.is_a?(ActionView::LookupContext) ?
- context : ActionView::LookupContext.new(context)
- lookup_context.formats = formats if formats
- lookup_context.prefixes = controller._prefixes if controller
- @view_renderer = ActionView::Renderer.new(lookup_context)
- end
-
- assign(assigns)
- assign_controller(controller)
- _prepare_context
- end
-
- ActiveSupport.run_load_hooks(:action_view, self)
- end
-end
diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb
deleted file mode 100644
index 2372d3c433..0000000000
--- a/actionpack/lib/action_view/buffers.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'active_support/core_ext/string/output_safety'
-
-module ActionView
- class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
- def initialize(*)
- super
- encode!
- end
-
- def <<(value)
- super(value.to_s)
- end
- alias :append= :<<
- alias :safe_append= :safe_concat
- end
-
- class StreamingBuffer #:nodoc:
- def initialize(block)
- @block = block
- end
-
- def <<(value)
- value = value.to_s
- value = ERB::Util.h(value) unless value.html_safe?
- @block.call(value)
- end
- alias :concat :<<
- alias :append= :<<
-
- def safe_concat(value)
- @block.call(value.to_s)
- end
- alias :safe_append= :safe_concat
-
- def html_safe?
- true
- end
-
- def html_safe
- self
- end
- end
-end
diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb
deleted file mode 100644
index ee263df484..0000000000
--- a/actionpack/lib/action_view/context.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module ActionView
- module CompiledTemplates #:nodoc:
- # holds compiled template code
- end
-
- # = Action View Context
- #
- # Action View contexts are supplied to Action Controller to render a template.
- # The default Action View context is ActionView::Base.
- #
- # In order to work with ActionController, a Context must just include this module.
- # The initialization of the variables used by the context (@output_buffer, @view_flow,
- # and @virtual_path) is responsibility of the object that includes this module
- # (although you can call _prepare_context defined below).
- module Context
- include CompiledTemplates
- attr_accessor :output_buffer, :view_flow
-
- # Prepares the context by setting the appropriate instance variables.
- # :api: plugin
- def _prepare_context
- @view_flow = OutputFlow.new
- @output_buffer = nil
- @virtual_path = nil
- end
-
- # Encapsulates the interaction with the view flow so it
- # returns the correct buffer on +yield+. This is usually
- # overwritten by helpers to add more behavior.
- # :api: plugin
- def _layout_for(name=nil)
- name ||= :layout
- view_flow.get(name).html_safe
- end
- end
-end
diff --git a/actionpack/lib/action_view/digestor.rb b/actionpack/lib/action_view/digestor.rb
deleted file mode 100644
index f5852dbe73..0000000000
--- a/actionpack/lib/action_view/digestor.rb
+++ /dev/null
@@ -1,104 +0,0 @@
-module ActionView
- class Digestor
- EXPLICIT_DEPENDENCY = /# Template Dependency: ([^ ]+)/
-
- # Matches:
- # render partial: "comments/comment", collection: commentable.comments
- # render "comments/comments"
- # render 'comments/comments'
- # render('comments/comments')
- #
- # render(@topic) => render("topics/topic")
- # render(topics) => render("topics/topic")
- # render(message.topics) => render("topics/topic")
- RENDER_DEPENDENCY = /
- render\s* # render, followed by optional whitespace
- \(? # start an optional parenthesis for the render call
- (partial:|:partial\s+=>)?\s* # naming the partial, used with collection -- 1st capture
- ([@a-z"'][@a-z_\/\."']+) # the template name itself -- 2nd capture
- /x
-
- cattr_reader(:cache)
- @@cache = Hash.new
-
- def self.digest(name, format, finder, options = {})
- cache["#{name}.#{format}"] ||= new(name, format, finder, options).digest
- end
-
- attr_reader :name, :format, :finder, :options
-
- def initialize(name, format, finder, options = {})
- @name, @format, @finder, @options = name, format, finder, options
- end
-
- def digest
- Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest|
- logger.try :info, "Cache digest for #{name}.#{format}: #{digest}"
- end
- rescue ActionView::MissingTemplate
- logger.try :error, "Couldn't find template for digesting: #{name}.#{format}"
- ''
- end
-
- def dependencies
- render_dependencies + explicit_dependencies
- rescue ActionView::MissingTemplate
- [] # File doesn't exist, so no dependencies
- end
-
- def nested_dependencies
- dependencies.collect do |dependency|
- dependencies = Digestor.new(dependency, format, finder, partial: true).nested_dependencies
- dependencies.any? ? { dependency => dependencies } : dependency
- end
- end
-
- private
-
- def logger
- ActionView::Base.logger
- end
-
- def logical_name
- name.gsub(%r|/_|, "/")
- end
-
- def directory
- name.split("/")[0..-2].join("/")
- end
-
- def partial?
- options[:partial] || name.include?("/_")
- end
-
- def source
- @source ||= finder.find(logical_name, [], partial?, formats: [ format ]).source
- end
-
- def dependency_digest
- dependencies.collect do |template_name|
- Digestor.digest(template_name, format, finder, partial: true)
- end.join("-")
- end
-
- def render_dependencies
- source.scan(RENDER_DEPENDENCY).
- collect(&:second).uniq.
-
- # render(@topic) => render("topics/topic")
- # render(topics) => render("topics/topic")
- # render(message.topics) => render("topics/topic")
- collect { |name| name.sub(/\A@?([a-z]+\.)*([a-z_]+)\z/) { "#{$2.pluralize}/#{$2.singularize}" } }.
-
- # render("headline") => render("message/headline")
- collect { |name| name.include?("/") ? name : "#{directory}/#{name}" }.
-
- # replace quotes from string renders
- collect { |name| name.gsub(/["']/, "") }
- end
-
- def explicit_dependencies
- source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
- end
- end
-end
diff --git a/actionpack/lib/action_view/flows.rb b/actionpack/lib/action_view/flows.rb
deleted file mode 100644
index c0e458cd41..0000000000
--- a/actionpack/lib/action_view/flows.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-require 'active_support/core_ext/string/output_safety'
-
-module ActionView
- class OutputFlow #:nodoc:
- attr_reader :content
-
- def initialize
- @content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
- end
-
- # Called by _layout_for to read stored values.
- def get(key)
- @content[key]
- end
-
- # Called by each renderer object to set the layout contents.
- def set(key, value)
- @content[key] = value
- end
-
- # Called by content_for
- def append(key, value)
- @content[key] << value
- end
- alias_method :append!, :append
-
- end
-
- class StreamingFlow < OutputFlow #:nodoc:
- def initialize(view, fiber)
- @view = view
- @parent = nil
- @child = view.output_buffer
- @content = view.view_flow.content
- @fiber = fiber
- @root = Fiber.current.object_id
- end
-
- # Try to get an stored content. If the content
- # is not available and we are inside the layout
- # fiber, we set that we are waiting for the given
- # key and yield.
- def get(key)
- return super if @content.key?(key)
-
- if inside_fiber?
- view = @view
-
- begin
- @waiting_for = key
- view.output_buffer, @parent = @child, view.output_buffer
- Fiber.yield
- ensure
- @waiting_for = nil
- view.output_buffer, @child = @parent, view.output_buffer
- end
- end
-
- super
- end
-
- # Appends the contents for the given key. This is called
- # by provides and resumes back to the fiber if it is
- # the key it is waiting for.
- def append!(key, value)
- super
- @fiber.resume if @waiting_for == key
- end
-
- private
-
- def inside_fiber?
- Fiber.current.object_id != @root
- end
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
deleted file mode 100644
index 269e78a021..0000000000
--- a/actionpack/lib/action_view/helpers.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-module ActionView #:nodoc:
- module Helpers #:nodoc:
- extend ActiveSupport::Autoload
-
- autoload :ActiveModelHelper
- autoload :AssetTagHelper
- autoload :AssetUrlHelper
- autoload :AtomFeedHelper
- autoload :BenchmarkHelper
- autoload :CacheHelper
- autoload :CaptureHelper
- autoload :ControllerHelper
- autoload :CsrfHelper
- autoload :DateHelper
- autoload :DebugHelper
- autoload :FormHelper
- autoload :FormOptionsHelper
- autoload :FormTagHelper
- autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
- autoload :NumberHelper
- autoload :OutputSafetyHelper
- autoload :RecordTagHelper
- autoload :RenderingHelper
- autoload :SanitizeHelper
- autoload :TagHelper
- autoload :TextHelper
- autoload :TranslationHelper
- autoload :UrlHelper
-
- extend ActiveSupport::Concern
-
- include ActiveModelHelper
- include AssetTagHelper
- include AssetUrlHelper
- include AtomFeedHelper
- include BenchmarkHelper
- include CacheHelper
- include CaptureHelper
- include ControllerHelper
- include CsrfHelper
- include DateHelper
- include DebugHelper
- include FormHelper
- include FormOptionsHelper
- include FormTagHelper
- include JavaScriptHelper
- include NumberHelper
- include OutputSafetyHelper
- include RecordTagHelper
- include RenderingHelper
- include SanitizeHelper
- include TagHelper
- include TextHelper
- include TranslationHelper
- include UrlHelper
- end
-end
diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
deleted file mode 100644
index 901f433c70..0000000000
--- a/actionpack/lib/action_view/helpers/active_model_helper.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-require 'active_support/core_ext/class/attribute_accessors'
-require 'active_support/core_ext/enumerable'
-
-module ActionView
- # = Active Model Helpers
- module Helpers
- module ActiveModelHelper
- end
-
- module ActiveModelInstanceTag
- def object
- @active_model_object ||= begin
- object = super
- object.respond_to?(:to_model) ? object.to_model : object
- end
- end
-
- def content_tag(*)
- error_wrapping(super)
- end
-
- def tag(type, options, *)
- tag_generate_errors?(options) ? error_wrapping(super) : super
- end
-
- def error_wrapping(html_tag)
- if object_has_errors?
- Base.field_error_proc.call(html_tag, self)
- else
- html_tag
- end
- end
-
- def error_message
- object.errors[@method_name]
- end
-
- private
-
- def object_has_errors?
- object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
- end
-
- def tag_generate_errors?(options)
- options['type'] != 'hidden'
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
deleted file mode 100644
index 4eac6514df..0000000000
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ /dev/null
@@ -1,293 +0,0 @@
-require 'active_support/core_ext/array/extract_options'
-require 'active_support/core_ext/hash/keys'
-require 'action_view/helpers/asset_url_helper'
-require 'action_view/helpers/tag_helper'
-
-module ActionView
- # = Action View Asset Tag Helpers
- module Helpers #:nodoc:
- # This module provides methods for generating HTML that links views to assets such
- # as images, javascripts, stylesheets, and feeds. These methods do not verify
- # the assets exist before linking to them:
- #
- # image_tag("rails.png")
- # # => <img alt="Rails" src="/assets/rails.png" />
- # stylesheet_link_tag("application")
- # # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />
- #
- module AssetTagHelper
- extend ActiveSupport::Concern
-
- include AssetUrlHelper
- include TagHelper
-
- # 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
- # to <tt>public/javascripts</tt>, full paths are assumed to be relative to the document
- # root. Relative paths are idiomatic, use absolute paths only when needed.
- #
- # When passing paths, the ".js" extension is optional.
- #
- # You can modify the HTML attributes of the script tag by passing a hash as the
- # last argument.
- #
- # javascript_include_tag "xmlhr"
- # # => <script src="/javascripts/xmlhr.js?1284139606"></script>
- #
- # javascript_include_tag "xmlhr.js"
- # # => <script src="/javascripts/xmlhr.js?1284139606"></script>
- #
- # javascript_include_tag "common.javascript", "/elsewhere/cools"
- # # => <script src="/javascripts/common.javascript?1284139606"></script>
- # # <script src="/elsewhere/cools.js?1423139606"></script>
- #
- # javascript_include_tag "http://www.example.com/xmlhr"
- # # => <script src="http://www.example.com/xmlhr"></script>
- #
- # javascript_include_tag "http://www.example.com/xmlhr.js"
- # # => <script src="http://www.example.com/xmlhr.js"></script>
- #
- def javascript_include_tag(*sources)
- options = sources.extract_options!.stringify_keys
- sources.uniq.map { |source|
- tag_options = {
- "src" => path_to_javascript(source)
- }.merge(options)
- content_tag(:script, "", tag_options)
- }.join("\n").html_safe
- end
-
- # 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.
- # For historical reasons, the 'media' attribute will always be present and defaults
- # to "screen", so you must explicitely set it to "all" for the stylesheet(s) to
- # apply to all media types.
- #
- # stylesheet_link_tag "style" # =>
- # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />
- #
- # stylesheet_link_tag "style.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" />
- #
- # stylesheet_link_tag "style", :media => "all" # =>
- # <link href="/stylesheets/style.css" media="all" rel="stylesheet" />
- #
- # stylesheet_link_tag "style", :media => "print" # =>
- # <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" />
- # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
- #
- def stylesheet_link_tag(*sources)
- options = sources.extract_options!.stringify_keys
- sources.uniq.map { |source|
- tag_options = {
- "rel" => "stylesheet",
- "media" => "screen",
- "href" => path_to_stylesheet(source)
- }.merge(options)
- tag(:link, tag_options)
- }.join("\n").html_safe
- end
-
- # 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
- # <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+.
- #
- # ==== Options
- # * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
- # * <tt>:type</tt> - Override the auto-generated mime type
- # * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
- #
- # ==== Examples
- # auto_discovery_link_tag
- # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
- # auto_discovery_link_tag(:atom)
- # # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
- # auto_discovery_link_tag(:rss, {:action => "feed"})
- # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
- # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"})
- # # => <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
- # auto_discovery_link_tag(:rss, {:controller => "news", :action => "feed"})
- # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
- # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "Example RSS"})
- # # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
- def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
- if !(type == :rss || type == :atom) && tag_options[:type].blank?
- message = "You have passed type other than :rss or :atom to auto_discovery_link_tag and haven't supplied " +
- "the :type option key. This behavior is deprecated and will be remove in Rails 4.1. You should pass " +
- ":type option explicitly if you want to use other types, for example: " +
- "auto_discovery_link_tag(:xml, '/feed.xml', :type => 'application/xml')"
- ActiveSupport::Deprecation.warn message
- end
-
- tag(
- "link",
- "rel" => tag_options[:rel] || "alternate",
- "type" => tag_options[:type] || Mime::Type.lookup_by_extension(type.to_s).to_s,
- "title" => tag_options[:title] || type.to_s.upcase,
- "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
- )
- end
-
- # <%= favicon_link_tag %>
- #
- # generates
- #
- # <link href="/assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
- #
- # You may specify a different file in the first argument:
- #
- # <%= favicon_link_tag '/myicon.ico' %>
- #
- # That's passed to +path_to_image+ as is, so it gives
- #
- # <link href="/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
- #
- # The helper accepts an additional options hash where you can override "rel" and "type".
- #
- # For example, Mobile Safari looks for a different LINK tag, pointing to an image that
- # will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad.
- # The following call would generate such a tag:
- #
- # <%= favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png' %>
- def favicon_link_tag(source='favicon.ico', options={})
- tag('link', {
- :rel => 'shortcut icon',
- :type => 'image/vnd.microsoft.icon',
- :href => path_to_image(source)
- }.merge(options.symbolize_keys))
- end
-
- # Returns an html image tag for the +source+. The +source+ can be a full
- # path or a file.
- #
- # ==== Options
- # You can add HTML attributes using the +options+. The +options+ supports
- # three additional keys for convenience and conformance:
- #
- # * <tt>:alt</tt> - If no alt text is given, the file name part of the
- # +source+ is used (capitalized and without the extension)
- # * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
- # width="30" and height="45", and "50" becomes width="50" and height="50".
- # <tt>:size</tt> will be ignored if the value is not in the correct format.
- #
- # image_tag("icon")
- # # => <img alt="Icon" src="/assets/icon" />
- # image_tag("icon.png")
- # # => <img alt="Icon" src="/assets/icon.png" />
- # image_tag("icon.png", :size => "16x10", :alt => "Edit Entry")
- # # => <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" />
- # image_tag("/icons/icon.gif", :size => "16")
- # # => <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
- # image_tag("/icons/icon.gif", :height => '32', :width => '32')
- # # => <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
- # image_tag("/icons/icon.gif", :class => "menu_icon")
- # # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
- def image_tag(source, options={})
- options = options.symbolize_keys
-
- src = options[:src] = path_to_image(source)
-
- unless src =~ /^(?:cid|data):/ || src.blank?
- options[:alt] = options.fetch(:alt){ image_alt(src) }
- end
-
- if size = options.delete(:size)
- options[:width], options[:height] = size.split("x") if size =~ %r{\A\d+x\d+\z}
- options[:width] = options[:height] = size if size =~ %r{\A\d+\z}
- end
-
- tag("img", options)
- end
-
- def image_alt(src)
- File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').capitalize
- end
-
- # Returns an html video tag for the +sources+. If +sources+ is a string,
- # a single video tag will be returned. If +sources+ is an array, a video
- # tag with nested source tags for each source will be returned. The
- # +sources+ can be full paths or files that exists in your public videos
- # directory.
- #
- # ==== Options
- # You can add HTML attributes using the +options+. The +options+ supports
- # two additional keys for convenience and conformance:
- #
- # * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
- # before the video loads. The path is calculated like the +src+ of +image_tag+.
- # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
- # width="30" and height="45". <tt>:size</tt> will be ignored if the
- # value is not in the correct format.
- #
- # video_tag("trailer")
- # # => <video src="/videos/trailer" />
- # video_tag("trailer.ogg")
- # # => <video src="/videos/trailer.ogg" />
- # video_tag("trailer.ogg", :controls => true, :autobuffer => true)
- # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
- # video_tag("trailer.m4v", :size => "16x10", :poster => "screenshot.png")
- # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" />
- # video_tag("/trailers/hd.avi", :size => "16x16")
- # # => <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="/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 size = options.delete(:size)
- options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
- end
- end
- end
-
- # Returns an html audio tag for the +source+.
- # The +source+ can be full path or file that exists in
- # your public audios directory.
- #
- # audio_tag("sound") # =>
- # <audio src="/audios/sound" />
- # audio_tag("sound.wav") # =>
- # <audio src="/audios/sound.wav" />
- # audio_tag("sound.wav", :autoplay => true, :controls => true) # =>
- # <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
- # 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
- 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
- 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
deleted file mode 100644
index 35f91cec18..0000000000
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-require 'thread'
-require 'active_support/core_ext/file'
-require 'active_support/core_ext/module/attribute_accessors'
-
-module ActionView
- module Helpers
- module AssetTagHelper
-
- class AssetPaths < ::ActionView::AssetPaths #:nodoc:
- # You can enable or disable the asset tag ids cache.
- # With the cache enabled, the asset tag helper methods will make fewer
- # expensive file system calls (the default implementation checks the file
- # system timestamp). However this prevents you from modifying any asset
- # files while the server is running.
- #
- # ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids = false
- mattr_accessor :cache_asset_ids
-
- # Add or change an asset id in the asset id cache. This can be used
- # for SASS on Heroku.
- # :api: public
- def add_to_asset_ids_cache(source, asset_id)
- self.asset_ids_cache_guard.synchronize do
- self.asset_ids_cache[source] = asset_id
- end
- end
-
- private
-
- def rewrite_extension(source, dir, ext)
- source_ext = File.extname(source)
-
- source_with_ext = if source_ext.empty?
- "#{source}.#{ext}"
- elsif ext != source_ext[1..-1]
- with_ext = "#{source}.#{ext}"
- with_ext if File.exist?(File.join(config.assets_dir, dir, with_ext))
- end
-
- source_with_ext || source
- end
-
- # Break out the asset path rewrite in case plugins wish to put the asset id
- # someplace other than the query string.
- def rewrite_asset_path(source, dir, options = nil)
- source = "/#{dir}/#{source}" unless source[0] == ?/
- path = config.asset_path
-
- if path && path.respond_to?(:call)
- return path.call(source)
- elsif path && path.is_a?(String)
- return path % [source]
- end
-
- asset_id = rails_asset_id(source)
- if asset_id.empty?
- source
- else
- "#{source}?#{asset_id}"
- end
- end
-
- mattr_accessor :asset_ids_cache
- self.asset_ids_cache = {}
-
- mattr_accessor :asset_ids_cache_guard
- self.asset_ids_cache_guard = Mutex.new
-
- # Use the RAILS_ASSET_ID environment variable or the source's
- # modification time as its cache-busting asset id.
- def rails_asset_id(source)
- if asset_id = ENV["RAILS_ASSET_ID"]
- asset_id
- else
- if self.cache_asset_ids && (asset_id = self.asset_ids_cache[source])
- asset_id
- else
- path = File.join(config.assets_dir, source)
- asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
-
- if self.cache_asset_ids
- add_to_asset_ids_cache(source, asset_id)
- end
-
- asset_id
- end
- end
- end
- end
-
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/asset_url_helper.rb b/actionpack/lib/action_view/helpers/asset_url_helper.rb
deleted file mode 100644
index 4554c0c473..0000000000
--- a/actionpack/lib/action_view/helpers/asset_url_helper.rb
+++ /dev/null
@@ -1,334 +0,0 @@
-require 'action_view/helpers/asset_tag_helpers/asset_paths'
-
-module ActionView
- # = Action View Asset URL Helpers
- module Helpers #:nodoc:
- # This module provides methods for generating asset paths and
- # urls.
- #
- # image_path("rails.png")
- # # => "/assets/rails.png"
- #
- # image_url("rails.png")
- # # => "http://www.example.com/assets/rails.png"
- #
- # === Using asset hosts
- #
- # By default, Rails links to these assets on the current host in the public
- # folder, but you can direct Rails to link to assets from a dedicated asset
- # server by setting <tt>ActionController::Base.asset_host</tt> 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, inside the <tt>configure</tt> block of your environment-specific
- # configuration files or <tt>config/application.rb</tt>:
- #
- # 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/assets/rails.png" />
- # stylesheet_link_tag("application")
- # # => <link href="http://assets.example.com/assets/application.css" 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
- # downloading. You can alleviate this by using a <tt>%d</tt> wildcard in the
- # +asset_host+. For example, "assets%d.example.com". If that wildcard is
- # present Rails distributes asset requests among the corresponding four hosts
- # "assets0.example.com", ..., "assets3.example.com". With this trick browsers
- # will open eight simultaneous connections rather than two.
- #
- # image_tag("rails.png")
- # # => <img alt="Rails" src="http://assets0.example.com/assets/rails.png" />
- # stylesheet_link_tag("application")
- # # => <link href="http://assets2.example.com/assets/application.css" 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
- # setting up your DNS CNAME records from your ISP.
- #
- # Note: This is purely a browser performance optimization and is not meant
- # for server load balancing. See http://www.die.net/musings/page_load_time/
- # for background.
- #
- # Alternatively, you can exert more control over the asset host by setting
- # +asset_host+ to a proc like this:
- #
- # ActionController::Base.asset_host = Proc.new { |source|
- # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com"
- # }
- # image_tag("rails.png")
- # # => <img alt="Rails" src="http://assets1.example.com/assets/rails.png" />
- # stylesheet_link_tag("application")
- # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
- #
- # The example above generates "http://assets1.example.com" and
- # "http://assets2.example.com". This option is useful for example if
- # you need fewer/more than four hosts, custom host names, etc.
- #
- # As you see the proc takes a +source+ parameter. That's a string with the
- # absolute path of the asset, for example "/assets/rails.png".
- #
- # ActionController::Base.asset_host = Proc.new { |source|
- # if source.ends_with?('.css')
- # "http://stylesheets.example.com"
- # else
- # "http://assets.example.com"
- # end
- # }
- # image_tag("rails.png")
- # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" />
- # stylesheet_link_tag("application")
- # # => <link href="http://stylesheets.example.com/assets/application.css" 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
- # example proc below disables asset hosting for HTTPS connections, while
- # still sending assets for plain HTTP requests from asset hosts. If you don't
- # have SSL certificates for each of the asset hosts this technique allows you
- # to avoid warnings in the client about mixed media.
- #
- # config.action_controller.asset_host = Proc.new { |source, request|
- # if request.ssl?
- # "#{request.protocol}#{request.host_with_port}"
- # else
- # "#{request.protocol}assets.example.com"
- # end
- # }
- #
- # You can also implement a custom asset host object that responds to +call+
- # and takes either one or two parameters just like the proc.
- #
- # config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
- # "http://asset%d.example.com", "https://asset1.example.com"
- # )
- #
- # === Customizing the asset path
- #
- # By default, Rails appends asset's timestamps to all asset paths. This allows
- # you to set a cache-expiration date for the asset far into the future, but
- # still be able to instantly invalidate it by simply updating the file (and
- # hence updating the timestamp, which then updates the URL as the timestamp
- # is part of that, which in turn busts the cache).
- #
- # It's the responsibility of the web server you use to set the far-future
- # expiration date on cache assets that you need to take advantage of this
- # feature. Here's an example for Apache:
- #
- # # Asset Expiration
- # ExpiresActive On
- # <FilesMatch "\.(ico|gif|jpe?g|png|js|css)$">
- # ExpiresDefault "access plus 1 year"
- # </FilesMatch>
- #
- # Also note that in order for this to work, all your application servers must
- # return the same timestamps. This means that they must have their clocks
- # synchronized. If one of them drifts out of sync, you'll see different
- # timestamps at random and the cache won't work. In that case the browser
- # will request the same assets over and over again even thought they didn't
- # change. You can use something like Live HTTP Headers for Firefox to verify
- # that the cache is indeed working.
- #
- # This strategy works well enough for most server setups and requires the
- # least configuration, but if you deploy several application servers at
- # different times - say to handle a temporary spike in load - then the
- # asset time stamps will be out of sync. In a setup like this you may want
- # to set the way that asset paths are generated yourself.
- #
- # Altering the asset paths that Rails generates can be done in two ways.
- # The easiest is to define the RAILS_ASSET_ID environment variable. The
- # contents of this variable will always be used in preference to
- # calculated timestamps. A more complex but flexible way is to set
- # <tt>ActionController::Base.config.asset_path</tt> to a proc
- # that takes the unmodified asset path and returns the path needed for
- # your asset caching to work. Typically you'd do something like this in
- # <tt>config/environments/production.rb</tt>:
- #
- # # Normally you'd calculate RELEASE_NUMBER at startup.
- # RELEASE_NUMBER = 12345
- # config.action_controller.asset_path = proc { |asset_path|
- # "/release-#{RELEASE_NUMBER}#{asset_path}"
- # }
- #
- # This example would cause the following behavior on all servers no
- # matter when they were deployed:
- #
- # 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" />
- #
- # 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
- # suitable for out-of-the-box use. To use the example given above you
- # could use something like this in your Apache VirtualHost configuration:
- #
- # <LocationMatch "^/release-\d+/(images|javascripts|stylesheets)/.*$">
- # # Some browsers still send conditional-GET requests if there's a
- # # Last-Modified header or an ETag header even if they haven't
- # # reached the expiry date sent in the Expires header.
- # Header unset Last-Modified
- # Header unset ETag
- # FileETag None
- #
- # # Assets requested using a cache-busting filename should be served
- # # only once and then cached for a really long time. The HTTP/1.1
- # # spec frowns on hugely-long expiration times though and suggests
- # # that assets which never expire be served with an expiration date
- # # 1 year from access.
- # ExpiresActive On
- # ExpiresDefault "access plus 1 year"
- # </LocationMatch>
- #
- # # We use cached-busting location names with the far-future expires
- # # headers to ensure that if a file does change it can force a new
- # # request. The actual asset filenames are still the same though so we
- # # need to rewrite the location from the cache-busting location to the
- # # real asset location so that we can serve it.
- # RewriteEngine On
- # RewriteRule ^/release-\d+/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L]
- #
- module AssetUrlHelper
- # Computes the path to a javascript asset in the public javascripts directory.
- # If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
- # Full paths from the document root will be passed through.
- # Used internally by javascript_include_tag to build the script path.
- #
- # javascript_path "xmlhr" # => /javascripts/xmlhr.js
- # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
- # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
- # javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
- # javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
- def javascript_path(source)
- asset_paths.compute_public_path(source, 'javascripts', :ext => 'js')
- 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
-
- # Computes the path to a stylesheet asset in the public stylesheets directory.
- # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
- # Full paths from the document root will be passed through.
- # Used internally by +stylesheet_link_tag+ to build the stylesheet path.
- #
- # stylesheet_path "style" # => /stylesheets/style.css
- # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
- # stylesheet_path "/dir/style.css" # => /dir/style.css
- # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
- # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
- def stylesheet_path(source)
- asset_paths.compute_public_path(source, 'stylesheets', :ext => 'css', :protocol => :request)
- 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
-
- # Computes the path to an image asset.
- # Full paths from the document root will be passed through.
- # Used internally by +image_tag+ to build the image path:
- #
- # image_path("edit") # => "/assets/edit"
- # image_path("edit.png") # => "/assets/edit.png"
- # image_path("icons/edit.png") # => "/assets/icons/edit.png"
- # image_path("/icons/edit.png") # => "/icons/edit.png"
- # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
- #
- # If you have images as application resources this method may conflict with their named routes.
- # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
- # plugin authors are encouraged to do so.
- def image_path(source)
- source.present? ? asset_paths.compute_public_path(source, 'images') : ""
- 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.
- # 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.
- #
- # video_path("hd") # => /videos/hd
- # video_path("hd.avi") # => /videos/hd.avi
- # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
- # video_path("/trailers/hd.avi") # => /trailers/hd.avi
- # video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi
- def video_path(source)
- asset_paths.compute_public_path(source, 'videos')
- 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.
- #
- # audio_path("horse") # => /audios/horse
- # audio_path("horse.wav") # => /audios/horse.wav
- # audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav
- # audio_path("/sounds/horse.wav") # => /sounds/horse.wav
- # audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav
- def audio_path(source)
- asset_paths.compute_public_path(source, 'audios')
- end
- alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
-
- # Computes the full URL to an 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.
- # Full paths from the document root will be passed through.
- #
- # font_path("font") # => /assets/font
- # font_path("font.ttf") # => /assets/font.ttf
- # font_path("dir/font.ttf") # => /assets/dir/font.ttf
- # font_path("/dir/font.ttf") # => /dir/font.ttf
- # font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
- def font_path(source)
- asset_paths.compute_public_path(source, 'fonts')
- 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.
- # 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
-
- private
- def asset_paths
- @asset_paths ||= AssetTagHelper::AssetPaths.new(config, controller)
- end
-
- def current_host
- url_for(:only_path => false)
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
deleted file mode 100644
index f9aa8d7cee..0000000000
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ /dev/null
@@ -1,203 +0,0 @@
-require 'set'
-
-module ActionView
- # = Action View Atom Feed Helpers
- module Helpers #:nodoc:
- module AtomFeedHelper
- # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
- # template languages).
- #
- # Full usage example:
- #
- # config/routes.rb:
- # Basecamp::Application.routes.draw do
- # resources :posts
- # root :to => "posts#index"
- # end
- #
- # app/controllers/posts_controller.rb:
- # class PostsController < ApplicationController::Base
- # # GET /posts.html
- # # GET /posts.atom
- # def index
- # @posts = Post.all
- #
- # respond_to do |format|
- # format.html
- # format.atom
- # end
- # end
- # end
- #
- # app/views/posts/index.atom.builder:
- # atom_feed do |feed|
- # feed.title("My great blog!")
- # feed.updated(@posts[0].created_at) if @posts.length > 0
- #
- # @posts.each do |post|
- # feed.entry(post) do |entry|
- # entry.title(post.title)
- # entry.content(post.body, :type => 'html')
- #
- # entry.author do |author|
- # author.name("DHH")
- # end
- # end
- # end
- # end
- #
- # The options for atom_feed are:
- #
- # * <tt>:language</tt>: Defaults to "en-US".
- # * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
- # * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
- # * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}"
- # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
- # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
- # 2005 is used (as an "I don't care" value).
- # * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]}
- #
- # Other namespaces can be added to the root element:
- #
- # app/views/posts/index.atom.builder:
- # atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app',
- # 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed|
- # feed.title("My great blog!")
- # feed.updated((@posts.first.created_at))
- # feed.tag!(openSearch:totalResults, 10)
- #
- # @posts.each do |post|
- # feed.entry(post) do |entry|
- # entry.title(post.title)
- # entry.content(post.body, :type => 'html')
- # entry.tag!('app:edited', Time.now)
- #
- # entry.author do |author|
- # author.name("DHH")
- # end
- # end
- # end
- # end
- #
- # The Atom spec defines five elements (content rights title subtitle
- # summary) which may directly contain xhtml content if :type => 'xhtml'
- # is specified as an attribute. If so, this helper will take care of
- # the enclosing div and xhtml namespace declaration. Example usage:
- #
- # entry.summary :type => 'xhtml' do |xhtml|
- # xhtml.p pluralize(order.line_items.count, "line item")
- # xhtml.p "Shipped to #{order.address}"
- # xhtml.p "Paid by #{order.pay_type}"
- # end
- #
- #
- # <tt>atom_feed</tt> yields an +AtomFeedBuilder+ instance. Nested elements yield
- # an +AtomBuilder+ instance.
- def atom_feed(options = {}, &block)
- if options[:schema_date]
- options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime)
- else
- options[:schema_date] = "2005" # The Atom spec copyright date
- end
-
- xml = options.delete(:xml) || eval("xml", block.binding)
- xml.instruct!
- if options[:instruct]
- options[:instruct].each do |target,attrs|
- if attrs.respond_to?(:keys)
- xml.instruct!(target, attrs)
- elsif attrs.respond_to?(:each)
- attrs.each { |attr_group| xml.instruct!(target, attr_group) }
- end
- end
- end
-
- feed_opts = {"xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom'}
- feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)}
-
- xml.feed(feed_opts) do
- xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}")
- xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port))
- xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url)
-
- yield AtomFeedBuilder.new(xml, self, options)
- end
- end
-
- class AtomBuilder
- XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set
-
- def initialize(xml)
- @xml = xml
- end
-
- private
- # Delegate to xml builder, first wrapping the element in a xhtml
- # namespaced div element if the method and arguments indicate
- # that an xhtml_block? is desired.
- def method_missing(method, *arguments, &block)
- if xhtml_block?(method, arguments)
- @xml.__send__(method, *arguments) do
- @xml.div(:xmlns => 'http://www.w3.org/1999/xhtml') do |xhtml|
- block.call(xhtml)
- end
- end
- else
- @xml.__send__(method, *arguments, &block)
- end
- end
-
- # True if the method name matches one of the five elements defined
- # in the Atom spec as potentially containing XHTML content and
- # if :type => 'xhtml' is, in fact, specified.
- def xhtml_block?(method, arguments)
- if XHTML_TAG_NAMES.include?(method.to_s)
- last = arguments.last
- last.is_a?(Hash) && last[:type].to_s == 'xhtml'
- end
- end
- end
-
- class AtomFeedBuilder < AtomBuilder
- def initialize(xml, view, feed_options = {})
- @xml, @view, @feed_options = xml, view, feed_options
- end
-
- # Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used.
- def updated(date_or_time = nil)
- @xml.updated((date_or_time || Time.now.utc).xmlschema)
- end
-
- # Creates an entry tag for a specific record and prefills the id using class and id.
- #
- # Options:
- #
- # * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
- # * <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}")
-
- if options[:published] || (record.respond_to?(:created_at) && record.created_at)
- @xml.published((options[:published] || record.created_at).xmlschema)
- end
-
- if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at)
- @xml.updated((options[:updated] || record.updated_at).xmlschema)
- end
-
- type = options.fetch(:type, 'text/html')
-
- @xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record))
-
- yield AtomBuilder.new(@xml)
- end
- end
- end
-
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/benchmark_helper.rb b/actionpack/lib/action_view/helpers/benchmark_helper.rb
deleted file mode 100644
index dfdd5a786d..0000000000
--- a/actionpack/lib/action_view/helpers/benchmark_helper.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require 'active_support/benchmarkable'
-
-module ActionView
- module Helpers
- module BenchmarkHelper
- include ActiveSupport::Benchmarkable
-
- def benchmark(*)
- capture { super }
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
deleted file mode 100644
index ddac87a37d..0000000000
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ /dev/null
@@ -1,147 +0,0 @@
-module ActionView
- # = Action View Cache Helper
- module Helpers
- module CacheHelper
- # This helper exposes a method for caching fragments of a view
- # rather than an entire action or page. This technique is useful
- # caching pieces like menus, lists of newstopics, static HTML
- # fragments, and so on. This method takes a block that contains
- # the content you wish to cache.
- #
- # The best way to use this is by doing key-based cache expiration
- # on top of a cache store like Memcached that'll automatically
- # kick out old entries. For more on key-based expiration, see:
- # http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works
- #
- # When using this method, you list the cache dependency as the name of the cache, like so:
- #
- # <% cache project do %>
- # <b>All the topics on this project</b>
- # <%= render project.topics %>
- # <% end %>
- #
- # This approach will assume that when a new topic is added, you'll touch
- # the project. The cache key generated from this call will be something like:
- #
- # views/projects/123-20120806214154/7a1156131a6928cb0026877f8b749ac9
- # ^class ^id ^updated_at ^template tree digest
- #
- # The cache is thus automatically bumped whenever the project updated_at is touched.
- #
- # If your template cache depends on multiple sources (try to avoid this to keep things simple),
- # you can name all these dependencies as part of an array:
- #
- # <% cache [ project, current_user ] do %>
- # <b>All the topics on this project</b>
- # <%= render project.topics %>
- # <% end %>
- #
- # This will include both records as part of the cache key and updating either of them will
- # expire the cache.
- #
- # ==== Template digest
- #
- # The template digest that's added to the cache key is computed by taking an md5 of the
- # contents of the entire template file. This ensures that your caches will automatically
- # expire when you change the template file.
- #
- # Note that the md5 is taken of the entire template file, not just what's within the
- # cache do/end call. So it's possible that changing something outside of that call will
- # still expire the cache.
- #
- # Additionally, the digestor will automatically look through your template file for
- # explicit and implicit dependencies, and include those as part of the digest.
- #
- # ==== Implicit dependencies
- #
- # Most template dependencies can be derived from calls to render in the template itself.
- # Here are some examples of render calls that Cache Digests knows how to decode:
- #
- # render partial: "comments/comment", collection: commentable.comments
- # render "comments/comments"
- # render 'comments/comments'
- # render('comments/comments')
- #
- # render "header" => render("comments/header")
- #
- # render(@topic) => render("topics/topic")
- # render(topics) => render("topics/topic")
- # render(message.topics) => render("topics/topic")
- #
- # It's not possible to derive all render calls like that, though. Here are a few examples of things that can't be derived:
- #
- # render group_of_attachments
- # render @project.documents.where(published: true).order('created_at')
- #
- # You will have to rewrite those to the explicit form:
- #
- # render partial: 'attachments/attachment', collection: group_of_attachments
- # render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at')
- #
- # === Explicit dependencies
- #
- # Some times you'll have template dependencies that can't be derived at all. This is typically
- # the case when you have template rendering that happens in helpers. Here's an example:
- #
- # <%= render_sortable_todolists @project.todolists %>
- #
- # You'll need to use a special comment format to call those out:
- #
- # <%# Template Dependency: todolists/todolist %>
- # <%= render_sortable_todolists @project.todolists %>
- #
- # The pattern used to match these is /# Template Dependency: ([^ ]+)/, so it's important that you type it out just so.
- # You can only declare one template dependency per line.
- #
- # === External dependencies
- #
- # If you use a helper method, for example, inside of a cached block and you then update that helper,
- # you'll have to bump the cache as well. It doesn't really matter how you do it, but the md5 of the template file
- # must change. One recommendation is to simply be explicit in a comment, like:
- #
- # <%# Helper Dependency Updated: May 6, 2012 at 6pm %>
- # <%= some_helper_method(person) %>
- #
- # Now all you'll have to do is change that timestamp when the helper method changes.
- def cache(name = {}, options = nil, &block)
- if controller.perform_caching
- safe_concat(fragment_for(fragment_name_with_digest(name), options, &block))
- else
- yield
- end
-
- nil
- end
-
- def fragment_name_with_digest(name) #:nodoc:
- if @virtual_path
- [
- *Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name),
- Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context)
- ]
- else
- name
- end
- end
-
- private
- # TODO: Create an object that has caching read/write on it
- def fragment_for(name = {}, options = nil, &block) #:nodoc:
- if fragment = controller.read_fragment(name, options)
- fragment
- else
- # VIEW TODO: Make #capture usable outside of ERB
- # This dance is needed because Builder can't use capture
- pos = output_buffer.length
- yield
- output_safe = output_buffer.html_safe?
- fragment = output_buffer.slice!(pos..-1)
- if output_safe
- self.output_buffer = output_buffer.class.new(output_buffer)
- end
- controller.write_fragment(name, fragment, options)
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
deleted file mode 100644
index c98101a195..0000000000
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ /dev/null
@@ -1,222 +0,0 @@
-require 'active_support/core_ext/string/output_safety'
-
-module ActionView
- # = Action View Capture Helper
- module Helpers
- # CaptureHelper exposes methods to let you extract generated markup which
- # can be used in other parts of a template or layout file.
- #
- # It provides a method to capture blocks into variables through capture and
- # a way to capture a block of markup for use in a layout through content_for.
- module CaptureHelper
- # The capture method allows you to extract part of a template into a
- # variable. You can then use this variable anywhere in your templates or layout.
- #
- # The capture method can be used in ERB templates...
- #
- # <% @greeting = capture do %>
- # Welcome to my shiny new web page! The date and time is
- # <%= Time.now %>
- # <% end %>
- #
- # ...and Builder (RXML) templates.
- #
- # @timestamp = capture do
- # "The current timestamp is #{Time.now}."
- # end
- #
- # You can then use that variable anywhere else. For example:
- #
- # <html>
- # <head><title><%= @greeting %></title></head>
- # <body>
- # <b><%= @greeting %></b>
- # </body></html>
- #
- def capture(*args)
- value = nil
- buffer = with_output_buffer { value = yield(*args) }
- if string = buffer.presence || value and string.is_a?(String)
- ERB::Util.html_escape string
- end
- end
-
- # Calling content_for stores a block of markup in an identifier for later use.
- # You can make subsequent calls to the stored content in other templates, helper modules
- # or the layout by passing the identifier as an argument to <tt>content_for</tt>.
- #
- # Note: <tt>yield</tt> can still be used to retrieve the stored content, but calling
- # <tt>yield</tt> doesn't work in helper modules, while <tt>content_for</tt> does.
- #
- # ==== Examples
- #
- # <% content_for :not_authorized do %>
- # alert('You are not authorized to do that!')
- # <% end %>
- #
- # You can then use <tt>content_for :not_authorized</tt> anywhere in your templates.
- #
- # <%= content_for :not_authorized if current_user.nil? %>
- #
- # This is equivalent to:
- #
- # <%= yield :not_authorized if current_user.nil? %>
- #
- # <tt>content_for</tt>, however, can also be used in helper modules.
- #
- # module StorageHelper
- # def stored_content
- # content_for(:storage) || "Your storage is empty"
- # end
- # end
- #
- # This helper works just like normal helpers.
- #
- # <%= stored_content %>
- #
- # You can use the <tt>yield</tt> syntax alongside an existing call to <tt>yield</tt> in a layout. For example:
- #
- # <%# This is the layout %>
- # <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- # <head>
- # <title>My Website</title>
- # <%= yield :script %>
- # </head>
- # <body>
- # <%= yield %>
- # </body>
- # </html>
- #
- # And now, we'll create a view that has a <tt>content_for</tt> call that
- # creates the <tt>script</tt> identifier.
- #
- # <%# This is our view %>
- # Please login!
- #
- # <% content_for :script do %>
- # <script>alert('You are not authorized to view this page!')</script>
- # <% end %>
- #
- # Then, in another view, you could to do something like this:
- #
- # <%= link_to 'Logout', :action => 'logout', :remote => true %>
- #
- # <% content_for :script do %>
- # <%= javascript_include_tag :defaults %>
- # <% end %>
- #
- # That will place +script+ tags for your default set of JavaScript files on the page;
- # this technique is useful if you'll only be using these scripts in a few views.
- #
- # Note that content_for concatenates (default) the blocks it is given for a particular
- # identifier in order. For example:
- #
- # <% content_for :navigation do %>
- # <li><%= link_to 'Home', :action => 'index' %></li>
- # <% end %>
- #
- # <%# Add some other content, or use a different template: %>
- #
- # <% content_for :navigation do %>
- # <li><%= link_to 'Login', :action => 'login' %></li>
- # <% end %>
- #
- # Then, in another template or layout, this code would render both links in order:
- #
- # <ul><%= content_for :navigation %></ul>
- #
- # If the flush parameter is true content_for replaces the blocks it is given. For example:
- #
- # <% content_for :navigation do %>
- # <li><%= link_to 'Home', :action => 'index' %></li>
- # <% end %>
- #
- # <%# Add some other content, or use a different template: %>
- #
- # <% content_for :navigation, flush: true do %>
- # <li><%= link_to 'Login', :action => 'login' %></li>
- # <% end %>
- #
- # Then, in another template or layout, this code would render only the last link:
- #
- # <ul><%= content_for :navigation %></ul>
- #
- # Lastly, simple content can be passed as a parameter:
- #
- # <% content_for :script, javascript_include_tag(:defaults) %>
- #
- # WARNING: content_for is ignored in caches. So you shouldn't use it
- # for elements that will be fragment cached.
- def content_for(name, content = nil, options = {}, &block)
- if content || block_given?
- if block_given?
- options = content if content
- content = capture(&block)
- end
- if content
- options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content)
- end
- nil
- else
- @view_flow.get(name)
- end
- end
-
- # The same as +content_for+ but when used with streaming flushes
- # straight back to the layout. In other words, if you want to
- # concatenate several times to the same buffer when rendering a given
- # template, you should use +content_for+, if not, use +provide+ to tell
- # the layout to stop looking for more contents.
- def provide(name, content = nil, &block)
- content = capture(&block) if block_given?
- result = @view_flow.append!(name, content) if content
- result unless content
- end
-
- # content_for? simply checks whether any content has been captured yet using content_for
- # Useful to render parts of your layout differently based on what is in your views.
- #
- # ==== Examples
- #
- # Perhaps you will use different css in you layout if no content_for :right_column
- #
- # <%# This is the layout %>
- # <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- # <head>
- # <title>My Website</title>
- # <%= yield :script %>
- # </head>
- # <body class="<%= content_for?(:right_col) ? 'one-column' : 'two-column' %>">
- # <%= yield %>
- # <%= yield :right_col %>
- # </body>
- # </html>
- def content_for?(name)
- @view_flow.get(name).present?
- end
-
- # Use an alternate output buffer for the duration of the block.
- # Defaults to a new empty string.
- def with_output_buffer(buf = nil) #:nodoc:
- unless buf
- buf = ActionView::OutputBuffer.new
- buf.force_encoding(output_buffer.encoding) if output_buffer
- end
- self.output_buffer, old_buffer = buf, output_buffer
- yield
- output_buffer
- ensure
- self.output_buffer = old_buffer
- end
-
- # Add the output buffer to the response body and start a new one.
- def flush_output_buffer #:nodoc:
- if output_buffer && !output_buffer.empty?
- response.stream.write output_buffer
- self.output_buffer = output_buffer.respond_to?(:clone_empty) ? output_buffer.clone_empty : output_buffer[0, 0]
- nil
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/controller_helper.rb b/actionpack/lib/action_view/helpers/controller_helper.rb
deleted file mode 100644
index 74ef25f7c1..0000000000
--- a/actionpack/lib/action_view/helpers/controller_helper.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'active_support/core_ext/module/attr_internal'
-
-module ActionView
- module Helpers
- # This module keeps all methods and behavior in ActionView
- # that simply delegates to the controller.
- module ControllerHelper #:nodoc:
- attr_internal :controller, :request
-
- delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers,
- :flash, :action_name, :controller_name, :controller_path, :to => :controller
-
- def assign_controller(controller)
- if @_controller = controller
- @_request = controller.request if controller.respond_to?(:request)
- @_config = controller.config.inheritable_copy if controller.respond_to?(:config)
- end
- end
-
- def logger
- controller.logger if controller.respond_to?(:logger)
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/csrf_helper.rb b/actionpack/lib/action_view/helpers/csrf_helper.rb
deleted file mode 100644
index eeb0ed94b9..0000000000
--- a/actionpack/lib/action_view/helpers/csrf_helper.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module ActionView
- # = Action View CSRF Helper
- module Helpers
- module CsrfHelper
- # Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site
- # request forgery protection parameter and token, respectively.
- #
- # <head>
- # <%= csrf_meta_tags %>
- # </head>
- #
- # These are used to generate the dynamic forms that implement non-remote links with
- # <tt>:method</tt>.
- #
- # Note that regular forms generate hidden fields, and that Ajax calls are whitelisted,
- # so they do not use these tags.
- def csrf_meta_tags
- if protect_against_forgery?
- [
- tag('meta', :name => 'csrf-param', :content => request_forgery_protection_token),
- tag('meta', :name => 'csrf-token', :content => form_authenticity_token)
- ].join("\n").html_safe
- end
- end
-
- # For backwards compatibility.
- alias csrf_meta_tag csrf_meta_tags
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
deleted file mode 100644
index 387dfeab17..0000000000
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ /dev/null
@@ -1,1045 +0,0 @@
-require 'date'
-require 'action_view/helpers/tag_helper'
-require 'active_support/core_ext/date/conversions'
-require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/object/with_options'
-
-module ActionView
- module Helpers
- # = Action View Date Helpers
- #
- # The Date Helper primarily creates select/option tags for different kinds of dates and times or date and time
- # 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.
- # * <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].
- module DateHelper
- # Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds.
- # 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
- # 30 secs <-> 1 min, 29 secs # => 1 minute
- # 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
- # 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
- # 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 <-> 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 => 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
- # 20-39 secs # => half a minute
- # 40-59 secs # => less than a minute
- # 60-89 secs # => 1 minute
- #
- # ==== 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, :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, :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 = {})
- if include_seconds_or_options.is_a?(Hash)
- options = include_seconds_or_options
- else
- 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
- 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)
- 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 options[:include_seconds]
-
- case distance_in_seconds
- when 0..4 then locale.t :less_than_x_seconds, :count => 5
- when 5..9 then locale.t :less_than_x_seconds, :count => 10
- when 10..19 then locale.t :less_than_x_seconds, :count => 20
- when 20..39 then locale.t :half_a_minute
- when 40..59 then locale.t :less_than_x_minutes, :count => 1
- else locale.t :x_minutes, :count => 1
- end
-
- 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
- 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
- locale.t(:about_x_years, :count => distance_in_years)
- elsif remainder < 394200
- locale.t(:over_x_years, :count => distance_in_years)
- else
- locale.t(:almost_x_years, :count => distance_in_years + 1)
- end
- end
- end
- end
-
- # Like <tt>distance_of_time_in_words</tt>, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
- #
- # time_ago_in_words(3.minutes.from_now) # => 3 minutes
- # time_ago_in_words(3.minutes.ago) # => 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
- #
- # from_time = (3.days + 14.minutes + 25.seconds).ago
- # time_ago_in_words(from_time) # => 3 days
- #
- # Note that you cannot pass a <tt>Numeric</tt> value to <tt>time_ago_in_words</tt>.
- #
- 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
-
- # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based
- # attribute (identified by +method+) on an object assigned to the template (identified by +object+).
- #
- #
- # ==== 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.
- # "2 - February" instead of "February").
- # * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names.
- # Note: You can also use Rails' i18n functionality for this.
- # * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
- # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Time.now.year - 5</tt>.
- # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Time.now.year + 5</tt>.
- # * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
- # as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
- # first of the given month in order to not create invalid dates like 31 February.
- # * <tt>:discard_month</tt> - Set to true if you don't want to show a month select. This includes the month
- # as a hidden field instead of showing a select field. Also note that this implicitly sets :discard_day to true.
- # * <tt>:discard_year</tt> - Set to true if you don't want to show a year select. This includes the year
- # as a hidden field instead of showing a select field.
- # * <tt>:order</tt> - Set to an array containing <tt>:day</tt>, <tt>:month</tt> and <tt>:year</tt> to
- # customize the order in which the select fields are shown. If you leave out any of the symbols, the respective
- # select will not be shown (like when you set <tt>:discard_xxx => true</tt>. Defaults to the order defined in
- # the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails).
- # * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty
- # dates.
- # * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil.
- # * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled.
- # * <tt>:prompt</tt> - Set to true (for a generic prompt), a prompt string or a hash of prompt strings
- # for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>.
- # Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds)
- # or the given prompt string.
- #
- # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
- #
- # NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed.
- #
- # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute.
- # date_select("article", "written_on")
- #
- # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
- # # with the year in the year drop down box starting at 1995.
- # date_select("article", "written_on", :start_year => 1995)
- #
- # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
- # # with the year in the year drop down box starting at 1995, numbers used for months instead of words,
- # # and without a day select box.
- # 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])
- #
- # # Generates a date select that when POSTed is stored in the user variable, in the birthday attribute
- # # lacking a year field.
- # date_select("user", "birthday", :order => [:month, :day])
- #
- # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
- # # which is initially set to the date 3 days from the current date
- # date_select("article", "written_on", :default => 3.days.from_now)
- #
- # # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute
- # # that will have a default day of 20.
- # date_select("credit_card", "bill_due", :default => { :day => 20 })
- #
- # # Generates a date select with custom prompts.
- # date_select("article", "written_on", :prompt => { :day => 'Select day', :month => 'Select month', :year => 'Select year' })
- #
- # The selects are prepared for multi-parameter assignment to an Active Record object.
- #
- # 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 = {})
- 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
- # specified time-based attribute (identified by +method+) on an object assigned to the template (identified by
- # +object+). You can include the seconds with <tt>:include_seconds</tt>. You can get hours in the AM/PM format
- # with <tt>:ampm</tt> option.
- #
- # This method will also generate 3 input hidden tags, for the actual year, month and day unless the option
- # <tt>:ignore_date</tt> is set to +true+. If you set the <tt>:ignore_date</tt> to +true+, you must have a
- # +date_select+ on the same method within the form otherwise an exception will be raised.
- #
- # If anything is passed in the html_options hash it will be applied to every select tag in the set.
- #
- # # Creates a time select tag that, when POSTed, will be stored in the article variable in the sunrise attribute.
- # time_select("article", "sunrise")
- #
- # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the article variables in
- # # the sunrise attribute.
- # time_select("article", "start_time", :include_seconds => true)
- #
- # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30 and 45.
- # time_select 'game', 'game_time', {:minute_step => 15}
- #
- # # Creates a time select tag with a custom prompt. Use <tt>:prompt => true</tt> for generic prompts.
- # time_select("article", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
- # time_select("article", "written_on", :prompt => {:hour => true}) # generic prompt for hours
- # time_select("article", "written_on", :prompt => true) # generic prompts for all
- #
- # # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
- # time_select 'game', 'game_time', {:ampm => true}
- #
- # The selects are prepared for multi-parameter assignment to an Active Record object.
- #
- # 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 = {})
- 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
- # specified datetime-based attribute (identified by +method+) on an object assigned to the template (identified
- # by +object+).
- #
- # If anything is passed in the html_options hash it will be applied to every select tag in the set.
- #
- # # Generates a datetime select that, when POSTed, will be stored in the article variable in the written_on
- # # attribute.
- # datetime_select("article", "written_on")
- #
- # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
- # # article variable in the written_on attribute.
- # datetime_select("article", "written_on", :start_year => 1995)
- #
- # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will
- # # be stored in the trip variable in the departing attribute.
- # datetime_select("trip", "departing", :default => 3.days.from_now)
- #
- # # Generate a datetime select with hours in the AM/PM format
- # datetime_select("article", "written_on", :ampm => true)
- #
- # # Generates a datetime select that discards the type that, when POSTed, will be stored in the article variable
- # # as the written_on attribute.
- # datetime_select("article", "written_on", :discard_type => true)
- #
- # # Generates a datetime select with a custom prompt. Use <tt>:prompt => true</tt> for generic prompts.
- # datetime_select("article", "written_on", :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
- # datetime_select("article", "written_on", :prompt => {:hour => true}) # generic prompt for hours
- # datetime_select("article", "written_on", :prompt => true) # generic prompts for all
- #
- # The selects are prepared for multi-parameter assignment to an Active Record object.
- def datetime_select(object_name, method, 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
- # +datetime+. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with
- # an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not
- # supply a Symbol, it will be appended onto the <tt>:order</tt> passed in. You can also add
- # <tt>:date_separator</tt>, <tt>:datetime_separator</tt> and <tt>:time_separator</tt> keys to the +options+ to
- # control visual display of the elements.
- #
- # If anything is passed in the html_options hash it will be applied to every select tag in the set.
- #
- # my_date_time = Time.now + 4.days
- #
- # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today).
- # select_datetime(my_date_time)
- #
- # # Generates a datetime select that defaults to today (no specified datetime)
- # select_datetime()
- #
- # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
- # # with the fields ordered year, month, day rather than month, day, year.
- # select_datetime(my_date_time, :order => [:year, :month, :day])
- #
- # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
- # # with a '/' between each date field.
- # select_datetime(my_date_time, :date_separator => '/')
- #
- # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
- # # with a date fields separated by '/', time fields separated by '' and the date and time fields
- # # separated by a comma (',').
- # select_datetime(my_date_time, :date_separator => '/', :time_separator => '', :datetime_separator => ',')
- #
- # # Generates a datetime select that discards the type of the field and defaults to the datetime in
- # # my_date_time (four days after today)
- # select_datetime(my_date_time, :discard_type => true)
- #
- # # Generate a datetime field with hours in the AM/PM format
- # select_datetime(my_date_time, :ampm => true)
- #
- # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
- # # prefixed with 'payday' rather than 'date'
- # select_datetime(my_date_time, :prefix => 'payday')
- #
- # # Generates a datetime select with a custom prompt. Use <tt>:prompt => true</tt> for generic prompts.
- # select_datetime(my_date_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
- # select_datetime(my_date_time, :prompt => {:hour => true}) # generic prompt for hours
- # select_datetime(my_date_time, :prompt => true) # generic prompts for all
- def select_datetime(datetime = Time.current, options = {}, html_options = {})
- DateTimeSelector.new(datetime, options, html_options).select_datetime
- end
-
- # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
- # It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
- # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order.
- # If the array passed to the <tt>:order</tt> option does not contain all the three symbols, all tags will be hidden.
- #
- # If anything is passed in the html_options hash it will be applied to every select tag in the set.
- #
- # my_date = Time.now + 6.days
- #
- # # Generates a date select that defaults to the date in my_date (six days after today).
- # select_date(my_date)
- #
- # # Generates a date select that defaults to today (no specified date).
- # select_date()
- #
- # # Generates a date select that defaults to the date in my_date (six days after today)
- # # with the fields ordered year, month, day rather than month, day, year.
- # select_date(my_date, :order => [:year, :month, :day])
- #
- # # Generates a date select that discards the type of the field and defaults to the date in
- # # my_date (six days after today).
- # select_date(my_date, :discard_type => true)
- #
- # # Generates a date select that defaults to the date in my_date,
- # # which has fields separated by '/'.
- # select_date(my_date, :date_separator => '/')
- #
- # # Generates a date select that defaults to the datetime in my_date (six days after today)
- # # prefixed with 'payday' rather than 'date'.
- # select_date(my_date, :prefix => 'payday')
- #
- # # Generates a date select with a custom prompt. Use <tt>:prompt => true</tt> for generic prompts.
- # select_date(my_date, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
- # select_date(my_date, :prompt => {:hour => true}) # generic prompt for hours
- # select_date(my_date, :prompt => true) # generic prompts for all
- def select_date(date = Date.current, options = {}, html_options = {})
- DateTimeSelector.new(date, options, html_options).select_date
- end
-
- # Returns a set of html select-tags (one for hour and minute).
- # You can set <tt>:time_separator</tt> key to format the output, and
- # the <tt>:include_seconds</tt> option to include an input for seconds.
- #
- # If anything is passed in the html_options hash it will be applied to every select tag in the set.
- #
- # my_time = Time.now + 5.days + 7.hours + 3.minutes + 14.seconds
- #
- # # Generates a time select that defaults to the time in my_time.
- # select_time(my_time)
- #
- # # Generates a time select that defaults to the current time (no specified time).
- # select_time()
- #
- # # Generates a time select that defaults to the time in my_time,
- # # which has fields separated by ':'.
- # select_time(my_time, :time_separator => ':')
- #
- # # Generates a time select that defaults to the time in my_time,
- # # that also includes an input for seconds.
- # select_time(my_time, :include_seconds => true)
- #
- # # Generates a time select that defaults to the time in my_time, that has fields
- # # separated by ':' and includes an input for seconds.
- # select_time(my_time, :time_separator => ':', :include_seconds => true)
- #
- # # Generate a time select field with hours in the AM/PM format
- # select_time(my_time, :ampm => true)
- #
- # # Generates a time select field with hours that range from 2 to 14
- # select_time(my_time, :start_hour => 2, :end_hour => 14)
- #
- # # Generates a time select with a custom prompt. Use <tt>:prompt</tt> to true for generic prompts.
- # select_time(my_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
- # select_time(my_time, :prompt => {:hour => true}) # generic prompt for hours
- # select_time(my_time, :prompt => true) # generic prompts for all
- def select_time(datetime = Time.current, options = {}, html_options = {})
- DateTimeSelector.new(datetime, options, html_options).select_time
- end
-
- # Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
- # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
- # Override the field name using the <tt>:field_name</tt> option, 'second' by default.
- #
- # my_time = Time.now + 16.minutes
- #
- # # Generates a select field for seconds that defaults to the seconds for the time in my_time.
- # select_second(my_time)
- #
- # # Generates a select field for seconds that defaults to the number given.
- # select_second(33)
- #
- # # Generates a select field for seconds that defaults to the seconds for the time in my_time
- # # that is named 'interval' rather than 'second'.
- # select_second(my_time, :field_name => 'interval')
- #
- # # Generates a select field for seconds with a custom prompt. Use <tt>:prompt => true</tt> for a
- # # generic prompt.
- # select_second(14, :prompt => 'Choose seconds')
- def select_second(datetime, options = {}, html_options = {})
- DateTimeSelector.new(datetime, options, html_options).select_second
- end
-
- # Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
- # Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute
- # selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
- # Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
- #
- # my_time = Time.now + 6.hours
- #
- # # Generates a select field for minutes that defaults to the minutes for the time in my_time.
- # select_minute(my_time)
- #
- # # Generates a select field for minutes that defaults to the number given.
- # select_minute(14)
- #
- # # Generates a select field for minutes that defaults to the minutes for the time in my_time
- # # that is named 'moment' rather than 'minute'.
- # select_minute(my_time, :field_name => 'moment')
- #
- # # Generates a select field for minutes with a custom prompt. Use <tt>:prompt => true</tt> for a
- # # generic prompt.
- # select_minute(14, :prompt => 'Choose minutes')
- def select_minute(datetime, options = {}, html_options = {})
- DateTimeSelector.new(datetime, options, html_options).select_minute
- end
-
- # Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
- # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
- # Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
- #
- # my_time = Time.now + 6.hours
- #
- # # Generates a select field for hours that defaults to the hour for the time in my_time.
- # select_hour(my_time)
- #
- # # Generates a select field for hours that defaults to the number given.
- # select_hour(13)
- #
- # # Generates a select field for hours that defaults to the hour for the time in my_time
- # # that is named 'stride' rather than 'hour'.
- # select_hour(my_time, :field_name => 'stride')
- #
- # # Generates a select field for hours with a custom prompt. Use <tt>:prompt => true</tt> for a
- # # generic prompt.
- # select_hour(13, :prompt => 'Choose hour')
- #
- # # Generate a select field for hours in the AM/PM format
- # select_hour(my_time, :ampm => true)
- #
- # # Generates a select field that includes options for hours from 2 to 14.
- # select_hour(my_time, :start_hour => 2, :end_hour => 14)
- def select_hour(datetime, options = {}, html_options = {})
- DateTimeSelector.new(datetime, options, html_options).select_hour
- end
-
- # 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.
- #
- # my_date = Time.now + 2.days
- #
- # # Generates a select field for days that defaults to the day for the date in my_date.
- # select_day(my_time)
- #
- # # 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')
- #
- # # Generates a select field for days with a custom prompt. Use <tt>:prompt => true</tt> for a
- # # generic prompt.
- # select_day(5, :prompt => 'Choose day')
- def select_day(date, options = {}, html_options = {})
- DateTimeSelector.new(date, options, html_options).select_day
- end
-
- # Returns a select tag with options for each of the months January through December with the current month
- # selected. The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are
- # used as values (what's submitted to the server). It's also possible to use month numbers for the presentation
- # instead of names -- set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you
- # 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.
- #
- # # Generates a select field for months that defaults to the current month that
- # # will use keys like "January", "March".
- # select_month(Date.today)
- #
- # # Generates a select field for months that defaults to the current month that
- # # is named "start" rather than "month".
- # select_month(Date.today, :field_name => 'start')
- #
- # # Generates a select field for months that defaults to the current month that
- # # will use keys like "1", "3".
- # select_month(Date.today, :use_month_numbers => true)
- #
- # # Generates a select field for months that defaults to the current month that
- # # will use keys like "1 - January", "3 - March".
- # select_month(Date.today, :add_month_numbers => true)
- #
- # # Generates a select field for months that defaults to the current month that
- # # will use keys like "Jan", "Mar".
- # select_month(Date.today, :use_short_month => true)
- #
- # # Generates a select field for months that defaults to the current month that
- # # 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')
- def select_month(date, options = {}, html_options = {})
- DateTimeSelector.new(date, options, html_options).select_month
- end
-
- # Returns a select tag with options for each of the five years on each side of the current, which is selected.
- # The five year radius can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the
- # +options+. Both ascending and descending year lists are supported by making <tt>:start_year</tt> less than or
- # greater than <tt>:end_year</tt>. The <tt>date</tt> can also be substituted for a year given as a number.
- # Override the field name using the <tt>:field_name</tt> option, 'year' by default.
- #
- # # Generates a select field for years that defaults to the current year that
- # # has ascending year values.
- # select_year(Date.today, :start_year => 1992, :end_year => 2007)
- #
- # # Generates a select field for years that defaults to the current year that
- # # is named 'birth' rather than 'year'.
- # select_year(Date.today, :field_name => 'birth')
- #
- # # Generates a select field for years that defaults to the current year that
- # # has descending year values.
- # select_year(Date.today, :start_year => 2005, :end_year => 1900)
- #
- # # Generates a select field for years that defaults to the year 2006 that
- # # has ascending year values.
- # select_year(2006, :start_year => 2000, :end_year => 2010)
- #
- # # Generates a select field for years with a custom prompt. Use <tt>:prompt => true</tt> for a
- # # generic prompt.
- # select_year(14, :prompt => 'Choose year')
- def select_year(date, options = {}, html_options = {})
- DateTimeSelector.new(date, options, html_options).select_year
- end
-
- # Returns an html time tag for the given date or time.
- #
- # time_tag Date.today # =>
- # <time datetime="2010-11-04">November 04, 2010</time>
- # time_tag Time.now # =>
- # <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time>
- # time_tag Date.yesterday, 'Yesterday' # =>
- # <time datetime="2010-11-03">Yesterday</time>
- # time_tag Date.today, :pubdate => true # =>
- # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
- #
- # <%= 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), &block)
- end
- end
-
- class DateTimeSelector #:nodoc:
- include ActionView::Helpers::TagHelper
-
- DEFAULT_PREFIX = 'date'.freeze
- POSITION = {
- :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6
- }.freeze
-
- AMPM_TRANSLATION = Hash[
- [[0, "12 AM"], [1, "01 AM"], [2, "02 AM"], [3, "03 AM"],
- [4, "04 AM"], [5, "05 AM"], [6, "06 AM"], [7, "07 AM"],
- [8, "08 AM"], [9, "09 AM"], [10, "10 AM"], [11, "11 AM"],
- [12, "12 PM"], [13, "01 PM"], [14, "02 PM"], [15, "03 PM"],
- [16, "04 PM"], [17, "05 PM"], [18, "06 PM"], [19, "07 PM"],
- [20, "08 PM"], [21, "09 PM"], [22, "10 PM"], [23, "11 PM"]]
- ].freeze
-
- def initialize(datetime, options = {}, html_options = {})
- @options = options.dup
- @html_options = html_options.dup
- @datetime = datetime
- @options[:datetime_separator] ||= ' &mdash; '
- @options[:time_separator] ||= ' : '
- end
-
- def select_datetime
- order = date_order.dup
- order -= [:hour, :minute, :second]
- @options[:discard_year] ||= true unless order.include?(:year)
- @options[:discard_month] ||= true unless order.include?(:month)
- @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
- @options[:discard_minute] ||= true if @options[:discard_hour]
- @options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute]
-
- set_day_if_discarded
-
- if @options[:tag] && @options[:ignore_date]
- select_time
- else
- [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
- order += [:hour, :minute, :second] unless @options[:discard_hour]
-
- build_selects_from_types(order)
- end
- end
-
- def select_date
- order = date_order.dup
-
- @options[:discard_hour] = true
- @options[:discard_minute] = true
- @options[:discard_second] = true
-
- @options[:discard_year] ||= true unless order.include?(:year)
- @options[:discard_month] ||= true unless order.include?(:month)
- @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
-
- set_day_if_discarded
-
- [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
-
- build_selects_from_types(order)
- end
-
- def select_time
- order = []
-
- @options[:discard_month] = true
- @options[:discard_year] = true
- @options[:discard_day] = true
- @options[:discard_second] ||= true unless @options[:include_seconds]
-
- order += [:year, :month, :day] unless @options[:ignore_date]
-
- order += [:hour, :minute]
- order << :second if @options[:include_seconds]
-
- build_selects_from_types(order)
- end
-
- def select_second
- if @options[:use_hidden] || @options[:discard_second]
- build_hidden(:second, sec) if @options[:include_seconds]
- else
- build_options_and_select(:second, sec)
- end
- end
-
- def select_minute
- if @options[:use_hidden] || @options[:discard_minute]
- build_hidden(:minute, min)
- else
- build_options_and_select(:minute, min, :step => @options[:minute_step])
- end
- end
-
- def select_hour
- if @options[:use_hidden] || @options[:discard_hour]
- build_hidden(:hour, hour)
- else
- options = {}
- options[:ampm] = @options[:ampm] || false
- options[:start] = @options[:start_hour] || 0
- options[:end] = @options[:end_hour] || 23
- build_options_and_select(:hour, hour, options)
- end
- end
-
- def select_day
- if @options[:use_hidden] || @options[:discard_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
- end
-
- def select_month
- if @options[:use_hidden] || @options[:discard_month]
- build_hidden(:month, month || 1)
- else
- month_options = []
- 1.upto(12) do |month_number|
- options = { :value => month_number }
- options[:selected] = "selected" if month == month_number
- month_options << content_tag(:option, month_name(month_number), options) + "\n"
- end
- build_select(:month, month_options.join)
- end
- end
-
- def select_year
- if !@datetime || @datetime == 0
- val = '1'
- middle_year = Date.today.year
- else
- val = middle_year = year
- end
-
- if @options[:use_hidden] || @options[:discard_year]
- build_hidden(:year, val)
- else
- options = {}
- options[:start] = @options[:start_year] || middle_year - 5
- options[:end] = @options[:end_year] || middle_year + 5
- options[:step] = options[:start] < options[:end] ? 1 : -1
- options[:leading_zeros] = false
- options[:max_years_allowed] = @options[:max_years_allowed] || 1000
-
- if (options[:end] - options[:start]).abs > options[:max_years_allowed]
- raise ArgumentError, "There're too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter"
- end
-
- build_options_and_select(:year, val, options)
- end
- end
-
- private
- %w( sec min hour day month year ).each do |method|
- define_method(method) do
- @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
-
- # Returns translated month names, but also ensures that a custom month
- # name array has a leading nil element.
- def month_names
- @month_names ||= begin
- month_names = @options[:use_month_names] || translated_month_names
- month_names.unshift(nil) if month_names.size < 13
- month_names
- end
- end
-
- # Returns translated month names.
- # => [nil, "January", "February", "March",
- # "April", "May", "June", "July",
- # "August", "September", "October",
- # "November", "December"]
- #
- # If <tt>:use_short_month</tt> option is set
- # => [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun",
- # "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
- def translated_month_names
- key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
- I18n.translate(key, :locale => @options[:locale])
- end
-
- # Lookup month name for number.
- # month_name(1) => "January"
- #
- # 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)
- if @options[:use_month_numbers]
- number
- elsif @options[:use_two_digit_numbers]
- sprintf "%02d", number
- elsif @options[:add_month_numbers]
- "#{number} - #{month_names[number]}"
- else
- month_names[number]
- end
- end
-
- def date_order
- @date_order ||= @options[:order] || translated_date_order
- end
-
- def translated_date_order
- 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.
- def build_options_and_select(type, selected, options = {})
- build_select(type, build_options(selected, options))
- end
-
- # Build select option html from date value and options.
- # build_options(15, :start => 1, :end => 31)
- # => "<option value="1">1</option>
- # <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>
- # <option value="3">3</option>
- # <option value="5">5</option>..."
- def build_options(selected, options = {})
- options = {
- leading_zeros: true, ampm: false, use_two_digit_numbers: false
- }.merge!(options)
-
- start = options.delete(:start) || 0
- stop = options.delete(:end) || 59
- step = options.delete(:step) || 1
- leading_zeros = options.delete(:leading_zeros)
-
- select_options = []
- start.step(stop, step) do |i|
- value = leading_zeros ? sprintf("%02d", i) : i
- tag_options = { :value => value }
- tag_options[:selected] = "selected" if selected == i
- text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
- text = options[:ampm] ? AMPM_TRANSLATION[i] : text
- select_options << content_tag(:option, text, tag_options)
- end
-
- (select_options.join("\n") + "\n").html_safe
- end
-
- # Builds select tag from date type and html select options.
- # build_select(:month, "<option value="1">January</option>...")
- # => "<select id="post_written_on_2i" name="post[written_on(2i)]">
- # <option value="1">January</option>...
- # </select>"
- def build_select(type, select_options_as_html)
- select_options = {
- :id => input_id_from_type(type),
- :name => input_name_from_type(type)
- }.merge!(@html_options)
- select_options[:disabled] = 'disabled' if @options[:disabled]
-
- select_html = "\n"
- select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
- select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
- select_html << select_options_as_html
-
- (content_tag(:select, select_html.html_safe, select_options) + "\n").html_safe
- end
-
- # Builds a prompt option tag with supplied options or from default options.
- # prompt_option_tag(:month, :prompt => 'Select month')
- # => "<option value="">Select month</option>"
- def prompt_option_tag(type, options)
- prompt = case options
- when Hash
- default_options = {:year => false, :month => false, :day => false, :hour => false, :minute => false, :second => false}
- default_options.merge!(options)[type.to_sym]
- when String
- options
- else
- I18n.translate(:"datetime.prompts.#{type}", :locale => @options[:locale])
- end
-
- prompt ? content_tag(:option, prompt, :value => '') : ''
- end
-
- # Builds hidden input tag for date part and value.
- # build_hidden(:year, 2008)
- # => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />"
- def build_hidden(type, value)
- select_options = {
- :type => "hidden",
- :id => input_id_from_type(type),
- :name => input_name_from_type(type),
- :value => value
- }.merge!(@html_options.slice(:disabled))
- select_options[:disabled] = 'disabled' if @options[:disabled]
-
- tag(:input, select_options) + "\n".html_safe
- end
-
- # Returns the name attribute for the input tag.
- # => post[written_on(1i)]
- def input_name_from_type(type)
- prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
- prefix += "[#{@options[:index]}]" if @options.has_key?(:index)
-
- field_name = @options[:field_name] || type
- if @options[:include_position]
- field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)"
- end
-
- @options[:discard_type] ? prefix : "#{prefix}[#{field_name}]"
- end
-
- # Returns the id attribute for the input tag.
- # => "post_written_on_1i"
- def input_id_from_type(type)
- 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
- # and join them with their appropriate separators.
- def build_selects_from_types(order)
- select = ''
- first_visible = order.find { |type| !@options[:"discard_#{type}"] }
- order.reverse.each do |type|
- separator = separator(type) unless type == first_visible # don't add before first visible field
- select.insert(0, separator.to_s + send("select_#{type}").to_s)
- end
- select.html_safe
- end
-
- # Returns the separator for a given datetime component.
- def separator(type)
- return "" if @options[:use_hidden]
-
- case type
- when :year, :month, :day
- @options[:"discard_#{type}"] ? "" : @options[:date_separator]
- when :hour
- (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
- when :minute, :second
- @options[:"discard_#{type}"] ? "" : @options[:time_separator]
- end
- end
- end
-
- class FormBuilder
- def date_select(method, options = {}, html_options = {})
- @template.date_select(@object_name, method, objectify_options(options), html_options)
- end
-
- def time_select(method, options = {}, html_options = {})
- @template.time_select(@object_name, method, objectify_options(options), html_options)
- end
-
- def datetime_select(method, options = {}, html_options = {})
- @template.datetime_select(@object_name, method, objectify_options(options), html_options)
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb
deleted file mode 100644
index d8b92c5cab..0000000000
--- a/actionpack/lib/action_view/helpers/debug_helper.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-module ActionView
- # = Action View Debug Helper
- #
- # Provides a set of methods for making it easier to debug Rails objects.
- module Helpers
- module DebugHelper
-
- include TagHelper
-
- # Returns a YAML representation of +object+ wrapped with <pre> and </pre>.
- # If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
- # Useful for inspecting an object at the time of rendering.
- #
- # @user = User.new({ :username => 'testing', :password => 'xyz', :age => 42}) %>
- # debug(@user)
- # # =>
- # <pre class='debug_dump'>--- !ruby/object:User
- # attributes:
- # &nbsp; updated_at:
- # &nbsp; username: testing
- #
- # &nbsp; age: 42
- # &nbsp; password: xyz
- # &nbsp; created_at:
- # attributes_cache: {}
- #
- # new_record: true
- # </pre>
- def debug(object)
- begin
- Marshal::dump(object)
- object = ERB::Util.html_escape(object.to_yaml).gsub(" ", "&nbsp; ").html_safe
- content_tag(:pre, object, :class => "debug_dump")
- rescue Exception # errors from Marshal or YAML
- # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
- content_tag(:code, object.to_yaml, :class => "debug_dump")
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
deleted file mode 100644
index b87c2e936f..0000000000
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ /dev/null
@@ -1,1416 +0,0 @@
-require 'cgi'
-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 'action_view/model_naming'
-require 'active_support/core_ext/class/attribute_accessors'
-require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/string/output_safety'
-require 'active_support/core_ext/array/extract_options'
-require 'active_support/core_ext/string/inflections'
-
-module ActionView
- # = Action View Form Helpers
- module Helpers
- # Form helpers are designed to make working with resources much easier
- # compared to using vanilla HTML.
- #
- # 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.
- #
- # 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
- # in the view template pass that object to +form_for+:
- #
- # <%= form_for @person do |f| %>
- # <%= f.label :first_name %>:
- # <%= f.text_field :first_name %><br />
- #
- # <%= f.label :last_name %>:
- # <%= f.text_field :last_name %><br />
- #
- # <%= f.submit %>
- # <% end %>
- #
- # The HTML generated for this would be (modulus formatting):
- #
- # <form action="/people" class="new_person" id="new_person" method="post">
- # <div style="margin:0;padding:0;display:inline">
- # <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]" type="text" /><br />
- #
- # <label for="person_last_name">Last name</label>:
- # <input id="person_last_name" name="person[last_name]" type="text" /><br />
- #
- # <input name="commit" type="submit" value="Create Person" />
- # </form>
- #
- # As you see, the HTML reflects knowledge about the resource in several spots,
- # like the path the form should be submitted to, or the names of the input fields.
- #
- # In particular, thanks to the conventions followed in the generated field names, the
- # controller gets a nested hash <tt>params[:person]</tt> with the person attributes
- # set in the form. That hash is ready to be passed to <tt>Person.create</tt>:
- #
- # if @person = Person.create(params[:person])
- # # success
- # else
- # # error handling
- # end
- #
- # Interestingly, the exact same view code in the previous example can be used to edit
- # a person. If <tt>@person</tt> is an existing record with name "John Smith" and ID 256,
- # the code above as is would yield instead:
- #
- # <form action="/people/256" class="edit_person" id="edit_person_256" method="post">
- # <div style="margin:0;padding:0;display:inline">
- # <input name="_method" type="hidden" value="put" />
- # <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]" type="text" value="John" /><br />
- #
- # <label for="person_last_name">Last name</label>:
- # <input id="person_last_name" name="person[last_name]" type="text" value="Smith" /><br />
- #
- # <input name="commit" type="submit" value="Update Person" />
- # </form>
- #
- # Note that the endpoint, default values, and submit button label are tailored for <tt>@person</tt>.
- # That works that way because the involved helpers know whether the resource is a new record or not,
- # and generate HTML accordingly.
- #
- # The controller would receive the form data again in <tt>params[:person]</tt>, ready to be
- # passed to <tt>Person#update_attributes</tt>:
- #
- # if @person.update_attributes(params[:person])
- # # success
- # else
- # # error handling
- # end
- #
- # That's how you typically work with resources.
- module FormHelper
- extend ActiveSupport::Concern
-
- include FormTagHelper
- include UrlHelper
- include ModelNaming
-
- # Creates a form that allows the user to create or update the attributes
- # of a specific model object.
- #
- # 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 />
- # Last name : <%= f.text_field :last_name %><br />
- # Biography : <%= f.text_area :biography %><br />
- # Admin? : <%= f.check_box :admin %><br />
- # <%= f.submit %>
- # <% end %>
- #
- # 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 %>
- #
- # 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 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.
- # * <tt>:html</tt> - Optional HTML attributes for the form tag.
- #
- # Also note that +form_for+ doesn't create an exclusive scope. It's still
- # possible to use both the stand-alone FormHelper methods and methods
- # from FormTagHelper. For example:
- #
- # <%= 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]", "1", @person.company.admin? %>
- # <%= f.submit %>
- # <% end %>
- #
- # 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.
- #
- # === #form_for with a model object
- #
- # 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
- #
- # <%= 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 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 a new record
- #
- # <%= form_for(Post.new) do |f| %>
- # ...
- # <% end %>
- #
- # is equivalent to something like:
- #
- # <%= form_for @post, as: :post, url: posts_path, html: { class: "new_post", id: "new_post" } do |f| %>
- # ...
- # <% end %>
- #
- # However you can still overwrite individual conventions, such as:
- #
- # <%= form_for(@post, url: super_posts_path) do |f| %>
- # ...
- # <% end %>
- #
- # You can also set the answer format, like this:
- #
- # <%= form_for(@post, format: :json) do |f| %>
- # ...
- # <% end %>
- #
- # For namespaced routes, like +admin_post_url+:
- #
- # <%= form_for([:admin, @post]) do |f| %>
- # ...
- # <% end %>
- #
- # If your resource has associations defined, for example, you want to add comments
- # to the document given that the routes are set correctly:
- #
- # <%= form_for([@document, @comment]) do |f| %>
- # ...
- # <% end %>
- #
- # Where <tt>@document = Document.find(params[:id])</tt> and
- # <tt>@comment = Comment.new</tt>.
- #
- # === Setting the method
- #
- # You can force the form to use the full array of HTTP verbs by setting
- #
- # 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.
- #
- # === Unobtrusive JavaScript
- #
- # Specifying:
- #
- # remote: true
- #
- # in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
- # behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular
- # POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
- # Even though it's using JavaScript to serialize the form elements, the form submission will work just like
- # a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>).
- #
- # Example:
- #
- # <%= form_for(@post, remote: true) do |f| %>
- # ...
- # <% end %>
- #
- # The HTML generated for this would be:
- #
- # <form action='http://www.example.com' method='post' data-remote='true'>
- # <div style='margin:0;padding:0;display:inline'>
- # <input name='_method' type='hidden' value='put' />
- # </div>
- # ...
- # </form>
- #
- # === Setting HTML options
- #
- # You can set data attributes directly by passing in a data hash, but all other HTML options must be wrapped in
- # the HTML key. Example:
- #
- # <%= form_for(@post, data: { behavior: "autosave" }, html: { name: "go" }) do |f| %>
- # ...
- # <% end %>
- #
- # The HTML generated for this would be:
- #
- # <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'>
- # <div style='margin:0;padding:0;display:inline'>
- # <input name='_method' type='hidden' value='put' />
- # </div>
- # ...
- # </form>
- #
- # === Removing hidden model id's
- #
- # The form_for method automatically includes the model id as a hidden field in the form.
- # This is used to maintain the correlation between the form data and its associated model.
- # Some ORM systems do not use IDs on nested models so in this case you want to be able
- # to disable the hidden id.
- #
- # In the following example the Post model has many Comments stored within it in a NoSQL database,
- # thus there is no primary key for comments.
- #
- # Example:
- #
- # <%= form_for(@post) do |f| %>
- # <%= f.fields_for(:comments, include_id: false) do |cf| %>
- # ...
- # <% end %>
- # <% end %>
- #
- # === Customized form builders
- #
- # You can also build forms using a customized FormBuilder class. Subclass
- # FormBuilder and override or define some more helpers, then use your
- # custom builder. For example, let's say you made a helper to
- # automatically add labels to form inputs.
- #
- # <%= form_for @person, url: { action: "create" }, builder: LabellingFormBuilder do |f| %>
- # <%= f.text_field :first_name %>
- # <%= f.text_field :last_name %>
- # <%= f.text_area :biography %>
- # <%= f.check_box :admin %>
- # <%= f.submit %>
- # <% end %>
- #
- # In this case, if you use this:
- #
- # <%= render f %>
- #
- # The rendered template is <tt>people/_labelling_form</tt> and the local
- # variable referencing the form builder is called
- # <tt>labelling_form</tt>.
- #
- # The custom FormBuilder class is automatically merged with the options
- # of a nested fields_for call, unless it's explicitly set.
- #
- # In many cases you will want to wrap the above in another helper, so you
- # could do something like the following:
- #
- # def labelled_form_for(record_or_name_or_array, *args, &proc)
- # options = args.extract_options!
- # form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &proc)
- # end
- #
- # If you don't need to attach a form to a model instance, then check out
- # FormTagHelper#form_tag.
- #
- # === Form to external resources
- #
- # When you build forms to external resources sometimes you need to set an authenticity token or just render a form
- # without it, for example when you submit data to a payment gateway number and types of fields could be limited.
- #
- # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
- #
- # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f|
- # ...
- # <% end %>
- #
- # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
- #
- # <%= form_for @invoice, url: external_url, authenticity_token: false do |f|
- # ...
- # <% end %>
- def form_for(record, options = {}, &proc)
- raise ArgumentError, "Missing block" unless block_given?
-
- options[:html] ||= {}
-
- case record
- when String, Symbol
- object_name = record
- object = nil
- else
- object = record.is_a?(Array) ? record.last : record
- raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
- object_name = options[:as] || model_name_from_record_or_class(object).param_key
- apply_form_for_options!(record, object, options)
- end
-
- options[:html][:data] = options.delete(:data) if options.has_key?(:data)
- options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote)
- 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)
- fields_for = fields_for(object_name, object, options, &proc)
- default_options = builder.multipart? ? { multipart: true } : {}
- 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, :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,
- method: method
- )
-
- options[:url] ||= polymorphic_path(record, format: options.delete(:format))
- end
- private :apply_form_for_options!
-
- # Creates a scope around a specific model object like form_for, but
- # doesn't create the form tags themselves. This makes fields_for suitable
- # for specifying additional model objects in the same form.
- #
- # === 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 :permission, @person.permission do |permission_fields| %>
- # Admin? : <%= permission_fields.check_box :admin %>
- # <% end %>
- #
- # <%= f.submit %>
- # <% end %>
- #
- # 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 :permission do |permission_fields| %>
- # Admin?: <%= permission_fields.check_box :admin %>
- # <% end %>
- #
- # ...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>.
- #
- # 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.
- #
- # === Nested Attributes Examples
- #
- # When the object belonging to the current scope has a nested attribute
- # writer for a certain attribute, fields_for will yield a new scope
- # for that attribute. This allows you to create forms that set or change
- # the attributes of a parent object and its associations in one go.
- #
- # Nested attribute writers are normal setter methods named after an
- # association. The most common way of defining these writers is either
- # with +accepts_nested_attributes_for+ in a model definition or by
- # defining a method with the proper name. For example: the attribute
- # writer for the association <tt>:address</tt> is called
- # <tt>address_attributes=</tt>.
- #
- # Whether a one-to-one or one-to-many style form builder will be yielded
- # depends on whether the normal reader method returns a _single_ object
- # or an _array_ of objects.
- #
- # ==== One-to-one
- #
- # Consider a Person class which returns a _single_ Address from the
- # <tt>address</tt> reader method and responds to the
- # <tt>address_attributes=</tt> writer method:
- #
- # class Person
- # def address
- # @address
- # end
- #
- # def address_attributes=(attributes)
- # # Process the attributes hash
- # end
- # end
- #
- # This model can now be used with a nested fields_for, like so:
- #
- # <%= form_for @person do |person_form| %>
- # ...
- # <%= person_form.fields_for :address do |address_fields| %>
- # Street : <%= address_fields.text_field :street %>
- # Zip code: <%= address_fields.text_field :zip_code %>
- # <% end %>
- # ...
- # <% end %>
- #
- # When address is already an association on a Person you can use
- # +accepts_nested_attributes_for+ to define the writer method for you:
- #
- # class Person < ActiveRecord::Base
- # has_one :address
- # accepts_nested_attributes_for :address
- # end
- #
- # If you want to destroy the associated model through the form, you have
- # to enable it first using the <tt>:allow_destroy</tt> option for
- # +accepts_nested_attributes_for+:
- #
- # class Person < ActiveRecord::Base
- # has_one :address
- # accepts_nested_attributes_for :address, allow_destroy: true
- # end
- #
- # Now, when you use a form element with the <tt>_destroy</tt> parameter,
- # with a value that evaluates to +true+, you will destroy the associated
- # model (eg. 1, '1', true, or 'true'):
- #
- # <%= form_for @person do |person_form| %>
- # ...
- # <%= person_form.fields_for :address do |address_fields| %>
- # ...
- # Delete: <%= address_fields.check_box :_destroy %>
- # <% end %>
- # ...
- # <% end %>
- #
- # ==== One-to-many
- #
- # Consider a Person class which returns an _array_ of Project instances
- # from the <tt>projects</tt> reader method and responds to the
- # <tt>projects_attributes=</tt> writer method:
- #
- # class Person
- # def projects
- # [@project1, @project2]
- # end
- #
- # def projects_attributes=(attributes)
- # # Process the attributes hash
- # end
- # end
- #
- # Note that the <tt>projects_attributes=</tt> writer method is in fact
- # required for fields_for to correctly identify <tt>:projects</tt> as a
- # collection, and the correct indices to be set in the form markup.
- #
- # When projects is already an association on Person you can use
- # +accepts_nested_attributes_for+ to define the writer method for you:
- #
- # class Person < ActiveRecord::Base
- # has_many :projects
- # accepts_nested_attributes_for :projects
- # end
- #
- # This model can now be used with a nested fields_for. The block given to
- # the nested fields_for call will be repeated for each instance in the
- # collection:
- #
- # <%= form_for @person do |person_form| %>
- # ...
- # <%= person_form.fields_for :projects do |project_fields| %>
- # <% if project_fields.object.active? %>
- # Name: <%= project_fields.text_field :name %>
- # <% end %>
- # <% end %>
- # ...
- # <% end %>
- #
- # It's also possible to specify the instance to be used:
- #
- # <%= form_for @person do |person_form| %>
- # ...
- # <% @person.projects.each do |project| %>
- # <% if project.active? %>
- # <%= person_form.fields_for :projects, project do |project_fields| %>
- # Name: <%= project_fields.text_field :name %>
- # <% end %>
- # <% end %>
- # <% end %>
- # ...
- # <% end %>
- #
- # Or a collection to be used:
- #
- # <%= form_for @person do |person_form| %>
- # ...
- # <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
- # Name: <%= project_fields.text_field :name %>
- # <% end %>
- # ...
- # <% end %>
- #
- # When projects is already an association on Person you can use
- # +accepts_nested_attributes_for+ to define the writer method for you:
- #
- # class Person < ActiveRecord::Base
- # has_many :projects
- # accepts_nested_attributes_for :projects
- # end
- #
- # If you want to destroy any of the associated models through the
- # form, you have to enable it first using the <tt>:allow_destroy</tt>
- # option for +accepts_nested_attributes_for+:
- #
- # class Person < ActiveRecord::Base
- # has_many :projects
- # accepts_nested_attributes_for :projects, allow_destroy: true
- # end
- #
- # This will allow you to specify which models to destroy in the
- # attributes hash by adding a form element for the <tt>_destroy</tt>
- # parameter with a value that evaluates to +true+
- # (eg. 1, '1', true, or 'true'):
- #
- # <%= form_for @person do |person_form| %>
- # ...
- # <%= person_form.fields_for :projects do |project_fields| %>
- # Delete: <%= project_fields.check_box :_destroy %>
- # <% 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)
- output = capture(builder, &block)
- output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
- output
- end
-
- # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
- # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
- # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
- # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
- # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
- # target labels for radio_button tags (where the value is used in the ID of the input tag).
- #
- # ==== Examples
- # label(:post, :title)
- # # => <label for="post_title">Title</label>
- #
- # You can localize your labels based on model and attribute names.
- # For example you can define the following in your locale (e.g. en.yml)
- #
- # helpers:
- # label:
- # post:
- # body: "Write your entire text here"
- #
- # Which then will result in
- #
- # label(:post, :body)
- # # => <label for="post_body">Write your entire text here</label>
- #
- # Localization can also be based purely on the translation of the attribute-name
- # (if you are using ActiveRecord):
- #
- # activerecord:
- # attributes:
- # post:
- # cost: "Total cost"
- #
- # label(:post, :cost)
- # # => <label for="post_cost">Total cost</label>
- #
- # label(:post, :title, "A short title")
- # # => <label for="post_title">A short title</label>
- #
- # label(:post, :title, "A short title", class: "title_label")
- # # => <label for="post_title" class="title_label">A short title</label>
- #
- # label(:post, :privacy, "Public Post", value: "public")
- # # => <label for="post_privacy_public">Public Post</label>
- #
- # label(:post, :terms) do
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
- # end
- def label(object_name, method, content_or_options = nil, options = nil, &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
- # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
- # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
- # shown.
- #
- # ==== Examples
- # text_field(:post, :title, size: 20)
- # # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
- #
- # text_field(:post, :title, class: "create_input")
- # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
- #
- # text_field(:session, :user, onchange: "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
- # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }"/>
- #
- # text_field(:snippet, :code, size: 20, class: 'code_input')
- # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
- def text_field(object_name, method, 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
- # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
- # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
- # shown. For security reasons this field is blank by default; pass in a value via +options+ if this is not desired.
- #
- # ==== Examples
- # password_field(:login, :pass, size: 20)
- # # => <input type="password" id="login_pass" name="login[pass]" size="20" />
- #
- # password_field(:account, :secret, class: "form_input", value: @account.secret)
- # # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
- #
- # password_field(:user, :password, onchange: "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
- # # => <input type="password" id="user_password" name="user[password]" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
- #
- # password_field(:account, :pin, size: 20, class: 'form_input')
- # # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
- def password_field(object_name, method, 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
- # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
- # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
- # shown.
- #
- # ==== Examples
- # hidden_field(:signup, :pass_confirm)
- # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
- #
- # hidden_field(:post, :tag_list)
- # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
- #
- # hidden_field(:user, :token)
- # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
- def hidden_field(object_name, method, 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
- # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
- # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
- # shown.
- #
- # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
- #
- # ==== Examples
- # file_field(:user, :avatar)
- # # => <input type="file" id="user_avatar" name="user[avatar]" />
- #
- # file_field(:post, :attached, accept: 'text/html')
- # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
- #
- # file_field(:attachment, :file, class: 'file_input')
- # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
- def file_field(object_name, method, options = {})
- 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+)
- # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
- # hash with +options+.
- #
- # ==== Examples
- # text_area(:post, :body, cols: 20, rows: 40)
- # # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
- # # #{@post.body}
- # # </textarea>
- #
- # text_area(:comment, :text, size: "20x30")
- # # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
- # # #{@comment.text}
- # # </textarea>
- #
- # text_area(:application, :notes, cols: 40, rows: 15, class: 'app_input')
- # # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
- # # #{@application.notes}
- # # </textarea>
- #
- # text_area(:entry, :body, size: "20x20", disabled: 'disabled')
- # # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
- # # #{@entry.body}
- # # </textarea>
- def text_area(object_name, method, 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
- # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
- # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
- # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
- # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
- #
- # ==== Gotcha
- #
- # The HTML specification says unchecked check boxes are not successful, and
- # thus web browsers do not send them. Unfortunately this introduces a gotcha:
- # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
- # invoice the user unchecks its check box, no +paid+ parameter is sent. So,
- # any mass-assignment idiom like
- #
- # @invoice.update_attributes(params[:invoice])
- #
- # wouldn't update the flag.
- #
- # To prevent this the helper generates an auxiliary hidden field before
- # the very check box. The hidden field has the same name and its
- # attributes mimic an unchecked check box.
- #
- # This way, the client either sends only the hidden field (representing
- # the check box is unchecked), or both fields. Since the HTML specification
- # says key/value pairs have to be sent in the same order they appear in the
- # form, and parameters extraction gets the last occurrence of any repeated
- # key in the query string, that works for ordinary forms.
- #
- # Unfortunately that workaround does not work when the check box goes
- # within an array-like parameter, as in
- #
- # <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %>
- # <%= form.check_box :paid %>
- # ...
- # <% end %>
- #
- # because parameter name repetition is precisely what Rails seeks to distinguish
- # the elements of the array. For each item with a checked check box you
- # get an extra ghost item with only that attribute, assigned to "0".
- #
- # In that case it is preferable to either use +check_box_tag+ or to use
- # hashes instead of arrays.
- #
- # # Let's say that @post.validated? is 1:
- # check_box("post", "validated")
- # # => <input name="post[validated]" type="hidden" value="0" />
- # # <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")
- # # => <input name="puppy[gooddog]" type="hidden" value="no" />
- # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
- #
- # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
- # # => <input name="eula[accepted]" type="hidden" value="no" />
- # # <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")
- 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
- # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
- # radio button will be checked.
- #
- # To force the radio button to be checked pass <tt>checked: true</tt> in the
- # +options+ hash. You may pass HTML options there as well.
- #
- # # Let's say that @post.category returns "rails":
- # radio_button("post", "category", "rails")
- # radio_button("post", "category", "java")
- # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
- # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
- #
- # radio_button("user", "receive_newsletter", "yes")
- # radio_button("user", "receive_newsletter", "no")
- # # => <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 = {})
- Tags::RadioButton.new(object_name, method, self, tag_value, options).render
- end
-
- # Returns a text_field of type "color".
- #
- # color_field("car", "color")
- # # => <input id="car_color" name="car[color]" type="color" value="#000000" />
- def color_field(object_name, method, options = {})
- Tags::ColorField.new(object_name, method, self, options).render
- end
-
- # Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
- # assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
- # some browsers.
- #
- # search_field(:user, :name)
- # # => <input id="user_name" name="user[name]" type="search" />
- # search_field(:user, :name, autosave: false)
- # # => <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" 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" type="search" />
- # search_field(:user, :name, onsearch: true)
- # # => <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" 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" type="search" />
- def search_field(object_name, method, 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]" type="tel" />
- #
- def telephone_field(object_name, method, options = {})
- Tags::TelField.new(object_name, method, self, options).render
- end
- # aliases telephone_field
- 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 "time".
- #
- # The default value is generated by trying to call +strftime+ with "%T.%L"
- # on the objects's value. It is still possible to override that
- # by passing the "value" option.
- #
- # === Options
- # * Accepts same options as time_field_tag
- #
- # === Example
- # time_field("task", "started_at")
- # # => <input id="task_started_at" name="task[started_at]" type="time" />
- #
- def time_field(object_name, method, options = {})
- Tags::TimeField.new(object_name, method, self, options).render
- end
-
- # Returns a text_field of type "datetime".
- #
- # datetime_field("user", "born_on")
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" />
- #
- # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z"
- # on the object's value, which makes it behave as expected for instances
- # of DateTime and ActiveSupport::TimeWithZone.
- #
- # @user.born_on = Date.new(1984, 1, 12)
- # datetime_field("user", "born_on")
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" />
- #
- def datetime_field(object_name, method, options = {})
- Tags::DatetimeField.new(object_name, method, self, options).render
- end
-
- # Returns a text_field of type "datetime-local".
- #
- # datetime_local_field("user", "born_on")
- # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
- #
- # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
- # on the object's value, which makes it behave as expected for instances
- # of DateTime and ActiveSupport::TimeWithZone.
- #
- # @user.born_on = Date.new(1984, 1, 12)
- # datetime_local_field("user", "born_on")
- # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
- #
- def datetime_local_field(object_name, method, options = {})
- Tags::DatetimeLocalField.new(object_name, method, self, options).render
- end
-
- # Returns a text_field of type "month".
- #
- # month_field("user", "born_on")
- # # => <input id="user_born_on" name="user[born_on]" type="month" />
- #
- # The default value is generated by trying to call +strftime+ with "%Y-%m"
- # on the object's value, which makes it behave as expected for instances
- # of DateTime and ActiveSupport::TimeWithZone.
- #
- # @user.born_on = Date.new(1984, 1, 27)
- # month_field("user", "born_on")
- # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-01" />
- #
- def month_field(object_name, method, options = {})
- Tags::MonthField.new(object_name, method, self, options).render
- end
-
- # Returns a text_field of type "week".
- #
- # week_field("user", "born_on")
- # # => <input id="user_born_on" name="user[born_on]" type="week" />
- #
- # The default value is generated by trying to call +strftime+ with "%Y-W%W"
- # on the object's value, which makes it behave as expected for instances
- # of DateTime and ActiveSupport::TimeWithZone.
- #
- # @user.born_on = Date.new(1984, 5, 12)
- # week_field("user", "born_on")
- # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-W19" />
- #
- def week_field(object_name, method, options = {})
- Tags::WeekField.new(object_name, method, self, options).render
- end
-
- # Returns a text_field of type "url".
- #
- # url_field("user", "homepage")
- # # => <input id="user_homepage" name="user[homepage]" type="url" />
- #
- def url_field(object_name, method, 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" name="user[address]" type="email" />
- #
- def email_field(object_name, method, options = {})
- Tags::EmailField.new(object_name, method, self, options).render
- end
-
- # Returns an input tag of type "number".
- #
- # ==== Options
- # * Accepts same options as number_field_tag
- def number_field(object_name, method, options = {})
- Tags::NumberField.new(object_name, method, self, options).render
- end
-
- # Returns an input tag of type "range".
- #
- # ==== Options
- # * Accepts same options as range_field_tag
- def range_field(object_name, method, options = {})
- Tags::RangeField.new(object_name, method, self, options).render
- end
-
- private
-
- def instantiate_builder(record_name, record_object, options)
- case record_name
- when String, Symbol
- object = record_object
- object_name = record_name
- else
- object = record_name
- object_name = model_name_from_record_or_class(object).param_key
- end
-
- builder = options[:builder] || default_form_builder
- builder.new(object_name, object, self, options)
- end
-
- def default_form_builder
- builder = ActionView::Base.default_form_builder
- builder.respond_to?(:constantize) ? builder.constantize : builder
- end
- end
-
- class FormBuilder
- include ModelNaming
-
- # The methods which wrap a form helper call.
- class_attribute :field_helpers
- self.field_helpers = FormHelper.instance_methods - [:form_for, :convert_to_model, :model_name_from_record_or_class]
-
- attr_accessor :object_name, :object, :options
-
- attr_reader :multipart, :parent_builder, :index
- alias :multipart? :multipart
-
- def multipart=(multipart)
- @multipart = multipart
- parent_builder.multipart = multipart if parent_builder
- end
-
- def self._to_partial_path
- @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
- end
-
- def to_partial_path
- self.class._to_partial_path
- end
-
- def to_model
- self
- end
-
- 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 = object_name, object, template, options
- @parent_builder = options[:parent_builder]
- @default_options = @options ? @options.slice(:index, :namespace) : {}
- if @object_name.to_s.match(/\[\]$/)
- if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
- @auto_index = 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
- @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|
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{selector}(method, options = {}) # def text_field(method, options = {})
- @template.send( # @template.send(
- #{selector.inspect}, # "text_field",
- @object_name, # @object_name,
- method, # method,
- objectify_options(options)) # objectify_options(options))
- end # end
- RUBY_EVAL
- end
-
- def fields_for(record_name, record_object = nil, fields_options = {}, &block)
- 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] = options[:namespace]
-
- case record_name
- when String, Symbol
- if nested_attributes_association?(record_name)
- return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
- end
- else
- record_object = record_name.is_a?(Array) ? record_name.last : record_name
- record_name = model_name_from_record_or_class(record_object).param_key
- end
-
- index = if options.has_key?(:index)
- options[:index]
- elsif defined?(@auto_index)
- self.object_name = @object_name.to_s.sub(/\[\]$/,"")
- @auto_index
- end
-
- 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
-
- def label(method, text = nil, options = {}, &block)
- @template.label(@object_name, method, text, objectify_options(options), &block)
- end
-
- def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
- @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
- end
-
- def radio_button(method, tag_value, options = {})
- @template.radio_button(@object_name, method, tag_value, objectify_options(options))
- end
-
- def hidden_field(method, options = {})
- @emitted_hidden_id = true if method == :id
- @template.hidden_field(@object_name, method, objectify_options(options))
- end
-
- def file_field(method, options = {})
- self.multipart = true
- @template.file_field(@object_name, method, objectify_options(options))
- end
-
- # Add the submit button for the given form. When no value is given, it checks
- # if the object is a new resource or not to create the proper label:
- #
- # <%= form_for @post do |f| %>
- # <%= f.submit %>
- # <% 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".
- #
- # Those labels can be customized using I18n, under the helpers.submit key and accept
- # the %{model} as translation interpolation:
- #
- # en:
- # helpers:
- # submit:
- # create: "Create a %{model}"
- # update: "Confirm changes to %{model}"
- #
- # It also searches for a key specific for the given object:
- #
- # en:
- # helpers:
- # submit:
- # post:
- # create: "Add %{model}"
- #
- def submit(value=nil, options={})
- value, options = nil, value if value.is_a?(Hash)
- value ||= submit_default_value
- @template.submit_tag(value, options)
- end
-
- # Add the submit button for the given form. When no value is given, it checks
- # if the object is a new resource or not to create the proper label:
- #
- # <%= form_for @post do |f| %>
- # <%= f.button %>
- # <% end %>
- #
- # In the example above, if @post is a new record, it will use "Create Post" as
- # button label, otherwise, it uses "Update Post".
- #
- # 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:
- # submit:
- # create: "Create a %{model}"
- # update: "Confirm changes to %{model}"
- #
- # It also searches for a key specific for the given object:
- #
- # en:
- # helpers:
- # submit:
- # post:
- # create: "Add %{model}"
- #
- # ==== Examples
- # button("Create a post")
- # # => <button name='button' type='submit'>Create post</button>
- #
- # button do
- # content_tag(:strong, 'Ask me!')
- # end
- # # => <button name='button' type='submit'>
- # # <strong>Ask me!</strong>
- # # </button>
- #
- def button(value = nil, options = {}, &block)
- value, options = nil, value if value.is_a?(Hash)
- value ||= submit_default_value
- @template.button_tag(value, options, &block)
- end
-
- def emitted_hidden_id?
- @emitted_hidden_id ||= nil
- end
-
- private
- def objectify_options(options)
- @default_options.merge(options.merge(object: @object))
- end
-
- def submit_default_value
- object = convert_to_model(@object)
- key = object ? (object.persisted? ? :update : :create) : :submit
-
- model = if object.class.respond_to?(:model_name)
- object.class.model_name.human
- else
- @object_name.to_s.humanize
- end
-
- defaults = []
- defaults << :"helpers.submit.#{object_name}.#{key}"
- defaults << :"helpers.submit.#{key}"
- defaults << "#{key.to_s.humanize} #{model}"
-
- I18n.t(defaults.shift, model: model, default: defaults)
- end
-
- def nested_attributes_association?(association_name)
- @object.respond_to?("#{association_name}_attributes=")
- end
-
- def fields_for_with_nested_attributes(association_name, association, options, block)
- name = "#{object_name}[#{association_name}_attributes]"
- association = convert_to_model(association)
-
- if association.respond_to?(:persisted?)
- association = [association] if @object.send(association_name).is_a?(Array)
- elsif !association.respond_to?(:to_ary)
- association = @object.send(association_name)
- end
-
- if association.respond_to?(:to_ary)
- explicit_child_index = options[:child_index]
- output = ActiveSupport::SafeBuffer.new
- association.each do |child|
- 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
- fields_for_nested_model(name, association, options, block)
- end
- end
-
- def fields_for_nested_model(name, object, options, block)
- object = convert_to_model(object)
-
- parent_include_id = self.options.fetch(:include_id, true)
- include_id = options.fetch(:include_id, parent_include_id)
- options[:hidden_field_id] = object.persisted? && include_id
- @template.fields_for(name, object, options, &block)
- end
-
- def nested_child_index(name)
- @nested_child_index[name] ||= -1
- @nested_child_index[name] += 1
- end
- end
- end
-
- ActiveSupport.on_load(:action_view) do
- 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
deleted file mode 100644
index 2bb526a539..0000000000
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ /dev/null
@@ -1,788 +0,0 @@
-require 'cgi'
-require 'erb'
-require 'action_view/helpers/form_helper'
-require 'active_support/core_ext/string/output_safety'
-
-module ActionView
- # = Action View Form Option Helpers
- module Helpers
- # Provides a number of methods for turning different kinds of containers into a set of option tags.
- # == Options
- # The <tt>collection_select</tt>, <tt>select</tt> and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter, a hash:
- #
- # * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
- #
- # For example,
- #
- # select("post", "category", Post::CATEGORIES, {:include_blank => true})
- #
- # could become:
- #
- # <select name="post[category]">
- # <option></option>
- # <option>joke</option>
- # <option>poem</option>
- # </select>
- #
- # Another common case is a select tag for an <tt>belongs_to</tt>-associated object.
- #
- # Example with @post.person_id => 2:
- #
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:include_blank => 'None'})
- #
- # could become:
- #
- # <select name="post[person_id]">
- # <option value="">None</option>
- # <option value="1">David</option>
- # <option value="2" selected="selected">Sam</option>
- # <option value="3">Tobias</option>
- # </select>
- #
- # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
- #
- # Example:
- #
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:prompt => 'Select Person'})
- #
- # could become:
- #
- # <select name="post[person_id]">
- # <option value="">Select Person</option>
- # <option value="1">David</option>
- # <option value="2">Sam</option>
- # <option value="3">Tobias</option>
- # </select>
- #
- # Like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
- # option to be in the +html_options+ parameter.
- #
- # Example:
- #
- # select("album[]", "genre", %w[rap rock country], {}, { :index => nil })
- #
- # becomes:
- #
- # <select name="album[][genre]" id="album__genre">
- # <option value="rap">rap</option>
- # <option value="rock">rock</option>
- # <option value="country">country</option>
- # </select>
- #
- # * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
- #
- # Example:
- #
- # select("post", "category", Post::CATEGORIES, {:disabled => 'restricted'})
- #
- # could become:
- #
- # <select name="post[category]">
- # <option></option>
- # <option>joke</option>
- # <option>poem</option>
- # <option disabled="disabled">restricted</option>
- # </select>
- #
- # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
- #
- # Example:
- #
- # collection_select(:post, :category_id, Category.all, :id, :name, {:disabled => lambda{|category| category.archived? }})
- #
- # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
- # <select name="post[category_id]">
- # <option value="1" disabled="disabled">2008 stuff</option>
- # <option value="2" disabled="disabled">Christmas</option>
- # <option value="3">Jokes</option>
- # <option value="4">Poems</option>
- # </select>
- #
- module FormOptionsHelper
- # ERB::Util can mask some helpers like textilize. Make sure to include them.
- include TextHelper
-
- # Create a select tag and a series of contained option tags for the provided object and method.
- # The option currently held by the object will be selected, provided that the object is available.
- #
- # There are two possible formats for the choices parameter, corresponding to other helpers' output:
- # * A flat collection: see options_for_select
- # * A nested collection: see grouped_options_for_select
- #
- # Example with @post.person_id => 1:
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
- #
- # could become:
- #
- # <select name="post[person_id]">
- # <option value=""></option>
- # <option value="1" selected="selected">David</option>
- # <option value="2">Sam</option>
- # <option value="3">Tobias</option>
- # </select>
- #
- # This can be used to provide a default set of options in the standard way: before rendering the create form, a
- # new model instance is assigned the default options and bound to @model_name. Usually this model is not saved
- # to the database. Instead, a second model object is created when the create request is received.
- # This allows the user to submit a form page more than once with the expected results of creating multiple records.
- # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
- #
- # By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
- # or <tt>:selected => nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
- # tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
- #
- # ==== Gotcha
- #
- # The HTML specification says when +multiple+ parameter passed to select and all options got deselected
- # web browsers do not send any value to server. Unfortunately this introduces a gotcha:
- # if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
- # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
- # any mass-assignment idiom like
- #
- # @user.update_attributes(params[:user])
- #
- # wouldn't update roles.
- #
- # To prevent this the helper generates an auxiliary hidden field before
- # every multiple select. The hidden field has the same name as multiple select and blank value.
- #
- # This way, the client either sends only the hidden field (representing
- # the deselected multiple select box), or both fields. Since the HTML specification
- # says key/value pairs have to be sent in the same order they appear in the
- # 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 = {})
- 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
- # +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>
- # or <tt>:include_blank</tt> in the +options+ hash.
- #
- # 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. 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_select(:post, :author_id, Author.all, :id, :name_with_initial, :prompt => true)
- #
- # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
- # <select name="post[author_id]">
- # <option value="">Please select</option>
- # <option value="1" selected="selected">D. Heinemeier Hansson</option>
- # <option value="2">D. Thomas</option>
- # <option value="3">M. Clark</option>
- # </select>
- def collection_select(object, method, 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>
- # or <tt>:include_blank</tt> in the +options+ hash.
- #
- # Parameters:
- # * +object+ - The instance of the class to be used for the select tag
- # * +method+ - The attribute of +object+ corresponding to the select tag
- # * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
- # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
- # array of child objects representing the <tt><option></tt> tags.
- # * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
- # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
- # * +option_key_method+ - The name of a method which, when called on a child object of a member of
- # +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
- # * +option_value_method+ - The name of a method which, when called on a child object of a member of
- # +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
- #
- # Example object structure for use with this method:
- # class Continent < ActiveRecord::Base
- # has_many :countries
- # # attribs: id, name
- # end
- # class Country < ActiveRecord::Base
- # belongs_to :continent
- # # attribs: id, name, continent_id
- # end
- # class City < ActiveRecord::Base
- # belongs_to :country
- # # attribs: id, name, country_id
- # end
- #
- # Sample usage:
- # grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name)
- #
- # Possible output:
- # <select name="city[country_id]">
- # <optgroup label="Africa">
- # <option value="1">South Africa</option>
- # <option value="3">Somalia</option>
- # </optgroup>
- # <optgroup label="Europe">
- # <option value="7" selected="selected">Denmark</option>
- # <option value="2">Ireland</option>
- # </optgroup>
- # </select>
- #
- def grouped_collection_select(object, method, 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
- # #time_zone_options_for_select to generate the list of option tags.
- #
- # In addition to the <tt>:include_blank</tt> option documented above,
- # this method also supports a <tt>:model</tt> option, which defaults
- # to ActiveSupport::TimeZone. This may be used by users to specify a
- # different time zone model object. (See +time_zone_options_for_select+
- # for more information.)
- #
- # You can also supply an array of ActiveSupport::TimeZone objects
- # as +priority_zones+, so that they will be listed above the rest of the
- # (long) list. (You can use ActiveSupport::TimeZone.us_zones as a convenience
- # for obtaining a list of the US time zones, or a Regexp to select the zones
- # of your choice)
- #
- # Finally, this method supports a <tt>:default</tt> option, which selects
- # a default ActiveSupport::TimeZone if the object's time zone is +nil+.
- #
- # time_zone_select( "user", "time_zone", nil, :include_blank => true)
- #
- # time_zone_select( "user", "time_zone", nil, :default => "Pacific Time (US & Canada)" )
- #
- # time_zone_select( "user", 'time_zone', ActiveSupport::TimeZone.us_zones, :default => "Pacific Time (US & Canada)")
- #
- # time_zone_select( "user", 'time_zone', [ ActiveSupport::TimeZone['Alaska'], ActiveSupport::TimeZone['Hawaii'] ])
- #
- # time_zone_select( "user", 'time_zone', /Australia/)
- #
- # 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 = {})
- 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
- # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
- # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
- # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+
- # may also be an array of values to be selected when using a multiple select.
- #
- # Examples (call, result):
- # options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
- # # <option value="$">Dollar</option>
- # # <option value="DKK">Kroner</option>
- #
- # options_for_select([ "VISA", "MasterCard" ], "MasterCard")
- # # <option>VISA</option>
- # # <option selected="selected">MasterCard</option>
- #
- # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
- # # <option value="$20">Basic</option>
- # # <option value="$40" selected="selected">Plus</option>
- #
- # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
- # # <option selected="selected">VISA</option>
- # # <option>MasterCard</option>
- # # <option selected="selected">Discover</option>
- #
- # You can optionally provide html attributes as the last element of the array.
- #
- # Examples:
- # options_for_select([ "Denmark", ["USA", {:class => 'bold'}], "Sweden" ], ["USA", "Sweden"])
- # # <option value="Denmark">Denmark</option>
- # # <option value="USA" class="bold" selected="selected">USA</option>
- # # <option value="Sweden" selected="selected">Sweden</option>
- #
- # options_for_select([["Dollar", "$", {:class => "bold"}], ["Kroner", "DKK", {:onclick => "alert('HI');"}]])
- # # <option value="$" class="bold">Dollar</option>
- # # <option value="DKK" onclick="alert('HI');">Kroner</option>
- #
- # If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value
- # or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags.
- #
- # Examples:
- # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => "Super Platinum")
- # # <option value="Free">Free</option>
- # # <option value="Basic">Basic</option>
- # # <option value="Advanced">Advanced</option>
- # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
- #
- # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => ["Advanced", "Super Platinum"])
- # # <option value="Free">Free</option>
- # # <option value="Basic">Basic</option>
- # # <option value="Advanced" disabled="disabled">Advanced</option>
- # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
- #
- # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :selected => "Free", :disabled => "Super Platinum")
- # # <option value="Free" selected="selected">Free</option>
- # # <option value="Basic">Basic</option>
- # # <option value="Advanced">Advanced</option>
- # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
- #
- # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
- def options_for_select(container, selected = nil)
- return container if String === container
-
- selected, disabled = extract_selected_and_disabled(selected).map do |r|
- Array(r).map { |item| item.to_s }
- end
-
- container.map do |element|
- html_attributes = option_html_attributes(element)
- text, value = option_text_and_value(element).map { |item| item.to_s }
-
- 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_string(: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 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')
- # This will output the same HTML as if you did this:
- # <option value="#{person.id}">#{person.name}</option>
- #
- # This is more often than not used inside a #select_tag like this example:
- # select_tag 'person', options_from_collection_for_select(@people, 'id', 'name')
- #
- # If +selected+ is specified as a value or array of values, the element(s) returning a match on +value_method+
- # will be selected option tag(s).
- #
- # If +selected+ is specified as a Proc, those members of the collection that return true for the anonymous
- # function are the selected values.
- #
- # +selected+ can also be a hash, specifying both <tt>:selected</tt> and/or <tt>:disabled</tt> values as required.
- #
- # Be sure to specify the same class as the +value_method+ when specifying selected or disabled options.
- # Failure to do this will produce undesired results. Example:
- # options_from_collection_for_select(@people, 'id', 'name', '1')
- # Will not select a person with the id of 1 because 1 (an Integer) is not the same as '1' (a string)
- # options_from_collection_for_select(@people, 'id', 'name', 1)
- # should produce the desired results.
- def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
- options = collection.map do |element|
- [value_for_collection(element, text_method), value_for_collection(element, value_method)]
- end
- selected, disabled = extract_selected_and_disabled(selected)
- 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
-
- # Returns a string of <tt><option></tt> tags, like <tt>options_from_collection_for_select</tt>, but
- # groups them by <tt><optgroup></tt> tags based on the object relationships of the arguments.
- #
- # Parameters:
- # * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
- # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
- # array of child objects representing the <tt><option></tt> tags.
- # * group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
- # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
- # * +option_key_method+ - The name of a method which, when called on a child object of a member of
- # +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
- # * +option_value_method+ - The name of a method which, when called on a child object of a member of
- # +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
- # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
- # which will have the +selected+ attribute set. Corresponds to the return value of one of the calls
- # to +option_key_method+. If +nil+, no selection is made. Can also be a hash if disabled values are
- # to be specified.
- #
- # Example object structure for use with this method:
- # class Continent < ActiveRecord::Base
- # has_many :countries
- # # attribs: id, name
- # end
- # class Country < ActiveRecord::Base
- # belongs_to :continent
- # # attribs: id, name, continent_id
- # end
- #
- # Sample usage:
- # option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3)
- #
- # Possible output:
- # <optgroup label="Africa">
- # <option value="1">Egypt</option>
- # <option value="4">Rwanda</option>
- # ...
- # </optgroup>
- # <optgroup label="Asia">
- # <option value="3" selected="selected">China</option>
- # <option value="12">India</option>
- # <option value="5">Japan</option>
- # ...
- # </optgroup>
- #
- # <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 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|
- 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
-
- # Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
- # wraps them with <tt><optgroup></tt> tags.
- #
- # Parameters:
- # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
- # <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
- # nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
- # Ex. ["North America",[["United States","US"],["Canada","CA"]]]
- # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
- # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
- # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
- #
- # Options:
- # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
- # prepends an option with a generic prompt - "Please select" - or the given prompt string.
- # * <tt>:divider</tt> - the divider for the options groups.
- #
- # Sample usage (Array):
- # grouped_options = [
- # ['North America',
- # [['United States','US'],'Canada']],
- # ['Europe',
- # ['Denmark','Germany','France']]
- # ]
- # grouped_options_for_select(grouped_options)
- #
- # Sample usage (Hash):
- # grouped_options = {
- # 'North America' => [['United States','US'], 'Canada'],
- # 'Europe' => ['Denmark','Germany','France']
- # }
- # grouped_options_for_select(grouped_options)
- #
- # Possible output:
- # <optgroup label="Europe">
- # <option value="Denmark">Denmark</option>
- # <option value="Germany">Germany</option>
- # <option value="France">France</option>
- # </optgroup>
- # <optgroup label="North America">
- # <option value="US">United States</option>
- # <option value="Canada">Canada</option>
- # </optgroup>
- #
- # Sample usage (divider):
- # grouped_options = [
- # [['United States','US'], 'Canada'],
- # ['Denmark','Germany','France']
- # ]
- # grouped_options_for_select(grouped_options, nil, divider: '---------')
- #
- # Possible output:
- # <optgroup label="---------">
- # <option value="US">United States</option>
- # <option value="Canada">Canada</option>
- # </optgroup>
- # <optgroup label="---------">
- # <option value="Denmark">Denmark</option>
- # <option value="Germany">Germany</option>
- # <option value="France">France</option>
- # </optgroup>
- #
- # <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, options = {})
- if options.is_a?(Hash)
- prompt = options[:prompt]
- divider = options[:divider]
- else
- prompt = options
- options = {}
- ActiveSupport::Deprecation.warn "Passing the prompt to grouped_options_for_select as an argument is deprecated. Please use an options hash like `{ prompt: #{prompt.inspect} }`."
- end
-
- body = "".html_safe
-
- if prompt
- body.safe_concat content_tag(:option, prompt_text(prompt), :value => "")
- end
-
- grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
-
- grouped_options.each do |container|
- if divider
- label = divider
- else
- label, container = container
- end
- body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label)
- end
-
- body
- end
-
- # Returns a string of option tags for pretty much any time zone in the
- # world. Supply a ActiveSupport::TimeZone name as +selected+ to have it
- # marked as the selected option tag. You can also supply an array of
- # ActiveSupport::TimeZone objects as +priority_zones+, so that they will
- # be listed above the rest of the (long) list. (You can use
- # ActiveSupport::TimeZone.us_zones as a convenience for obtaining a list
- # of the US time zones, or a Regexp to select the zones of your choice)
- #
- # The +selected+ parameter must be either +nil+, or a string that names
- # a ActiveSupport::TimeZone.
- #
- # By default, +model+ is the ActiveSupport::TimeZone constant (which can
- # be obtained in Active Record as a value object). The only requirement
- # is that the +model+ parameter be an object that responds to +all+, and
- # returns an array of objects that represent time zones.
- #
- # 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 = "".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 = zones.select { |z| z =~ priority_zones }
- end
-
- 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.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)
- if Array === element
- element.select { |e| Hash === e }.reduce({}, :merge!)
- else
- {}
- end
- end
-
- def option_text_and_value(option)
- # Options are [text, value] pairs or strings used for both.
- 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]
- end
- end
-
- def option_value_selected?(value, selected)
- Array(selected).include? value
- end
-
- def extract_selected_and_disabled(selected)
- if selected.is_a?(Proc)
- [selected, nil]
- else
- selected = Array.wrap(selected)
- options = selected.extract_options!.symbolize_keys
- selected_items = options.fetch(:selected, selected)
- [selected_items, options[:disabled]]
- end
- end
-
- def extract_values_from_collection(collection, value_method, selected)
- if selected.is_a?(Proc)
- collection.map do |element|
- element.send(value_method) if selected.call(element)
- end.compact
- else
- selected
- end
- end
-
- def value_for_collection(item, value)
- value.respond_to?(:call) ? value.call(item) : item.send(value)
- end
-
- def prompt_text(prompt)
- prompt = prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select')
- end
- end
-
- class FormBuilder
- def select(method, choices, options = {}, html_options = {})
- @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
- end
-
- def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
- @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
- end
-
- def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
- @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options))
- end
-
- 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
deleted file mode 100644
index f16e33d08d..0000000000
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ /dev/null
@@ -1,773 +0,0 @@
-require 'cgi'
-require 'action_view/helpers/tag_helper'
-require 'active_support/core_ext/string/output_safety'
-require 'active_support/core_ext/module/attribute_accessors'
-
-module ActionView
- # = Action View Form Tag Helpers
- module Helpers
- # Provides a number of methods for creating form tags that don't rely on an Active Record object assigned to the template like
- # FormHelper does. Instead, you provide the names and values manually.
- #
- # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
- # <tt>:disabled => true</tt> will give <tt>disabled="disabled"</tt>.
- module FormTagHelper
- extend ActiveSupport::Concern
-
- 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.
- #
- # ==== Options
- # * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data".
- # * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
- # If "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt>
- # is added to simulate the verb over post.
- # * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to
- # pass custom authenticity token string, or to not add authenticity_token field at all
- # (by passing <tt>false</tt>). 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.
- #
- # ==== Examples
- # form_tag('/posts')
- # # => <form action="/posts" method="post">
- #
- # form_tag('/posts/1', :method => :put)
- # # => <form action="/posts/1" method="post"> ... <input name="_method" type="hidden" value="put" /> ...
- #
- # form_tag('/upload', :multipart => true)
- # # => <form action="/upload" method="post" enctype="multipart/form-data">
- #
- # <%= form_tag('/posts') do -%>
- # <div><%= submit_tag 'Save' %></div>
- # <% end -%>
- # # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
- #
- # <%= form_tag('/posts', :remote => true) %>
- # # => <form action="/posts" method="post" data-remote="true">
- #
- # form_tag('http://far.away.com/form', :authenticity_token => false)
- # # form without authenticity token
- #
- # form_tag('http://far.away.com/form', :authenticity_token => "cf50faa3fe97702ca1ae")
- # # form with custom authenticity token
- #
- def form_tag(url_for_options = {}, options = {}, &block)
- html_options = html_options_for_form(url_for_options, options)
- if block_given?
- form_tag_in_block(html_options, &block)
- else
- form_tag_html(html_options)
- end
- end
-
- # Creates a dropdown selection box, or if the <tt>:multiple</tt> option is set to true, a multiple
- # choice selection box.
- #
- # Helpers::FormOptions can be used to create common select boxes such as countries, time zones, or
- # associated records. <tt>option_tags</tt> is a string containing the option tags for the select box.
- #
- # ==== Options
- # * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
- # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
- # * <tt>:include_blank</tt> - If set to true, an empty option will be create
- # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something
- # * Any other key creates standard HTML attributes for the tag.
- #
- # ==== Examples
- # select_tag "people", options_from_collection_for_select(@people, "id", "name")
- # # <select id="people" name="people"><option value="1">David</option></select>
- #
- # select_tag "people", "<option>David</option>".html_safe
- # # => <select id="people" name="people"><option>David</option></select>
- #
- # select_tag "count", "<option>1</option><option>2</option><option>3</option><option>4</option>".html_safe
- # # => <select id="count" name="count"><option>1</option><option>2</option>
- # # <option>3</option><option>4</option></select>
- #
- # select_tag "colors", "<option>Red</option><option>Green</option><option>Blue</option>".html_safe, :multiple => true
- # # => <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 id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>
- # # <option>Out</option></select>
- #
- # select_tag "access", "<option>Read</option><option>Write</option>".html_safe, :multiple => true, :class => 'form_input'
- # # => <select class="form_input" id="access" multiple="multiple" name="access[]"><option>Read</option>
- # # <option>Write</option></select>
- #
- # select_tag "people", options_from_collection_for_select(@people, "id", "name"), :include_blank => true
- # # => <select id="people" name="people"><option value=""></option><option value="1">David</option></select>
- #
- # select_tag "people", options_from_collection_for_select(@people, "id", "name"), :prompt => "Select something"
- # # => <select id="people" name="people"><option value="">Select something</option><option value="1">David</option></select>
- #
- # select_tag "destination", "<option>NYC</option><option>Paris</option><option>Rome</option>".html_safe, :disabled => true
- # # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option>
- # # <option>Paris</option><option>Rome</option></select>
- #
- # select_tag "credit_card", options_for_select([ "VISA", "MasterCard" ], "MasterCard")
- # # => <select id="credit_card" name="credit_card"><option>VISA</option>
- # # <option selected="selected">MasterCard</option></select>
- def select_tag(name, option_tags = nil, options = {})
- option_tags ||= ""
- html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
-
- if options.delete(:include_blank)
- option_tags = content_tag(:option, '', :value => '').safe_concat(option_tags)
- end
-
- if prompt = options.delete(:prompt)
- 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)
- end
-
- # Creates a standard text field; use these text fields to input smaller chunks of text like a username
- # or a search query.
- #
- # ==== Options
- # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
- # * <tt>:size</tt> - The number of visible characters that will fit in the input.
- # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
- # * <tt>:placeholder</tt> - The text contained in the field by default which is removed when the field receives focus.
- # * Any other key creates standard HTML attributes for the tag.
- #
- # ==== Examples
- # text_field_tag 'name'
- # # => <input id="name" name="name" type="text" />
- #
- # text_field_tag 'query', 'Enter your search query here'
- # # => <input id="query" name="query" type="text" value="Enter your search query here" />
- #
- # text_field_tag 'search', nil, :placeholder => 'Enter search term...'
- # # => <input id="search" name="search" placeholder="Enter search term..." type="text" />
- #
- # text_field_tag 'request', nil, :class => 'special_input'
- # # => <input class="special_input" id="request" name="request" type="text" />
- #
- # text_field_tag 'address', '', :size => 75
- # # => <input id="address" name="address" size="75" type="text" value="" />
- #
- # text_field_tag 'zip', nil, :maxlength => 5
- # # => <input id="zip" maxlength="5" name="zip" type="text" />
- #
- # text_field_tag 'payment_amount', '$0.00', :disabled => true
- # # => <input disabled="disabled" id="payment_amount" name="payment_amount" type="text" value="$0.00" />
- #
- # text_field_tag 'ip', '0.0.0.0', :maxlength => 15, :size => 20, :class => "ip-input"
- # # => <input class="ip-input" id="ip" maxlength="15" name="ip" size="20" type="text" value="0.0.0.0" />
- def text_field_tag(name, value = nil, options = {})
- tag :input, { "type" => "text", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys)
- end
-
- # Creates a label element. Accepts a block.
- #
- # ==== Options
- # * Creates standard HTML attributes for the tag.
- #
- # ==== Examples
- # label_tag 'name'
- # # => <label for="name">Name</label>
- #
- # label_tag 'name', 'Your name'
- # # => <label for="name">Your Name</label>
- #
- # label_tag 'name', nil, :class => 'small_label'
- # # => <label for="name" class="small_label">Name</label>
- def label_tag(name = nil, content_or_options = nil, options = nil, &block)
- if block_given? && content_or_options.is_a?(Hash)
- options = content_or_options = content_or_options.stringify_keys
- else
- options ||= {}
- options = options.stringify_keys
- end
- options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for")
- content_tag :label, content_or_options || name.to_s.humanize, options, &block
- end
-
- # Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or
- # data that should be hidden from the user.
- #
- # ==== Options
- # * Creates standard HTML attributes for the tag.
- #
- # ==== Examples
- # hidden_field_tag 'tags_list'
- # # => <input id="tags_list" name="tags_list" type="hidden" />
- #
- # hidden_field_tag 'token', 'VUBJKB23UIVI1UU1VOBVI@'
- # # => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" />
- #
- # hidden_field_tag 'collected_input', '', :onchange => "alert('Input collected!')"
- # # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
- # # type="hidden" value="" />
- def hidden_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
- end
-
- # Creates a file upload field. If you are using file uploads then you will also need
- # to set the multipart option for the form tag:
- #
- # <%= form_tag '/upload', :multipart => true do %>
- # <label for="file">File to Upload</label> <%= file_field_tag "file" %>
- # <%= submit_tag %>
- # <% end %>
- #
- # The specified URL will then be passed a File object containing the selected file, or if the field
- # was left blank, a StringIO object.
- #
- # ==== Options
- # * Creates standard HTML attributes for the tag.
- # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
- #
- # ==== Examples
- # file_field_tag 'attachment'
- # # => <input id="attachment" name="attachment" type="file" />
- #
- # file_field_tag 'avatar', :class => 'profile_input'
- # # => <input class="profile_input" id="avatar" name="avatar" type="file" />
- #
- # file_field_tag 'picture', :disabled => true
- # # => <input disabled="disabled" id="picture" name="picture" type="file" />
- #
- # file_field_tag 'resume', :value => '~/resume.doc'
- # # => <input id="resume" name="resume" type="file" value="~/resume.doc" />
- #
- # file_field_tag 'user_pic', :accept => 'image/png,image/gif,image/jpeg'
- # # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" />
- #
- # file_field_tag 'file', :accept => 'text/html', :class => 'upload', :value => 'index.html'
- # # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
- def file_field_tag(name, options = {})
- text_field_tag(name, nil, options.update("type" => "file"))
- end
-
- # Creates a password field, a masked text field that will hide the users input behind a mask character.
- #
- # ==== Options
- # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
- # * <tt>:size</tt> - The number of visible characters that will fit in the input.
- # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
- # * Any other key creates standard HTML attributes for the tag.
- #
- # ==== Examples
- # password_field_tag 'pass'
- # # => <input id="pass" name="pass" type="password" />
- #
- # password_field_tag 'secret', 'Your secret here'
- # # => <input id="secret" name="secret" type="password" value="Your secret here" />
- #
- # password_field_tag 'masked', nil, :class => 'masked_input_field'
- # # => <input class="masked_input_field" id="masked" name="masked" type="password" />
- #
- # password_field_tag 'token', '', :size => 15
- # # => <input id="token" name="token" size="15" type="password" value="" />
- #
- # password_field_tag 'key', nil, :maxlength => 16
- # # => <input id="key" maxlength="16" name="key" type="password" />
- #
- # password_field_tag 'confirm_pass', nil, :disabled => true
- # # => <input disabled="disabled" id="confirm_pass" name="confirm_pass" type="password" />
- #
- # password_field_tag 'pin', '1234', :maxlength => 4, :size => 6, :class => "pin_input"
- # # => <input class="pin_input" id="pin" maxlength="4" name="pin" size="6" type="password" value="1234" />
- def password_field_tag(name = "password", value = nil, options = {})
- text_field_tag(name, value, options.update("type" => "password"))
- end
-
- # Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions.
- #
- # ==== Options
- # * <tt>:size</tt> - A string specifying the dimensions (columns by rows) of the textarea (e.g., "25x10").
- # * <tt>:rows</tt> - Specify the number of rows in the textarea
- # * <tt>:cols</tt> - Specify the number of columns in the textarea
- # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
- # * <tt>:escape</tt> - By default, the contents of the text input are HTML escaped.
- # If you need unescaped contents, set this to false.
- # * Any other key creates standard HTML attributes for the tag.
- #
- # ==== Examples
- # text_area_tag 'post'
- # # => <textarea id="post" name="post"></textarea>
- #
- # text_area_tag 'bio', @user.bio
- # # => <textarea id="bio" name="bio">This is my biography.</textarea>
- #
- # text_area_tag 'body', nil, :rows => 10, :cols => 25
- # # => <textarea cols="25" id="body" name="body" rows="10"></textarea>
- #
- # text_area_tag 'body', nil, :size => "25x10"
- # # => <textarea name="body" id="body" cols="25" rows="10"></textarea>
- #
- # text_area_tag 'description', "Description goes here.", :disabled => true
- # # => <textarea disabled="disabled" id="description" name="description">Description goes here.</textarea>
- #
- # text_area_tag 'comment', nil, :class => 'comment_input'
- # # => <textarea class="comment_input" id="comment" name="comment"></textarea>
- def text_area_tag(name, content = nil, options = {})
- options = options.stringify_keys
-
- if size = options.delete("size")
- options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
- end
-
- 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)
- end
-
- # Creates a check box form input tag.
- #
- # ==== Options
- # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
- # * Any other key creates standard HTML options for the tag.
- #
- # ==== Examples
- # check_box_tag 'accept'
- # # => <input id="accept" name="accept" type="checkbox" value="1" />
- #
- # check_box_tag 'rock', 'rock music'
- # # => <input id="rock" name="rock" type="checkbox" value="rock music" />
- #
- # check_box_tag 'receive_email', 'yes', true
- # # => <input checked="checked" id="receive_email" name="receive_email" type="checkbox" value="yes" />
- #
- # check_box_tag 'tos', 'yes', false, :class => 'accept_tos'
- # # => <input class="accept_tos" id="tos" name="tos" type="checkbox" value="yes" />
- #
- # check_box_tag 'eula', 'accepted', false, :disabled => true
- # # => <input disabled="disabled" id="eula" name="eula" type="checkbox" value="accepted" />
- def check_box_tag(name, value = "1", checked = false, options = {})
- html_options = { "type" => "checkbox", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys)
- html_options["checked"] = "checked" if checked
- tag :input, html_options
- end
-
- # Creates a radio button; use groups of radio buttons named the same to allow users to
- # select from a group of options.
- #
- # ==== Options
- # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
- # * Any other key creates standard HTML options for the tag.
- #
- # ==== Examples
- # radio_button_tag 'gender', 'male'
- # # => <input id="gender_male" name="gender" type="radio" value="male" />
- #
- # radio_button_tag 'receive_updates', 'no', true
- # # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" />
- #
- # radio_button_tag 'time_slot', "3:00 p.m.", false, :disabled => true
- # # => <input disabled="disabled" id="time_slot_300_pm" name="time_slot" type="radio" value="3:00 p.m." />
- #
- # radio_button_tag 'color', "green", true, :class => "color_input"
- # # => <input checked="checked" class="color_input" id="color_green" name="color" type="radio" value="green" />
- def radio_button_tag(name, value, checked = false, options = {})
- html_options = { "type" => "radio", "name" => name, "id" => "#{sanitize_to_id(name)}_#{sanitize_to_id(value)}", "value" => value }.update(options.stringify_keys)
- html_options["checked"] = "checked" if checked
- tag :input, html_options
- end
-
- # Creates a submit button with the text <tt>value</tt> as the caption.
- #
- # ==== Options
- # * <tt>:data</tt> - This option can be used to add custom data attributes.
- # * <tt>:disabled</tt> - If true, the user will not be able to use this input.
- # * Any other key creates standard HTML options for the tag.
- #
- # ==== Data attributes
- #
- # * <tt>:confirm => 'question?'</tt> - If present the unobtrusive JavaScript
- # drivers will provide a prompt with the question specified. If the user accepts,
- # the form is processed normally, otherwise no action is taken.
- # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a
- # disabled version of the submit button when the form is submitted. This feature is
- # provided by the unobtrusive JavaScript driver.
- #
- # ==== Examples
- # submit_tag
- # # => <input name="commit" type="submit" value="Save changes" />
- #
- # submit_tag "Edit this article"
- # # => <input name="commit" type="submit" value="Edit this article" />
- #
- # submit_tag "Save edits", :disabled => true
- # # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
- #
- # submit_tag "Complete sale", :data => { :disable_with => "Please wait..." }
- # # => <input name="commit" data-disable-with="Please wait..." type="submit" value="Complete sale" />
- #
- # submit_tag nil, :class => "form_submit"
- # # => <input class="form_submit" name="commit" type="submit" />
- #
- # submit_tag "Edit", :class => "edit_button"
- # # => <input class="edit_button" name="commit" type="submit" value="Edit" />
- #
- # submit_tag "Save", :data => { :confirm => "Are you sure?" }
- # # => <input name='commit' type='submit' value='Save' data-confirm="Are you sure?" />
- #
- def submit_tag(value = "Save changes", options = {})
- options = options.stringify_keys
-
- if disable_with = options.delete("disable_with")
- ActiveSupport::Deprecation.warn ":disable_with option is deprecated and will be removed from Rails 4.1. Use ':data => { :disable_with => \'Text\' }' instead"
-
- options["data-disable-with"] = disable_with
- end
-
- if confirm = options.delete("confirm")
- ActiveSupport::Deprecation.warn ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead'"
-
- options["data-confirm"] = confirm
- end
-
- tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options)
- end
-
- # Creates a button element that defines a <tt>submit</tt> button,
- # <tt>reset</tt>button or a generic button which can be used in
- # JavaScript, for example. You can use the button tag as a regular
- # submit tag but it isn't supported in legacy browsers. However,
- # the button tag allows richer labels such as images and emphasis,
- # so this helper will also accept a block.
- #
- # ==== Options
- # * <tt>:data</tt> - This option can be used to add custom data attributes.
- # * <tt>:disabled</tt> - If true, the user will not be able to
- # use this input.
- # * Any other key creates standard HTML options for the tag.
- #
- # ==== Data attributes
- #
- # * <tt>:confirm => 'question?'</tt> - If present, the
- # unobtrusive JavaScript drivers will provide a prompt with
- # the question specified. If the user accepts, the form is
- # processed normally, otherwise no action is taken.
- # * <tt>:disable_with</tt> - Value of this parameter will be
- # used as the value for a disabled version of the submit
- # button when the form is submitted. This feature is provided
- # by the unobtrusive JavaScript driver.
- #
- # ==== Examples
- # button_tag
- # # => <button name="button" type="submit">Button</button>
- #
- # button_tag(:type => 'button') do
- # content_tag(:strong, 'Ask me!')
- # end
- # # => <button name="button" type="button">
- # # <strong>Ask me!</strong>
- # # </button>
- #
- # button_tag "Checkout", :data => { disable_with => "Please wait..." }
- # # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button>
- #
- def button_tag(content_or_options = nil, options = nil, &block)
- options = content_or_options if block_given? && content_or_options.is_a?(Hash)
- options ||= {}
- options = options.stringify_keys
-
- if disable_with = options.delete("disable_with")
- ActiveSupport::Deprecation.warn ":disable_with option is deprecated and will be removed from Rails 4.1. Use ':data => { :disable_with => \'Text\' }' instead"
-
- options["data-disable-with"] = disable_with
- end
-
- if confirm = options.delete("confirm")
- ActiveSupport::Deprecation.warn ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead'"
-
- options["data-confirm"] = confirm
- end
-
- options.reverse_merge! 'name' => 'button', 'type' => 'submit'
-
- content_tag :button, content_or_options || 'Button', options, &block
- end
-
- # Displays an image which when clicked will submit the form.
- #
- # <tt>source</tt> is passed to AssetTagHelper#path_to_image
- #
- # ==== Options
- # * <tt>:data</tt> - This option can be used to add custom data attributes.
- # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
- # * Any other key creates standard HTML options for the tag.
- #
- # ==== Data attributes
- #
- # * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm
- # prompt with the question specified. If the user accepts, the form is
- # processed normally, otherwise no action is taken.
- #
- # ==== Examples
- # image_submit_tag("login.png")
- # # => <input src="/images/login.png" type="image" />
- #
- # image_submit_tag("purchase.png", :disabled => true)
- # # => <input disabled="disabled" src="/images/purchase.png" type="image" />
- #
- # image_submit_tag("search.png", :class => 'search_button')
- # # => <input class="search_button" src="/images/search.png" type="image" />
- #
- # image_submit_tag("agree.png", :disabled => true, :class => "agree_disagree_button")
- # # => <input class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" />
- #
- # image_submit_tag("save.png", :data => { :confirm => "Are you sure?" })
- # # => <input src="/images/save.png" data-confirm="Are you sure?" type="image" />
- def image_submit_tag(source, options = {})
- options = options.stringify_keys
-
- if confirm = options.delete("confirm")
- ActiveSupport::Deprecation.warn ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead'"
-
- options["data-confirm"] = confirm
- end
-
- tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options)
- end
-
- # Creates a field set for grouping HTML form elements.
- #
- # <tt>legend</tt> will become the fieldset's title (optional as per W3C).
- # <tt>options</tt> accept the same values as tag.
- #
- # ==== Examples
- # <%= field_set_tag do %>
- # <p><%= text_field_tag 'name' %></p>
- # <% end %>
- # # => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset>
- #
- # <%= field_set_tag 'Your details' do %>
- # <p><%= text_field_tag 'name' %></p>
- # <% end %>
- # # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset>
- #
- # <%= field_set_tag nil, :class => 'format' do %>
- # <p><%= text_field_tag 'name' %></p>
- # <% end %>
- # # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
- def field_set_tag(legend = nil, options = nil, &block)
- output = tag(:fieldset, options, true)
- output.safe_concat(content_tag(:legend, legend)) unless legend.blank?
- output.concat(capture(&block)) if block_given?
- output.safe_concat("</fieldset>")
- end
-
- # Creates a text field of type "color".
- #
- # ==== Options
- # * Accepts the same options as text_field_tag.
- def color_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "color"))
- end
-
- # Creates a text field of type "search".
- #
- # ==== Options
- # * Accepts the same options as text_field_tag.
- def search_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "search"))
- end
-
- # Creates a text field of type "tel".
- #
- # ==== Options
- # * Accepts the same options as text_field_tag.
- def telephone_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "tel"))
- 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 "time".
- #
- # === Options
- # * <tt>:min</tt> - The minimum acceptable value.
- # * <tt>:max</tt> - The maximum acceptable value.
- # * <tt>:step</tt> - The acceptable value granularity.
- # * Otherwise accepts the same options as text_field_tag.
- def time_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "time"))
- end
-
- # Creates a text field of type "datetime".
- #
- # === Options
- # * <tt>:min</tt> - The minimum acceptable value.
- # * <tt>:max</tt> - The maximum acceptable value.
- # * <tt>:step</tt> - The acceptable value granularity.
- # * Otherwise accepts the same options as text_field_tag.
- def datetime_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "datetime"))
- end
-
- # Creates a text field of type "datetime-local".
- #
- # === Options
- # * <tt>:min</tt> - The minimum acceptable value.
- # * <tt>:max</tt> - The maximum acceptable value.
- # * <tt>:step</tt> - The acceptable value granularity.
- # * Otherwise accepts the same options as text_field_tag.
- def datetime_local_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "datetime-local"))
- end
-
- # Creates a text field of type "month".
- #
- # === Options
- # * <tt>:min</tt> - The minimum acceptable value.
- # * <tt>:max</tt> - The maximum acceptable value.
- # * <tt>:step</tt> - The acceptable value granularity.
- # * Otherwise accepts the same options as text_field_tag.
- def month_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "month"))
- end
-
- # Creates a text field of type "week".
- #
- # === Options
- # * <tt>:min</tt> - The minimum acceptable value.
- # * <tt>:max</tt> - The maximum acceptable value.
- # * <tt>:step</tt> - The acceptable value granularity.
- # * Otherwise accepts the same options as text_field_tag.
- def week_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "week"))
- end
-
- # Creates a text field of type "url".
- #
- # ==== Options
- # * Accepts the same options as text_field_tag.
- def url_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "url"))
- end
-
- # Creates a text field of type "email".
- #
- # ==== Options
- # * Accepts the same options as text_field_tag.
- def email_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.stringify_keys.update("type" => "email"))
- end
-
- # Creates a number field.
- #
- # ==== Options
- # * <tt>:min</tt> - The minimum acceptable value.
- # * <tt>:max</tt> - The maximum acceptable value.
- # * <tt>:in</tt> - A range specifying the <tt>:min</tt> and
- # <tt>:max</tt> values.
- # * <tt>:step</tt> - The acceptable value granularity.
- # * Otherwise accepts the same options as text_field_tag.
- #
- # ==== Examples
- # number_field_tag 'quantity', nil, :in => 1...10
- # # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
- def number_field_tag(name, value = nil, options = {})
- options = options.stringify_keys
- options["type"] ||= "number"
- if range = options.delete("in") || options.delete("within")
- options.update("min" => range.min, "max" => range.max)
- end
- text_field_tag(name, value, options)
- end
-
- # Creates a range form element.
- #
- # ==== Options
- # * Accepts the same options as number_field_tag.
- def range_field_tag(name, value = nil, options = {})
- number_field_tag(name, value, options.stringify_keys.update("type" => "range"))
- end
-
- # Creates the hidden UTF8 enforcer tag. Override this method in a helper
- # to customize the tag.
- def utf8_enforcer_tag
- tag(:input, :type => "hidden", :name => "utf8", :value => "&#x2713;".html_safe)
- end
-
- private
- def html_options_for_form(url_for_options, options)
- options.stringify_keys.tap do |html_options|
- html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
- # The following URL is unescaped, this is just a hash of options, and it is the
- # 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")
-
- 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
-
- def extra_tags_for_form(html_options)
- authenticity_token = html_options.delete("authenticity_token")
- method = html_options.delete("method").to_s
-
- method_tag = case method
- when /^get$/i # must be case-insensitive, but can't use downcase as might be nil
- html_options["method"] = "get"
- ''
- when /^post$/i, "", nil
- html_options["method"] = "post"
- token_tag(authenticity_token)
- else
- html_options["method"] = "post"
- method_tag(method) + token_tag(authenticity_token)
- end
-
- tags = utf8_enforcer_tag << method_tag
- content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline')
- end
-
- def form_tag_html(html_options)
- extra_tags = extra_tags_for_form(html_options)
- tag(:form, html_options, true) + extra_tags
- end
-
- def form_tag_in_block(html_options, &block)
- content = capture(&block)
- output = form_tag_html(html_options)
- output << content
- output.safe_concat("</form>")
- 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:.]/, "_")
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
deleted file mode 100644
index 9f8cd8caaa..0000000000
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-require 'action_view/helpers/tag_helper'
-
-module ActionView
- module Helpers
- module JavaScriptHelper
- JS_ESCAPE_MAP = {
- '\\' => '\\\\',
- '</' => '<\/',
- "\r\n" => '\n',
- "\n" => '\n',
- "\r" => '\n',
- '"' => '\\"',
- "'" => "\\'"
- }
-
- 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.
- #
- # Also available through the alias j(). This is particularly helpful in JavaScript responses, like:
- #
- # $('some_element').replaceWith('<%=j render 'some/element_template' %>');
- def escape_javascript(javascript)
- if javascript
- 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
- ''
- end
- end
-
- alias_method :j, :escape_javascript
-
- # Returns a JavaScript tag with the +content+ inside. Example:
- # javascript_tag "alert('All is good')"
- #
- # Returns:
- # <script>
- # //<![CDATA[
- # alert('All is good')
- # //]]>
- # </script>
- #
- # +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">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.
- # <%= javascript_tag :defer => 'defer' do -%>
- # alert('All is good')
- # <% end -%>
- def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
- content =
- if block_given?
- html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
- capture(&block)
- else
- content_or_options_with_block
- end
-
- 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={})
- message = "button_to_function is deprecated and will be removed from Rails 4.1. Use Unobtrusive JavaScript instead."
- ActiveSupport::Deprecation.warn message
-
- 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={})
- message = "link_to_function is deprecated and will be removed from Rails 4.1. Use Unobtrusive JavaScript instead."
- ActiveSupport::Deprecation.warn message
-
- 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
deleted file mode 100644
index 9720e90429..0000000000
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ /dev/null
@@ -1,442 +0,0 @@
-# encoding: utf-8
-
-require 'active_support/core_ext/hash/keys'
-require 'active_support/core_ext/string/output_safety'
-require 'active_support/number_helper'
-
-module ActionView
- # = Action View Number Helpers
- module Helpers #:nodoc:
-
- # Provides methods for converting numbers into formatted strings.
- # Methods are provided for phone numbers, currency, percentage,
- # precision, positional notation, file size and pretty printing.
- #
- # Most methods expect a +number+ argument, and will return it
- # unchanged if can't be converted into a valid number.
- module NumberHelper
-
- # Raised when argument +number+ param given to the helpers is invalid and
- # the option :raise is set to +true+.
- class InvalidNumberError < StandardError
- attr_accessor :number
- def initialize(number)
- @number = number
- end
- end
-
- # Formats a +number+ into a US phone number (e.g., (555)
- # 123-9876). You can customize the format in the +options+ hash.
- #
- # ==== Options
- #
- # * <tt>:area_code</tt> - Adds parentheses around the area code.
- # * <tt>:delimiter</tt> - Specifies the delimiter to use
- # (defaults to "-").
- # * <tt>:extension</tt> - Specifies an extension to add to the
- # end of the generated number.
- # * <tt>:country_code</tt> - Sets the country code for the phone
- # number.
- # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
- # the argument is invalid.
- #
- # ==== Examples
- #
- # number_to_phone(5551234) # => 555-1234
- # number_to_phone("5551234") # => 555-1234
- # number_to_phone(1235551234) # => 123-555-1234
- # number_to_phone(1235551234, :area_code => true) # => (123) 555-1234
- # number_to_phone(1235551234, :delimiter => " ") # => 123 555 1234
- # number_to_phone(1235551234, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
- # number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234
- # number_to_phone("123a456") # => 123a456
- #
- # number_to_phone("1234a567", :raise => true) # => InvalidNumberError
- #
- # number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".")
- # # => +1.123.555.1234 x 1343
- def number_to_phone(number, options = {})
- return unless number
- options = options.symbolize_keys
-
- parse_float(number, true) if options.delete(:raise)
- ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options))
- end
-
- # Formats a +number+ into a currency string (e.g., $13.65). You
- # can customize the format in the +options+ hash.
- #
- # ==== Options
- #
- # * <tt>:locale</tt> - Sets the locale to be used for formatting
- # (defaults to current locale).
- # * <tt>:precision</tt> - Sets the level of precision (defaults
- # to 2).
- # * <tt>:unit</tt> - Sets the denomination of the currency
- # (defaults to "$").
- # * <tt>:separator</tt> - Sets the separator between the units
- # (defaults to ".").
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
- # to ",").
- # * <tt>:format</tt> - Sets the format for non-negative numbers
- # (defaults to "%u%n"). Fields are <tt>%u</tt> for the
- # currency, and <tt>%n</tt> for the number.
- # * <tt>:negative_format</tt> - Sets the format for negative
- # numbers (defaults to prepending an hyphen to the formatted
- # number given by <tt>:format</tt>). Accepts the same fields
- # than <tt>:format</tt>, except <tt>%n</tt> is here the
- # absolute value of the number.
- # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
- # the argument is invalid.
- #
- # ==== Examples
- #
- # number_to_currency(1234567890.50) # => $1,234,567,890.50
- # number_to_currency(1234567890.506) # => $1,234,567,890.51
- # number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506
- # number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,51 €
- # number_to_currency("123a456") # => $123a456
- #
- # number_to_currency("123a456", :raise => true) # => InvalidNumberError
- #
- # number_to_currency(-1234567890.50, :negative_format => "(%u%n)")
- # # => ($1,234,567,890.50)
- # number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "")
- # # => &pound;1234567890,50
- # number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
- # # => 1234567890,50 &pound;
- def number_to_currency(number, options = {})
- return unless number
- options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
- wrap_with_output_safety_handling(number, options.delete(:raise)) {
- ActiveSupport::NumberHelper.number_to_currency(number, options)
- }
- end
-
- # Formats a +number+ as a percentage string (e.g., 65%). You can
- # customize the format in the +options+ hash.
- #
- # ==== Options
- #
- # * <tt>:locale</tt> - Sets the locale to be used for formatting
- # (defaults to current locale).
- # * <tt>:precision</tt> - Sets the precision of the number
- # (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the #
- # of significant_digits. If +false+, the # of fractional
- # digits (defaults to +false+).
- # * <tt>:separator</tt> - Sets the separator between the
- # fractional and integer digits (defaults to ".").
- # * <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
- #
- # number_to_percentage(100) # => 100.000%
- # number_to_percentage("98") # => 98.000%
- # number_to_percentage(100, :precision => 0) # => 100%
- # number_to_percentage(1000, :delimiter => '.', :separator => ',') # => 1.000,000%
- # 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 = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
- wrap_with_output_safety_handling(number, options.delete(:raise)) {
- ActiveSupport::NumberHelper.number_to_percentage(number, options)
- }
- end
-
- # Formats a +number+ with grouped thousands using +delimiter+
- # (e.g., 12,324). You can customize the format in the +options+
- # hash.
- #
- # ==== Options
- #
- # * <tt>:locale</tt> - Sets the locale to be used for formatting
- # (defaults to current locale).
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
- # to ",").
- # * <tt>:separator</tt> - Sets the separator between the
- # fractional and integer digits (defaults to ".").
- # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
- # the argument is invalid.
- #
- # ==== Examples
- #
- # number_with_delimiter(12345678) # => 12,345,678
- # number_with_delimiter("123456") # => 123,456
- # number_with_delimiter(12345678.05) # => 12,345,678.05
- # number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678
- # number_with_delimiter(12345678, :delimiter => ",") # => 12,345,678
- # number_with_delimiter(12345678.05, :separator => " ") # => 12,345,678 05
- # number_with_delimiter(12345678.05, :locale => :fr) # => 12 345 678,05
- # number_with_delimiter("112a") # => 112a
- # number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",")
- # # => 98 765 432,98
- #
- # number_with_delimiter("112a", :raise => true) # => raise InvalidNumberError
- def number_with_delimiter(number, options = {})
- options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
- wrap_with_output_safety_handling(number, options.delete(:raise)) {
- ActiveSupport::NumberHelper.number_to_delimited(number, options)
- }
- end
-
- # Formats a +number+ with the specified level of
- # <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
- # +:significant+ is +false+, and 5 if +:significant+ is +true+).
- # You can customize the format in the +options+ hash.
- #
- # ==== Options
- #
- # * <tt>:locale</tt> - Sets the locale to be used for formatting
- # (defaults to current locale).
- # * <tt>:precision</tt> - Sets the precision of the number
- # (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the #
- # of significant_digits. If +false+, the # of fractional
- # digits (defaults to +false+).
- # * <tt>:separator</tt> - Sets the separator between the
- # fractional and integer digits (defaults to ".").
- # * <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
- # number_with_precision(111.2345, :precision => 2) # => 111.23
- # number_with_precision(13, :precision => 5) # => 13.00000
- # number_with_precision(389.32314, :precision => 0) # => 389
- # number_with_precision(111.2345, :significant => true) # => 111
- # number_with_precision(111.2345, :precision => 1, :significant => true) # => 100
- # number_with_precision(13, :precision => 5, :significant => true) # => 13.000
- # number_with_precision(111.234, :locale => :fr) # => 111,234
- #
- # number_with_precision(13, :precision => 5, :significant => true, :strip_insignificant_zeros => true)
- # # => 13
- #
- # number_with_precision(389.32314, :precision => 4, :significant => true) # => 389.3
- # number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
- # # => 1.111,23
- def number_with_precision(number, options = {})
- options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
- wrap_with_output_safety_handling(number, options.delete(:raise)) {
- ActiveSupport::NumberHelper.number_to_rounded(number, options)
- }
- end
-
-
- # Formats the bytes in +number+ into a more understandable
- # representation (e.g., giving it 1500 yields 1.5 KB). This
- # method is useful for reporting file sizes to users. You can
- # customize the format in the +options+ hash.
- #
- # See <tt>number_to_human</tt> if you want to pretty-print a
- # generic number.
- #
- # ==== Options
- #
- # * <tt>:locale</tt> - Sets the locale to be used for formatting
- # (defaults to current locale).
- # * <tt>:precision</tt> - Sets the precision of the number
- # (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the #
- # of significant_digits. If +false+, the # of fractional
- # digits (defaults to +true+)
- # * <tt>:separator</tt> - Sets the separator between the
- # fractional and integer digits (defaults to ".").
- # * <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
- # number_to_human_size(12345) # => 12.1 KB
- # number_to_human_size(1234567) # => 1.18 MB
- # number_to_human_size(1234567890) # => 1.15 GB
- # number_to_human_size(1234567890123) # => 1.12 TB
- # number_to_human_size(1234567, :precision => 2) # => 1.2 MB
- # number_to_human_size(483989, :precision => 2) # => 470 KB
- # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB
- #
- # Non-significant zeros after the fractional separator are
- # stripped out by default (set
- # <tt>:strip_insignificant_zeros</tt> to +false+ to change
- # that):
- # 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 = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
- wrap_with_output_safety_handling(number, options.delete(:raise)) {
- ActiveSupport::NumberHelper.number_to_human_size(number, options)
- }
- end
-
- # Pretty prints (formats and approximates) a number in a way it
- # is more readable by humans (eg.: 1200000000 becomes "1.2
- # Billion"). This is useful for numbers that can get very large
- # (and too hard to read).
- #
- # See <tt>number_to_human_size</tt> if you want to print a file
- # size.
- #
- # You can also define you own unit-quantifier names if you want
- # to use other decimal units (eg.: 1500 becomes "1.5
- # kilometers", 0.150 becomes "150 milliliters", etc). You may
- # define a wide range of unit quantifiers, even fractional ones
- # (centi, deci, mili, etc).
- #
- # ==== Options
- #
- # * <tt>:locale</tt> - Sets the locale to be used for formatting
- # (defaults to current locale).
- # * <tt>:precision</tt> - Sets the precision of the number
- # (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the #
- # of significant_digits. If +false+, the # of fractional
- # digits (defaults to +true+)
- # * <tt>:separator</tt> - Sets the separator between the
- # fractional and integer digits (defaults to ".").
- # * <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>:units</tt> - A Hash of unit quantifier names. Or a
- # string containing an i18n scope where to find this hash. It
- # might have the following keys:
- # * *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"
- # number_to_human(1234) # => "1.23 Thousand"
- # number_to_human(12345) # => "12.3 Thousand"
- # number_to_human(1234567) # => "1.23 Million"
- # number_to_human(1234567890) # => "1.23 Billion"
- # number_to_human(1234567890123) # => "1.23 Trillion"
- # number_to_human(1234567890123456) # => "1.23 Quadrillion"
- # number_to_human(1234567890123456789) # => "1230 Quadrillion"
- # number_to_human(489939, :precision => 2) # => "490 Thousand"
- # number_to_human(489939, :precision => 4) # => "489.9 Thousand"
- # number_to_human(1234567, :precision => 4,
- # :significant => false) # => "1.2346 Million"
- # number_to_human(1234567, :precision => 1,
- # :separator => ',',
- # :significant => false) # => "1,2 Million"
- #
- # Non-significant zeros after the decimal separator are stripped
- # out by default (set <tt>:strip_insignificant_zeros</tt> to
- # +false+ to change that):
- # number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion"
- # number_to_human(500000000, :precision => 5) # => "500 Million"
- #
- # ==== Custom Unit Quantifiers
- #
- # You can also use your own custom unit quantifiers:
- # number_to_human(500000, :units => {:unit => "ml", :thousand => "lt"}) # => "500 lt"
- #
- # If in your I18n locale you have:
- # distance:
- # centi:
- # one: "centimeter"
- # other: "centimeters"
- # unit:
- # one: "meter"
- # other: "meters"
- # thousand:
- # one: "kilometer"
- # other: "kilometers"
- # billion: "gazillion-distance"
- #
- # Then you could do:
- #
- # number_to_human(543934, :units => :distance) # => "544 kilometers"
- # number_to_human(54393498, :units => :distance) # => "54400 kilometers"
- # number_to_human(54393498000, :units => :distance) # => "54.4 gazillion-distance"
- # number_to_human(343, :units => :distance, :precision => 1) # => "300 meters"
- # number_to_human(1, :units => :distance) # => "1 meter"
- # number_to_human(0.34, :units => :distance) # => "34 centimeters"
- #
- def number_to_human(number, options = {})
- options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
- wrap_with_output_safety_handling(number, options.delete(:raise)) {
- ActiveSupport::NumberHelper.number_to_human(number, options)
- }
- end
-
- private
-
- def escape_unsafe_delimiters_and_separators(options)
- options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe?
- options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe?
- options
- end
-
- def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
- valid_float = valid_float?(number)
- raise InvalidNumberError, number if raise_on_invalid && !valid_float
-
- formatted_number = yield
-
- if valid_float || number.html_safe?
- formatted_number.html_safe
- else
- formatted_number
- end
- end
-
- def valid_float?(number)
- !parse_float(number, false).nil?
- 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/output_safety_helper.rb b/actionpack/lib/action_view/helpers/output_safety_helper.rb
deleted file mode 100644
index 2e7e9dc50c..0000000000
--- a/actionpack/lib/action_view/helpers/output_safety_helper.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'active_support/core_ext/string/output_safety'
-
-module ActionView #:nodoc:
- # = Action View Raw Output Helper
- module Helpers #:nodoc:
- module OutputSafetyHelper
- # This method outputs without escaping a string. Since escaping tags is
- # now default, this can be used when you don't want Rails to automatically
- # escape tags. This is not recommended if the data is coming from the user's
- # input.
- #
- # For example:
- #
- # <%=raw @user.name %>
- def raw(stringish)
- stringish.to_s.html_safe
- end
-
- # This method returns a html safe string similar to what <tt>Array#join</tt>
- # would return. All items in the array, including the supplied separator, are
- # html escaped unless they are html safe, and the returned string is marked
- # as html safe.
- #
- # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />")
- # # => "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;"
- #
- # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>".html_safe], "<br />".html_safe)
- # # => "<p>foo</p><br /><p>bar</p>"
- #
- def safe_join(array, sep=$,)
- sep = ERB::Util.html_escape(sep)
-
- array.map { |i| ERB::Util.html_escape(i) }.join(sep).html_safe
- 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
deleted file mode 100644
index dded9aab7c..0000000000
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-module ActionView
- # = Action View Record Tag Helpers
- module Helpers
- module RecordTagHelper
- include ActionView::RecordIdentifier
-
- # Produces a wrapper DIV element with id and class parameters that
- # relate to the specified Active Record object. Usage example:
- #
- # <%= div_for(@person, :class => "foo") do %>
- # <%= @person.name %>
- # <% end %>
- #
- # produces:
- #
- # <div id="person_123" class="person foo"> Joe Bloggs </div>
- #
- # You can also pass an array of Active Record objects, which will then
- # get iterated over and yield each record as an argument for the block.
- # For example:
- #
- # <%= div_for(@people, :class => "foo") do |person| %>
- # <%= person.name %>
- # <% end %>
- #
- # produces:
- #
- # <div id="person_123" class="person foo"> Joe Bloggs </div>
- # <div id="person_124" class="person foo"> Jane Bloggs </div>
- #
- def div_for(record, *args, &block)
- content_tag_for(:div, record, *args, &block)
- end
-
- # content_tag_for creates an HTML element with id and class parameters
- # that relate to the specified Active Record object. For example:
- #
- # <%= content_tag_for(:tr, @person) do %>
- # <td><%= @person.first_name %></td>
- # <td><%= @person.last_name %></td>
- # <% end %>
- #
- # would produce the following HTML (assuming @person is an instance of
- # a Person object, with an id value of 123):
- #
- # <tr id="person_123" class="person">....</tr>
- #
- # If you require the HTML id attribute to have a prefix, you can specify it:
- #
- # <%= content_tag_for(:tr, @person, :foo) do %> ...
- #
- # produces:
- #
- # <tr id="foo_person_123" class="person">...
- #
- # You can also pass an array of objects which this method will loop through
- # and yield the current object to the supplied block, reducing the need for
- # having to iterate through the object (using <tt>each</tt>) beforehand.
- # For example (assuming @people is an array of Person objects):
- #
- # <%= content_tag_for(:tr, @people) do |person| %>
- # <td><%= person.first_name %></td>
- # <td><%= person.last_name %></td>
- # <% end %>
- #
- # produces:
- #
- # <tr id="person_123" class="person">...</tr>
- # <tr id="person_124" class="person">...</tr>
- #
- # content_tag_for also accepts a hash of options, which will be converted to
- # additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined
- # with the default class name for your object. For example:
- #
- # <%= content_tag_for(:li, @person, :class => "bar") %>...
- #
- # produces:
- #
- # <li id="person_123" class="person bar">...
- #
- def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block)
- 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
-
- # 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 = 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
-end
diff --git a/actionpack/lib/action_view/helpers/rendering_helper.rb b/actionpack/lib/action_view/helpers/rendering_helper.rb
deleted file mode 100644
index 626e1a1ab7..0000000000
--- a/actionpack/lib/action_view/helpers/rendering_helper.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-module ActionView
- module Helpers
- # = Action View Rendering
- #
- # Implements methods that allow rendering from a view context.
- # In order to use this module, all you need is to implement
- # view_renderer that returns an ActionView::Renderer object.
- module RenderingHelper
- # Returns the result of a render that's dictated by the options hash. The primary options are:
- #
- # * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt>.
- # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
- # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
- # * <tt>:text</tt> - Renders the text passed in out.
- #
- # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
- # as the locals hash.
- def render(options = {}, locals = {}, &block)
- case options
- when Hash
- if block_given?
- view_renderer.render_partial(self, options.merge(:partial => options[:layout]), &block)
- else
- view_renderer.render(self, options)
- end
- else
- view_renderer.render_partial(self, :partial => options, :locals => locals)
- end
- end
-
- # Overwrites _layout_for in the context object so it supports the case a block is
- # passed to a partial. Returns the contents that are yielded to a layout, given a
- # name or a block.
- #
- # You can think of a layout as a method that is called with a block. If the user calls
- # <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>.
- # If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>.
- #
- # The user can override this default by passing a block to the layout:
- #
- # # The template
- # <%= render :layout => "my_layout" do %>
- # Content
- # <% end %>
- #
- # # The layout
- # <html>
- # <%= yield %>
- # </html>
- #
- # In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>,
- # this method returns the block that was passed in to <tt>render :layout</tt>, and the response
- # would be
- #
- # <html>
- # Content
- # </html>
- #
- # Finally, the block can take block arguments, which can be passed in by +yield+:
- #
- # # The template
- # <%= render :layout => "my_layout" do |customer| %>
- # Hello <%= customer.name %>
- # <% end %>
- #
- # # The layout
- # <html>
- # <%= yield Struct.new(:name).new("David") %>
- # </html>
- #
- # In this case, the layout would receive the block passed into <tt>render :layout</tt>,
- # and the struct specified would be passed into the block as an argument. The result
- # would be
- #
- # <html>
- # Hello David
- # </html>
- #
- def _layout_for(*args, &block)
- name = args.first
-
- if block && !name.is_a?(Symbol)
- capture(*args, &block)
- else
- super
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb
deleted file mode 100644
index 9c76c26ace..0000000000
--- a/actionpack/lib/action_view/helpers/sanitize_helper.rb
+++ /dev/null
@@ -1,256 +0,0 @@
-require 'active_support/core_ext/object/try'
-require 'action_view/vendor/html-scanner'
-
-module ActionView
- # = Action View Sanitize Helpers
- module Helpers #:nodoc:
- # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
- # These helper methods extend Action View making them callable within your template files.
- module SanitizeHelper
- extend ActiveSupport::Concern
- # This +sanitize+ helper will html encode all tags and strip all attributes that
- # aren't specifically allowed.
- #
- # It also strips href/src tags with invalid protocols, like javascript: especially.
- # It does its best to counter any tricks that hackers may use, like throwing in
- # unicode/ascii/hex values to get past the javascript: filters. Check out
- # the extensive test suite.
- #
- # <%= sanitize @article.body %>
- #
- # You can add or remove tags/attributes if you want to customize it a bit.
- # See ActionView::Base for full docs on the available options. You can add
- # tags/attributes for single uses of +sanitize+ by passing either the
- # <tt>:attributes</tt> or <tt>:tags</tt> options:
- #
- # Normal Use
- #
- # <%= sanitize @article.body %>
- #
- # Custom Use (only the mentioned tags and attributes are allowed, nothing else)
- #
- # <%= sanitize @article.body, :tags => %w(table tr td), :attributes => %w(id class style) %>
- #
- # Add table tags to the default allowed tags
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
- # end
- #
- # Remove tags to the default allowed tags
- #
- # class Application < Rails::Application
- # config.after_initialize do
- # ActionView::Base.sanitized_allowed_tags.delete 'div'
- # end
- # end
- #
- # Change allowed default attributes
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style'
- # end
- #
- # Please note that sanitizing user-provided text does not guarantee that the
- # resulting markup is valid (conforming to a document type) or even well-formed.
- # The output may still contain e.g. unescaped '<', '>', '&' characters and
- # confuse browsers.
- #
- def sanitize(html, options = {})
- self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe)
- end
-
- # Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
- def sanitize_css(style)
- self.class.white_list_sanitizer.sanitize_css(style)
- end
-
- # Strips all HTML tags from the +html+, including comments. This uses the
- # html-scanner tokenizer and so its HTML parsing ability is limited by
- # that of html-scanner.
- #
- # strip_tags("Strip <i>these</i> tags!")
- # # => Strip these tags!
- #
- # strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
- # # => Bold no more! See more here...
- #
- # strip_tags("<div id='top-bar'>Welcome to my website!</div>")
- # # => Welcome to my website!
- def strip_tags(html)
- self.class.full_sanitizer.sanitize(html)
- end
-
- # Strips all link tags from +text+ leaving just the link text.
- #
- # strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
- # # => Ruby on Rails
- #
- # strip_links('Please e-mail me at <a href="mailto:me@email.com">me@email.com</a>.')
- # # => Please e-mail me at me@email.com.
- #
- # strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.')
- # # => Blog: Visit.
- def strip_links(html)
- self.class.link_sanitizer.sanitize(html)
- end
-
- module ClassMethods #:nodoc:
- attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
-
- def sanitized_protocol_separator
- white_list_sanitizer.protocol_separator
- end
-
- def sanitized_uri_attributes
- white_list_sanitizer.uri_attributes
- end
-
- def sanitized_bad_tags
- white_list_sanitizer.bad_tags
- end
-
- def sanitized_allowed_tags
- white_list_sanitizer.allowed_tags
- end
-
- def sanitized_allowed_attributes
- white_list_sanitizer.allowed_attributes
- end
-
- def sanitized_allowed_css_properties
- white_list_sanitizer.allowed_css_properties
- end
-
- def sanitized_allowed_css_keywords
- white_list_sanitizer.allowed_css_keywords
- end
-
- def sanitized_shorthand_css_properties
- white_list_sanitizer.shorthand_css_properties
- end
-
- def sanitized_allowed_protocols
- white_list_sanitizer.allowed_protocols
- end
-
- def sanitized_protocol_separator=(value)
- white_list_sanitizer.protocol_separator = value
- end
-
- # Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with
- # any object that responds to +sanitize+.
- #
- # class Application < Rails::Application
- # config.action_view.full_sanitizer = MySpecialSanitizer.new
- # end
- #
- def full_sanitizer
- @full_sanitizer ||= HTML::FullSanitizer.new
- end
-
- # Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with
- # any object that responds to +sanitize+.
- #
- # class Application < Rails::Application
- # config.action_view.link_sanitizer = MySpecialSanitizer.new
- # end
- #
- def link_sanitizer
- @link_sanitizer ||= HTML::LinkSanitizer.new
- end
-
- # Gets the HTML::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
- # Replace with any object that responds to +sanitize+.
- #
- # class Application < Rails::Application
- # config.action_view.white_list_sanitizer = MySpecialSanitizer.new
- # end
- #
- def white_list_sanitizer
- @white_list_sanitizer ||= HTML::WhiteListSanitizer.new
- end
-
- # Adds valid HTML attributes that the +sanitize+ helper checks for URIs.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_uri_attributes = 'lowsrc', 'target'
- # end
- #
- def sanitized_uri_attributes=(attributes)
- HTML::WhiteListSanitizer.uri_attributes.merge(attributes)
- end
-
- # Adds to the Set of 'bad' tags for the +sanitize+ helper.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_bad_tags = 'embed', 'object'
- # end
- #
- def sanitized_bad_tags=(attributes)
- HTML::WhiteListSanitizer.bad_tags.merge(attributes)
- end
-
- # Adds to the Set of allowed tags for the +sanitize+ helper.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
- # end
- #
- def sanitized_allowed_tags=(attributes)
- HTML::WhiteListSanitizer.allowed_tags.merge(attributes)
- end
-
- # Adds to the Set of allowed HTML attributes for the +sanitize+ helper.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc'
- # end
- #
- def sanitized_allowed_attributes=(attributes)
- HTML::WhiteListSanitizer.allowed_attributes.merge(attributes)
- end
-
- # Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_css_properties = 'expression'
- # end
- #
- def sanitized_allowed_css_properties=(attributes)
- HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes)
- end
-
- # Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_css_keywords = 'expression'
- # end
- #
- def sanitized_allowed_css_keywords=(attributes)
- HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes)
- end
-
- # Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_shorthand_css_properties = 'expression'
- # end
- #
- def sanitized_shorthand_css_properties=(attributes)
- HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes)
- end
-
- # Adds to the Set of allowed protocols for the +sanitize+ helper.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_protocols = 'ssh', 'feed'
- # end
- #
- def sanitized_allowed_protocols=(attributes)
- HTML::WhiteListSanitizer.allowed_protocols.merge(attributes)
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
deleted file mode 100644
index 3327c69d61..0000000000
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ /dev/null
@@ -1,173 +0,0 @@
-require 'active_support/core_ext/string/output_safety'
-require 'set'
-
-module ActionView
- # = Action View Tag Helpers
- module Helpers #:nodoc:
- # Provides methods to generate HTML tags programmatically when you can't use
- # a Builder. By default, they output XHTML compliant tags.
- module TagHelper
- extend ActiveSupport::Concern
- include CaptureHelper
-
- 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 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
- # hash to +options+. Set +escape+ to false to disable attribute value
- # escaping.
- #
- # ==== Options
- # You can use symbols or strings for the attribute names.
- #
- # Use +true+ with boolean attributes that can render with no value, like
- # +disabled+ and +readonly+.
- #
- # HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
- # pointing to a hash of sub-attributes.
- #
- # To play nicely with JavaScript conventions sub-attributes are dasherized.
- # For example, a key +user_id+ would render as <tt>data-user-id</tt> and
- # thus accessed as <tt>dataset.userId</tt>.
- #
- # Values are encoded to JSON, with the exception of strings and symbols.
- # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
- # from 1.4.3.
- #
- # ==== Examples
- # tag("br")
- # # => <br />
- #
- # tag("br", nil, true)
- # # => <br>
- #
- # tag("input", :type => 'text', :disabled => true)
- # # => <input type="text" disabled="disabled" />
- #
- # tag("img", :src => "open & shut.png")
- # # => <img src="open &amp; shut.png" />
- #
- # tag("img", {:src => "open &amp; shut.png"}, false, false)
- # # => <img src="open &amp; shut.png" />
- #
- # tag("div", :data => {:name => 'Stephen', :city_state => %w(Chicago IL)})
- # # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
- def tag(name, options = nil, open = false, escape = true)
- "<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
- end
-
- # Returns an HTML block tag of type +name+ surrounding the +content+. Add
- # HTML attributes by passing an attributes hash to +options+.
- # Instead of passing the content as an argument, you can also use a block
- # in which case, you pass your +options+ as the second parameter.
- # Set escape to false to disable attribute value escaping.
- #
- # ==== Options
- # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
- # <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
- # symbols or strings for the attribute names.
- #
- # ==== Examples
- # content_tag(:p, "Hello world!")
- # # => <p>Hello world!</p>
- # content_tag(:div, content_tag(:p, "Hello world!"), :class => "strong")
- # # => <div class="strong"><p>Hello world!</p></div>
- # content_tag("select", options, :multiple => true)
- # # => <select multiple="multiple">...options...</select>
- #
- # <%= content_tag :div, :class => "strong" do -%>
- # Hello world!
- # <% end -%>
- # # => <div class="strong">Hello world!</div>
- def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
- if block_given?
- options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
- content_tag_string(name, capture(&block), options, escape)
- else
- content_tag_string(name, content_or_options_with_block, options, escape)
- end
- end
-
- # Returns a CDATA section with the given +content+. CDATA sections
- # are used to escape blocks of text containing characters which would
- # otherwise be recognized as markup. CDATA sections begin with the string
- # <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
- #
- # cdata_section("<hello world>")
- # # => <![CDATA[<hello world>]]>
- #
- # 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)
- splitted = content.gsub(']]>', ']]]]><![CDATA[>')
- "<![CDATA[#{splitted}]]>".html_safe
- end
-
- # Returns an escaped version of +html+ without affecting existing escaped entities.
- #
- # escape_once("1 < 2 &amp; 3")
- # # => "1 &lt; 2 &amp; 3"
- #
- # escape_once("&lt;&lt; Accept & Checkout")
- # # => "&lt;&lt; Accept &amp; Checkout"
- def escape_once(html)
- ERB::Util.html_escape_once(html)
- end
-
- private
-
- 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}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe
- end
-
- def tag_options(options, escape = true)
- return if options.blank?
- attrs = []
- options.each_pair do |key, value|
- if key.to_s == 'data' && value.is_a?(Hash)
- value.each_pair do |k, v|
- attrs << data_tag_option(k, v, escape)
- end
- elsif BOOLEAN_ATTRIBUTES.include?(key)
- attrs << boolean_tag_option(key) if value
- elsif !value.nil?
- attrs << tag_option(key, value, escape)
- end
- end
- " #{attrs.sort * ' '}".html_safe unless attrs.empty?
- end
-
- def data_tag_option(key, value, escape)
- key = "data-#{key.to_s.dasherize}"
- unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
- value = value.to_json
- end
- tag_option(key, value, escape)
- end
-
- def boolean_tag_option(key)
- %(#{key}="#{key}")
- end
-
- def tag_option(key, value, escape)
- value = value.join(" ") if value.is_a?(Array)
- value = ERB::Util.h(value) if escape
- %(#{key}="#{value}")
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb
deleted file mode 100644
index a05e16979a..0000000000
--- a/actionpack/lib/action_view/helpers/tags.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-module ActionView
- module Helpers
- module Tags #:nodoc:
- extend ActiveSupport::Autoload
-
- autoload :Base
- autoload :CheckBox
- autoload :CollectionCheckBoxes
- autoload :CollectionRadioButtons
- autoload :CollectionSelect
- autoload :ColorField
- autoload :DateField
- autoload :DateSelect
- autoload :DatetimeField
- autoload :DatetimeLocalField
- autoload :DatetimeSelect
- autoload :EmailField
- autoload :FileField
- autoload :GroupedCollectionSelect
- autoload :HiddenField
- autoload :Label
- autoload :MonthField
- autoload :NumberField
- autoload :PasswordField
- autoload :RadioButton
- autoload :RangeField
- autoload :SearchField
- autoload :Select
- autoload :TelField
- autoload :TextArea
- autoload :TextField
- autoload :TimeField
- autoload :TimeSelect
- autoload :TimeZoneSelect
- autoload :UrlField
- autoload :WeekField
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb
deleted file mode 100644
index 192f5eebaa..0000000000
--- a/actionpack/lib/action_view/helpers/tags/base.rb
+++ /dev/null
@@ -1,150 +0,0 @@
-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)
- options[:include_blank] ||= true unless options[:prompt] || select_not_required?(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 select_not_required?(html_options)
- !html_options["required"] || html_options["multiple"] || html_options["size"].to_i > 1
- end
-
- def add_options(option_tags, options, value = nil)
- if options[:include_blank]
- option_tags = content_tag_string('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags
- end
- if value.blank? && options[:prompt]
- option_tags = content_tag_string('option', prompt_text(options[: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
deleted file mode 100644
index 9d17a1dde3..0000000000
--- a/actionpack/lib/action_view/helpers/tags/check_box.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-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
deleted file mode 100644
index b97c0c68d7..0000000000
--- a/actionpack/lib/action_view/helpers/tags/checkable.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-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
deleted file mode 100644
index e23f5113fb..0000000000
--- a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-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
deleted file mode 100644
index 4e33e79a36..0000000000
--- a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-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).map(&:to_s).include?(value.to_s)
- 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
deleted file mode 100644
index ba2035f074..0000000000
--- a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-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
deleted file mode 100644
index ec78e6e5f9..0000000000
--- a/actionpack/lib/action_view/helpers/tags/collection_select.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-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/color_field.rb b/actionpack/lib/action_view/helpers/tags/color_field.rb
deleted file mode 100644
index 6f08f8483a..0000000000
--- a/actionpack/lib/action_view/helpers/tags/color_field.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module ActionView
- module Helpers
- module Tags
- class ColorField < TextField #:nodoc:
- def render
- options = @options.stringify_keys
- options["value"] = @options.fetch("value") { validate_color_string(value(object)) }
- @options = options
- super
- end
-
- private
-
- def validate_color_string(string)
- regex = /#[0-9a-fA-F]{6}/
- if regex.match(string)
- string.downcase
- else
- "#000000"
- end
- 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
deleted file mode 100644
index 64c29dea3d..0000000000
--- a/actionpack/lib/action_view/helpers/tags/date_field.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module ActionView
- module Helpers
- module Tags
- class DateField < DatetimeField #:nodoc:
- private
-
- def format_date(value)
- value.try(:strftime, "%Y-%m-%d")
- 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
deleted file mode 100644
index 5d706087b0..0000000000
--- a/actionpack/lib/action_view/helpers/tags/date_select.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-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_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_field.rb
deleted file mode 100644
index e407146e96..0000000000
--- a/actionpack/lib/action_view/helpers/tags/datetime_field.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-module ActionView
- module Helpers
- module Tags
- class DatetimeField < TextField #:nodoc:
- def render
- options = @options.stringify_keys
- options["value"] = @options.fetch("value") { format_date(value(object)) }
- options["min"] = format_date(options["min"])
- options["max"] = format_date(options["max"])
- @options = options
- super
- end
-
- private
-
- def format_date(value)
- value.try(:strftime, "%Y-%m-%dT%T.%L%z")
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb
deleted file mode 100644
index 6668d6d718..0000000000
--- a/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module ActionView
- module Helpers
- module Tags
- class DatetimeLocalField < DatetimeField #:nodoc:
- class << self
- def field_type
- @field_type ||= "datetime-local"
- end
- end
-
- private
-
- def format_date(value)
- value.try(:strftime, "%Y-%m-%dT%T")
- 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
deleted file mode 100644
index a32c840bce..0000000000
--- a/actionpack/lib/action_view/helpers/tags/datetime_select.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-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
deleted file mode 100644
index 45cde507d7..0000000000
--- a/actionpack/lib/action_view/helpers/tags/email_field.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-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
deleted file mode 100644
index 59f2ff71b4..0000000000
--- a/actionpack/lib/action_view/helpers/tags/file_field.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-module ActionView
- module Helpers
- module Tags
- class FileField < TextField #:nodoc:
- 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
deleted file mode 100644
index 507ba8835f..0000000000
--- a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-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
deleted file mode 100644
index a8d13dc1b1..0000000000
--- a/actionpack/lib/action_view/helpers/tags/hidden_field.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-module ActionView
- module Helpers
- module Tags
- class HiddenField < TextField #:nodoc:
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/tags/label.rb b/actionpack/lib/action_view/helpers/tags/label.rb
deleted file mode 100644
index 16135fcd5a..0000000000
--- a/actionpack/lib/action_view/helpers/tags/label.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-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/month_field.rb b/actionpack/lib/action_view/helpers/tags/month_field.rb
deleted file mode 100644
index 3d3c32d847..0000000000
--- a/actionpack/lib/action_view/helpers/tags/month_field.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module ActionView
- module Helpers
- module Tags
- class MonthField < DatetimeField #:nodoc:
- private
-
- def format_date(value)
- value.try(:strftime, "%Y-%m")
- 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
deleted file mode 100644
index 9cd04434f0..0000000000
--- a/actionpack/lib/action_view/helpers/tags/number_field.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-module ActionView
- module Helpers
- module Tags
- class NumberField < TextField #:nodoc:
- def render
- options = @options.stringify_keys
-
- 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
deleted file mode 100644
index 6e7a4d3c36..0000000000
--- a/actionpack/lib/action_view/helpers/tags/password_field.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-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
deleted file mode 100644
index 8a0421f061..0000000000
--- a/actionpack/lib/action_view/helpers/tags/radio_button.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-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
deleted file mode 100644
index 47db4680e7..0000000000
--- a/actionpack/lib/action_view/helpers/tags/range_field.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-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
deleted file mode 100644
index 818fd4b887..0000000000
--- a/actionpack/lib/action_view/helpers/tags/search_field.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-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
deleted file mode 100644
index 53a108b7e6..0000000000
--- a/actionpack/lib/action_view/helpers/tags/select.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-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
deleted file mode 100644
index 87c1f6b6b6..0000000000
--- a/actionpack/lib/action_view/helpers/tags/tel_field.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-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
deleted file mode 100644
index f74652c5e7..0000000000
--- a/actionpack/lib/action_view/helpers/tags/text_area.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-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
deleted file mode 100644
index 024a1a8af2..0000000000
--- a/actionpack/lib/action_view/helpers/tags/text_field.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-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_field.rb b/actionpack/lib/action_view/helpers/tags/time_field.rb
deleted file mode 100644
index a3941860c9..0000000000
--- a/actionpack/lib/action_view/helpers/tags/time_field.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module ActionView
- module Helpers
- module Tags
- class TimeField < DatetimeField #:nodoc:
- private
-
- def format_date(value)
- value.try(:strftime, "%T.%L")
- 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
deleted file mode 100644
index 9e97deb706..0000000000
--- a/actionpack/lib/action_view/helpers/tags/time_select.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-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
deleted file mode 100644
index 0a176157c3..0000000000
--- a/actionpack/lib/action_view/helpers/tags/time_zone_select.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-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
deleted file mode 100644
index 1ffdfe0b3c..0000000000
--- a/actionpack/lib/action_view/helpers/tags/url_field.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-module ActionView
- module Helpers
- module Tags
- class UrlField < TextField #:nodoc:
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/tags/week_field.rb b/actionpack/lib/action_view/helpers/tags/week_field.rb
deleted file mode 100644
index 1e13939a0a..0000000000
--- a/actionpack/lib/action_view/helpers/tags/week_field.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module ActionView
- module Helpers
- module Tags
- class WeekField < DatetimeField #:nodoc:
- private
-
- def format_date(value)
- value.try(:strftime, "%Y-W%W")
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
deleted file mode 100644
index 527bfe0cab..0000000000
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ /dev/null
@@ -1,439 +0,0 @@
-require 'active_support/core_ext/string/filters'
-require 'active_support/core_ext/array/extract_options'
-
-module ActionView
- # = Action View Text Helpers
- module Helpers #:nodoc:
- # The TextHelper module provides a set of methods for filtering, formatting
- # and transforming strings, which can reduce the amount of inline Ruby code in
- # your views. These helper methods extend Action View making them callable
- # within your template files.
- #
- # ==== Sanitization
- #
- # Most text helpers by default sanitize the given content, but do not escape it.
- # This means HTML tags will appear in the page but all malicious code will be removed.
- # Let's look at some examples using the +simple_format+ method:
- #
- # simple_format('<a href="http://example.com/">Example</a>')
- # # => "<p><a href=\"http://example.com/\">Example</a></p>"
- #
- # simple_format('<a href="javascript:alert(\'no!\')">Example</a>')
- # # => "<p><a>Example</a></p>"
- #
- # If you want to escape all content, you should invoke the +h+ method before
- # calling the text helper.
- #
- # simple_format h('<a href="http://example.com/">Example</a>')
- # # => "<p>&lt;a href=\"http://example.com/\"&gt;Example&lt;/a&gt;</p>"
- module TextHelper
- extend ActiveSupport::Concern
-
- include SanitizeHelper
- include TagHelper
- # The preferred method of outputting text in your views is to use the
- # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
- # do not operate as expected in an eRuby code block. If you absolutely must
- # output text within a non-output code block (i.e., <% %>), you can use the concat method.
- #
- # <%
- # concat "hello"
- # # is the equivalent of <%= "hello" %>
- #
- # if logged_in
- # concat "Logged in!"
- # else
- # concat link_to('login', :action => :login)
- # end
- # # will either display "Logged in!" or a login link
- # %>
- def concat(string)
- output_buffer << string
- end
-
- def safe_concat(string)
- output_buffer.respond_to?(:safe_concat) ? output_buffer.safe_concat(string) : concat(string)
- end
-
- # Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
- # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...")
- # for a total length not exceeding <tt>:length</tt>.
- #
- # Pass a <tt>:separator</tt> to truncate +text+ at a natural break.
- #
- # Pass a block if you want to show extra content when the text is truncated.
- #
- # The result is marked as HTML-safe, but it is escaped by default, unless <tt>:escape</tt> is
- # +false+. Care should be taken if +text+ contains HTML tags or entities, because truncation
- # may produce invalid HTML (such as unbalanced or incomplete tags).
- #
- # truncate("Once upon a time in a world far far away")
- # # => "Once upon a time in a world..."
- #
- # truncate("Once upon a time in a world far far away", :length => 17)
- # # => "Once upon a ti..."
- #
- # truncate("Once upon a time in a world far far away", :length => 17, :separator => ' ')
- # # => "Once upon a..."
- #
- # truncate("And they found that many people were sleeping better.", :length => 25, :omission => '... (continued)')
- # # => "And they f... (continued)"
- #
- # truncate("<p>Once upon a time in a world far far away</p>")
- # # => "<p>Once upon a time in a wo..."
- #
- # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" }
- # # => "Once upon a time in a wo...<a href="#">Continue</a>"
- def truncate(text, options = {}, &block)
- if text
- length = options.fetch(:length, 30)
-
- content = text.truncate(length, options)
- content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content)
- content << capture(&block) if block_given? && text.length > length
- content
- end
- end
-
- # 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 <tt>\1</tt> where the phrase is to be inserted (defaults to
- # '<mark>\1</mark>')
- #
- # highlight('You searched for: rails', 'rails')
- # # => You searched for: <mark>rails</mark>
- #
- # highlight('You searched for: ruby, rails, dhh', 'actionpack')
- # # => You searched for: ruby, rails, dhh
- #
- # highlight('You searched for: rails', ['for', 'rails'], :highlighter => '<em>\1</em>')
- # # => You searched <em>for</em>: <em>rails</em>
- #
- # highlight('You searched for: rails', 'rails', :highlighter => '<a href="search?q=\1">\1</a>')
- # # => You searched for: <a href="search?q=rails">rails</a>
- def highlight(text, phrases, options = {})
- highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
-
- text = sanitize(text) if options.fetch(:sanitize, true)
- if text.blank? || phrases.blank?
- text
- else
- match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
- text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
- end.html_safe
- end
-
- # Extracts an excerpt from +text+ that matches the first instance of +phrase+.
- # The <tt>:radius</tt> option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
- # defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
- # then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. The
- # <tt>:separator</tt> enable to choose the delimation. The resulting string will be stripped in any case. If the +phrase+
- # isn't found, nil is returned.
- #
- # excerpt('This is an example', 'an', :radius => 5)
- # # => ...s is an exam...
- #
- # excerpt('This is an example', 'is', :radius => 5)
- # # => This is a...
- #
- # excerpt('This is an example', 'is')
- # # => This is an example
- #
- # excerpt('This next thing is an example', 'ex', :radius => 2)
- # # => ...next...
- #
- # excerpt('This is also an example', 'an', :radius => 8, :omission => '<chop> ')
- # # => <chop> is also an example
- #
- # excerpt('This is a very beautiful morning', 'very', :separator => ' ', :radius => 1)
- # # => ...a very beautiful...
- def excerpt(text, phrase, options = {})
- return unless text && phrase
-
- separator = options.fetch(:separator, "")
- phrase = Regexp.escape(phrase)
- regex = /#{phrase}/i
-
- return unless matches = text.match(regex)
- phrase = matches[0]
-
- text.split(separator).each do |value|
- if value.match(regex)
- regex = phrase = value
- break
- end
- end
-
- first_part, second_part = text.split(regex, 2)
-
- prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
- postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
-
- prefix + (first_part + separator + phrase + separator + second_part).strip + postfix
- end
-
- # Attempts to pluralize the +singular+ word unless +count+ is 1. If
- # +plural+ is supplied, it will use that when count is > 1, otherwise
- # it will use the Inflector to determine the plural form.
- #
- # pluralize(1, 'person')
- # # => 1 person
- #
- # pluralize(2, 'person')
- # # => 2 people
- #
- # pluralize(3, 'person', 'users')
- # # => 3 users
- #
- # pluralize(0, 'person')
- # # => 0 people
- def pluralize(count, singular, plural = nil)
- word = if (count == 1 || count =~ /^1(\.0+)?$/)
- singular
- else
- plural || singular.pluralize
- end
-
- "#{count || 0} #{word}"
- end
-
- # Wraps the +text+ into lines no longer than +line_width+ width. This method
- # breaks on the first whitespace character that does not exceed +line_width+
- # (which is 80 by default).
- #
- # word_wrap('Once upon a time')
- # # => Once upon a time
- #
- # word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...')
- # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined...
- #
- # word_wrap('Once upon a time', :line_width => 8)
- # # => Once\nupon a\ntime
- #
- # word_wrap('Once upon a time', :line_width => 1)
- # # => Once\nupon\na\ntime
- def word_wrap(text, options = {})
- line_width = options.fetch(:line_width, 80)
-
- text.split("\n").collect do |line|
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
- end * "\n"
- end
-
- # Returns +text+ transformed into HTML using simple formatting rules.
- # Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a
- # paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is
- # considered as a linebreak and a <tt><br /></tt> tag is appended. This
- # method does not remove the newlines from the +text+.
- #
- # You can pass any HTML attributes into <tt>html_options</tt>. These
- # will be added to all created paragraphs.
- #
- # ==== Options
- # * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
- # * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt>
- #
- # ==== Examples
- # my_text = "Here is some basic text...\n...with a line break."
- #
- # simple_format(my_text)
- # # => "<p>Here is some basic text...\n<br />...with a line break.</p>"
- #
- # simple_format(my_text, {}, :wrapper_tag => "div")
- # # => "<div>Here is some basic text...\n<br />...with a line break.</div>"
- #
- # more_text = "We want to put a paragraph...\n\n...right there."
- #
- # simple_format(more_text)
- # # => "<p>We want to put a paragraph...</p>\n\n<p>...right there.</p>"
- #
- # simple_format("Look ma! A class!", :class => 'description')
- # # => "<p class='description'>Look ma! A class!</p>"
- #
- # simple_format("<span>I'm allowed!</span> It's true.", {}, :sanitize => false)
- # # => "<p><span>I'm allowed!</span> It's true.</p>"
- def simple_format(text, html_options = {}, options = {})
- wrapper_tag = options.fetch(:wrapper_tag, :p)
-
- text = sanitize(text) if options.fetch(:sanitize, true)
- paragraphs = split_paragraphs(text)
-
- if paragraphs.empty?
- content_tag(wrapper_tag, nil, html_options)
- else
- paragraphs.map { |paragraph|
- content_tag(wrapper_tag, paragraph, html_options, options[:sanitize])
- }.join("\n\n").html_safe
- end
- end
-
- # Creates a Cycle object whose _to_s_ method cycles through elements of an
- # array every time it is called. This can be used for example, to alternate
- # classes for table rows. You can use named cycles to allow nesting in loops.
- # Passing a Hash as the last parameter with a <tt>:name</tt> key will create a
- # named cycle. The default name for a cycle without a +:name+ key is
- # <tt>"default"</tt>. You can manually reset a cycle by calling reset_cycle
- # and passing the name of the cycle. The current cycle string can be obtained
- # anytime using the current_cycle method.
- #
- # # Alternate CSS classes for even and odd numbers...
- # @items = [1,2,3,4]
- # <table>
- # <% @items.each do |item| %>
- # <tr class="<%= cycle("odd", "even") -%>">
- # <td>item</td>
- # </tr>
- # <% end %>
- # </table>
- #
- #
- # # Cycle CSS classes for rows, and text colors for values within each row
- # @items = x = [{:first => 'Robert', :middle => 'Daniel', :last => 'James'},
- # {:first => 'Emily', :middle => 'Shannon', :maiden => 'Pike', :last => 'Hicks'},
- # {:first => 'June', :middle => 'Dae', :last => 'Jones'}]
- # <% @items.each do |item| %>
- # <tr class="<%= cycle("odd", "even", :name => "row_class") -%>">
- # <td>
- # <% item.values.each do |value| %>
- # <%# Create a named cycle "colors" %>
- # <span style="color:<%= cycle("red", "green", "blue", :name => "colors") -%>">
- # <%= value %>
- # </span>
- # <% end %>
- # <% reset_cycle("colors") %>
- # </td>
- # </tr>
- # <% end %>
- def cycle(first_value, *values)
- options = values.extract_options!
- name = options.fetch(:name, 'default')
-
- values.unshift(first_value)
-
- cycle = get_cycle(name)
- unless cycle && cycle.values == values
- cycle = set_cycle(name, Cycle.new(*values))
- end
- cycle.to_s
- end
-
- # Returns the current cycle string after a cycle has been started. Useful
- # for complex table highlighting or any other design need which requires
- # the current cycle string in more than one place.
- #
- # # Alternate background colors
- # @items = [1,2,3,4]
- # <% @items.each do |item| %>
- # <div style="background-color:<%= cycle("red","white","blue") %>">
- # <span style="background-color:<%= current_cycle %>"><%= item %></span>
- # </div>
- # <% end %>
- def current_cycle(name = "default")
- cycle = get_cycle(name)
- cycle.current_value if cycle
- end
-
- # Resets a cycle so that it starts from the first element the next time
- # it is called. Pass in +name+ to reset a named cycle.
- #
- # # Alternate CSS classes for even and odd numbers...
- # @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]]
- # <table>
- # <% @items.each do |item| %>
- # <tr class="<%= cycle("even", "odd") -%>">
- # <% item.each do |value| %>
- # <span style="color:<%= cycle("#333", "#666", "#999", :name => "colors") -%>">
- # <%= value %>
- # </span>
- # <% end %>
- #
- # <% reset_cycle("colors") %>
- # </tr>
- # <% end %>
- # </table>
- def reset_cycle(name = "default")
- cycle = get_cycle(name)
- cycle.reset if cycle
- end
-
- class Cycle #:nodoc:
- attr_reader :values
-
- def initialize(first_value, *values)
- @values = values.unshift(first_value)
- reset
- end
-
- def reset
- @index = 0
- end
-
- def current_value
- @values[previous_index].to_s
- end
-
- def to_s
- value = @values[@index].to_s
- @index = next_index
- return value
- end
-
- private
-
- def next_index
- step_index(1)
- end
-
- def previous_index
- step_index(-1)
- end
-
- def step_index(n)
- (@index + n) % @values.size
- end
- end
-
- private
- # The cycle helpers need to store the cycles in a place that is
- # guaranteed to be reset every time a page is rendered, so it
- # uses an instance variable of ActionView::Base.
- def get_cycle(name)
- @_cycles = Hash.new unless defined?(@_cycles)
- return @_cycles[name]
- end
-
- def set_cycle(name, cycle_object)
- @_cycles = Hash.new unless defined?(@_cycles)
- @_cycles[name] = cycle_object
- end
-
- def split_paragraphs(text)
- return [] if text.blank?
-
- text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t|
- t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t
- end
- end
-
- def cut_excerpt_part(part_position, part, separator, options)
- return "", "" unless part
-
- radius = options.fetch(:radius, 100)
- omission = options.fetch(:omission, "...")
-
- part = part.split(separator)
- part.delete("")
- affix = part.size > radius ? omission : ""
-
- part = if part_position == :first
- drop_index = [part.length - radius, 0].max
- part.drop(drop_index)
- else
- part.first(radius)
- end
-
- return affix, part.join(separator)
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
deleted file mode 100644
index 552c9ba660..0000000000
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ /dev/null
@@ -1,107 +0,0 @@
-require 'action_view/helpers/tag_helper'
-require 'i18n/exceptions'
-
-module I18n
- class ExceptionHandler
- include Module.new {
- def call(exception, locale, key, options)
- exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super
- end
- }
- end
-end
-
-module ActionView
- # = Action View Translation Helpers
- module Helpers
- module TranslationHelper
- # Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
- #
- # First, it'll pass the <tt>:rescue_format => :html</tt> option to I18n so that any
- # thrown +MissingTranslation+ messages will be turned into inline spans that
- #
- # * have a "translation-missing" class set,
- # * contain the missing key as a title attribute and
- # * a titleized version of the last key segment as a text.
- #
- # E.g. the value returned for a missing translation key :"blog.post.title" will be
- # <span class="translation_missing" title="translation missing: en.blog.post.title">Title</span>.
- # This way your views will display rather reasonable strings but it will still
- # be easy to spot missing translations.
- #
- # Second, it'll scope the key by the current partial if the key starts
- # with a period. So if you call <tt>translate(".foo")</tt> from the
- # <tt>people/index.html.erb</tt> template, you'll actually be calling
- # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
- # to translate many keys within the same partials and gives you a simple framework
- # for scoping them consistently. If you don't prepend the key with a period,
- # nothing is converted.
- #
- # Third, it'll mark the translation as safe HTML if the key has the suffix
- # "_html" or the last element of the key is the word "html". For example,
- # calling translate("footer_html") or translate("footer.html") will return
- # a safe HTML string that won't be escaped by other HTML helper methods. This
- # naming convention helps to identify translations that include HTML tags so that
- # 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|
- unless name == :count && value.is_a?(Numeric)
- html_safe_options[name] = ERB::Util.html_escape(value.to_s)
- end
- end
- translation = I18n.translate(scope_key_by_partial(key), html_safe_options)
-
- translation.respond_to?(:html_safe) ? translation.html_safe : translation
- else
- I18n.translate(scope_key_by_partial(key), options)
- end
- end
- alias :t :translate
-
- # Delegates to <tt>I18n.localize</tt> with no additional functionality.
- #
- # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
- # for more information.
- def localize(*args)
- I18n.localize(*args)
- end
- alias :l :localize
-
- private
- def scope_key_by_partial(key)
- if key.to_s.first == "."
- if @virtual_path
- @virtual_path.gsub(%r{/_?}, ".") + key.to_s
- else
- raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
- end
- else
- key
- end
- end
-
- 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_defaults << 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
deleted file mode 100644
index 5105d0e585..0000000000
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ /dev/null
@@ -1,654 +0,0 @@
-require 'action_view/helpers/javascript_helper'
-require 'active_support/core_ext/array/access'
-require 'active_support/core_ext/hash/keys'
-require 'active_support/core_ext/string/output_safety'
-
-module ActionView
- # = Action View URL Helpers
- module Helpers #:nodoc:
- # Provides a set of methods for making links and getting URLs that
- # depend on the routing subsystem (see ActionDispatch::Routing).
- # This allows you to use the same format for links in views
- # and controllers.
- module UrlHelper
- # This helper may be included in any class that includes the
- # URL helpers of a routes (routes.url_helpers). Some methods
- # provided here will only work in the context of a request
- # (link_to_unless_current, for instance), which must be provided
- # as a method called #request on the context.
-
- extend ActiveSupport::Concern
-
- include TagHelper
-
- module ClassMethods
- def _url_for_modules
- ActionView::RoutingUrlFor
- end
- end
-
- # Basic implementation of url_for to allow use helpers without routes existence
- def url_for(options = nil) # :nodoc:
- case options
- when String
- options
- when :back
- _back_url
- else
- raise ArgumentError, "arguments passed to url_for can't be handled. Please require " +
- "routes or provide your own implementation"
- end
- end
-
- def _back_url # :nodoc:
- referrer = controller.respond_to?(:request) && controller.request.env["HTTP_REFERER"]
- referrer || 'javascript:history.back()'
- end
- protected :_back_url
-
- # Creates a link tag of the given +name+ using a URL created by the set of +options+.
- # See the valid options in the documentation for +url_for+. It's also possible to
- # pass a String instead of an options hash, which generates a link tag that uses the
- # value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead
- # of an options hash will generate a link to the referrer (a JavaScript back link
- # will be used in place of a referrer if none exists). If +nil+ is passed as the name
- # the value of the link itself will become the name.
- #
- # ==== Signatures
- #
- # link_to(body, url, html_options = {})
- # # url is a String; you can use URL helpers like
- # # posts_path
- #
- # link_to(body, url_options = {}, html_options = {})
- # # url_options, except :method, is passed to url_for
- #
- # link_to(options = {}, html_options = {}) do
- # # name
- # end
- #
- # link_to(url, html_options = {}) do
- # # name
- # end
- #
- # ==== Options
- # * <tt>:data</tt> - This option can be used to add custom data attributes.
- # * <tt>:method => symbol of HTTP verb</tt> - This modifier will dynamically
- # 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>, <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>, <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
- # completion of the Ajax request and performing JavaScript operations once
- # they're complete
- #
- # ==== Data attributes
- #
- # * <tt>:confirm => 'question?'</tt> - This will allow the unobtrusive JavaScript
- # driver to prompt with the question specified. If the user accepts, the link is
- # processed normally, otherwise no action is taken.
- # * <tt>:disable_with</tt> - Value of this parameter will be
- # used as the value for a disabled version of the submit
- # button when the form is submitted. This feature is provided
- # by the unobtrusive JavaScript driver.
- #
- # ==== Examples
- # Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments
- # and newer RESTful routes. Current Rails style favors RESTful routes whenever possible, so base
- # your application on resources and use
- #
- # link_to "Profile", profile_path(@profile)
- # # => <a href="/profiles/1">Profile</a>
- #
- # or the even pithier
- #
- # link_to "Profile", @profile
- # # => <a href="/profiles/1">Profile</a>
- #
- # in place of the older more verbose, non-resource-oriented
- #
- # link_to "Profile", controller: "profiles", action: "show", id: @profile
- # # => <a href="/profiles/show/1">Profile</a>
- #
- # Similarly,
- #
- # link_to "Profiles", profiles_path
- # # => <a href="/profiles">Profiles</a>
- #
- # is better than
- #
- # link_to "Profiles", controller: "profiles"
- # # => <a href="/profiles">Profiles</a>
- #
- # You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
- #
- # <%= link_to(@profile) do %>
- # <strong><%= @profile.name %></strong> -- <span>Check it out!</span>
- # <% end %>
- # # => <a href="/profiles/1">
- # <strong>David</strong> -- <span>Check it out!</span>
- # </a>
- #
- # Classes and ids for CSS are easy to produce:
- #
- # link_to "Articles", articles_path, id: "news", class: "article"
- # # => <a href="/articles" class="article" id="news">Articles</a>
- #
- # Be careful when using the older argument style, as an extra literal hash is needed:
- #
- # link_to "Articles", { controller: "articles" }, id: "news", class: "article"
- # # => <a href="/articles" class="article" id="news">Articles</a>
- #
- # Leaving the hash off gives the wrong link:
- #
- # link_to "WRONG!", controller: "articles", id: "news", class: "article"
- # # => <a href="/articles/index/news?class=article">WRONG!</a>
- #
- # +link_to+ can also produce links with anchors or query strings:
- #
- # link_to "Comment wall", profile_path(@profile, anchor: "wall")
- # # => <a href="/profiles/1#wall">Comment wall</a>
- #
- # link_to "Ruby on Rails search", controller: "searches", query: "ruby on rails"
- # # => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a>
- #
- # link_to "Nonsense search", searches_path(foo: "bar", baz: "quux")
- # # => <a href="/searches?foo=bar&amp;baz=quux">Nonsense search</a>
- #
- # The only option specific to +link_to+ (<tt>:method</tt>) is used as follows:
- #
- # link_to("Destroy", "http://www.example.com", method: :delete)
- # # => <a href='http://www.example.com' rel="nofollow" data-method="delete">Destroy</a>
- #
- # You can also use custom data attributes using the <tt>:data</tt> option:
- #
- # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" }
- # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?"">Visit Other Site</a>
- def link_to(name = nil, options = nil, html_options = nil, &block)
- html_options, options = options, name if block_given?
- options ||= {}
-
- html_options = convert_options_to_data_attributes(options, html_options)
-
- url = url_for(options)
- html_options['href'] ||= url
-
- content_tag(:a, name || url, html_options, &block)
- end
-
- # Generates a form containing a single button that submits to the URL created
- # by the set of +options+. This is the safest method to ensure links that
- # cause changes to your data are not triggered by search bots or accelerators.
- # If the HTML button does not work with your layout, you can also consider
- # using the +link_to+ method with the <tt>:method</tt> modifier as described in
- # the +link_to+ documentation.
- #
- # By default, the generated form element has a class name of <tt>button_to</tt>
- # to allow styling of the form itself and its children. This can be changed
- # using the <tt>:form_class</tt> modifier within +html_options+. You can control
- # the form submission and input element behavior using +html_options+.
- # This method accepts the <tt>:method</tt> modifier described in the +link_to+ documentation.
- # If no <tt>:method</tt> modifier is given, it will default to performing a POST operation.
- # You can also disable the button by passing <tt>disabled: true</tt> in +html_options+.
- # If you are using RESTful routes, you can pass the <tt>:method</tt>
- # to change the HTTP verb used to submit the form.
- #
- # ==== Options
- # The +options+ hash accepts the same options as +url_for+.
- #
- # 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>, <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>:data</tt> - This option can be used to add custom data attributes.
- # * <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.
- # * <tt>:form</tt> - This hash will be form attributes
- # * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
- # be placed
- #
- # ==== Data attributes
- #
- # * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to
- # prompt with the question specified. If the user accepts, the link is
- # processed normally, otherwise no action is taken.
- # * <tt>:disable_with</tt> - Value of this parameter will be
- # used as the value for a disabled version of the submit
- # button when the form is submitted. This feature is provided
- # by the unobtrusive JavaScript driver.
- #
- # ==== Examples
- # <%= button_to "New", action: "new" %>
- # # => "<form method="post" action="/controller/new" class="button_to">
- # # <div><input value="New" type="submit" /></div>
- # # </form>"
- #
- # <%= button_to [:make_happy, @user] do %>
- # Make happy <strong><%= @user.name %></strong>
- # <% end %>
- # # => "<form method="post" action="/users/1/make_happy" class="button_to">
- # # <div>
- # # <button type="submit">
- # # Make happy <strong><%= @user.name %></strong>
- # # </button>
- # # </div>
- # # </form>"
- #
- # <%= button_to "New", action: "new", form_class: "new-thing" %>
- # # => "<form method="post" action="/controller/new" class="new-thing">
- # # <div><input value="New" type="submit" /></div>
- # # </form>"
- #
- #
- # <%= button_to "Create", action: "create", remote: true, form: { "data-type" => "json" } %>
- # # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
- # # <div>
- # # <input value="Create" type="submit" />
- # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
- # # </div>
- # # </form>"
- #
- #
- # <%= button_to "Delete Image", { action: "delete", id: @image.id },
- # method: :delete, data: { confirm: "Are you sure?" } %>
- # # => "<form method="post" action="/images/delete/1" class="button_to">
- # # <div>
- # # <input type="hidden" name="_method" value="delete" />
- # # <input data-confirm='Are you sure?' value="Delete Image" type="submit" />
- # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
- # # </div>
- # # </form>"
- #
- #
- # <%= button_to('Destroy', 'http://www.example.com',
- # method: "delete", remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %>
- # # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
- # # <div>
- # # <input name='_method' value='delete' type='hidden' />
- # # <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' />
- # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
- # # </div>
- # # </form>"
- # #
- def button_to(name = nil, options = nil, html_options = nil, &block)
- html_options, options = options, name if block_given?
- options ||= {}
- html_options ||= {}
-
- html_options = html_options.stringify_keys
- convert_boolean_attributes!(html_options, %w(disabled))
-
- url = options.is_a?(String) ? options : url_for(options)
- remote = html_options.delete('remote')
-
- 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
-
- request_token_tag = form_method == 'post' ? token_tag : ''
-
- html_options = convert_options_to_data_attributes(options, html_options)
- html_options['type'] = 'submit'
-
- button = if block_given?
- content_tag('button', html_options, &block)
- else
- html_options['value'] = name || url
- tag('input', html_options)
- end
-
- inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
- content_tag('form', content_tag('div', inner_tags), form_options)
- end
-
- # Creates a link tag of the given +name+ using a URL created by the set of
- # +options+ unless the current request URI is the same as the links, in
- # which case only the name is returned (or the given block is yielded, if
- # one exists). You can give +link_to_unless_current+ a block which will
- # specialize the default behavior (e.g., show a "Start Here" link rather
- # than the link's text).
- #
- # ==== Examples
- # Let's say you have a navigation menu...
- #
- # <ul id="navbar">
- # <li><%= link_to_unless_current("Home", { action: "index" }) %></li>
- # <li><%= link_to_unless_current("About Us", { action: "about" }) %></li>
- # </ul>
- #
- # If in the "about" action, it will render...
- #
- # <ul id="navbar">
- # <li><a href="/controller/index">Home</a></li>
- # <li>About Us</li>
- # </ul>
- #
- # ...but if in the "index" action, it will render:
- #
- # <ul id="navbar">
- # <li>Home</li>
- # <li><a href="/controller/about">About Us</a></li>
- # </ul>
- #
- # The implicit block given to +link_to_unless_current+ is evaluated if the current
- # action is the action given. So, if we had a comments page and wanted to render a
- # "Go Back" link instead of a link to the comments page, we could do something like this...
- #
- # <%=
- # link_to_unless_current("Comment", { controller: "comments", action: "new" }) do
- # link_to("Go back", { controller: "posts", action: "index" })
- # end
- # %>
- def link_to_unless_current(name, options = {}, html_options = {}, &block)
- link_to_unless current_page?(options), name, options, html_options, &block
- end
-
- # Creates a link tag of the given +name+ using a URL created by the set of
- # +options+ unless +condition+ is true, in which case only the name is
- # returned. To specialize the default behavior (i.e., show a login link rather
- # than just the plaintext link text), you can pass a block that
- # accepts the name or the full argument list for +link_to_unless+.
- #
- # ==== Examples
- # <%= link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) %>
- # # If the user is logged in...
- # # => <a href="/controller/reply/">Reply</a>
- #
- # <%=
- # link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) do |name|
- # link_to(name, { controller: "accounts", action: "signup" })
- # end
- # %>
- # # If the user is logged in...
- # # => <a href="/controller/reply/">Reply</a>
- # # If not...
- # # => <a href="/accounts/signup">Reply</a>
- def link_to_unless(condition, name, options = {}, html_options = {}, &block)
- if condition
- if block_given?
- block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
- else
- name
- end
- else
- link_to(name, options, html_options)
- end
- end
-
- # Creates a link tag of the given +name+ using a URL created by the set of
- # +options+ if +condition+ is true, otherwise only the name is
- # returned. To specialize the default behavior, you can pass a block that
- # accepts the name or the full argument list for +link_to_unless+ (see the examples
- # in +link_to_unless+).
- #
- # ==== Examples
- # <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %>
- # # If the user isn't logged in...
- # # => <a href="/sessions/new/">Login</a>
- #
- # <%=
- # link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) do
- # link_to(@current_user.login, { controller: "accounts", action: "show", id: @current_user })
- # end
- # %>
- # # If the user isn't logged in...
- # # => <a href="/sessions/new/">Login</a>
- # # If they are logged in...
- # # => <a href="/accounts/show/3">my_username</a>
- def link_to_if(condition, name, options = {}, html_options = {}, &block)
- link_to_unless !condition, name, options, html_options, &block
- end
-
- # Creates a mailto link tag to the specified +email_address+, which is
- # also used as the name of the link unless +name+ is specified. Additional
- # HTML attributes for the link can be passed in +html_options+.
- #
- # +mail_to+ has several methods for hindering email harvesters and customizing
- # the email itself by passing special keys to +html_options+.
- #
- # ==== Options
- # * <tt>:encode</tt> - This key will accept the strings "javascript" or "hex".
- # Passing "javascript" will dynamically create and encode the mailto link then
- # eval it into the DOM of the page. This method will not show the link on
- # the page if the user has JavaScript disabled. Passing "hex" will hex
- # encode the +email_address+ before outputting the mailto link.
- # * <tt>:replace_at</tt> - When the link +name+ isn't provided, the
- # +email_address+ is used for the link label. You can use this option to
- # obfuscate the +email_address+ by substituting the @ sign with the string
- # given as the value.
- # * <tt>:replace_dot</tt> - When the link +name+ isn't provided, the
- # +email_address+ is used for the link label. You can use this option to
- # obfuscate the +email_address+ by substituting the . in the email with the
- # 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 additional recipients on the email.
- # * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
- #
- # ==== Examples
- # mail_to "me@domain.com"
- # # => <a href="mailto:me@domain.com">me@domain.com</a>
- #
- # mail_to "me@domain.com", "My email", encode: "javascript"
- # # => <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>
- #
- # mail_to "me@domain.com", nil, replace_at: "_at_", replace_dot: "_dot_", class: "email"
- # # => <a href="mailto:me@domain.com" class="email">me_at_domain_dot_com</a>
- #
- # mail_to "me@domain.com", "My email", cc: "ccaddress@domain.com",
- # subject: "This is an example email"
- # # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
- def mail_to(email_address, name = nil, html_options = {})
- email_address = ERB::Util.html_escape(email_address)
-
- html_options = html_options.stringify_keys
- encode = html_options.delete("encode").to_s
-
- extras = %w{ cc bcc body subject }.map { |item|
- option = html_options.delete(item) || next
- "#{item}=#{Rack::Utils.escape_path(option)}"
- }.compact
- extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&'))
-
- email_address_obfuscated = email_address.to_str
- email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.key?("replace_at")
- email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.key?("replace_dot")
- case encode
- when "javascript"
- string = ''
- html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))
- html = escape_javascript(html.to_str)
- "document.write('#{html}');".each_byte do |c|
- string << sprintf("%%%x", c)
- end
- "<script>eval(decodeURIComponent('#{string}'))</script>".html_safe
- when "hex"
- email_address_encoded = email_address_obfuscated.unpack('C*').map {|c|
- sprintf("&#%d;", c)
- }.join
-
- string = 'mailto:'.unpack('C*').map { |c|
- sprintf("&#%d;", c)
- }.join + email_address.unpack('C*').map { |c|
- char = c.chr
- char =~ /\w/ ? sprintf("%%%x", c) : char
- }.join
-
- content_tag "a", name || email_address_encoded.html_safe, html_options.merge("href" => "#{string}#{extras}".html_safe)
- else
- content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)
- end
- end
-
- # True if the current request URI was generated by the given +options+.
- #
- # ==== Examples
- # Let's say we're in the <tt>/shop/checkout?order=desc</tt> action.
- #
- # current_page?(action: 'process')
- # # => false
- #
- # current_page?(controller: 'shop', action: 'checkout')
- # # => true
- #
- # current_page?(controller: 'shop', action: 'checkout', order: 'asc')
- # # => false
- #
- # current_page?(action: 'checkout')
- # # => true
- #
- # current_page?(controller: 'library', action: 'checkout')
- # # => false
- #
- # Let's say we're in the <tt>/shop/checkout?order=desc&page=1</tt> action.
- #
- # current_page?(action: 'process')
- # # => false
- #
- # current_page?(controller: 'shop', action: 'checkout')
- # # => true
- #
- # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '1')
- # # => true
- #
- # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '2')
- # # => false
- #
- # current_page?(controller: 'shop', action: 'checkout', order: 'desc')
- # # => false
- #
- # current_page?(action: 'checkout')
- # # => true
- #
- # current_page?(controller: 'library', action: 'checkout')
- # # => false
- #
- # Let's say we're in the <tt>/products</tt> action with method POST in case of invalid product.
- #
- # current_page?(controller: 'product', action: 'index')
- # # => false
- #
- def current_page?(options)
- unless request
- raise "You cannot use helpers that need to determine the current " \
- "page unless your view context provides a Request object " \
- "in a #request method"
- end
-
- return false unless request.get?
-
- url_string = url_for(options)
-
- # 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
- request_uri = url_string.index("?") ? request.fullpath : request.path
-
- if url_string =~ /^\w+:\/\//
- url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
- else
- url_string == request_uri
- end
- end
-
- private
- def convert_options_to_data_attributes(options, html_options)
- if html_options
- html_options = html_options.stringify_keys
- html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options)
-
- disable_with = html_options.delete("disable_with")
- confirm = html_options.delete('confirm')
- method = html_options.delete('method')
-
- if confirm
- ActiveSupport::Deprecation.warn ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead"
-
- html_options["data-confirm"] = confirm
- end
-
- add_method_to_attributes!(html_options, method) if method
-
- if disable_with
- ActiveSupport::Deprecation.warn ":disable_with option is deprecated and will be removed from Rails 4.1. Use ':data => { :disable_with => \'Text\' }' instead"
-
- html_options["data-disable-with"] = disable_with
- end
-
- html_options
- else
- link_to_remote_options?(options) ? {'data-remote' => 'true'} : {}
- end
- end
-
- def link_to_remote_options?(options)
- if options.is_a?(Hash)
- options.delete('remote') || options.delete(:remote)
- end
- 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".lstrip
- end
- html_options["data-method"] = method
- end
-
- # Processes the +html_options+ hash, converting the boolean
- # attributes from true/false form into the form required by
- # HTML/XHTML. (An attribute is considered to be boolean if
- # its name is listed in the given +bool_attrs+ array.)
- #
- # More specifically, for each boolean attribute in +html_options+
- # given as:
- #
- # "attr" => bool_value
- #
- # if the associated +bool_value+ evaluates to true, it is
- # replaced with the attribute's name; otherwise the attribute is
- # removed from the +html_options+ hash. (See the XHTML 1.0 spec,
- # section 4.5 "Attribute Minimization" for more:
- # http://www.w3.org/TR/xhtml1/#h-4.5)
- #
- # Returns the updated +html_options+ hash, which is also modified
- # in place.
- #
- # Example:
- #
- # convert_boolean_attributes!( html_options,
- # %w( checked disabled readonly ) )
- def convert_boolean_attributes!(html_options, bool_attrs)
- 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?
- token ||= form_authenticity_token
- tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
- else
- ''
- 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
deleted file mode 100644
index 8a56f147b8..0000000000
--- a/actionpack/lib/action_view/locale/en.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-"en":
- # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
- datetime:
- distance_in_words:
- half_a_minute: "half a minute"
- less_than_x_seconds:
- one: "less than 1 second"
- other: "less than %{count} seconds"
- x_seconds:
- one: "1 second"
- other: "%{count} seconds"
- less_than_x_minutes:
- one: "less than a minute"
- other: "less than %{count} minutes"
- x_minutes:
- one: "1 minute"
- other: "%{count} minutes"
- about_x_hours:
- one: "about 1 hour"
- other: "about %{count} hours"
- x_days:
- one: "1 day"
- other: "%{count} days"
- about_x_months:
- one: "about 1 month"
- other: "about %{count} months"
- x_months:
- one: "1 month"
- other: "%{count} months"
- about_x_years:
- one: "about 1 year"
- other: "about %{count} years"
- over_x_years:
- one: "over 1 year"
- other: "over %{count} years"
- almost_x_years:
- one: "almost 1 year"
- other: "almost %{count} years"
- prompts:
- year: "Year"
- month: "Month"
- day: "Day"
- hour: "Hour"
- minute: "Minute"
- second: "Seconds"
-
- helpers:
- select:
- # Default value for :prompt => true in FormOptionsHelper
- prompt: "Please select"
-
- # Default translation keys for submit and button FormHelper
- submit:
- 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
deleted file mode 100644
index fd9a543e0a..0000000000
--- a/actionpack/lib/action_view/log_subscriber.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module ActionView
- # = Action View Log Subscriber
- #
- # Provides functionality so that Rails can output logs from Action View.
- class LogSubscriber < ActiveSupport::LogSubscriber
- VIEWS_PATTERN = /^app\/views\//.freeze
-
- def render_template(event)
- return unless logger.info?
- message = " Rendered #{from_rails_root(event.payload[:identifier])}"
- message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
- message << " (#{event.duration.round(1)}ms)"
- info(message)
- end
- alias :render_partial :render_template
- alias :render_collection :render_template
-
- def logger
- ActionView::Base.logger
- end
-
- protected
-
- def from_rails_root(string)
- string.sub("#{Rails.root}/", "").sub(VIEWS_PATTERN, "")
- end
- end
-end
-
-ActionView::LogSubscriber.attach_to :action_view
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
deleted file mode 100644
index 76f4dea7b8..0000000000
--- a/actionpack/lib/action_view/lookup_context.rb
+++ /dev/null
@@ -1,233 +0,0 @@
-require 'active_support/core_ext/module/remove_method'
-
-module ActionView
- # = Action View Lookup Context
- #
- # LookupContext is the object responsible to hold all information required to lookup
- # templates, i.e. view paths and details. The LookupContext is also responsible to
- # 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, :rendered_format
-
- mattr_accessor :fallbacks
- @@fallbacks = FallbackFileSystemResolver.instances
-
- mattr_accessor :registered_details
- self.registered_details = []
-
- def self.register_detail(name, options = {}, &block)
- self.registered_details << name
- initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }
-
- Accessors.send :define_method, :"default_#{name}", &block
- Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
- def #{name}
- @details.fetch(:#{name}, [])
- end
-
- def #{name}=(value)
- value = value.present? ? Array(value) : default_#{name}
- _set_detail(:#{name}, value) if value != @details[:#{name}]
- end
-
- remove_possible_method :initialize_details
- def initialize_details(details)
- #{initialize.join("\n")}
- end
- METHOD
- end
-
- # Holds accessors for the registered details.
- module Accessors #:nodoc:
- end
-
- register_detail(:locale) { [I18n.locale, I18n.default_locale].uniq }
- register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
- register_detail(:handlers){ Template::Handlers.extensions }
-
- class DetailsKey #:nodoc:
- alias :eql? :equal?
- alias :object_hash :hash
-
- attr_reader :hash
- @details_keys = Hash.new
-
- def self.get(details)
- @details_keys[details] ||= new
- end
-
- def self.clear
- @details_keys.clear
- end
-
- def initialize
- @hash = object_hash
- end
- end
-
- # Add caching behavior on top of Details.
- module DetailsCache
- attr_accessor :cache
-
- # Calculate the details key. Remove the handlers from calculation to improve performance
- # since the user cannot modify it explicitly.
- def details_key #:nodoc:
- @details_key ||= DetailsKey.get(@details) if @cache
- end
-
- # Temporary skip passing the details_key forward.
- def disable_cache
- old_value, @cache = @cache, false
- yield
- ensure
- @cache = old_value
- end
-
- protected
-
- def _set_detail(key, value)
- @details = @details.dup if @details_key
- @details_key = nil
- @details[key] = value
- end
- end
-
- # Helpers related to template lookup using the lookup context information.
- module ViewPaths
- attr_reader :view_paths, :html_fallback_for_js
-
- # Whenever setting view paths, makes a copy so we can manipulate then in
- # instance objects as we wish.
- def view_paths=(paths)
- @view_paths = ActionView::PathSet.new(Array(paths))
- end
-
- def find(name, prefixes = [], partial = false, keys = [], options = {})
- @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
- end
- alias :find_template :find
-
- def find_all(name, prefixes = [], partial = false, keys = [], options = {})
- @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
- end
-
- def exists?(name, prefixes = [], partial = false, keys = [], options = {})
- @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options))
- end
- alias :template_exists? :exists?
-
- # Add fallbacks to the view paths. Useful in cases you are rendering a :file.
- def with_fallbacks
- added_resolvers = 0
- self.class.fallbacks.each do |resolver|
- next if view_paths.include?(resolver)
- view_paths.push(resolver)
- added_resolvers += 1
- end
- yield
- ensure
- added_resolvers.times { view_paths.pop }
- end
-
- protected
-
- def args_for_lookup(name, prefixes, partial, keys, details_options) #:nodoc:
- name, prefixes = normalize_name(name, prefixes)
- details, details_key = detail_args_for(details_options)
- [name, prefixes, partial || false, details, details_key, keys]
- end
-
- # Compute details hash and key according to user options (e.g. passed from #render).
- def detail_args_for(options)
- return @details, details_key if options.empty? # most common path.
- user_details = @details.merge(options)
- [user_details, DetailsKey.get(user_details)]
- end
-
- # Support legacy foo.erb names even though we now ignore .erb
- # as well as incorrectly putting part of the path in the template
- # name instead of the prefix.
- def normalize_name(name, prefixes) #:nodoc:
- prefixes = prefixes.presence
- parts = name.to_s.split('/')
- parts.shift if parts.first.empty?
- name = parts.pop
-
- return name, prefixes || [""] if parts.empty?
-
- parts = parts.join('/')
- prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
-
- return name, prefixes
- end
- end
-
- include Accessors
- include DetailsCache
- include ViewPaths
-
- def initialize(view_paths, details = {}, prefixes = [])
- @details, @details_key = {}, nil
- @skip_default_locale = false
- @cache = true
- @prefixes = prefixes
- @rendered_format = nil
-
- self.view_paths = view_paths
- initialize_details(details)
- end
-
- # Override formats= to expand ["*/*"] values and automatically
- # add :html as fallback to :js.
- def formats=(values)
- if values
- values.concat(default_formats) if values.delete "*/*"
- if values == [:js]
- values << :html
- @html_fallback_for_js = true
- end
- end
- super(values)
- end
-
- # Do not use the default locale on template lookup.
- def skip_default_locale!
- @skip_default_locale = true
- self.locale = nil
- end
-
- # Override locale to return a symbol instead of array.
- def locale
- @details[:locale].first
- end
-
- # Overload locale= to also set the I18n.locale. If the current I18n.config object responds
- # to original_config, it means that it's has a copy of the original I18n configuration and it's
- # acting as proxy, which we need to skip.
- def locale=(value)
- if value
- config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config
- config.locale = value
- end
-
- super(@skip_default_locale ? I18n.locale : default_locale)
- end
-
- # A method which only uses the first format in the formats array for layout lookup.
- def with_layout_format
- if formats.size == 1
- yield
- else
- old_formats = formats
- _set_detail(:formats, formats[0,1])
-
- begin
- yield
- ensure
- _set_detail(:formats, old_formats)
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/model_naming.rb b/actionpack/lib/action_view/model_naming.rb
deleted file mode 100644
index e09ebd60df..0000000000
--- a/actionpack/lib/action_view/model_naming.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-module ActionView
- module ModelNaming
- # Converts the given object to an ActiveModel compliant one.
- def convert_to_model(object)
- object.respond_to?(:to_model) ? object.to_model : object
- end
-
- def model_name_from_record_or_class(record_or_class)
- (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
- end
- end
-end
diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb
deleted file mode 100644
index bbb1af8154..0000000000
--- a/actionpack/lib/action_view/path_set.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-module ActionView #:nodoc:
- # = Action View PathSet
- class PathSet #:nodoc:
- include Enumerable
-
- attr_reader :paths
-
- def initialize(paths = [])
- @paths = typecast paths
- end
-
- def initialize_copy(other)
- @paths = other.paths.dup
- self
- end
-
- def [](i)
- paths[i]
- end
-
- def to_ary
- paths.dup
- end
-
- def include?(item)
- paths.include? item
- end
-
- def pop
- paths.pop
- end
-
- def size
- paths.size
- end
-
- def each(&block)
- paths.each(&block)
- end
-
- def compact
- PathSet.new paths.compact
- end
-
- def +(array)
- PathSet.new(paths + array)
- end
-
- %w(<< concat push insert unshift).each do |method|
- class_eval <<-METHOD, __FILE__, __LINE__ + 1
- def #{method}(*args)
- paths.#{method}(*typecast(args))
- end
- METHOD
- end
-
- def find(*args)
- find_all(*args).first || raise(MissingTemplate.new(self, *args))
- end
-
- def find_all(path, prefixes = [], *args)
- prefixes = [prefixes] if String === prefixes
- prefixes.each do |prefix|
- paths.each do |resolver|
- templates = resolver.find_all(path, prefix, *args)
- return templates unless templates.empty?
- end
- end
- []
- end
-
- def exists?(path, prefixes, *args)
- find_all(path, prefixes, *args).any?
- end
-
- private
-
- def typecast(paths)
- paths.map do |path|
- case path
- when Pathname, String
- OptimizedFileSystemResolver.new path.to_s
- else
- path
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
deleted file mode 100644
index 55f6ea5522..0000000000
--- a/actionpack/lib/action_view/railtie.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-require "action_view"
-require "rails"
-
-module ActionView
- # = Action View Railtie
- class Railtie < Rails::Railtie
- config.action_view = ActiveSupport::OrderedOptions.new
- config.action_view.embed_authenticity_token_in_remote_forms = false
-
- config.eager_load_namespaces << ActionView
-
- 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
- ActiveSupport.on_load(:action_view) do
- ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids = false
- end
- end
- end
-
- initializer "action_view.set_configs" do |app|
- ActiveSupport.on_load(:action_view) do
- app.config.action_view.each do |k,v|
- send "#{k}=", v
- end
- end
- end
-
- initializer "action_view.caching" do |app|
- ActiveSupport.on_load(:action_view) do
- if app.config.action_view.cache_template_loading.nil?
- ActionView::Resolver.caching = app.config.cache_classes
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/record_identifier.rb b/actionpack/lib/action_view/record_identifier.rb
deleted file mode 100644
index 2953654972..0000000000
--- a/actionpack/lib/action_view/record_identifier.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-require 'active_support/core_ext/module'
-require 'action_view/model_naming'
-
-module ActionView
- # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records 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.
- #
- # # routes
- # resources :posts
- #
- # # view
- # <%= div_for(post) do %> <div id="post_45" class="post">
- # <%= post.body %> What a wonderful world!
- # <% end %> </div>
- #
- # # controller
- # def update
- # post = Post.find(params[:id])
- # post.update_attributes(params[:post])
- #
- # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
- # end
- #
- # As the example above shows, you can stop caring to a large extent what the actual id of the post is.
- # You just know that one is being assigned and that the subsequent calls in redirect_to expect that
- # same naming convention and allows you to write less code if you follow it.
- module RecordIdentifier
- extend self
- extend ModelNaming
-
- include ModelNaming
-
- JOIN = '_'.freeze
- NEW = 'new'.freeze
-
- # The DOM class convention is to use the singular form of an object or class.
- #
- # dom_class(post) # => "post"
- # dom_class(Person) # => "person"
- #
- # If you need to address multiple instances of the same class in the same view, you can prefix the dom_class:
- #
- # dom_class(post, :edit) # => "edit_post"
- # dom_class(Person, :edit) # => "edit_person"
- def dom_class(record_or_class, prefix = nil)
- singular = model_name_from_record_or_class(record_or_class).param_key
- prefix ? "#{prefix}#{JOIN}#{singular}" : singular
- end
-
- # The DOM id convention is to use the singular form of an object or class with the id following an underscore.
- # If no id is found, prefix with "new_" instead.
- #
- # dom_id(Post.find(45)) # => "post_45"
- # dom_id(Post.new) # => "new_post"
- #
- # 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}"
- else
- dom_class(record, prefix || NEW)
- end
- end
-
- protected
-
- # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id.
- # This can be overwritten to customize the default generated string representation if desired.
- # If you need to read back a key from a dom_id in order to query for the underlying database record,
- # you should write a helper like 'person_record_from_dom_id' that will extract the key either based
- # on the default implementation (which just joins all key attributes with '_') or on your own
- # overwritten version of the method. By default, this implementation passes the key string through a
- # method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to
- # make sure yourself that your dom ids are valid, in case you overwrite this method.
- def record_key_for_dom_id(record)
- key = convert_to_model(record).to_key
- key ? key.join('_') : key
- end
- end
-end
diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb
deleted file mode 100644
index 6fb8cbb46c..0000000000
--- a/actionpack/lib/action_view/renderer/abstract_renderer.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-module ActionView
- class AbstractRenderer #:nodoc:
- delegate :find_template, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
-
- def initialize(lookup_context)
- @lookup_context = lookup_context
- end
-
- def render
- raise NotImplementedError
- end
-
- protected
-
- def extract_details(options)
- @lookup_context.registered_details.each_with_object({}) do |key, details|
- next unless value = options[key]
- details[key] = Array(value)
- end
- end
-
- def instrument(name, options={})
- ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield }
- end
-
- def prepend_formats(formats)
- formats = Array(formats)
- return if formats.empty? || @lookup_context.html_fallback_for_js
- @lookup_context.formats = formats | @lookup_context.formats
- end
- end
-end
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
deleted file mode 100644
index edefeac184..0000000000
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ /dev/null
@@ -1,475 +0,0 @@
-
-module ActionView
- # = Action View Partials
- #
- # There's also a convenience method for rendering sub templates within the current controller that depends on a
- # single object (we call this kind of sub templates for partials). It relies on the fact that partials should
- # follow the naming convention of being prefixed with an underscore -- as to separate them from regular
- # templates that could be rendered on their own.
- #
- # In a template for Advertiser#account:
- #
- # <%= render :partial => "account" %>
- #
- # This would render "advertiser/_account.html.erb".
- #
- # In another template for Advertiser#buy, we could have:
- #
- # <%= render :partial => "account", :locals => { :account => @buyer } %>
- #
- # <% @advertisements.each do |ad| %>
- # <%= render :partial => "ad", :locals => { :ad => ad } %>
- # <% end %>
- #
- # This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then
- # render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display.
- #
- # == The :as and :object options
- #
- # By default <tt>ActionView::PartialRenderer</tt> doesn't have any local variables.
- # The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
- #
- # <%= render :partial => "account", :object => @buyer %>
- #
- # 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 } %>
- #
- # With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we
- # wanted it to be +user+ instead of +account+ we'd do:
- #
- # <%= render :partial => "account", :object => @buyer, :as => 'user' %>
- #
- # This is equivalent to
- #
- # <%= render :partial => "account", :locals => { :user => @buyer } %>
- #
- # == Rendering a collection of partials
- #
- # The example of partial use describes a familiar pattern where a template needs to iterate over an array and
- # render a sub template for each of the elements. This pattern has been implemented as a single method that
- # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined
- # example in "Using partials" can be rewritten with a single line:
- #
- # <%= render :partial => "ad", :collection => @advertisements %>
- #
- # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An
- # iteration counter will automatically be made available to the template with a name of the form
- # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+.
- #
- # The <tt>:as</tt> option may be used when rendering partials.
- #
- # You can specify a partial to be rendered between elements via the <tt>:spacer_template</tt> option.
- # The following example will render <tt>advertiser/_ad_divider.html.erb</tt> between each ad partial:
- #
- # <%= render :partial => "ad", :collection => @advertisements, :spacer_template => "ad_divider" %>
- #
- # If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil. This will allow you
- # to specify a text which will displayed instead by using this form:
- #
- # <%= render(:partial => "ad", :collection => @advertisements) || "There's no ad to be displayed" %>
- #
- # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
- # just keep domain objects, like Active Records, in there.
- #
- # == Rendering shared partials
- #
- # Two controllers can share a set of partials and render them like this:
- #
- # <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>
- #
- # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
- #
- # == 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_partial_path` method.
- #
- # # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
- # # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
- # <%= render :partial => @account %>
- #
- # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
- # # that's why we can replace:
- # # <%= render :partial => "posts/post", :collection => @posts %>
- # <%= render :partial => @posts %>
- #
- # == Rendering the default case
- #
- # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
- # defaults of render to render partials. Examples:
- #
- # # Instead of <%= render :partial => "account" %>
- # <%= render "account" %>
- #
- # # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %>
- # <%= render "account", :account => @buyer %>
- #
- # # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
- # # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
- # <%= render @account %>
- #
- # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
- # # that's why we can replace:
- # # <%= render :partial => "posts/post", :collection => @posts %>
- # <%= render @posts %>
- #
- # == Rendering partials with layouts
- #
- # Partials can have their own layouts applied to them. These layouts are different than the ones that are
- # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
- # of users:
- #
- # <%# app/views/users/index.html.erb &>
- # Here's the administrator:
- # <%= render :partial => "user", :layout => "administrator", :locals => { :user => administrator } %>
- #
- # Here's the editor:
- # <%= render :partial => "user", :layout => "editor", :locals => { :user => editor } %>
- #
- # <%# app/views/users/_user.html.erb &>
- # Name: <%= user.name %>
- #
- # <%# app/views/users/_administrator.html.erb &>
- # <div id="administrator">
- # Budget: $<%= user.budget %>
- # <%= yield %>
- # </div>
- #
- # <%# app/views/users/_editor.html.erb &>
- # <div id="editor">
- # Deadline: <%= user.deadline %>
- # <%= yield %>
- # </div>
- #
- # ...this will return:
- #
- # Here's the administrator:
- # <div id="administrator">
- # Budget: $<%= user.budget %>
- # Name: <%= user.name %>
- # </div>
- #
- # Here's the editor:
- # <div id="editor">
- # Deadline: <%= user.deadline %>
- # 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 &>
- # <%= render(:layout => "administrator", :locals => { :user => chief }) do %>
- # Title: <%= chief.title %>
- # <% end %>
- #
- # ...this will return:
- #
- # <div id="administrator">
- # Budget: $<%= user.budget %>
- # Title: <%= chief.name %>
- # </div>
- #
- # As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
- #
- # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
- # an array to layout and treat it as an enumerable.
- #
- # <%# app/views/users/_user.html.erb &>
- # <div class="user">
- # Budget: $<%= user.budget %>
- # <%= yield user %>
- # </div>
- #
- # <%# app/views/users/index.html.erb &>
- # <%= render :layout => @users do |user| %>
- # Title: <%= user.title %>
- # <% end %>
- #
- # This will render the layout for each user and yield to the block, passing the user, each time.
- #
- # You can also yield multiple times in one layout and use block arguments to differentiate the sections.
- #
- # <%# app/views/users/_user.html.erb &>
- # <div class="user">
- # <%= yield user, :header %>
- # Budget: $<%= user.budget %>
- # <%= yield user, :footer %>
- # </div>
- #
- # <%# app/views/users/index.html.erb &>
- # <%= render :layout => @users do |user, section| %>
- # <%- case section when :header -%>
- # Title: <%= user.title %>
- # <%- when :footer -%>
- # Deadline: <%= user.deadline %>
- # <%- end -%>
- # <% end %>
- class PartialRenderer < AbstractRenderer
- PREFIXED_PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} }
-
- def initialize(*)
- super
- @context_prefix = @lookup_context.prefixes.first
- 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
- end
- else
- instrument(:partial, :identifier => identifier) do
- render_partial
- end
- end
- end
-
- def render_collection
- return nil if @collection.blank?
-
- if @options.key?(:spacer_template)
- spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
- end
-
- result = @template ? collection_with_template : collection_without_template
- result.join(spacer).html_safe
- end
-
- def render_partial
- view, locals, block = @view, @locals, @block
- object, as = @object, @variable
-
- if !block && (layout = @options[:layout])
- layout = find_template(layout, @template_keys)
- end
-
- object ||= locals[as]
- locals[as] = object
-
- content = @template.render(view, locals) do |*name|
- view._layout_for(*name, &block)
- end
-
- content = layout.render(view, locals){ content } if layout
- content
- end
-
- private
-
- def setup(context, options, block)
- @view = context
- partial = options[:partial]
-
- @options = options
- @locals = options[:locals] || {}
- @block = block
- @details = extract_details(options)
-
- prepend_formats(options[:formats])
-
- if String === partial
- @object = options[:object]
- @path = partial
- @collection = collection
- else
- @object = partial
-
- if @collection = collection_from_object || collection
- paths = @collection_data = @collection.map { |o| partial_path(o) }
- @path = paths.uniq.size == 1 ? paths.first : nil
- else
- @path = partial_path
- end
- end
-
- if as = options[:as]
- raise_invalid_identifier(as) unless as.to_s =~ /\A[a-z_]\w*\z/
- as = as.to_sym
- end
-
- if @path
- @variable, @variable_counter = retrieve_variable(@path, as)
- @template_keys = retrieve_template_keys
- else
- paths.map! { |path| retrieve_variable(path, as).unshift(path) }
- end
-
- self
- end
-
- def collection
- if @options.key?(:collection)
- collection = @options[:collection]
- collection.respond_to?(:to_ary) ? collection.to_ary : []
- end
- end
-
- def collection_from_object
- @object.to_ary if @object.respond_to?(:to_ary)
- end
-
- def find_partial
- if path = @path
- find_template(path, @template_keys)
- end
- end
-
- def find_template(path, locals)
- prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
- @lookup_context.find_template(path, prefixes, true, locals, @details)
- end
-
- def collection_with_template
- view, locals, template = @view, @locals, @template
- as, counter = @variable, @variable_counter
-
- if layout = @options[:layout]
- layout = find_template(layout, @template_keys)
- end
-
- index = -1
- @collection.map do |object|
- locals[as] = object
- locals[counter] = (index += 1)
-
- content = template.render(view, locals)
- content = layout.render(view, locals) { content } if layout
- content
- end
- end
-
- def collection_without_template
- view, locals, collection_data = @view, @locals, @collection_data
- cache = {}
- keys = @locals.keys
-
- index = -1
- @collection.map do |object|
- index += 1
- path, as, counter = collection_data[index]
-
- locals[as] = object
- locals[counter] = index
-
- template = (cache[path] ||= find_template(path, keys + [as, counter]))
- template.render(view, locals)
- end
- end
-
- def partial_path(object = @object)
- object = object.to_model if object.respond_to?(:to_model)
-
- path = if object.respond_to?(:to_partial_path)
- object.to_partial_path
- else
- raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
- end
-
- 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)
- if prefix.include?(?/) && object_path.include?(?/)
- prefixes = []
- prefix_array = File.dirname(prefix).split('/')
- object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
-
- prefix_array.each_with_index do |dir, index|
- break if dir == object_path_array[index]
- prefixes << dir
- end
-
- (prefixes << object_path).join("/")
- else
- object_path
- end
- end
-
- def retrieve_template_keys
- keys = @locals.keys
- keys << @variable
- keys << @variable_counter if @collection
- keys
- end
-
- def retrieve_variable(path, as)
- variable = as || begin
- base = path[-1] == "/" ? "" : File.basename(path)
- raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/
- $1.to_sym
- end
- variable_counter = :"#{variable}_counter" if @collection
- [variable, variable_counter]
- end
-
- IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a lowercase letter or underscore, " +
- "and is followed by any combination of letters, numbers and underscores."
-
- def raise_invalid_identifier(path)
- raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
- end
- end
-end
diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionpack/lib/action_view/renderer/renderer.rb
deleted file mode 100644
index bf1b5a7d22..0000000000
--- a/actionpack/lib/action_view/renderer/renderer.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-module ActionView
- # This is the main entry point for rendering. It basically delegates
- # to other objects like TemplateRenderer and PartialRenderer which
- # actually renders the template.
- class Renderer
- attr_accessor :lookup_context
-
- def initialize(lookup_context)
- @lookup_context = lookup_context
- end
-
- # Main render entry point shared by AV and AC.
- def render(context, options)
- if options.key?(:partial)
- render_partial(context, options)
- else
- render_template(context, options)
- end
- end
-
- # Render but returns a valid Rack body. If fibers are defined, we return
- # a streaming body that renders the template piece by piece.
- #
- # Note that partials are not supported to be rendered with streaming,
- # so in such cases, we just wrap them in an array.
- def render_body(context, options)
- if options.key?(:partial)
- [render_partial(context, options)]
- else
- StreamingTemplateRenderer.new(@lookup_context).render(context, options)
- end
- end
-
- # Direct accessor to template rendering.
- def render_template(context, options) #:nodoc:
- _template_renderer.render(context, options)
- end
-
- # Direct access to partial rendering.
- def render_partial(context, options, &block) #:nodoc:
- _partial_renderer.render(context, options, block)
- end
-
- private
-
- def _template_renderer #:nodoc:
- @_template_renderer ||= TemplateRenderer.new(@lookup_context)
- end
-
- def _partial_renderer #:nodoc:
- @_partial_renderer ||= PartialRenderer.new(@lookup_context)
- end
- end
-end
diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
deleted file mode 100644
index 9cf6eb0c65..0000000000
--- a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
+++ /dev/null
@@ -1,103 +0,0 @@
-require 'fiber'
-
-module ActionView
- # == TODO
- #
- # * Support streaming from child templates, partials and so on.
- # * Integrate exceptions with exceptron
- # * Rack::Cache needs to support streaming bodies
- class StreamingTemplateRenderer < TemplateRenderer #:nodoc:
- # A valid Rack::Body (i.e. it responds to each).
- # It is initialized with a block that, when called, starts
- # rendering the template.
- class Body #:nodoc:
- def initialize(&start)
- @start = start
- end
-
- def each(&block)
- begin
- @start.call(block)
- rescue Exception => exception
- log_error(exception)
- block.call ActionView::Base.streaming_completion_on_exception
- end
- self
- end
-
- private
-
- # This is the same logging logic as in ShowExceptions middleware.
- # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron.
- def log_error(exception) #:nodoc:
- logger = ActionView::Base.logger
- return unless logger
-
- message = "\n#{exception.class} (#{exception.message}):\n"
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
- message << " " << exception.backtrace.join("\n ")
- logger.fatal("#{message}\n\n")
- end
- end
-
- # For streaming, instead of rendering a given a template, we return a Body
- # object that responds to each. This object is initialized with a block
- # that knows how to render the template.
- def render_template(template, layout_name = nil, locals = {}) #:nodoc:
- return [super] unless layout_name && template.supports_streaming?
-
- locals ||= {}
- layout = layout_name && find_layout(layout_name, locals.keys)
-
- Body.new do |buffer|
- delayed_render(buffer, template, layout, @view, locals)
- end
- end
-
- private
-
- def delayed_render(buffer, template, layout, view, locals)
- # Wrap the given buffer in the StreamingBuffer and pass it to the
- # underlying template handler. Now, everytime something is concatenated
- # to the buffer, it is not appended to an array, but streamed straight
- # to the client.
- output = ActionView::StreamingBuffer.new(buffer)
- yielder = lambda { |*name| view._layout_for(*name) }
-
- instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
- fiber = Fiber.new do
- if layout
- layout.render(view, locals, output, &yielder)
- else
- # If you don't have a layout, just render the thing
- # and concatenate the final result. This is the same
- # as a layout with just <%= yield %>
- output.safe_concat view._layout_for
- end
- end
-
- # Set the view flow to support streaming. It will be aware
- # when to stop rendering the layout because it needs to search
- # something in the template and vice-versa.
- view.view_flow = StreamingFlow.new(view, fiber)
-
- # Yo! Start the fiber!
- fiber.resume
-
- # If the fiber is still alive, it means we need something
- # from the template, so start rendering it. If not, it means
- # the layout exited without requiring anything from the template.
- if fiber.alive?
- content = template.render(view, locals, &yielder)
-
- # Once rendering the template is done, sets its content in the :layout key.
- view.view_flow.set(:layout, content)
-
- # In case the layout continues yielding, we need to resume
- # the fiber until all yields are handled.
- fiber.resume while fiber.alive?
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
deleted file mode 100644
index 156ad4e547..0000000000
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-require 'active_support/core_ext/object/try'
-
-module ActionView
- class TemplateRenderer < AbstractRenderer #:nodoc:
- def render(context, options)
- @view = context
- @details = extract_details(options)
- template = determine_template(options)
- context = @lookup_context
-
- prepend_formats(template.formats)
-
- unless context.rendered_format
- context.rendered_format = template.formats.first || formats.last
- end
-
- render_template(template, options[:layout], options[:locals])
- end
-
- # Determine the template to be rendered using the given options.
- def determine_template(options) #:nodoc:
- keys = options.fetch(:locals, {}).keys
-
- if options.key?(:text)
- Template::Text.new(options[:text], formats.first)
- elsif options.key?(:file)
- with_fallbacks { find_template(options[:file], nil, false, keys, @details) }
- elsif options.key?(:inline)
- handler = Template.handler_for_extension(options[:type] || "erb")
- Template.new(options[:inline], "inline template", handler, :locals => keys)
- elsif options.key?(:template)
- options[:template].respond_to?(:render) ?
- options[:template] : find_template(options[:template], options[:prefixes], false, keys, @details)
- else
- raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option."
- end
- end
-
- # Renders the given template. A string representing the layout can be
- # supplied as well.
- def render_template(template, layout_name = nil, locals = {}) #:nodoc:
- view, locals = @view, locals || {}
-
- render_with_layout(layout_name, locals) do |layout|
- instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
- template.render(view, locals) { |*name| view._layout_for(*name) }
- end
- end
- end
-
- def render_with_layout(path, locals) #:nodoc:
- layout = path && find_layout(path, locals.keys)
- content = yield(layout)
-
- if layout
- view = @view
- view.view_flow.set(:layout, content)
- layout.render(view, locals){ |*name| view._layout_for(*name) }
- else
- content
- end
- end
-
- # This is the method which actually finds the layout using details in the lookup
- # context object. If no layout is found, it checks if at least a layout with
- # the given name exists across all details before raising the error.
- def find_layout(layout, keys)
- with_layout_format { resolve_layout(layout, keys) }
- end
-
- def resolve_layout(layout, keys)
- case layout
- when String
- begin
- if layout =~ /^\//
- with_fallbacks { find_template(layout, nil, false, keys, @details) }
- else
- find_template(layout, nil, false, keys, @details)
- end
- rescue ActionView::MissingTemplate
- all_details = @details.merge(:formats => @lookup_context.default_formats)
- raise unless template_exists?(layout, nil, false, keys, all_details)
- end
- when Proc
- resolve_layout(layout.call, keys)
- when FalseClass
- nil
- else
- layout
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/routing_url_for.rb b/actionpack/lib/action_view/routing_url_for.rb
deleted file mode 100644
index d1488e2332..0000000000
--- a/actionpack/lib/action_view/routing_url_for.rb
+++ /dev/null
@@ -1,107 +0,0 @@
-module ActionView
- module RoutingUrlFor
-
- # 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
- # <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative "/controller/action"
- # instead of the fully qualified URL like "http://example.com/controller/action".
- #
- # ==== Options
- # * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path.
- # * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified).
- # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this
- # is currently not recommended since it breaks caching.
- # * <tt>:host</tt> - Overrides the default (current) host if provided.
- # * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
- # * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
- # * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
- #
- # ==== Relying on named routes
- #
- # 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).
- #
- # ==== Implicit Controller Namespacing
- #
- # Controllers passed in using the +:controller+ option will retain their namespace unless it is an absolute one.
- #
- # ==== Examples
- # <%= url_for(:action => 'index') %>
- # # => /blog/
- #
- # <%= url_for(:action => 'find', :controller => 'books') %>
- # # => /books/find
- #
- # <%= url_for(:action => 'login', :controller => 'members', :only_path => false, :protocol => 'https') %>
- # # => https://www.example.com/members/login/
- #
- # <%= url_for(:action => 'play', :anchor => 'player') %>
- # # => /messages/play/#player
- #
- # <%= url_for(:action => 'jump', :anchor => 'tax&ship') %>
- # # => /testing/jump/#tax&ship
- #
- # <%= url_for(Workshop.new) %>
- # # relies on Workshop answering a persisted? call (and in this case returning false)
- # # => /workshops
- #
- # <%= url_for(@workshop) %>
- # # calls @workshop.to_param which by default returns the id
- # # => /workshops/5
- #
- # # to_param can be re-defined in a model to provide different URL names:
- # # => /workshops/1-workshop-name
- #
- # <%= url_for("http://www.example.com") %>
- # # => http://www.example.com
- #
- # <%= url_for(:back) %>
- # # if request.env["HTTP_REFERER"] is set to "http://www.example.com"
- # # => http://www.example.com
- #
- # <%= url_for(:back) %>
- # # if request.env["HTTP_REFERER"] is not set or is blank
- # # => javascript:history.back()
- #
- # <%= url_for(:action => 'index', :controller => 'users') %>
- # # Assuming an "admin" namespace
- # # => /admin/users
- #
- # <%= url_for(:action => 'index', :controller => '/users') %>
- # # Specify absolute path with beginning slash
- # # => /users
- def url_for(options = nil)
- case options
- when String
- options
- when nil, Hash
- options ||= {}
- options = { :only_path => options[:host].nil? }.merge!(options.symbolize_keys)
- super
- when :back
- _back_url
- else
- polymorphic_path(options)
- end
- end
-
- 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?, true) ?
- controller.optimize_routes_generation? : super
- end
- protected :optimize_routes_generation?
- end
-end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
deleted file mode 100644
index 379cdc8a25..0000000000
--- a/actionpack/lib/action_view/template.rb
+++ /dev/null
@@ -1,339 +0,0 @@
-require 'active_support/core_ext/object/try'
-require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/deprecation'
-require 'thread'
-
-module ActionView
- # = Action View Template
- class Template
- extend ActiveSupport::Autoload
-
- # === Encodings in ActionView::Template
- #
- # ActionView::Template is one of a few sources of potential
- # encoding issues in Rails. This is because the source for
- # templates are usually read from disk, and Ruby (like most
- # encoding-aware programming languages) assumes that the
- # String retrieved through File IO is encoded in the
- # <tt>default_external</tt> encoding. In Rails, the default
- # <tt>default_external</tt> encoding is UTF-8.
- #
- # As a result, if a user saves their template as ISO-8859-1
- # (for instance, using a non-Unicode-aware text editor),
- # and uses characters outside of the ASCII range, their
- # users will see diamonds with question marks in them in
- # the browser.
- #
- # For the rest of this documentation, when we say "UTF-8",
- # we mean "UTF-8 or whatever the default_internal encoding
- # is set to". By default, it will be UTF-8.
- #
- # To mitigate this problem, we use a few strategies:
- # 1. If the source is not valid UTF-8, we raise an exception
- # when the template is compiled to alert the user
- # to the problem.
- # 2. The user can specify the encoding using Ruby-style
- # encoding comments in any template engine. If such
- # a comment is supplied, Rails will apply that encoding
- # to the resulting compiled source returned by the
- # template handler.
- # 3. In all cases, we transcode the resulting String to
- # the UTF-8.
- #
- # This means that other parts of Rails can always assume
- # that templates are encoded in UTF-8, even if the original
- # source of the template was not UTF-8.
- #
- # From a user's perspective, the easiest thing to do is
- # to save your templates as UTF-8. If you do this, you
- # do not need to do anything else for things to "just work".
- #
- # === Instructions for template handlers
- #
- # The easiest thing for you to do is to simply ignore
- # encodings. Rails will hand you the template source
- # as the default_internal (generally UTF-8), raising
- # an exception for the user before sending the template
- # to you if it could not determine the original encoding.
- #
- # For the greatest simplicity, you can support only
- # UTF-8 as the <tt>default_internal</tt>. This means
- # that from the perspective of your handler, the
- # entire pipeline is just UTF-8.
- #
- # === Advanced: Handlers with alternate metadata sources
- #
- # If you want to provide an alternate mechanism for
- # specifying encodings (like ERB does via <%# encoding: ... %>),
- # you may indicate that you will handle encodings yourself
- # by implementing <tt>self.handles_encoding?</tt>
- # on your handler.
- #
- # If you do, Rails will not try to encode the String
- # into the default_internal, passing you the unaltered
- # bytes tagged with the assumed encoding (from
- # default_external).
- #
- # In this case, make sure you return a String from
- # your handler encoded in the default_internal. Since
- # you are handling out-of-band metadata, you are
- # also responsible for alerting the user to any
- # problems with converting the user's data to
- # the <tt>default_internal</tt>.
- #
- # To do so, simply raise the raise +WrongEncodingError+
- # as follows:
- #
- # raise WrongEncodingError.new(
- # problematic_string,
- # expected_encoding
- # )
-
- eager_autoload do
- autoload :Error
- autoload :Handlers
- autoload :Text
- autoload :Types
- end
-
- extend Template::Handlers
-
- attr_accessor :locals, :formats, :virtual_path
-
- attr_reader :source, :identifier, :handler, :original_encoding, :updated_at
-
- # This finalizer is needed (and exactly with a proc inside another proc)
- # otherwise templates leak in development.
- Finalizer = proc do |method_name, mod|
- proc do
- mod.module_eval do
- remove_possible_method method_name
- end
- end
- end
-
- def initialize(source, identifier, handler, details)
- format = details[:format] || (handler.default_format if handler.respond_to?(:default_format))
-
- @source = source
- @identifier = identifier
- @handler = handler
- @compiled = false
- @original_encoding = nil
- @locals = details[:locals] || []
- @virtual_path = details[:virtual_path]
- @updated_at = details[:updated_at] || Time.now
- @formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f }
- @compile_mutex = Mutex.new
- end
-
- # Returns if the underlying handler supports streaming. If so,
- # a streaming buffer *may* be passed when it start rendering.
- def supports_streaming?
- handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
- end
-
- # Render a template. If the template was not compiled yet, it is done
- # exactly before rendering.
- #
- # This method is instrumented as "!render_template.action_view". Notice that
- # we use a bang in this instrumentation because you don't want to
- # consume this in production. This is only slow if it's being listened to.
- def render(view, locals, buffer=nil, &block)
- ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
- compile!(view)
- view.send(method_name, locals, buffer, &block)
- end
- rescue Exception => e
- handle_render_error(view, e)
- end
-
- def mime_type
- ActiveSupport::Deprecation.warn 'Template#mime_type is deprecated and will be removed in Rails 4.1. Please use type method instead.'
- @mime_type ||= Mime::Type.lookup_by_extension(@formats.first.to_s) if @formats.first
- end
-
- def type
- @type ||= Types[@formats.first] if @formats.first
- end
-
- # Receives a view object and return a template similar to self by using @virtual_path.
- #
- # This method is useful if you have a template object but it does not contain its source
- # anymore since it was already compiled. In such cases, all you need to do is to call
- # refresh passing in the view object.
- #
- # Notice this method raises an error if the template to be refreshed does not have a
- # virtual path set (true just for inline templates).
- def refresh(view)
- raise "A template needs to have a virtual path in order to be refreshed" unless @virtual_path
- lookup = view.lookup_context
- pieces = @virtual_path.split("/")
- name = pieces.pop
- partial = !!name.sub!(/^_/, "")
- lookup.disable_cache do
- lookup.find_template(name, [ pieces.join('/') ], partial, @locals)
- end
- end
-
- def inspect
- @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier
- end
-
- # This method is responsible for properly setting the encoding of the
- # source. Until this point, we assume that the source is BINARY data.
- # If no additional information is supplied, we assume the encoding is
- # the same as <tt>Encoding.default_external</tt>.
- #
- # The user can also specify the encoding via a comment on the first
- # line of the template (# encoding: NAME-OF-ENCODING). This will work
- # with any template engine, as we process out the encoding comment
- # before passing the source on to the template engine, leaving a
- # blank line in its stead.
- def encode!
- return unless source.encoding == Encoding::BINARY
-
- # Look for # encoding: *. If we find one, we'll encode the
- # String in that encoding, otherwise, we'll use the
- # default external encoding.
- if source.sub!(/\A#{ENCODING_FLAG}/, '')
- encoding = magic_encoding = $1
- else
- encoding = Encoding.default_external
- end
-
- # Tag the source with the default external encoding
- # or the encoding specified in the file
- source.force_encoding(encoding)
-
- # If the user didn't specify an encoding, and the handler
- # handles encodings, we simply pass the String as is to
- # the handler (with the default_external tag)
- if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
- source
- # Otherwise, if the String is valid in the encoding,
- # encode immediately to default_internal. This means
- # that if a handler doesn't handle encodings, it will
- # always get Strings in the default_internal
- elsif source.valid_encoding?
- source.encode!
- # Otherwise, since the String is invalid in the encoding
- # specified, raise an exception
- else
- raise WrongEncodingError.new(source, encoding)
- end
- end
-
- protected
-
- # Compile a template. This method ensures a template is compiled
- # just once and removes the source after it is compiled.
- def compile!(view) #:nodoc:
- return if @compiled
-
- # Templates can be used concurrently in threaded environments
- # so compilation and any instance variable modification must
- # be synchronized
- @compile_mutex.synchronize do
- # Any thread holding this lock will be compiling the template needed
- # by the threads waiting. So re-check the @compiled flag to avoid
- # re-compilation
- return if @compiled
-
- if view.is_a?(ActionView::CompiledTemplates)
- mod = ActionView::CompiledTemplates
- else
- mod = view.singleton_class
- end
-
- compile(view, mod)
-
- # Just discard the source if we have a virtual path. This
- # means we can get the template back.
- @source = nil if @virtual_path
- @compiled = true
- end
- end
-
- # Among other things, this method is responsible for properly setting
- # the encoding of the compiled template.
- #
- # If the template engine handles encodings, we send the encoded
- # String to the engine without further processing. This allows
- # the template engine to support additional mechanisms for
- # specifying the encoding. For instance, ERB supports <%# encoding: %>
- #
- # Otherwise, after we figure out the correct encoding, we then
- # encode the source into <tt>Encoding.default_internal</tt>.
- # In general, this means that templates will be UTF-8 inside of Rails,
- # regardless of the original source encoding.
- def compile(view, mod) #:nodoc:
- encode!
- method_name = self.method_name
- code = @handler.call(self)
-
- # Make sure that the resulting String to be evalled is in the
- # encoding of the code
- source = <<-end_src
- def #{method_name}(local_assigns, output_buffer)
- _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
- ensure
- @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
- end
- end_src
-
- # Make sure the source is in the encoding of the returned code
- source.force_encoding(code.encoding)
-
- # In case we get back a String from a handler that is not in
- # BINARY or the default_internal, encode it to the default_internal
- source.encode!
-
- # Now, validate that the source we got back from the template
- # handler is valid in the default_internal. This is for handlers
- # that handle encoding but screw up
- unless source.valid_encoding?
- raise WrongEncodingError.new(@source, Encoding.default_internal)
- end
-
- begin
- mod.module_eval(source, identifier, 0)
- ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
- rescue Exception => e # errors from template code
- if logger = (view && view.logger)
- logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
- logger.debug "Function body: #{source}"
- logger.debug "Backtrace: #{e.backtrace.join("\n")}"
- end
-
- raise ActionView::Template::Error.new(self, e)
- end
- end
-
- def handle_render_error(view, e) #:nodoc:
- if e.is_a?(Template::Error)
- e.sub_template_of(self)
- raise e
- else
- template = self
- unless template.source
- template = refresh(view)
- template.encode!
- end
- raise Template::Error.new(template, e)
- end
- end
-
- def locals_code #:nodoc:
- @locals.map { |key| "#{key} = local_assigns[:#{key}];" }.join
- end
-
- def method_name #:nodoc:
- @method_name ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".gsub('-', "_")
- end
-
- def identifier_method_name #:nodoc:
- inspect.gsub(/[^a-z_]/, '_')
- end
- end
-end
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
deleted file mode 100644
index e00056781d..0000000000
--- a/actionpack/lib/action_view/template/error.rb
+++ /dev/null
@@ -1,130 +0,0 @@
-require "active_support/core_ext/enumerable"
-
-module ActionView
- # = Action View Errors
- class ActionViewError < StandardError #:nodoc:
- end
-
- class EncodingError < StandardError #:nodoc:
- end
-
- class MissingRequestError < StandardError #:nodoc:
- end
-
- class WrongEncodingError < EncodingError #:nodoc:
- def initialize(string, encoding)
- @string, @encoding = string, encoding
- end
-
- def message
- @string.force_encoding("BINARY")
- "Your template was not saved as valid #{@encoding}. Please " \
- "either specify #{@encoding} as the encoding for your template " \
- "in your text editor, or mark the template with its " \
- "encoding by inserting the following as the first line " \
- "of the template:\n\n# encoding: <name of correct encoding>.\n\n" \
- "The source of your template was:\n\n#{@string}"
- end
- end
-
- class MissingTemplate < ActionViewError #:nodoc:
- attr_reader :path
-
- def initialize(paths, path, prefixes, partial, details, *)
- @path = path
- prefixes = Array(prefixes)
- template_type = if partial
- "partial"
- elsif path =~ /layouts/i
- 'layout'
- else
- 'template'
- end
-
- searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
-
- out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n"
- out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
- super out
- end
- end
-
- class Template
- # The Template::Error exception is raised when the compilation or rendering of the template
- # fails. This exception then gathers a bunch of intimate details and uses it to report a
- # precise exception message.
- class Error < ActionViewError #:nodoc:
- SOURCE_CODE_RADIUS = 3
-
- attr_reader :original_exception, :backtrace
-
- def initialize(template, original_exception)
- super(original_exception.message)
- @template, @original_exception = template, original_exception
- @sub_templates = nil
- @backtrace = original_exception.backtrace
- end
-
- def file_name
- @template.identifier
- end
-
- def sub_template_message
- if @sub_templates
- "Trace of template inclusion: " +
- @sub_templates.collect { |template| template.inspect }.join(", ")
- else
- ""
- end
- end
-
- def source_extract(indentation = 0)
- return unless num = line_number
- num = num.to_i
-
- source_code = @template.source.split("\n")
-
- start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
- end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
-
- indent = end_on_line.to_s.size + indentation
- line_counter = start_on_line
- return unless source_code = source_code[start_on_line..end_on_line]
-
- source_code.sum do |line|
- line_counter += 1
- "%#{indent}s: %s\n" % [line_counter, line]
- end
- end
-
- def sub_template_of(template_path)
- @sub_templates ||= []
- @sub_templates << template_path
- end
-
- def line_number
- @line_number ||=
- if file_name
- regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
- $1 if message =~ regexp || backtrace.find { |line| line =~ regexp }
- end
- end
-
- def annoted_source_code
- source_extract(4)
- end
-
- private
-
- def source_location
- if line_number
- "on line ##{line_number} of "
- else
- 'in '
- end + file_name
- end
- end
- end
-
- TemplateError = Template::Error
-end
diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb
deleted file mode 100644
index d9cddc0040..0000000000
--- a/actionpack/lib/action_view/template/handlers.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-module ActionView #:nodoc:
- # = Action View Template Handlers
- class Template
- module Handlers #:nodoc:
- autoload :ERB, 'action_view/template/handlers/erb'
- autoload :Builder, 'action_view/template/handlers/builder'
- autoload :Raw, 'action_view/template/handlers/raw'
-
- def self.extended(base)
- base.register_default_template_handler :erb, ERB.new
- base.register_template_handler :builder, Builder.new
- base.register_template_handler :raw, Raw.new
- base.register_template_handler :ruby, :source.to_proc
- end
-
- @@template_handlers = {}
- @@default_template_handlers = nil
-
- def self.extensions
- @@template_extensions ||= @@template_handlers.keys
- end
-
- # Register an object that knows how to handle template files with the given
- # extensions. This can be used to implement new template types.
- # 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(*extensions, handler)
- raise(ArgumentError, "Extension is required") if extensions.empty?
- extensions.each do |extension|
- @@template_handlers[extension.to_sym] = handler
- end
- @@template_extensions = nil
- end
-
- def template_handler_extensions
- @@template_handlers.keys.map {|key| key.to_s }.sort
- end
-
- def registered_template_handler(extension)
- extension && @@template_handlers[extension.to_sym]
- end
-
- def register_default_template_handler(extension, klass)
- register_template_handler(extension, klass)
- @@default_template_handlers = klass
- end
-
- def handler_for_extension(extension)
- registered_template_handler(extension) || @@default_template_handlers
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/template/handlers/builder.rb b/actionpack/lib/action_view/template/handlers/builder.rb
deleted file mode 100644
index d90b0c6378..0000000000
--- a/actionpack/lib/action_view/template/handlers/builder.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module ActionView
- module Template::Handlers
- class Builder
- # Default format used by Builder.
- class_attribute :default_format
- self.default_format = :xml
-
- def call(template)
- require_engine
- "xml = ::Builder::XmlMarkup.new(:indent => 2);" +
- "self.output_buffer = xml.target!;" +
- template.source +
- ";xml.target!;"
- end
-
- protected
-
- def require_engine
- @required ||= begin
- require "builder"
- true
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
deleted file mode 100644
index aa8eac7846..0000000000
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ /dev/null
@@ -1,104 +0,0 @@
-require 'action_dispatch/http/mime_type'
-require 'erubis'
-
-module ActionView
- class Template
- module Handlers
- class Erubis < ::Erubis::Eruby
- def add_preamble(src)
- src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
- end
-
- def add_text(src, text)
- return if text.empty?
- src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
- end
-
- BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
-
- def add_expr_literal(src, code)
- if code =~ BLOCK_EXPR
- src << '@output_buffer.append= ' << code
- else
- src << '@output_buffer.append= (' << code << ');'
- end
- end
-
- def add_expr_escaped(src, code)
- if code =~ BLOCK_EXPR
- src << "@output_buffer.safe_append= " << code
- else
- src << "@output_buffer.safe_concat((" << code << ").to_s);"
- end
- end
-
- def add_postamble(src)
- src << '@output_buffer.to_s'
- end
- end
-
- class ERB
- # Specify trim mode for the ERB compiler. Defaults to '-'.
- # See ERB documentation for suitable values.
- class_attribute :erb_trim_mode
- self.erb_trim_mode = '-'
-
- # Default implementation used.
- class_attribute :erb_implementation
- self.erb_implementation = Erubis
-
- ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
-
- def self.call(template)
- new.call(template)
- end
-
- def supports_streaming?
- true
- end
-
- def handles_encoding?
- true
- end
-
- def call(template)
- # First, convert to BINARY, so in case the encoding is
- # wrong, we can still find an encoding tag
- # (<%# encoding %>) inside the String using a regular
- # expression
- template_source = template.source.dup.force_encoding("BINARY")
-
- erb = template_source.gsub(ENCODING_TAG, '')
- encoding = $2
-
- erb.force_encoding valid_encoding(template.source.dup, encoding)
-
- # Always make sure we return a String in the default_internal
- erb.encode!
-
- self.class.erb_implementation.new(
- erb,
- :trim => (self.class.erb_trim_mode == "-")
- ).src
- end
-
- private
-
- def valid_encoding(string, encoding)
- # If a magic encoding comment was found, tag the
- # String with this encoding. This is for a case
- # where the original String was assumed to be,
- # for instance, UTF-8, but a magic comment
- # proved otherwise
- string.force_encoding(encoding) if encoding
-
- # If the String is valid, return the encoding we found
- return string.encoding if string.valid_encoding?
-
- # Otherwise, raise an exception
- raise WrongEncodingError.new(string, string.encoding)
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/template/handlers/raw.rb b/actionpack/lib/action_view/template/handlers/raw.rb
deleted file mode 100644
index 0c0d1fffcb..0000000000
--- a/actionpack/lib/action_view/template/handlers/raw.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module ActionView
- module Template::Handlers
- class Raw
- def call(template)
- escaped = template.source.gsub(':', '\:')
-
- '%q:' + escaped + ':;'
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
deleted file mode 100644
index 25c6fd4aa8..0000000000
--- a/actionpack/lib/action_view/template/resolver.rb
+++ /dev/null
@@ -1,321 +0,0 @@
-require "pathname"
-require "active_support/core_ext/class"
-require "active_support/core_ext/class/attribute_accessors"
-require "action_view/template"
-require "thread"
-require "mutex_m"
-
-module ActionView
- # = Action View Resolver
- class Resolver
- # Keeps all information about view path and builds virtual path.
- class Path
- attr_reader :name, :prefix, :partial, :virtual
- alias_method :partial?, :partial
-
- def self.build(name, prefix, partial)
- virtual = ""
- virtual << "#{prefix}/" unless prefix.empty?
- virtual << (partial ? "_#{name}" : name)
- new name, prefix, partial, virtual
- end
-
- def initialize(name, prefix, partial, virtual)
- @name = name
- @prefix = prefix
- @partial = partial
- @virtual = virtual
- end
-
- def to_str
- @virtual
- end
- alias :to_s :to_str
- end
-
- # Threadsafe template cache
- class Cache #:nodoc:
- class CacheEntry
- include Mutex_m
-
- attr_accessor :templates
- end
-
- def initialize
- @data = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2|
- h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }
- @mutex = Mutex.new
- end
-
- # Cache the templates returned by the block
- def cache(key, name, prefix, partial, locals)
- cache_entry = nil
-
- # first obtain a lock on the main data structure to create the cache entry
- @mutex.synchronize do
- cache_entry = @data[key][name][prefix][partial][locals] ||= CacheEntry.new
- end
-
- # then to avoid a long lasting global lock, obtain a more granular lock
- # on the CacheEntry itself
- cache_entry.synchronize do
- if Resolver.caching?
- cache_entry.templates ||= yield
- else
- fresh_templates = yield
-
- if templates_have_changed?(cache_entry.templates, fresh_templates)
- cache_entry.templates = fresh_templates
- else
- cache_entry.templates ||= []
- end
- end
- end
- end
-
- def clear
- @mutex.synchronize do
- @data.clear
- end
- end
-
- private
-
- def templates_have_changed?(cached_templates, fresh_templates)
- # if either the old or new template list is empty, we don't need to (and can't)
- # compare modification times, and instead just check whether the lists are different
- if cached_templates.blank? || fresh_templates.blank?
- return fresh_templates.blank? != cached_templates.blank?
- end
-
- cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
-
- # if a template has changed, it will be now be newer than all the cached templates
- fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
- end
- end
-
- cattr_accessor :caching
- self.caching = true
-
- class << self
- alias :caching? :caching
- end
-
- def initialize
- @cache = Cache.new
- end
-
- def clear_cache
- @cache.clear
- end
-
- # Normalizes the arguments and passes it on to find_template.
- def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
- cached(key, [name, prefix, partial], details, locals) do
- find_templates(name, prefix, partial, details)
- end
- end
-
- private
-
- delegate :caching?, :to => "self.class"
-
- # This is what child classes implement. No defaults are needed
- # because Resolver guarantees that the arguments are present and
- # normalized.
- def find_templates(name, prefix, partial, details)
- raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details) method"
- end
-
- # Helpers that builds a path. Useful for building virtual paths.
- def build_path(name, prefix, partial)
- Path.build(name, prefix, partial)
- end
-
- # Handles templates caching. If a key is given and caching is on
- # always check the cache before hitting the resolver. Otherwise,
- # it always hits the resolver but if the key is present, check if the
- # resolver is fresher before returning it.
- def cached(key, path_info, details, locals) #:nodoc:
- name, prefix, partial = path_info
- locals = locals.map { |x| x.to_s }.sort!
-
- if key
- @cache.cache(key, name, prefix, partial, locals) do
- decorate(yield, path_info, details, locals)
- end
- else
- decorate(yield, path_info, details, locals)
- end
- end
-
- # Ensures all the resolver information is set in the template.
- def decorate(templates, path_info, details, locals) #:nodoc:
- cached = nil
- templates.each do |t|
- t.locals = locals
- t.formats = details[:formats] || [:html] if t.formats.empty?
- t.virtual_path ||= (cached ||= build_path(*path_info))
- end
- end
- end
-
- # An abstract class that implements a Resolver with path semantics.
- class PathResolver < Resolver #:nodoc:
- EXTENSIONS = [:locale, :formats, :handlers]
- DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}"
-
- def initialize(pattern=nil)
- @pattern = pattern || DEFAULT_PATTERN
- super()
- end
-
- private
-
- def find_templates(name, prefix, partial, details)
- path = Path.build(name, prefix, partial)
- query(path, details, details[:formats])
- end
-
- def query(path, details, formats)
- query = build_query(path, details)
-
- # deals with case-insensitive file systems.
- sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
-
- template_paths = Dir[query].reject { |filename|
- File.directory?(filename) ||
- !sanitizer[File.dirname(filename)].include?(filename)
- }
-
- template_paths.map { |template|
- handler, format = extract_handler_and_format(template, formats)
- contents = File.binread template
-
- Template.new(contents, File.expand_path(template), handler,
- :virtual_path => path.virtual,
- :format => format,
- :updated_at => mtime(template))
- }
- end
-
- # Helper for building query glob string based on resolver's pattern.
- def build_query(path, details)
- query = @pattern.dup
-
- prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
- query.gsub!(/\:prefix(\/)?/, prefix)
-
- partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
- query.gsub!(/\:action/, partial)
-
- details.each do |ext, variants|
- query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
- end
-
- File.expand_path(query, @path)
- end
-
- def escape_entry(entry)
- entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
- end
-
- # Returns the file mtime from the filesystem.
- def mtime(p)
- File.mtime(p)
- end
-
- # Extract handler and formats from path. If a format cannot be a found neither
- # from the path, or the handler, we should return the array of formats given
- # to the resolver.
- def extract_handler_and_format(path, default_formats)
- pieces = File.basename(path).split(".")
- pieces.shift
- extension = pieces.pop
- ActiveSupport::Deprecation.warn "The file #{path} did not specify a template handler. The default is currently ERB, but will change to RAW in the future." unless extension
- handler = Template.handler_for_extension(extension)
- format = pieces.last && Template::Types[pieces.last]
- [handler, format]
- end
- end
-
- # 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
- #
- # Default pattern, loads views the same way as previous versions of rails, eg. when you're
- # looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}`
- #
- # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
- #
- # This one allows you to keep files with different formats in seperated subdirectories,
- # eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
- # `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
- #
- # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}")
- #
- # 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:
- #
- # ActionController::Base.view_paths = FileSystemResolver.new(
- # Rails.root.join("app/views"),
- # ":prefix{/:locale}/:action{.:formats,}{.:handlers,}"
- # )
- #
- # ==== Pattern format and variables
- #
- # Pattern has to be a valid glob string, and it allows you to use the
- # following variables:
- #
- # * <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...)
- # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
- #
- class FileSystemResolver < PathResolver
- def initialize(path, pattern=nil)
- raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
- super(pattern)
- @path = File.expand_path(path)
- end
-
- def to_s
- @path.to_s
- end
- alias :to_path :to_s
-
- def eql?(resolver)
- self.class.equal?(resolver.class) && to_path == resolver.to_path
- end
- alias :== :eql?
- end
-
- # An Optimized resolver for Rails' most common case.
- class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
- def build_query(path, details)
- exts = EXTENSIONS.map { |ext| details[ext] }
- query = escape_entry(File.join(@path, path))
-
- query + exts.map { |ext|
- "{#{ext.compact.uniq.map { |e| ".#{e}," }.join}}"
- }.join
- end
- end
-
- # The same as FileSystemResolver but does not allow templates to store
- # a virtual path since it is invalid for such resolvers.
- class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
- def self.instances
- [new(""), new("/")]
- end
-
- def decorate(*)
- super.each { |t| t.virtual_path = nil }
- end
- end
-end
diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb
deleted file mode 100644
index 859c7bc3ce..0000000000
--- a/actionpack/lib/action_view/template/text.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module ActionView #:nodoc:
- # = Action View Text Template
- class Template
- class Text #:nodoc:
- attr_accessor :type
-
- def initialize(string, type = nil)
- @string = string.to_s
- @type = Types[type] || type if type
- @type ||= Types[:text]
- end
-
- def identifier
- 'text template'
- end
-
- def inspect
- 'text template'
- end
-
- def to_str
- @string
- end
-
- def render(*args)
- to_str
- end
-
- def formats
- [@type.to_sym]
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/template/types.rb b/actionpack/lib/action_view/template/types.rb
deleted file mode 100644
index 7611c9e708..0000000000
--- a/actionpack/lib/action_view/template/types.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-require 'set'
-require 'active_support/core_ext/class/attribute_accessors'
-require 'active_support/core_ext/object/blank'
-
-module ActionView
- class Template
- class Types
- class Type
- cattr_accessor :types
- self.types = Set.new
-
- def self.register(*t)
- types.merge(t.map { |type| type.to_s })
- end
-
- register :html, :text, :js, :css, :xml, :json
-
- def self.[](type)
- return type if type.is_a?(self)
-
- if type.is_a?(Symbol) || types.member?(type.to_s)
- new(type)
- end
- end
-
- attr_reader :symbol
-
- def initialize(symbol)
- @symbol = symbol.to_sym
- end
-
- delegate :to_s, :to_sym, :to => :symbol
- alias to_str to_s
-
- def ref
- to_sym || to_s
- end
-
- def ==(type)
- return false if type.blank?
- symbol.to_sym == type.to_sym
- end
- end
-
- cattr_accessor :type_klass
-
- def self.delegate_to(klass)
- self.type_klass = klass
- end
-
- delegate_to Type
-
- def self.[](type)
- type_klass[type]
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
deleted file mode 100644
index a548b44780..0000000000
--- a/actionpack/lib/action_view/test_case.rb
+++ /dev/null
@@ -1,268 +0,0 @@
-require 'active_support/core_ext/module/remove_method'
-require 'action_controller'
-require 'action_controller/test_case'
-require 'action_view'
-
-module ActionView
- # = Action View Test Case
- class TestCase < ActiveSupport::TestCase
- class TestController < ActionController::Base
- include ActionDispatch::TestProcess
-
- attr_accessor :request, :response, :params
-
- class << self
- attr_writer :controller_path
- end
-
- def controller_path=(path)
- self.class.controller_path=(path)
- end
-
- def initialize
- super
- self.class.controller_path = ""
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
-
- @request.env.delete('PATH_INFO')
- @params = {}
- end
- end
-
- # Use AV::TestCase for the base class for helpers and views
- register_spec_type(/(Helper|View)( ?Test)?\z/i, self)
-
- module Behavior
- extend ActiveSupport::Concern
-
- include ActionDispatch::Assertions, ActionDispatch::TestProcess
- include ActionController::TemplateAssertions
- include ActionView::Context
-
- include ActionDispatch::Routing::PolymorphicRoutes
-
- include AbstractController::Helpers
- include ActionView::Helpers
- include ActionView::RecordIdentifier
- include ActionView::RoutingUrlFor
-
- include ActiveSupport::Testing::ConstantLookup
-
- delegate :lookup_context, :to => :controller
- attr_accessor :controller, :output_buffer, :rendered
-
- module ClassMethods
- def tests(helper_class)
- case helper_class
- when String, Symbol
- self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize
- when Module
- self.helper_class = helper_class
- end
- end
-
- def determine_default_helper_class(name)
- determine_constant_from_test_name(name) do |constant|
- Module === constant && !(Class === constant)
- end
- end
-
- def helper_method(*methods)
- # Almost a duplicate from ActionController::Helpers
- methods.flatten.each do |method|
- _helpers.module_eval <<-end_eval
- def #{method}(*args, &block) # def current_user(*args, &block)
- _test_case.send(%(#{method}), *args, &block) # _test_case.send(%(current_user), *args, &block)
- end # end
- end_eval
- end
- end
-
- attr_writer :helper_class
-
- def helper_class
- @helper_class ||= determine_default_helper_class(name)
- end
-
- def new(*)
- include_helper_modules!
- super
- end
-
- private
-
- def include_helper_modules!
- helper(helper_class) if helper_class
- include _helpers
- end
-
- end
-
- def setup_with_controller
- @controller = ActionView::TestCase::TestController.new
- @request = @controller.request
- @output_buffer = ActiveSupport::SafeBuffer.new
- @rendered = ''
-
- make_test_case_available_to_view!
- say_no_to_protect_against_forgery!
- end
-
- def config
- @controller.config if @controller.respond_to?(:config)
- end
-
- def render(options = {}, local_assigns = {}, &block)
- view.assign(view_assigns)
- @rendered << output = view.render(options, local_assigns, &block)
- output
- end
-
- def rendered_views
- @_rendered_views ||= RenderedViewsCollection.new
- end
-
- class RenderedViewsCollection
- def initialize
- @rendered_views ||= {}
- end
-
- def add(view, locals)
- @rendered_views[view] ||= []
- @rendered_views[view] << locals
- end
-
- def locals_for(view)
- @rendered_views[view]
- end
-
- def view_rendered?(view, expected_locals)
- locals_for(view).any? do |actual_locals|
- expected_locals.all? {|key, value| value == actual_locals[key] }
- end
- end
- end
-
- included do
- setup :setup_with_controller
- end
-
- private
-
- # Support the selector assertions
- #
- # Need to experiment if this priority is the best one: rendered => output_buffer
- def response_from_page
- HTML::Document.new(@rendered.blank? ? @output_buffer : @rendered).root
- end
-
- def say_no_to_protect_against_forgery!
- _helpers.module_eval do
- remove_possible_method :protect_against_forgery?
- def protect_against_forgery?
- false
- end
- end
- end
-
- def make_test_case_available_to_view!
- test_case_instance = self
- _helpers.module_eval do
- unless private_method_defined?(:_test_case)
- define_method(:_test_case) { test_case_instance }
- private :_test_case
- end
- end
- end
-
- module Locals
- attr_accessor :rendered_views
-
- def render(options = {}, local_assigns = {})
- case options
- when Hash
- if block_given?
- rendered_views.add options[:layout], options[:locals]
- elsif options.key?(:partial)
- rendered_views.add options[:partial], options[:locals]
- end
- else
- rendered_views.add options, local_assigns
- end
-
- super
- end
- end
-
- # The instance of ActionView::Base that is used by +render+.
- def view
- @view ||= begin
- view = @controller.view_context
- view.singleton_class.send :include, _helpers
- view.extend(Locals)
- view.rendered_views = self.rendered_views
- view.output_buffer = self.output_buffer
- view
- end
- end
-
- alias_method :_view, :view
-
- INTERNAL_IVARS = [
- :@__name__,
- :@__io__,
- :@_assertion_wrapped,
- :@_assertions,
- :@_result,
- :@_routes,
- :@controller,
- :@_layouts,
- :@_rendered_views,
- :@method_name,
- :@output_buffer,
- :@_partials,
- :@passed,
- :@rendered,
- :@request,
- :@routes,
- :@tagged_logger,
- :@_templates,
- :@options,
- :@test_passed,
- :@view,
- :@view_context_class
- ]
-
- def _user_defined_ivars
- instance_variables - INTERNAL_IVARS
- end
-
- # Returns a Hash of instance variables and their values, as defined by
- # the user in the test case, which are then assigned to the view being
- # rendered. This is generally intended for internal use and extension
- # frameworks.
- def view_assigns
- Hash[_user_defined_ivars.map do |ivar|
- [ivar[1..-1].to_sym, instance_variable_get(ivar)]
- end]
- end
-
- def _routes
- @controller._routes if @controller.respond_to?(:_routes)
- end
-
- def method_missing(selector, *args)
- if @controller.respond_to?(:_routes) &&
- ( @controller._routes.named_routes.helpers.include?(selector) ||
- @controller._routes.mounted_helpers.method_defined?(selector) )
- @controller.__send__(selector, *args)
- else
- super
- end
- end
- end
-
- include Behavior
- end
-end
diff --git a/actionpack/lib/action_view/testing/resolvers.rb b/actionpack/lib/action_view/testing/resolvers.rb
deleted file mode 100644
index 7afa2fa613..0000000000
--- a/actionpack/lib/action_view/testing/resolvers.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-require 'action_view/template/resolver'
-
-module ActionView #:nodoc:
- # Use FixtureResolver in your tests to simulate the presence of files on the
- # file system. This is used internally by Rails' own test suite, and is
- # useful for testing extensions that have no way of knowing what the file
- # system will look like at runtime.
- class FixtureResolver < PathResolver
- attr_reader :hash
-
- def initialize(hash = {}, pattern=nil)
- super(pattern)
- @hash = hash
- end
-
- def to_s
- @hash.keys.join(', ')
- end
-
- private
-
- def query(path, exts, formats)
- query = ""
- EXTENSIONS.each do |ext|
- query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
- end
- query = /^(#{Regexp.escape(path)})#{query}$/
-
- templates = []
- @hash.each do |_path, array|
- source, updated_at = array
- next unless _path =~ query
- handler, format = extract_handler_and_format(_path, formats)
- templates << Template.new(source, _path, handler,
- :virtual_path => path.virtual, :format => format, :updated_at => updated_at)
- end
-
- templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
- end
- end
-
- class NullResolver < PathResolver
- def query(path, exts, formats)
- handler, format = extract_handler_and_format(path, formats)
- [ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format)]
- end
- end
-
-end
-
diff --git a/actionpack/lib/action_view/vendor/html-scanner.rb b/actionpack/lib/action_view/vendor/html-scanner.rb
deleted file mode 100644
index 879b31e60e..0000000000
--- a/actionpack/lib/action_view/vendor/html-scanner.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-$LOAD_PATH << "#{File.dirname(__FILE__)}/html-scanner"
-
-module HTML
- extend ActiveSupport::Autoload
-
- eager_autoload do
- autoload :CDATA, 'html/node'
- autoload :Document, 'html/document'
- autoload :FullSanitizer, 'html/sanitizer'
- autoload :LinkSanitizer, 'html/sanitizer'
- autoload :Node, 'html/node'
- autoload :Sanitizer, 'html/sanitizer'
- autoload :Selector, 'html/selector'
- autoload :Tag, 'html/node'
- autoload :Text, 'html/node'
- autoload :Tokenizer, 'html/tokenizer'
- autoload :Version, 'html/version'
- autoload :WhiteListSanitizer, 'html/sanitizer'
- end
-end
diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/document.rb b/actionpack/lib/action_view/vendor/html-scanner/html/document.rb
deleted file mode 100644
index 386820300a..0000000000
--- a/actionpack/lib/action_view/vendor/html-scanner/html/document.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'html/tokenizer'
-require 'html/node'
-require 'html/selector'
-require 'html/sanitizer'
-
-module HTML #:nodoc:
- # A top-level HTML document. You give it a body of text, and it will parse that
- # text into a tree of nodes.
- class Document #:nodoc:
-
- # The root of the parsed document.
- attr_reader :root
-
- # Create a new Document from the given text.
- def initialize(text, strict=false, xml=false)
- tokenizer = Tokenizer.new(text)
- @root = Node.new(nil)
- node_stack = [ @root ]
- while token = tokenizer.next
- node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token, strict)
-
- node_stack.last.children << node unless node.tag? && node.closing == :close
- if node.tag?
- if node_stack.length > 1 && node.closing == :close
- if node_stack.last.name == node.name
- if node_stack.last.children.empty?
- node_stack.last.children << Text.new(node_stack.last, node.line, node.position, "")
- end
- node_stack.pop
- else
- open_start = node_stack.last.position - 20
- open_start = 0 if open_start < 0
- close_start = node.position - 20
- close_start = 0 if close_start < 0
- msg = <<EOF.strip
-ignoring attempt to close #{node_stack.last.name} with #{node.name}
- opened at byte #{node_stack.last.position}, line #{node_stack.last.line}
- closed at byte #{node.position}, line #{node.line}
- attributes at open: #{node_stack.last.attributes.inspect}
- text around open: #{text[open_start,40].inspect}
- text around close: #{text[close_start,40].inspect}
-EOF
- strict ? raise(msg) : warn(msg)
- end
- elsif !node.childless?(xml) && node.closing != :close
- node_stack.push node
- end
- end
- end
- end
-
- # Search the tree for (and return) the first node that matches the given
- # conditions. The conditions are interpreted differently for different node
- # types, see HTML::Text#find and HTML::Tag#find.
- def find(conditions)
- @root.find(conditions)
- end
-
- # Search the tree for (and return) all nodes that match the given
- # conditions. The conditions are interpreted differently for different node
- # types, see HTML::Text#find and HTML::Tag#find.
- def find_all(conditions)
- @root.find_all(conditions)
- end
-
- end
-
-end
diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/node.rb b/actionpack/lib/action_view/vendor/html-scanner/html/node.rb
deleted file mode 100644
index 4e1f016431..0000000000
--- a/actionpack/lib/action_view/vendor/html-scanner/html/node.rb
+++ /dev/null
@@ -1,532 +0,0 @@
-require 'strscan'
-
-module HTML #:nodoc:
-
- class Conditions < Hash #:nodoc:
- def initialize(hash)
- super()
- hash = { :content => hash } unless Hash === hash
- hash = keys_to_symbols(hash)
- hash.each do |k,v|
- case k
- when :tag, :content then
- # keys are valid, and require no further processing
- when :attributes then
- hash[k] = keys_to_strings(v)
- when :parent, :child, :ancestor, :descendant, :sibling, :before,
- :after
- hash[k] = Conditions.new(v)
- when :children
- hash[k] = v = keys_to_symbols(v)
- v.each do |key,value|
- case key
- when :count, :greater_than, :less_than
- # keys are valid, and require no further processing
- when :only
- v[key] = Conditions.new(value)
- else
- raise "illegal key #{key.inspect} => #{value.inspect}"
- end
- end
- else
- raise "illegal key #{k.inspect} => #{v.inspect}"
- end
- end
- update hash
- end
-
- private
-
- def keys_to_strings(hash)
- Hash[hash.keys.map {|k| [k.to_s, hash[k]]}]
- end
-
- def keys_to_symbols(hash)
- Hash[hash.keys.map do |k|
- raise "illegal key #{k.inspect}" unless k.respond_to?(:to_sym)
- [k.to_sym, hash[k]]
- end]
- end
- end
-
- # The base class of all nodes, textual and otherwise, in an HTML document.
- class Node #:nodoc:
- # The array of children of this node. Not all nodes have children.
- attr_reader :children
-
- # The parent node of this node. All nodes have a parent, except for the
- # root node.
- attr_reader :parent
-
- # The line number of the input where this node was begun
- attr_reader :line
-
- # The byte position in the input where this node was begun
- attr_reader :position
-
- # Create a new node as a child of the given parent.
- def initialize(parent, line=0, pos=0)
- @parent = parent
- @children = []
- @line, @position = line, pos
- end
-
- # Return a textual representation of the node.
- def to_s
- @children.join()
- end
-
- # Return false (subclasses must override this to provide specific matching
- # behavior.) +conditions+ may be of any type.
- def match(conditions)
- false
- end
-
- # Search the children of this node for the first node for which #find
- # returns non +nil+. Returns the result of the #find call that succeeded.
- def find(conditions)
- conditions = validate_conditions(conditions)
- @children.each do |child|
- node = child.find(conditions)
- return node if node
- end
- nil
- end
-
- # Search for all nodes that match the given conditions, and return them
- # as an array.
- def find_all(conditions)
- conditions = validate_conditions(conditions)
-
- matches = []
- matches << self if match(conditions)
- @children.each do |child|
- matches.concat child.find_all(conditions)
- end
- matches
- end
-
- # Returns +false+. Subclasses may override this if they define a kind of
- # tag.
- def tag?
- false
- end
-
- def validate_conditions(conditions)
- Conditions === conditions ? conditions : Conditions.new(conditions)
- end
-
- def ==(node)
- return false unless self.class == node.class && children.size == node.children.size
-
- equivalent = true
-
- children.size.times do |i|
- equivalent &&= children[i] == node.children[i]
- end
-
- equivalent
- end
-
- class <<self
- def parse(parent, line, pos, content, strict=true)
- if content !~ /^<\S/
- Text.new(parent, line, pos, content)
- else
- scanner = StringScanner.new(content)
-
- unless scanner.skip(/</)
- if strict
- raise "expected <"
- else
- return Text.new(parent, line, pos, content)
- end
- end
-
- if scanner.skip(/!\[CDATA\[/)
- unless scanner.skip_until(/\]\]>/)
- if strict
- raise "expected ]]> (got #{scanner.rest.inspect} for #{content})"
- else
- scanner.skip_until(/\Z/)
- end
- end
-
- return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
- end
-
- closing = ( scanner.scan(/\//) ? :close : nil )
- return Text.new(parent, line, pos, content) unless name = scanner.scan(/[^\s!>\/]+/)
- name.downcase!
-
- unless closing
- scanner.skip(/\s*/)
- attributes = {}
- while attr = scanner.scan(/[-\w:]+/)
- value = true
- if scanner.scan(/\s*=\s*/)
- if delim = scanner.scan(/['"]/)
- value = ""
- while text = scanner.scan(/[^#{delim}\\]+|./)
- case text
- when "\\" then
- value << text
- break if scanner.eos?
- value << scanner.getch
- when delim
- break
- else value << text
- end
- end
- else
- value = scanner.scan(/[^\s>\/]+/)
- end
- end
- attributes[attr.downcase] = value
- scanner.skip(/\s*/)
- end
-
- closing = ( scanner.scan(/\//) ? :self : nil )
- end
-
- unless scanner.scan(/\s*>/)
- if strict
- raise "expected > (got #{scanner.rest.inspect} for #{content}, #{attributes.inspect})"
- else
- # throw away all text until we find what we're looking for
- scanner.skip_until(/>/) or scanner.terminate
- end
- end
-
- Tag.new(parent, line, pos, name, attributes, closing)
- end
- end
- end
- end
-
- # A node that represents text, rather than markup.
- class Text < Node #:nodoc:
-
- attr_reader :content
-
- # Creates a new text node as a child of the given parent, with the given
- # content.
- def initialize(parent, line, pos, content)
- super(parent, line, pos)
- @content = content
- end
-
- # Returns the content of this node.
- def to_s
- @content
- end
-
- # Returns +self+ if this node meets the given conditions. Text nodes support
- # conditions of the following kinds:
- #
- # * if +conditions+ is a string, it must be a substring of the node's
- # content
- # * if +conditions+ is a regular expression, it must match the node's
- # content
- # * if +conditions+ is a hash, it must contain a <tt>:content</tt> key that
- # is either a string or a regexp, and which is interpreted as described
- # above.
- def find(conditions)
- match(conditions) && self
- end
-
- # Returns non-+nil+ if this node meets the given conditions, or +nil+
- # otherwise. See the discussion of #find for the valid conditions.
- def match(conditions)
- case conditions
- when String
- @content == conditions
- when Regexp
- @content =~ conditions
- when Hash
- conditions = validate_conditions(conditions)
-
- # Text nodes only have :content, :parent, :ancestor
- unless (conditions.keys - [:content, :parent, :ancestor]).empty?
- return false
- end
-
- match(conditions[:content])
- else
- nil
- end
- end
-
- def ==(node)
- return false unless super
- content == node.content
- end
- end
-
- # A CDATA node is simply a text node with a specialized way of displaying
- # itself.
- class CDATA < Text #:nodoc:
- def to_s
- "<![CDATA[#{super}]]>"
- end
- end
-
- # A Tag is any node that represents markup. It may be an opening tag, a
- # closing tag, or a self-closing tag. It has a name, and may have a hash of
- # attributes.
- class Tag < Node #:nodoc:
-
- # Either +nil+, <tt>:close</tt>, or <tt>:self</tt>
- attr_reader :closing
-
- # Either +nil+, or a hash of attributes for this node.
- attr_reader :attributes
-
- # The name of this tag.
- attr_reader :name
-
- # Create a new node as a child of the given parent, using the given content
- # to describe the node. It will be parsed and the node name, attributes and
- # closing status extracted.
- def initialize(parent, line, pos, name, attributes, closing)
- super(parent, line, pos)
- @name = name
- @attributes = attributes
- @closing = closing
- end
-
- # A convenience for obtaining an attribute of the node. Returns +nil+ if
- # the node has no attributes.
- def [](attr)
- @attributes ? @attributes[attr] : nil
- end
-
- # Returns non-+nil+ if this tag can contain child nodes.
- def childless?(xml = false)
- return false if xml && @closing.nil?
- !@closing.nil? ||
- @name =~ /^(img|br|hr|link|meta|area|base|basefont|
- col|frame|input|isindex|param)$/ox
- end
-
- # Returns a textual representation of the node
- def to_s
- if @closing == :close
- "</#{@name}>"
- else
- s = "<#{@name}"
- @attributes.each do |k,v|
- s << " #{k}"
- s << "=\"#{v}\"" if String === v
- end
- s << " /" if @closing == :self
- s << ">"
- @children.each { |child| s << child.to_s }
- s << "</#{@name}>" if @closing != :self && !@children.empty?
- s
- end
- end
-
- # If either the node or any of its children meet the given conditions, the
- # matching node is returned. Otherwise, +nil+ is returned. (See the
- # description of the valid conditions in the +match+ method.)
- def find(conditions)
- match(conditions) && self || super
- end
-
- # Returns +true+, indicating that this node represents an HTML tag.
- def tag?
- true
- end
-
- # Returns +true+ if the node meets any of the given conditions. The
- # +conditions+ parameter must be a hash of any of the following keys
- # (all are optional):
- #
- # * <tt>:tag</tt>: the node name must match the corresponding value
- # * <tt>:attributes</tt>: a hash. The node's values must match the
- # corresponding values in the hash.
- # * <tt>:parent</tt>: a hash. The node's parent must match the
- # corresponding hash.
- # * <tt>:child</tt>: a hash. At least one of the node's immediate children
- # must meet the criteria described by the hash.
- # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
- # meet the criteria described by the hash.
- # * <tt>:descendant</tt>: a hash. At least one of the node's descendants
- # must meet the criteria described by the hash.
- # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
- # meet the criteria described by the hash.
- # * <tt>:after</tt>: a hash. The node must be after any sibling meeting
- # the criteria described by the hash, and at least one sibling must match.
- # * <tt>:before</tt>: a hash. The node must be before any sibling meeting
- # the criteria described by the hash, and at least one sibling must match.
- # * <tt>:children</tt>: a hash, for counting children of a node. Accepts the
- # keys:
- # ** <tt>:count</tt>: either a number or a range which must equal (or
- # include) the number of children that match.
- # ** <tt>:less_than</tt>: the number of matching children must be less than
- # this number.
- # ** <tt>:greater_than</tt>: the number of matching children must be
- # greater than this number.
- # ** <tt>:only</tt>: another hash consisting of the keys to use
- # to match on the children, and only matching children will be
- # counted.
- #
- # Conditions are matched using the following algorithm:
- #
- # * if the condition is a string, it must be a substring of the value.
- # * if the condition is a regexp, it must match the value.
- # * if the condition is a number, the value must match number.to_s.
- # * if the condition is +true+, the value must not be +nil+.
- # * if the condition is +false+ or +nil+, the value must be +nil+.
- #
- # Usage:
- #
- # # test if the node is a "span" tag
- # node.match :tag => "span"
- #
- # # test if the node's parent is a "div"
- # node.match :parent => { :tag => "div" }
- #
- # # test if any of the node's ancestors are "table" tags
- # node.match :ancestor => { :tag => "table" }
- #
- # # test if any of the node's immediate children are "em" tags
- # node.match :child => { :tag => "em" }
- #
- # # test if any of the node's descendants are "strong" tags
- # node.match :descendant => { :tag => "strong" }
- #
- # # test if the node has between 2 and 4 span tags as immediate children
- # node.match :children => { :count => 2..4, :only => { :tag => "span" } }
- #
- # # get funky: test to see if the node is a "div", has a "ul" ancestor
- # # and an "li" parent (with "class" = "enum"), and whether or not it has
- # # a "span" descendant that contains # text matching /hello world/:
- # node.match :tag => "div",
- # :ancestor => { :tag => "ul" },
- # :parent => { :tag => "li",
- # :attributes => { :class => "enum" } },
- # :descendant => { :tag => "span",
- # :child => /hello world/ }
- def match(conditions)
- conditions = validate_conditions(conditions)
- # check content of child nodes
- if conditions[:content]
- if children.empty?
- return false unless match_condition("", conditions[:content])
- else
- return false unless children.find { |child| child.match(conditions[:content]) }
- end
- end
-
- # test the name
- return false unless match_condition(@name, conditions[:tag]) if conditions[:tag]
-
- # test attributes
- (conditions[:attributes] || {}).each do |key, value|
- return false unless match_condition(self[key], value)
- end
-
- # test parent
- return false unless parent.match(conditions[:parent]) if conditions[:parent]
-
- # test children
- return false unless children.find { |child| child.match(conditions[:child]) } if conditions[:child]
-
- # test ancestors
- if conditions[:ancestor]
- return false unless catch :found do
- p = self
- throw :found, true if p.match(conditions[:ancestor]) while p = p.parent
- end
- end
-
- # test descendants
- if conditions[:descendant]
- return false unless children.find do |child|
- # test the child
- child.match(conditions[:descendant]) ||
- # test the child's descendants
- child.match(:descendant => conditions[:descendant])
- end
- end
-
- # count children
- if opts = conditions[:children]
- matches = children.select do |c|
- (c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
- end
-
- matches = matches.select { |c| c.match(opts[:only]) } if opts[:only]
- opts.each do |key, value|
- next if key == :only
- case key
- when :count
- if Integer === value
- return false if matches.length != value
- else
- return false unless value.include?(matches.length)
- end
- when :less_than
- return false unless matches.length < value
- when :greater_than
- return false unless matches.length > value
- else raise "unknown count condition #{key}"
- end
- end
- end
-
- # test siblings
- if conditions[:sibling] || conditions[:before] || conditions[:after]
- siblings = parent ? parent.children : []
- self_index = siblings.index(self)
-
- if conditions[:sibling]
- return false unless siblings.detect do |s|
- s != self && s.match(conditions[:sibling])
- end
- end
-
- if conditions[:before]
- return false unless siblings[self_index+1..-1].detect do |s|
- s != self && s.match(conditions[:before])
- end
- end
-
- if conditions[:after]
- return false unless siblings[0,self_index].detect do |s|
- s != self && s.match(conditions[:after])
- end
- end
- end
-
- true
- end
-
- def ==(node)
- return false unless super
- return false unless closing == node.closing && self.name == node.name
- attributes == node.attributes
- end
-
- private
- # Match the given value to the given condition.
- def match_condition(value, condition)
- case condition
- when String
- value && value == condition
- when Regexp
- value && value.match(condition)
- when Numeric
- value == condition.to_s
- when true
- !value.nil?
- when false, nil
- value.nil?
- else
- false
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb
deleted file mode 100644
index 6b4ececda2..0000000000
--- a/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb
+++ /dev/null
@@ -1,188 +0,0 @@
-require 'set'
-require 'cgi'
-require 'active_support/core_ext/class/attribute_accessors'
-
-module HTML
- class Sanitizer
- def sanitize(text, options = {})
- validate_options(options)
- return text unless sanitizeable?(text)
- tokenize(text, options).join
- end
-
- def sanitizeable?(text)
- !(text.nil? || text.empty? || !text.index("<"))
- end
-
- protected
- def tokenize(text, options)
- tokenizer = HTML::Tokenizer.new(text)
- result = []
- while token = tokenizer.next
- node = Node.parse(nil, 0, 0, token, false)
- process_node node, result, options
- end
- result
- end
-
- 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
- def sanitize(text, options = {})
- result = super
- # strip any comments, and if they have a newline at the end (ie. line with
- # only a comment) strip that too
- result = result.gsub(/<!--(.*?)-->[\n]?/m, "") if (result && result =~ /<!--(.*?)-->[\n]?/m)
- # Recurse - handle all dirty nested tags
- result == text ? result : sanitize(result, options)
- end
-
- def process_node(node, result, options)
- result << node.to_s if node.class == HTML::Text
- end
- end
-
- class LinkSanitizer < FullSanitizer
- cattr_accessor :included_tags, :instance_writer => false
- self.included_tags = Set.new(%w(a href))
-
- def sanitizeable?(text)
- !(text.nil? || text.empty? || !((text.index("<a") || text.index("<href")) && text.index(">")))
- end
-
- protected
- def process_node(node, result, options)
- result << node.to_s unless node.is_a?(HTML::Tag) && included_tags.include?(node.name)
- end
- end
-
- class WhiteListSanitizer < Sanitizer
- [:protocol_separator, :uri_attributes, :allowed_attributes, :allowed_tags, :allowed_protocols, :bad_tags,
- :allowed_css_properties, :allowed_css_keywords, :shorthand_css_properties].each do |attr|
- class_attribute attr, :instance_writer => false
- end
-
- # A regular expression of the valid characters used to separate protocols like
- # the ':' in 'http://foo.com'
- self.protocol_separator = /:|(&#0*58)|(&#x70)|(%|&#37;)3A/
-
- # Specifies a Set of HTML attributes that can have URIs.
- self.uri_attributes = Set.new(%w(href src cite action longdesc xlink:href lowsrc))
-
- # Specifies a Set of 'bad' tags that the #sanitize helper will remove completely, as opposed
- # to just escaping harmless tags like &lt;font&gt;
- self.bad_tags = Set.new(%w(script))
-
- # Specifies the default Set of tags that the #sanitize helper will allow unscathed.
- self.allowed_tags = Set.new(%w(strong em b i p code pre tt samp kbd var sub
- sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dl dt dd abbr
- acronym a img blockquote del ins))
-
- # Specifies the default Set of html attributes that the #sanitize helper will leave
- # in the allowed tag.
- self.allowed_attributes = Set.new(%w(href src width height alt cite datetime title class name xml:lang abbr))
-
- # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept.
- 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 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
- overflow pause pause-after pause-before pitch pitch-range richness speak speak-header speak-numeral speak-punctuation
- speech-rate stress text-align text-decoration text-indent unicode-bidi vertical-align voice-family volume white-space
- width))
-
- # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept.
- self.allowed_css_keywords = Set.new(%w(auto aqua black block blue bold both bottom brown center
- collapse dashed dotted fuchsia gray green !important italic left lime maroon medium none navy normal
- nowrap olive pointer purple red right solid silver teal top transparent underline white yellow))
-
- # Specifies the default Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers.
- self.shorthand_css_properties = Set.new(%w(background border margin padding))
-
- # Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute
- def sanitize_css(style)
- # disallow urls
- style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
-
- # gauntlet
- if style !~ /^([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*$/ ||
- style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*$/
- return ''
- end
-
- clean = []
- style.scan(/([-\w]+)\s*:\s*([^:;]*)/) do |prop,val|
- if allowed_css_properties.include?(prop.downcase)
- clean << prop + ': ' + val + ';'
- elsif shorthand_css_properties.include?(prop.split('-')[0].downcase)
- unless val.split().any? do |keyword|
- !allowed_css_keywords.include?(keyword) &&
- keyword !~ /^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$/
- end
- clean << prop + ': ' + val + ';'
- end
- end
- end
- clean.join(' ')
- end
-
- protected
- def tokenize(text, options)
- options[:parent] = []
- options[:attributes] ||= allowed_attributes
- options[:tags] ||= allowed_tags
- super
- end
-
- def process_node(node, result, options)
- result << case node
- when HTML::Tag
- if node.closing == :close
- options[:parent].shift
- else
- options[:parent].unshift node.name
- end
-
- process_attributes_for node, options
-
- options[:tags].include?(node.name) ? node : nil
- else
- bad_tags.include?(options[:parent].first) ? nil : node.to_s.gsub(/</, "&lt;")
- end
- end
-
- def process_attributes_for(node, options)
- return unless node.attributes
- node.attributes.keys.each do |attr_name|
- value = node.attributes[attr_name].to_s
-
- if !options[:attributes].include?(attr_name) || contains_bad_protocols?(attr_name, value)
- node.attributes.delete(attr_name)
- else
- node.attributes[attr_name] = attr_name == 'style' ? sanitize_css(value) : CGI::escapeHTML(CGI::unescapeHTML(value))
- end
- end
- end
-
- def contains_bad_protocols?(attr_name, value)
- uri_attributes.include?(attr_name) &&
- (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
- end
- end
-end
diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb b/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb
deleted file mode 100644
index 60b6783b19..0000000000
--- a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb
+++ /dev/null
@@ -1,830 +0,0 @@
-#--
-# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
-# Under MIT and/or CC By license.
-#++
-
-module HTML
-
- # Selects HTML elements using CSS 2 selectors.
- #
- # The +Selector+ class uses CSS selector expressions to match and select
- # HTML elements.
- #
- # For example:
- # selector = HTML::Selector.new "form.login[action=/login]"
- # creates a new selector that matches any +form+ element with the class
- # +login+ and an attribute +action+ with the value <tt>/login</tt>.
- #
- # === Matching Elements
- #
- # Use the #match method to determine if an element matches the selector.
- #
- # For simple selectors, the method returns an array with that element,
- # or +nil+ if the element does not match. For complex selectors (see below)
- # the method returns an array with all matched elements, of +nil+ if no
- # match found.
- #
- # For example:
- # if selector.match(element)
- # puts "Element is a login form"
- # end
- #
- # === Selecting Elements
- #
- # Use the #select method to select all matching elements starting with
- # one element and going through all children in depth-first order.
- #
- # This method returns an array of all matching elements, an empty array
- # if no match is found
- #
- # For example:
- # selector = HTML::Selector.new "input[type=text]"
- # matches = selector.select(element)
- # matches.each do |match|
- # puts "Found text field with name #{match.attributes['name']}"
- # end
- #
- # === Expressions
- #
- # Selectors can match elements using any of the following criteria:
- # * <tt>name</tt> -- Match an element based on its name (tag name).
- # For example, <tt>p</tt> to match a paragraph. You can use <tt>*</tt>
- # to match any element.
- # * <tt>#</tt><tt>id</tt> -- Match an element based on its identifier (the
- # <tt>id</tt> attribute). For example, <tt>#</tt><tt>page</tt>.
- # * <tt>.class</tt> -- Match an element based on its class name, all
- # class names if more than one specified.
- # * <tt>[attr]</tt> -- Match an element that has the specified attribute.
- # * <tt>[attr=value]</tt> -- Match an element that has the specified
- # attribute and value. (More operators are supported see below)
- # * <tt>:pseudo-class</tt> -- Match an element based on a pseudo class,
- # such as <tt>:nth-child</tt> and <tt>:empty</tt>.
- # * <tt>:not(expr)</tt> -- Match an element that does not match the
- # negation expression.
- #
- # When using a combination of the above, the element name comes first
- # followed by identifier, class names, attributes, pseudo classes and
- # negation in any order. Do not separate these parts with spaces!
- # Space separation is used for descendant selectors.
- #
- # For example:
- # selector = HTML::Selector.new "form.login[action=/login]"
- # The matched element must be of type +form+ and have the class +login+.
- # It may have other classes, but the class +login+ is required to match.
- # It must also have an attribute called +action+ with the value
- # <tt>/login</tt>.
- #
- # This selector will match the following element:
- # <form class="login form" method="post" action="/login">
- # but will not match the element:
- # <form method="post" action="/logout">
- #
- # === Attribute Values
- #
- # Several operators are supported for matching attributes:
- # * <tt>name</tt> -- The element must have an attribute with that name.
- # * <tt>name=value</tt> -- The element must have an attribute with that
- # name and value.
- # * <tt>name^=value</tt> -- The attribute value must start with the
- # specified value.
- # * <tt>name$=value</tt> -- The attribute value must end with the
- # specified value.
- # * <tt>name*=value</tt> -- The attribute value must contain the
- # specified value.
- # * <tt>name~=word</tt> -- The attribute value must contain the specified
- # word (space separated).
- # * <tt>name|=word</tt> -- The attribute value must start with specified
- # word.
- #
- # For example, the following two selectors match the same element:
- # #my_id
- # [id=my_id]
- # and so do the following two selectors:
- # .my_class
- # [class~=my_class]
- #
- # === Alternatives, siblings, children
- #
- # Complex selectors use a combination of expressions to match elements:
- # * <tt>expr1 expr2</tt> -- Match any element against the second expression
- # if it has some parent element that matches the first expression.
- # * <tt>expr1 > expr2</tt> -- Match any element against the second expression
- # if it is the child of an element that matches the first expression.
- # * <tt>expr1 + expr2</tt> -- Match any element against the second expression
- # if it immediately follows an element that matches the first expression.
- # * <tt>expr1 ~ expr2</tt> -- Match any element against the second expression
- # that comes after an element that matches the first expression.
- # * <tt>expr1, expr2</tt> -- Match any element against the first expression,
- # or against the second expression.
- #
- # Since children and sibling selectors may match more than one element given
- # the first element, the #match method may return more than one match.
- #
- # === Pseudo classes
- #
- # Pseudo classes were introduced in CSS 3. They are most often used to select
- # elements in a given position:
- # * <tt>:root</tt> -- Match the element only if it is the root element
- # (no parent element).
- # * <tt>:empty</tt> -- Match the element only if it has no child elements,
- # and no text content.
- # * <tt>:content(string)</tt> -- Match the element only if it has <tt>string</tt>
- # as its text content (ignoring leading and trailing whitespace).
- # * <tt>:only-child</tt> -- Match the element if it is the only child (element)
- # of its parent element.
- # * <tt>:only-of-type</tt> -- Match the element if it is the only child (element)
- # of its parent element and its type.
- # * <tt>:first-child</tt> -- Match the element if it is the first child (element)
- # of its parent element.
- # * <tt>:first-of-type</tt> -- Match the element if it is the first child (element)
- # of its parent element of its type.
- # * <tt>:last-child</tt> -- Match the element if it is the last child (element)
- # of its parent element.
- # * <tt>:last-of-type</tt> -- Match the element if it is the last child (element)
- # of its parent element of its type.
- # * <tt>:nth-child(b)</tt> -- Match the element if it is the b-th child (element)
- # of its parent element. The value <tt>b</tt> specifies its index, starting with 1.
- # * <tt>:nth-child(an+b)</tt> -- Match the element if it is the b-th child (element)
- # in each group of <tt>a</tt> child elements of its parent element.
- # * <tt>:nth-child(-an+b)</tt> -- Match the element if it is the first child (element)
- # in each group of <tt>a</tt> child elements, up to the first <tt>b</tt> child
- # elements of its parent element.
- # * <tt>:nth-child(odd)</tt> -- Match element in the odd position (i.e. first, third).
- # Same as <tt>:nth-child(2n+1)</tt>.
- # * <tt>:nth-child(even)</tt> -- Match element in the even position (i.e. second,
- # fourth). Same as <tt>:nth-child(2n+2)</tt>.
- # * <tt>:nth-of-type(..)</tt> -- As above, but only counts elements of its type.
- # * <tt>:nth-last-child(..)</tt> -- As above, but counts from the last child.
- # * <tt>:nth-last-of-type(..)</tt> -- As above, but counts from the last child and
- # only elements of its type.
- # * <tt>:not(selector)</tt> -- Match the element only if the element does not
- # match the simple selector.
- #
- # As you can see, <tt>:nth-child</tt> pseudo class and its variant can get quite
- # tricky and the CSS specification doesn't do a much better job explaining it.
- # But after reading the examples and trying a few combinations, it's easy to
- # figure out.
- #
- # For example:
- # table tr:nth-child(odd)
- # Selects every second row in the table starting with the first one.
- #
- # div p:nth-child(4)
- # Selects the fourth paragraph in the +div+, but not if the +div+ contains
- # other elements, since those are also counted.
- #
- # div p:nth-of-type(4)
- # Selects the fourth paragraph in the +div+, counting only paragraphs, and
- # ignoring all other elements.
- #
- # div p:nth-of-type(-n+4)
- # Selects the first four paragraphs, ignoring all others.
- #
- # And you can always select an element that matches one set of rules but
- # not another using <tt>:not</tt>. For example:
- # p:not(.post)
- # Matches all paragraphs that do not have the class <tt>.post</tt>.
- #
- # === Substitution Values
- #
- # You can use substitution with identifiers, class names and element values.
- # A substitution takes the form of a question mark (<tt>?</tt>) and uses the
- # next value in the argument list following the CSS expression.
- #
- # The substitution value may be a string or a regular expression. All other
- # values are converted to strings.
- #
- # For example:
- # selector = HTML::Selector.new "#?", /^\d+$/
- # matches any element whose identifier consists of one or more digits.
- #
- # See http://www.w3.org/TR/css3-selectors/
- class Selector
-
-
- # An invalid selector.
- class InvalidSelectorError < StandardError #:nodoc:
- end
-
-
- class << self
-
- # :call-seq:
- # Selector.for_class(cls) => selector
- #
- # Creates a new selector for the given class name.
- def for_class(cls)
- self.new([".?", cls])
- end
-
-
- # :call-seq:
- # Selector.for_id(id) => selector
- #
- # Creates a new selector for the given id.
- def for_id(id)
- self.new(["#?", id])
- end
-
- end
-
-
- # :call-seq:
- # Selector.new(string, [values ...]) => selector
- #
- # Creates a new selector from a CSS 2 selector expression.
- #
- # The first argument is the selector expression. All other arguments
- # are used for value substitution.
- #
- # Throws InvalidSelectorError is the selector expression is invalid.
- def initialize(selector, *values)
- raise ArgumentError, "CSS expression cannot be empty" if selector.empty?
- @source = ""
- values = values[0] if values.size == 1 && values[0].is_a?(Array)
-
- # We need a copy to determine if we failed to parse, and also
- # preserve the original pass by-ref statement.
- statement = selector.strip.dup
-
- # Create a simple selector, along with negation.
- simple_selector(statement, values).each { |name, value| instance_variable_set("@#{name}", value) }
-
- @alternates = []
- @depends = nil
-
- # Alternative selector.
- if statement.sub!(/^\s*,\s*/, "")
- second = Selector.new(statement, values)
- @alternates << second
- # If there are alternate selectors, we group them in the top selector.
- if alternates = second.instance_variable_get(:@alternates)
- second.instance_variable_set(:@alternates, [])
- @alternates.concat alternates
- end
- @source << " , " << second.to_s
- # Sibling selector: create a dependency into second selector that will
- # match element immediately following this one.
- elsif statement.sub!(/^\s*\+\s*/, "")
- second = next_selector(statement, values)
- @depends = lambda do |element, first|
- if element = next_element(element)
- second.match(element, first)
- end
- end
- @source << " + " << second.to_s
- # Adjacent selector: create a dependency into second selector that will
- # match all elements following this one.
- elsif statement.sub!(/^\s*~\s*/, "")
- second = next_selector(statement, values)
- @depends = lambda do |element, first|
- matches = []
- while element = next_element(element)
- if subset = second.match(element, first)
- if first && !subset.empty?
- matches << subset.first
- break
- else
- matches.concat subset
- end
- end
- end
- matches.empty? ? nil : matches
- end
- @source << " ~ " << second.to_s
- # Child selector: create a dependency into second selector that will
- # match a child element of this one.
- elsif statement.sub!(/^\s*>\s*/, "")
- second = next_selector(statement, values)
- @depends = lambda do |element, first|
- matches = []
- element.children.each do |child|
- if child.tag? && subset = second.match(child, first)
- if first && !subset.empty?
- matches << subset.first
- break
- else
- matches.concat subset
- end
- end
- end
- matches.empty? ? nil : matches
- end
- @source << " > " << second.to_s
- # Descendant selector: create a dependency into second selector that
- # will match all descendant elements of this one. Note,
- elsif statement =~ /^\s+\S+/ && statement != selector
- second = next_selector(statement, values)
- @depends = lambda do |element, first|
- matches = []
- stack = element.children.reverse
- while node = stack.pop
- next unless node.tag?
- if subset = second.match(node, first)
- if first && !subset.empty?
- matches << subset.first
- break
- else
- matches.concat subset
- end
- elsif children = node.children
- stack.concat children.reverse
- end
- end
- matches.empty? ? nil : matches
- end
- @source << " " << second.to_s
- else
- # The last selector is where we check that we parsed
- # all the parts.
- unless statement.empty? || statement.strip.empty?
- raise ArgumentError, "Invalid selector: #{statement}"
- end
- end
- end
-
-
- # :call-seq:
- # match(element, first?) => array or nil
- #
- # Matches an element against the selector.
- #
- # For a simple selector this method returns an array with the
- # element if the element matches, nil otherwise.
- #
- # For a complex selector (sibling and descendant) this method
- # returns an array with all matching elements, nil if no match is
- # found.
- #
- # Use +first_only=true+ if you are only interested in the first element.
- #
- # For example:
- # if selector.match(element)
- # puts "Element is a login form"
- # end
- def match(element, first_only = false)
- # Match element if no element name or element name same as element name
- if matched = (!@tag_name || @tag_name == element.name)
- # No match if one of the attribute matches failed
- for attr in @attributes
- if element.attributes[attr[0]] !~ attr[1]
- matched = false
- break
- end
- end
- end
-
- # Pseudo class matches (nth-child, empty, etc).
- if matched
- for pseudo in @pseudo
- unless pseudo.call(element)
- matched = false
- break
- end
- end
- end
-
- # Negation. Same rules as above, but we fail if a match is made.
- if matched && @negation
- for negation in @negation
- if negation[:tag_name] == element.name
- matched = false
- else
- for attr in negation[:attributes]
- if element.attributes[attr[0]] =~ attr[1]
- matched = false
- break
- end
- end
- end
- if matched
- for pseudo in negation[:pseudo]
- if pseudo.call(element)
- matched = false
- break
- end
- end
- end
- break unless matched
- end
- end
-
- # If element matched but depends on another element (child,
- # sibling, etc), apply the dependent matches instead.
- if matched && @depends
- matches = @depends.call(element, first_only)
- else
- matches = matched ? [element] : nil
- end
-
- # If this selector is part of the group, try all the alternative
- # selectors (unless first_only).
- if !first_only || !matches
- @alternates.each do |alternate|
- break if matches && first_only
- if subset = alternate.match(element, first_only)
- if matches
- matches.concat subset
- else
- matches = subset
- end
- end
- end
- end
-
- matches
- end
-
-
- # :call-seq:
- # select(root) => array
- #
- # Selects and returns an array with all matching elements, beginning
- # with one node and traversing through all children depth-first.
- # Returns an empty array if no match is found.
- #
- # The root node may be any element in the document, or the document
- # itself.
- #
- # For example:
- # selector = HTML::Selector.new "input[type=text]"
- # matches = selector.select(element)
- # matches.each do |match|
- # puts "Found text field with name #{match.attributes['name']}"
- # end
- def select(root)
- matches = []
- stack = [root]
- while node = stack.pop
- if node.tag? && subset = match(node, false)
- subset.each do |match|
- matches << match unless matches.any? { |item| item.equal?(match) }
- end
- elsif children = node.children
- stack.concat children.reverse
- end
- end
- matches
- end
-
-
- # Similar to #select but returns the first matching element. Returns +nil+
- # if no element matches the selector.
- def select_first(root)
- stack = [root]
- while node = stack.pop
- if node.tag? && subset = match(node, true)
- return subset.first if !subset.empty?
- elsif children = node.children
- stack.concat children.reverse
- end
- end
- nil
- end
-
-
- def to_s #:nodoc:
- @source
- end
-
-
- # Return the next element after this one. Skips sibling text nodes.
- #
- # With the +name+ argument, returns the next element with that name,
- # skipping other sibling elements.
- def next_element(element, name = nil)
- if siblings = element.parent.children
- found = false
- siblings.each do |node|
- if node.equal?(element)
- found = true
- elsif found && node.tag?
- return node if (name.nil? || node.name == name)
- end
- end
- end
- nil
- end
-
-
- protected
-
-
- # Creates a simple selector given the statement and array of
- # substitution values.
- #
- # Returns a hash with the values +tag_name+, +attributes+,
- # +pseudo+ (classes) and +negation+.
- #
- # Called the first time with +can_negate+ true to allow
- # negation. Called a second time with false since negation
- # cannot be negated.
- def simple_selector(statement, values, can_negate = true)
- tag_name = nil
- attributes = []
- pseudo = []
- negation = []
-
- # Element name. (Note that in negation, this can come at
- # any order, but for simplicity we allow if only first).
- statement.sub!(/^(\*|[[:alpha:]][\w\-]*)/) do |match|
- match.strip!
- tag_name = match.downcase unless match == "*"
- @source << match
- "" # Remove
- end
-
- # Get identifier, class, attribute name, pseudo or negation.
- while true
- # Element identifier.
- next if statement.sub!(/^#(\?|[\w\-]+)/) do |match|
- id = $1
- if id == "?"
- id = values.shift
- end
- @source << "##{id}"
- id = Regexp.new("^#{Regexp.escape(id.to_s)}$") unless id.is_a?(Regexp)
- attributes << ["id", id]
- "" # Remove
- end
-
- # Class name.
- next if statement.sub!(/^\.([\w\-]+)/) do |match|
- class_name = $1
- @source << ".#{class_name}"
- class_name = Regexp.new("(^|\s)#{Regexp.escape(class_name)}($|\s)") unless class_name.is_a?(Regexp)
- attributes << ["class", class_name]
- "" # Remove
- end
-
- # Attribute value.
- next if statement.sub!(/^\[\s*([[:alpha:]][\w\-:]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do |match|
- name, equality, value = $1, $2, $3
- if value == "?"
- value = values.shift
- else
- # Handle single and double quotes.
- value.strip!
- if (value[0] == ?" || value[0] == ?') && value[0] == value[-1]
- value = value[1..-2]
- end
- end
- @source << "[#{name}#{equality}'#{value}']"
- attributes << [name.downcase.strip, attribute_match(equality, value)]
- "" # Remove
- end
-
- # Root element only.
- next if statement.sub!(/^:root/) do |match|
- pseudo << lambda do |element|
- element.parent.nil? || !element.parent.tag?
- end
- @source << ":root"
- "" # Remove
- end
-
- # Nth-child including last and of-type.
- next if statement.sub!(/^:nth-(last-)?(child|of-type)\((odd|even|(\d+|\?)|(-?\d*|\?)?n([+\-]\d+|\?)?)\)/) do |match|
- reverse = $1 == "last-"
- of_type = $2 == "of-type"
- @source << ":nth-#{$1}#{$2}("
- case $3
- when "odd"
- pseudo << nth_child(2, 1, of_type, reverse)
- @source << "odd)"
- when "even"
- pseudo << nth_child(2, 2, of_type, reverse)
- @source << "even)"
- when /^(\d+|\?)$/ # b only
- b = ($1 == "?" ? values.shift : $1).to_i
- pseudo << nth_child(0, b, of_type, reverse)
- @source << "#{b})"
- when /^(-?\d*|\?)?n([+\-]\d+|\?)?$/
- a = ($1 == "?" ? values.shift :
- $1 == "" ? 1 : $1 == "-" ? -1 : $1).to_i
- b = ($2 == "?" ? values.shift : $2).to_i
- pseudo << nth_child(a, b, of_type, reverse)
- @source << (b >= 0 ? "#{a}n+#{b})" : "#{a}n#{b})")
- else
- raise ArgumentError, "Invalid nth-child #{match}"
- end
- "" # Remove
- end
- # First/last child (of type).
- next if statement.sub!(/^:(first|last)-(child|of-type)/) do |match|
- reverse = $1 == "last"
- of_type = $2 == "of-type"
- pseudo << nth_child(0, 1, of_type, reverse)
- @source << ":#{$1}-#{$2}"
- "" # Remove
- end
- # Only child (of type).
- next if statement.sub!(/^:only-(child|of-type)/) do |match|
- of_type = $1 == "of-type"
- pseudo << only_child(of_type)
- @source << ":only-#{$1}"
- "" # Remove
- end
-
- # Empty: no child elements or meaningful content (whitespaces
- # are ignored).
- next if statement.sub!(/^:empty/) do |match|
- pseudo << lambda do |element|
- empty = true
- for child in element.children
- if child.tag? || !child.content.strip.empty?
- empty = false
- break
- end
- end
- empty
- end
- @source << ":empty"
- "" # Remove
- end
- # Content: match the text content of the element, stripping
- # leading and trailing spaces.
- next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do |match|
- content = $1
- if content == "?"
- content = values.shift
- elsif (content[0] == ?" || content[0] == ?') && content[0] == content[-1]
- content = content[1..-2]
- end
- @source << ":content('#{content}')"
- content = Regexp.new("^#{Regexp.escape(content.to_s)}$") unless content.is_a?(Regexp)
- pseudo << lambda do |element|
- text = ""
- for child in element.children
- unless child.tag?
- text << child.content
- end
- end
- text.strip =~ content
- end
- "" # Remove
- end
-
- # Negation. Create another simple selector to handle it.
- if statement.sub!(/^:not\(\s*/, "")
- raise ArgumentError, "Double negatives are not missing feature" unless can_negate
- @source << ":not("
- negation << simple_selector(statement, values, false)
- raise ArgumentError, "Negation not closed" unless statement.sub!(/^\s*\)/, "")
- @source << ")"
- next
- end
-
- # No match: moving on.
- break
- end
-
- # Return hash. The keys are mapped to instance variables.
- {:tag_name=>tag_name, :attributes=>attributes, :pseudo=>pseudo, :negation=>negation}
- end
-
-
- # Create a regular expression to match an attribute value based
- # on the equality operator (=, ^=, |=, etc).
- def attribute_match(equality, value)
- regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
- case equality
- when "=" then
- # Match the attribute value in full
- Regexp.new("^#{regexp}$")
- when "~=" then
- # Match a space-separated word within the attribute value
- Regexp.new("(^|\s)#{regexp}($|\s)")
- when "^="
- # Match the beginning of the attribute value
- Regexp.new("^#{regexp}")
- when "$="
- # Match the end of the attribute value
- Regexp.new("#{regexp}$")
- when "*="
- # Match substring of the attribute value
- regexp.is_a?(Regexp) ? regexp : Regexp.new(regexp)
- when "|=" then
- # Match the first space-separated item of the attribute value
- Regexp.new("^#{regexp}($|\s)")
- else
- raise InvalidSelectorError, "Invalid operation/value" unless value.empty?
- # Match all attributes values (existence check)
- //
- end
- end
-
-
- # Returns a lambda that can match an element against the nth-child
- # pseudo class, given the following arguments:
- # * +a+ -- Value of a part.
- # * +b+ -- Value of b part.
- # * +of_type+ -- True to test only elements of this type (of-type).
- # * +reverse+ -- True to count in reverse order (last-).
- def nth_child(a, b, of_type, reverse)
- # a = 0 means select at index b, if b = 0 nothing selected
- return lambda { |element| false } if a == 0 && b == 0
- # a < 0 and b < 0 will never match against an index
- return lambda { |element| false } if a < 0 && b < 0
- b = a + b + 1 if b < 0 # b < 0 just picks last element from each group
- b -= 1 unless b == 0 # b == 0 is same as b == 1, otherwise zero based
- lambda do |element|
- # Element must be inside parent element.
- return false unless element.parent && element.parent.tag?
- index = 0
- # Get siblings, reverse if counting from last.
- siblings = element.parent.children
- siblings = siblings.reverse if reverse
- # Match element name if of-type, otherwise ignore name.
- name = of_type ? element.name : nil
- found = false
- for child in siblings
- # Skip text nodes/comments.
- if child.tag? && (name == nil || child.name == name)
- if a == 0
- # Shortcut when a == 0 no need to go past count
- if index == b
- found = child.equal?(element)
- break
- end
- elsif a < 0
- # Only look for first b elements
- break if index > b
- if child.equal?(element)
- found = (index % a) == 0
- break
- end
- else
- # Otherwise, break if child found and count == an+b
- if child.equal?(element)
- found = (index % a) == b
- break
- end
- end
- index += 1
- end
- end
- found
- end
- end
-
-
- # Creates a only child lambda. Pass +of-type+ to only look at
- # elements of its type.
- def only_child(of_type)
- lambda do |element|
- # Element must be inside parent element.
- return false unless element.parent && element.parent.tag?
- name = of_type ? element.name : nil
- other = false
- for child in element.parent.children
- # Skip text nodes/comments.
- if child.tag? && (name == nil || child.name == name)
- unless child.equal?(element)
- other = true
- break
- end
- end
- end
- !other
- end
- end
-
-
- # Called to create a dependent selector (sibling, descendant, etc).
- # Passes the remainder of the statement that will be reduced to zero
- # eventually, and array of substitution values.
- #
- # This method is called from four places, so it helps to put it here
- # for reuse. The only logic deals with the need to detect comma
- # separators (alternate) and apply them to the selector group of the
- # top selector.
- def next_selector(statement, values)
- second = Selector.new(statement, values)
- # If there are alternate selectors, we group them in the top selector.
- if alternates = second.instance_variable_get(:@alternates)
- second.instance_variable_set(:@alternates, [])
- @alternates.concat alternates
- end
- second
- end
-
- end
-
-
- # See HTML::Selector.new
- def self.selector(statement, *values)
- Selector.new(statement, *values)
- end
-
-
- class Tag
-
- def select(selector, *values)
- selector = HTML::Selector.new(selector, values)
- selector.select(self)
- end
-
- end
-
-end
diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/tokenizer.rb b/actionpack/lib/action_view/vendor/html-scanner/html/tokenizer.rb
deleted file mode 100644
index 8ac8d34430..0000000000
--- a/actionpack/lib/action_view/vendor/html-scanner/html/tokenizer.rb
+++ /dev/null
@@ -1,107 +0,0 @@
-require 'strscan'
-
-module HTML #:nodoc:
-
- # A simple HTML tokenizer. It simply breaks a stream of text into tokens, where each
- # token is a string. Each string represents either "text", or an HTML element.
- #
- # This currently assumes valid XHTML, which means no free < or > characters.
- #
- # Usage:
- #
- # tokenizer = HTML::Tokenizer.new(text)
- # while token = tokenizer.next
- # p token
- # end
- class Tokenizer #:nodoc:
-
- # The current (byte) position in the text
- attr_reader :position
-
- # The current line number
- attr_reader :line
-
- # Create a new Tokenizer for the given text.
- def initialize(text)
- text.encode!
- @scanner = StringScanner.new(text)
- @position = 0
- @line = 0
- @current_line = 1
- end
-
- # Return the next token in the sequence, or +nil+ if there are no more tokens in
- # the stream.
- def next
- return nil if @scanner.eos?
- @position = @scanner.pos
- @line = @current_line
- if @scanner.check(/<\S/)
- update_current_line(scan_tag)
- else
- update_current_line(scan_text)
- end
- end
-
- private
-
- # Treat the text at the current position as a tag, and scan it. Supports
- # comments, doctype tags, and regular tags, and ignores less-than and
- # greater-than characters within quoted strings.
- def scan_tag
- tag = @scanner.getch
- if @scanner.scan(/!--/) # comment
- tag << @scanner.matched
- tag << (@scanner.scan_until(/--\s*>/) || @scanner.scan_until(/\Z/))
- elsif @scanner.scan(/!\[CDATA\[/)
- tag << @scanner.matched
- tag << (@scanner.scan_until(/\]\]>/) || @scanner.scan_until(/\Z/))
- elsif @scanner.scan(/!/) # doctype
- tag << @scanner.matched
- tag << consume_quoted_regions
- else
- tag << consume_quoted_regions
- end
- tag
- end
-
- # Scan all text up to the next < character and return it.
- def scan_text
- "#{@scanner.getch}#{@scanner.scan(/[^<]*/)}"
- end
-
- # Counts the number of newlines in the text and updates the current line
- # accordingly.
- def update_current_line(text)
- text.scan(/\r?\n/) { @current_line += 1 }
- end
-
- # Skips over quoted strings, so that less-than and greater-than characters
- # within the strings are ignored.
- def consume_quoted_regions
- text = ""
- loop do
- match = @scanner.scan_until(/['"<>]/) or break
-
- delim = @scanner.matched
- if delim == "<"
- match = match.chop
- @scanner.pos -= 1
- end
-
- text << match
- break if delim == "<" || delim == ">"
-
- # consume the quoted region
- while match = @scanner.scan_until(/[\\#{delim}]/)
- text << match
- break if @scanner.matched == delim
- break if @scanner.eos?
- text << @scanner.getch # skip the escaped character
- end
- end
- text
- end
- end
-
-end
diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/version.rb b/actionpack/lib/action_view/vendor/html-scanner/html/version.rb
deleted file mode 100644
index 6d645c3e14..0000000000
--- a/actionpack/lib/action_view/vendor/html-scanner/html/version.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module HTML #:nodoc:
- module Version #:nodoc:
-
- MAJOR = 0
- MINOR = 5
- TINY = 3
-
- STRING = [ MAJOR, MINOR, TINY ].join(".")
-
- end
-end