aboutsummaryrefslogtreecommitdiffstats
path: root/actionview/lib/action_view/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'actionview/lib/action_view/helpers')
-rw-r--r--actionview/lib/action_view/helpers/active_model_helper.rb8
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb153
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb24
-rw-r--r--actionview/lib/action_view/helpers/csp_helper.rb24
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb33
-rw-r--r--actionview/lib/action_view/helpers/debug_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb72
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb42
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb17
-rw-r--r--actionview/lib/action_view/helpers/javascript_helper.rb13
-rw-r--r--actionview/lib/action_view/helpers/record_tag_helper.rb23
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb11
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb18
-rw-r--r--actionview/lib/action_view/helpers/tags/check_box.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/radio_button.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/text_area.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/text_field.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/translator.rb7
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb15
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb32
24 files changed, 333 insertions, 189 deletions
diff --git a/actionview/lib/action_view/helpers/active_model_helper.rb b/actionview/lib/action_view/helpers/active_model_helper.rb
index f1ef715710..e41a95d2ce 100644
--- a/actionview/lib/action_view/helpers/active_model_helper.rb
+++ b/actionview/lib/action_view/helpers/active_model_helper.rb
@@ -17,8 +17,8 @@ module ActionView
end
end
- def content_tag(*)
- error_wrapping(super)
+ def content_tag(type, options, *)
+ select_markup_helper?(type) ? super : error_wrapping(super)
end
def tag(type, options, *)
@@ -43,6 +43,10 @@ module ActionView
object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
end
+ def select_markup_helper?(type)
+ ["optgroup", "option"].include?(type)
+ end
+
def tag_generate_errors?(options)
options["type"] != "hidden"
end
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index bc2713d13e..14bd8ffa84 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -2,8 +2,10 @@
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/hash/keys"
-require_relative "asset_url_helper"
-require_relative "tag_helper"
+require "active_support/core_ext/object/inclusion"
+require "active_support/core_ext/object/try"
+require "action_view/helpers/asset_url_helper"
+require "action_view/helpers/tag_helper"
module ActionView
# = Action View Asset Tag Helpers
@@ -37,19 +39,24 @@ module ActionView
# When the Asset Pipeline is enabled, you can pass the name of your manifest as
# source, and include other JavaScript or CoffeeScript files inside the manifest.
#
+ # If the server supports Early Hints header links for these assets will be
+ # automatically pushed.
+ #
# ==== Options
#
# When the last parameter is a hash you can add HTML attributes using that
# parameter. The following options are supported:
#
- # * <tt>:extname</tt> - Append an extension to the generated url unless the extension
- # already exists. This only applies for relative urls.
- # * <tt>:protocol</tt> - Sets the protocol of the generated url, this option only
- # applies when a relative url and +host+ options are provided.
- # * <tt>:host</tt> - When a relative url is provided the host is added to the
+ # * <tt>:extname</tt> - Append an extension to the generated URL unless the extension
+ # already exists. This only applies for relative URLs.
+ # * <tt>:protocol</tt> - Sets the protocol of the generated URL. This option only
+ # applies when a relative URL and +host+ options are provided.
+ # * <tt>:host</tt> - When a relative URL is provided the host is added to the
# that path.
# * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
# when it is set to true.
+ # * <tt>:nonce<tt> - When set to true, adds an automatic nonce value if
+ # you have Content Security Policy enabled.
#
# ==== Examples
#
@@ -74,15 +81,29 @@ module ActionView
#
# javascript_include_tag "http://www.example.com/xmlhr.js"
# # => <script src="http://www.example.com/xmlhr.js"></script>
+ #
+ # javascript_include_tag "http://www.example.com/xmlhr.js", nonce: true
+ # # => <script src="http://www.example.com/xmlhr.js" nonce="..."></script>
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
- sources.uniq.map { |source|
+ early_hints_links = []
+
+ sources_tags = sources.uniq.map { |source|
+ href = path_to_javascript(source, path_options)
+ early_hints_links << "<#{href}>; rel=preload; as=script"
tag_options = {
- "src" => path_to_javascript(source, path_options)
+ "src" => href
}.merge!(options)
+ if tag_options["nonce"] == true
+ tag_options["nonce"] = content_security_policy_nonce
+ end
content_tag("script".freeze, "", tag_options)
}.join("\n").html_safe
+
+ request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) && request
+
+ sources_tags
end
# Returns a stylesheet link tag for the sources specified as arguments. If
@@ -92,6 +113,9 @@ module ActionView
# to "screen", so you must explicitly set it to "all" for the stylesheet(s) to
# apply to all media types.
#
+ # If the server supports Early Hints header links for these assets will be
+ # automatically pushed.
+ #
# stylesheet_link_tag "style"
# # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
#
@@ -113,14 +137,22 @@ module ActionView
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys
- sources.uniq.map { |source|
+ early_hints_links = []
+
+ sources_tags = sources.uniq.map { |source|
+ href = path_to_stylesheet(source, path_options)
+ early_hints_links << "<#{href}>; rel=preload; as=style"
tag_options = {
"rel" => "stylesheet",
"media" => "screen",
- "href" => path_to_stylesheet(source, path_options)
+ "href" => href
}.merge!(options)
tag(:link, tag_options)
}.join("\n").html_safe
+
+ request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) && request
+
+ sources_tags
end
# Returns a link tag that browsers and feed readers can use to auto-detect
@@ -199,8 +231,69 @@ module ActionView
}.merge!(options.symbolize_keys))
end
+ # Returns a link tag that browsers can use to preload the +source+.
+ # The +source+ can be the path of a resource managed by asset pipeline,
+ # a full path, or an URI.
+ #
+ # ==== Options
+ #
+ # * <tt>:type</tt> - Override the auto-generated mime type, defaults to the mime type for +source+ extension.
+ # * <tt>:as</tt> - Override the auto-generated value for as attribute, calculated using +source+ extension and mime type.
+ # * <tt>:crossorigin</tt> - Specify the crossorigin attribute, required to load cross-origin resources.
+ # * <tt>:nopush</tt> - Specify if the use of server push is not desired for the resource. Defaults to +false+.
+ #
+ # ==== Examples
+ #
+ # preload_link_tag("custom_theme.css")
+ # # => <link rel="preload" href="/assets/custom_theme.css" as="style" type="text/css" />
+ #
+ # preload_link_tag("/videos/video.webm")
+ # # => <link rel="preload" href="/videos/video.mp4" as="video" type="video/webm" />
+ #
+ # preload_link_tag(post_path(format: :json), as: "fetch")
+ # # => <link rel="preload" href="/posts.json" as="fetch" type="application/json" />
+ #
+ # preload_link_tag("worker.js", as: "worker")
+ # # => <link rel="preload" href="/assets/worker.js" as="worker" type="text/javascript" />
+ #
+ # preload_link_tag("//example.com/font.woff2")
+ # # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="anonymous"/>
+ #
+ # preload_link_tag("//example.com/font.woff2", crossorigin: "use-credentials")
+ # # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="use-credentials" />
+ #
+ # preload_link_tag("/media/audio.ogg", nopush: true)
+ # # => <link rel="preload" href="/media/audio.ogg" as="audio" type="audio/ogg" />
+ #
+ def preload_link_tag(source, options = {})
+ href = asset_path(source, skip_pipeline: options.delete(:skip_pipeline))
+ extname = File.extname(source).downcase.delete(".")
+ mime_type = options.delete(:type) || Template::Types[extname].try(:to_s)
+ as_type = options.delete(:as) || resolve_link_as(extname, mime_type)
+ crossorigin = options.delete(:crossorigin)
+ crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font")
+ nopush = options.delete(:nopush) || false
+
+ link_tag = tag.link({
+ rel: "preload",
+ href: href,
+ as: as_type,
+ type: mime_type,
+ crossorigin: crossorigin
+ }.merge!(options.symbolize_keys))
+
+ early_hints_link = "<#{href}>; rel=preload; as=#{as_type}"
+ early_hints_link += "; type=#{mime_type}" if mime_type
+ early_hints_link += "; crossorigin=#{crossorigin}" if crossorigin
+ early_hints_link += "; nopush" if nopush
+
+ request.send_early_hints("Link" => early_hints_link) if respond_to?(:request) && request
+
+ link_tag
+ end
+
# Returns an HTML image tag for the +source+. The +source+ can be a full
- # path, a file or an Active Storage attachment.
+ # path, a file, or an Active Storage attachment.
#
# ==== Options
#
@@ -240,9 +333,9 @@ module ActionView
#
# image_tag(user.avatar)
# # => <img src="/rails/active_storage/blobs/.../tiger.jpg" />
- # image_tag(user.avatar.variant(resize: "100x100"))
+ # image_tag(user.avatar.variant(resize_to_fit: [100, 100]))
# # => <img src="/rails/active_storage/variants/.../tiger.jpg" />
- # image_tag(user.avatar.variant(resize: "100x100"), size: '100')
+ # image_tag(user.avatar.variant(resize_to_fit: [100, 100]), size: '100')
# # => <img width="100" height="100" src="/rails/active_storage/variants/.../tiger.jpg" />
def image_tag(source, options = {})
options = options.symbolize_keys
@@ -288,12 +381,13 @@ module ActionView
# 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
+ # +sources+ can be full paths or files that exist in your public videos
# directory.
#
# ==== Options
- # You can add HTML attributes using the +options+. The +options+ supports
- # two additional keys for convenience and conformance:
+ #
+ # When the last parameter is a hash you can add HTML attributes using that
+ # parameter. The following options are supported:
#
# * <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+.
@@ -310,7 +404,7 @@ module ActionView
# video_tag("trailer.ogg")
# # => <video src="/videos/trailer.ogg"></video>
# video_tag("trailer.ogg", controls: true, preload: 'none')
- # # => <video preload="none" controls="controls" src="/videos/trailer.ogg" ></video>
+ # # => <video preload="none" controls="controls" src="/videos/trailer.ogg"></video>
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video>
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png", poster_skip_pipeline: true)
@@ -337,9 +431,14 @@ module ActionView
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.
+ # Returns an HTML audio tag for the +sources+. If +sources+ is a string,
+ # a single audio tag will be returned. If +sources+ is an array, an audio
+ # tag with nested source tags for each source will be returned. The
+ # +sources+ can be full paths or files that exist in your public audios
+ # directory.
+ #
+ # When the last parameter is a hash you can add HTML attributes using that
+ # parameter.
#
# audio_tag("sound")
# # => <audio src="/audios/sound"></audio>
@@ -395,6 +494,18 @@ module ActionView
raise ArgumentError, "Cannot pass a :size option with a :height or :width option"
end
end
+
+ def resolve_link_as(extname, mime_type)
+ if extname == "js"
+ "script"
+ elsif extname == "css"
+ "style"
+ elsif extname == "vtt"
+ "track"
+ elsif (type = mime_type.to_s.split("/")[0]) && type.in?(%w(audio video font))
+ type
+ end
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index a4dcfc9a6c..8cbe107e41 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -6,7 +6,7 @@ module ActionView
# = Action View Asset URL Helpers
module Helpers #:nodoc:
# This module provides methods for generating asset paths and
- # urls.
+ # URLs.
#
# image_path("rails.png")
# # => "/assets/rails.png"
@@ -57,8 +57,8 @@ module ActionView
# 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 and http://www.browserscope.org/?category=network for
+ # for server load balancing. See https://www.die.net/musings/page_load_time/
+ # for background and https://www.browserscope.org/?category=network for
# connection limit data.
#
# Alternatively, you can exert more control over the asset host by setting
@@ -97,7 +97,7 @@ module ActionView
# 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.
- # Note that the request parameter might not be supplied, e.g. when the assets
+ # Note that the +request+ parameter might not be supplied, e.g. when the assets
# are precompiled via a Rake task. Make sure to use a +Proc+ instead of a lambda,
# since a +Proc+ allows missing parameters and sets them to +nil+.
#
@@ -149,13 +149,13 @@ module ActionView
# Below lists scenarios that apply to +asset_path+ whether or not you're
# using the asset pipeline.
#
- # - All fully qualified urls are returned immediately. This bypasses the
+ # - All fully qualified URLs are returned immediately. This bypasses the
# asset pipeline and all other behavior described.
#
# asset_path("http://www.example.com/js/xmlhr.js") # => "http://www.example.com/js/xmlhr.js"
#
# - All assets that begin with a forward slash are assumed to be full
- # urls and will not be expanded. This will bypass the asset pipeline.
+ # URLs and will not be expanded. This will bypass the asset pipeline.
#
# asset_path("/foo.png") # => "/foo.png"
#
@@ -324,7 +324,7 @@ module ActionView
# Since +javascript_url+ is based on +asset_url+ method you can set :host options. If :host
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
#
- # javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/dir/xmlhr.js
+ # javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/js/xmlhr.js
#
def javascript_url(source, options = {})
url_to_asset(source, { type: :javascript }.merge!(options))
@@ -351,7 +351,7 @@ module ActionView
# Since +stylesheet_url+ is based on +asset_url+ method you can set :host options. If :host
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
#
- # stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/css/style.css
+ # stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/assets/css/style.css
#
def stylesheet_url(source, options = {})
url_to_asset(source, { type: :stylesheet }.merge!(options))
@@ -381,7 +381,7 @@ module ActionView
# Since +image_url+ is based on +asset_url+ method you can set :host options. If :host
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
#
- # image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/edit.png
+ # image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/assets/edit.png
#
def image_url(source, options = {})
url_to_asset(source, { type: :image }.merge!(options))
@@ -407,7 +407,7 @@ module ActionView
# Since +video_url+ is based on +asset_url+ method you can set :host options. If :host
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
#
- # video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/hd.avi
+ # video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/videos/hd.avi
#
def video_url(source, options = {})
url_to_asset(source, { type: :video }.merge!(options))
@@ -433,7 +433,7 @@ module ActionView
# Since +audio_url+ is based on +asset_url+ method you can set :host options. If :host
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
#
- # audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/horse.wav
+ # audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/audios/horse.wav
#
def audio_url(source, options = {})
url_to_asset(source, { type: :audio }.merge!(options))
@@ -458,7 +458,7 @@ module ActionView
# Since +font_url+ is based on +asset_url+ method you can set :host options. If :host
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
#
- # font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/font.ttf
+ # font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/fonts/font.ttf
#
def font_url(source, options = {})
url_to_asset(source, { type: :font }.merge!(options))
diff --git a/actionview/lib/action_view/helpers/csp_helper.rb b/actionview/lib/action_view/helpers/csp_helper.rb
new file mode 100644
index 0000000000..e2e065c218
--- /dev/null
+++ b/actionview/lib/action_view/helpers/csp_helper.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module ActionView
+ # = Action View CSP Helper
+ module Helpers #:nodoc:
+ module CspHelper
+ # Returns a meta tag "csp-nonce" with the per-session nonce value
+ # for allowing inline <script> tags.
+ #
+ # <head>
+ # <%= csp_meta_tag %>
+ # </head>
+ #
+ # This is used by the Rails UJS helper to create dynamically
+ # loaded inline <script> elements.
+ #
+ def csp_meta_tag
+ if content_security_policy?
+ tag("meta", name: "csp-nonce", content: content_security_policy_nonce)
+ end
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 642bd0fec6..620e1e9f21 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "date"
-require_relative "tag_helper"
+require "action_view/helpers/tag_helper"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/date/conversions"
require "active_support/core_ext/hash/slice"
@@ -116,7 +116,7 @@ module ActionView
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
+ else locale.t :x_minutes, count: 1
end
when 2...45 then locale.t :x_minutes, count: distance_in_minutes
@@ -131,7 +131,7 @@ module ActionView
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
+ else
from_year = from_time.year
from_year += 1 if from_time.month >= 3
to_year = to_time.year
@@ -302,15 +302,15 @@ module ActionView
# 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}
+ # 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: { 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}
+ # time_select 'game', 'game_time', { ampm: true }
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
@@ -346,8 +346,8 @@ module ActionView
# 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: { 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.
@@ -397,8 +397,8 @@ module ActionView
# 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: { 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
@@ -436,8 +436,8 @@ module ActionView
# 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: { 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
@@ -476,8 +476,8 @@ module ActionView
# 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: { 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
@@ -681,9 +681,8 @@ module ActionView
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.iso8601
- content_tag("time".freeze, content, options.reverse_merge(datetime: datetime), &block)
+ content_tag("time".freeze, content, options.reverse_merge(datetime: date_or_time.iso8601), &block)
end
private
diff --git a/actionview/lib/action_view/helpers/debug_helper.rb b/actionview/lib/action_view/helpers/debug_helper.rb
index 52dff1f750..88ceba414b 100644
--- a/actionview/lib/action_view/helpers/debug_helper.rb
+++ b/actionview/lib/action_view/helpers/debug_helper.rb
@@ -24,7 +24,7 @@ module ActionView
# created_at:
# </pre>
def debug(object)
- Marshal::dump(object)
+ Marshal.dump(object)
object = ERB::Util.html_escape(object.to_yaml)
content_tag(:pre, object, class: "debug_dump")
rescue # errors from Marshal or YAML
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 6702c65ccb..2d5c5684c1 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
require "cgi"
-require_relative "date_helper"
-require_relative "tag_helper"
-require_relative "form_tag_helper"
-require_relative "active_model_helper"
-require_relative "../model_naming"
-require_relative "../record_identifier"
+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/model_naming"
+require "action_view/record_identifier"
require "active_support/core_ext/module/attribute_accessors"
require "active_support/core_ext/hash/slice"
require "active_support/core_ext/string/output_safety"
@@ -19,7 +19,7 @@ module ActionView
# 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
+ # 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
@@ -166,7 +166,7 @@ module ActionView
# 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
+ # 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
@@ -478,6 +478,8 @@ module ActionView
mattr_accessor :form_with_generates_remote_forms, default: true
+ mattr_accessor :form_with_generates_ids, default: false
+
# Creates a form tag based on mixing URLs, scopes, or models.
#
# # Using just a URL:
@@ -606,10 +608,10 @@ module ActionView
# This is helpful when fragment-caching the form. Remote forms
# get the authenticity token from the <tt>meta</tt> tag, so embedding is
# unnecessary unless you support browsers without JavaScript.
- # * <tt>:local</tt> - By default form submits are remote and unobstrusive XHRs.
+ # * <tt>:local</tt> - By default form submits are remote and unobtrusive XHRs.
# Disable remote submits with <tt>local: true</tt>.
- # * <tt>:skip_enforcing_utf8</tt> - By default a hidden field named +utf8+
- # is output to enforce UTF-8 submits. Set to true to skip the field.
+ # * <tt>:skip_enforcing_utf8</tt> - If set to true, a hidden input with name
+ # utf8 is not output.
# * <tt>:builder</tt> - Override the object used to build the form.
# * <tt>:id</tt> - Optional HTML id attribute.
# * <tt>:class</tt> - Optional HTML class attribute.
@@ -640,16 +642,6 @@ module ActionView
#
# Where <tt>@document = Document.find(params[:id])</tt>.
#
- # When using labels +form_with+ requires setting the id on the field being
- # labelled:
- #
- # <%= form_with(model: @post) do |form| %>
- # <%= form.label :title %>
- # <%= form.text_field :title, id: :post_title %>
- # <% end %>
- #
- # See +label+ for more on how the +for+ attribute is derived.
- #
# === Mixing with other form helpers
#
# While +form_with+ uses a FormBuilder object it's possible to mix and
@@ -746,7 +738,7 @@ module ActionView
# end
def form_with(model: nil, scope: nil, url: nil, format: nil, **options)
options[:allow_method_names_outside_object] = true
- options[:skip_default_ids] = true
+ options[:skip_default_ids] = !form_with_generates_ids
if model
url ||= polymorphic_path(model, format: format)
@@ -1022,14 +1014,13 @@ module ActionView
# <%= fields :comment do |fields| %>
# <%= fields.text_field :body %>
# <% end %>
- # # => <input type="text" name="comment[body]>
+ # # => <input type="text" name="comment[body]">
#
# # Using a model infers the scope and assigns field values:
- # <%= fields model: Comment.new(body: "full bodied") do |fields| %<
+ # <%= fields model: Comment.new(body: "full bodied") do |fields| %>
# <%= fields.text_field :body %>
# <% end %>
- # # =>
- # <input type="text" name="comment[body] value="full bodied">
+ # # => <input type="text" name="comment[body]" value="full bodied">
#
# # Using +fields+ with +form_with+:
# <%= form_with model: @post do |form| %>
@@ -1044,16 +1035,6 @@ module ActionView
# or model is yielded, so any generated field names are prefixed with
# either the passed scope or the scope inferred from the <tt>:model</tt>.
#
- # When using labels +fields+ requires setting the id on the field being
- # labelled:
- #
- # <%= fields :comment do |fields| %>
- # <%= fields.label :body %>
- # <%= fields.text_field :body, id: :comment_body %>
- # <% end %>
- #
- # See +label+ for more on how the +for+ attribute is derived.
- #
# === Mixing with other form helpers
#
# While +form_with+ uses a FormBuilder object it's possible to mix and
@@ -1072,7 +1053,7 @@ module ActionView
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
def fields(scope = nil, model: nil, **options, &block)
options[:allow_method_names_outside_object] = true
- options[:skip_default_ids] = true
+ options[:skip_default_ids] = !form_with_generates_ids
if model
scope ||= model_name_from_record_or_class(model).param_key
@@ -1538,10 +1519,10 @@ module ActionView
private
def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
- skip_enforcing_utf8: false, **options)
+ skip_enforcing_utf8: nil, **options)
html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
- html_options[:enforce_utf8] = !skip_enforcing_utf8
+ html_options[:enforce_utf8] = !skip_enforcing_utf8 unless skip_enforcing_utf8.nil?
html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
@@ -1985,11 +1966,11 @@ module ActionView
# See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
def fields(scope = nil, model: nil, **options, &block)
options[:allow_method_names_outside_object] = true
- options[:skip_default_ids] = true
+ options[:skip_default_ids] = !FormHelper.form_with_generates_ids
convert_to_legacy_options(options)
- fields_for(scope || model, model, **options, &block)
+ fields_for(scope || model, model, options, &block)
end
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
@@ -2265,7 +2246,7 @@ module ActionView
@template.button_tag(value, options, &block)
end
- def emitted_hidden_id?
+ def emitted_hidden_id? # :nodoc:
@emitted_hidden_id ||= nil
end
@@ -2285,7 +2266,12 @@ module ActionView
end
defaults = []
- defaults << :"helpers.submit.#{object_name}.#{key}"
+ # Object is a model and it is not overwritten by as and scope option.
+ if object.respond_to?(:model_name) && object_name.to_s == model.downcase
+ defaults << :"helpers.submit.#{object.model_name.i18n_key}.#{key}"
+ else
+ defaults << :"helpers.submit.#{object_name}.#{key}"
+ end
defaults << :"helpers.submit.#{key}"
defaults << "#{key.to_s.humanize} #{model}"
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 1517abfad0..d02f641867 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -2,7 +2,7 @@
require "cgi"
require "erb"
-require_relative "form_helper"
+require "action_view/helpers/form_helper"
require "active_support/core_ext/string/output_safety"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/array/wrap"
@@ -16,7 +16,7 @@ module ActionView
#
# * <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.
#
- # select("post", "category", Post::CATEGORIES, {include_blank: true})
+ # select("post", "category", Post::CATEGORIES, { include_blank: true })
#
# could become:
#
@@ -30,7 +30,7 @@ module ActionView
#
# Example with <tt>@post.person_id => 2</tt>:
#
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'})
+ # select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: 'None' })
#
# could become:
#
@@ -43,7 +43,7 @@ module ActionView
#
# * <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.
#
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'})
+ # select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { prompt: 'Select Person' })
#
# could become:
#
@@ -69,7 +69,7 @@ module ActionView
#
# * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
#
- # select("post", "category", Post::CATEGORIES, {disabled: 'restricted'})
+ # select("post", "category", Post::CATEGORIES, { disabled: 'restricted' })
#
# could become:
#
@@ -82,7 +82,7 @@ module ActionView
#
# 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.
#
- # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: -> (category) { category.archived? }})
+ # collection_select(:post, :category_id, Category.all, :id, :name, { disabled: -> (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]" id="post_category_id">
@@ -107,7 +107,7 @@ module ActionView
#
# For example:
#
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true })
+ # select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: true })
#
# would become:
#
@@ -214,9 +214,13 @@ module ActionView
# * +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.
+ # array of child objects representing the <tt><option></tt> tags. It 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.
# * +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.
+ # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag. It 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 label.
# * +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
@@ -279,17 +283,17 @@ module ActionView
# 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, include_blank: true)
#
- # time_zone_select( "user", "time_zone", nil, default: "Pacific Time (US & Canada)" )
+ # 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.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', [ ActiveSupport::TimeZone['Alaska'], ActiveSupport::TimeZone['Hawaii'] ])
#
- # time_zone_select( "user", 'time_zone', /Australia/)
+ # time_zone_select("user", 'time_zone', /Australia/)
#
- # time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, model: ActiveSupport::TimeZone)
+ # 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
@@ -319,12 +323,12 @@ module ActionView
#
# You can optionally provide HTML attributes as the last element of the array.
#
- # options_for_select([ "Denmark", ["USA", {class: 'bold'}], "Sweden" ], ["USA", "Sweden"])
+ # 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');"}]])
+ # 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>
#
@@ -457,9 +461,9 @@ module ActionView
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)
+ value_for_collection(group, group_method), option_key_method, option_value_method, selected_key)
- content_tag("optgroup".freeze, option_tags, label: group.send(group_label_method))
+ content_tag("optgroup".freeze, option_tags, label: value_for_collection(group, group_label_method))
end.join.html_safe
end
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 31a1f8be8c..ba09738beb 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "cgi"
-require_relative "tag_helper"
+require "action_view/helpers/tag_helper"
require "active_support/core_ext/string/output_safety"
require "active_support/core_ext/module/attribute_accessors"
@@ -22,6 +22,8 @@ module ActionView
mattr_accessor :embed_authenticity_token_in_remote_forms
self.embed_authenticity_token_in_remote_forms = nil
+ mattr_accessor :default_enforce_utf8, default: true
+
# Starts a form tag that points the action to a url configured with <tt>url_for_options</tt> just like
# ActionController::Base#url_for. The method for the form defaults to POST.
#
@@ -115,7 +117,7 @@ module ActionView
# # <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 id="people" name="people"><option value="" label=" "></option><option value="1">David</option></select>
#
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: "All"
# # => <select id="people" name="people"><option value="">All</option><option value="1">David</option></select>
@@ -387,14 +389,14 @@ module ActionView
# * 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 'favorite_color', 'maroon'
+ # # => <input id="favorite_color_maroon" name="favorite_color" type="radio" value="maroon" />
#
# 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." />
+ # # => <input disabled="disabled" id="time_slot_3:00_p.m." 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" />
@@ -549,7 +551,8 @@ module ActionView
# # => <input src="/assets/save.png" data-confirm="Are you sure?" type="image" />
def image_submit_tag(source, options = {})
options = options.stringify_keys
- tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options)
+ src = path_to_image(source, skip_pipeline: options.delete("skip_pipeline"))
+ tag :input, { "type" => "image", "src" => src }.update(options)
end
# Creates a field set for grouping HTML form elements.
@@ -866,7 +869,7 @@ module ActionView
})
end
- if html_options.delete("enforce_utf8") { true }
+ if html_options.delete("enforce_utf8") { default_enforce_utf8 }
utf8_enforcer_tag + method_tag
else
method_tag
diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb
index 11eefe0ee0..830088bea3 100644
--- a/actionview/lib/action_view/helpers/javascript_helper.rb
+++ b/actionview/lib/action_view/helpers/javascript_helper.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "tag_helper"
+require "action_view/helpers/tag_helper"
module ActionView
module Helpers #:nodoc:
@@ -63,6 +63,13 @@ module ActionView
# <%= javascript_tag defer: 'defer' do -%>
# alert('All is good')
# <% end -%>
+ #
+ # If you have a content security policy enabled then you can add an automatic
+ # nonce value by passing <tt>nonce: true</tt> as part of +html_options+. Example:
+ #
+ # <%= javascript_tag nonce: true do -%>
+ # alert('All is good')
+ # <% end -%>
def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
content =
if block_given?
@@ -72,6 +79,10 @@ module ActionView
content_or_options_with_block
end
+ if html_options[:nonce] == true
+ html_options[:nonce] = content_security_policy_nonce
+ end
+
content_tag("script".freeze, javascript_cdata_section(content), html_options)
end
diff --git a/actionview/lib/action_view/helpers/record_tag_helper.rb b/actionview/lib/action_view/helpers/record_tag_helper.rb
deleted file mode 100644
index a6953ee905..0000000000
--- a/actionview/lib/action_view/helpers/record_tag_helper.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-module ActionView
- module Helpers #:nodoc:
- module RecordTagHelper
- def div_for(*) # :nodoc:
- raise NoMethodError, "The `div_for` method has been removed from " \
- "Rails. To continue using it, add the `record_tag_helper` gem to " \
- "your Gemfile:\n" \
- " gem 'record_tag_helper', '~> 1.0'\n" \
- "Consult the Rails upgrade guide for details."
- end
-
- def content_tag_for(*) # :nodoc:
- raise NoMethodError, "The `content_tag_for` method has been removed from " \
- "Rails. To continue using it, add the `record_tag_helper` gem to " \
- "your Gemfile:\n" \
- " gem 'record_tag_helper', '~> 1.0'\n" \
- "Consult the Rails upgrade guide for details."
- end
- end
- end
-end
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index a64d7e396e..d12989ea64 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -88,9 +88,10 @@ module ActionView
if value.is_a?(Array)
value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze)
else
- value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
+ value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s.dup
end
- %(#{key}="#{value.gsub('"'.freeze, '&quot;'.freeze)}")
+ value.gsub!('"'.freeze, "&quot;".freeze)
+ %(#{key}="#{value}")
end
private
@@ -166,7 +167,7 @@ module ActionView
# This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
# from 1.4.3.
#
- # tag.div data: { city_state: %w( Chigaco IL ) }
+ # tag.div data: { city_state: %w( Chicago IL ) }
# # => <div data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]"></div>
#
# The generated attributes are escaped by default. This can be disabled using
@@ -227,10 +228,10 @@ module ActionView
# tag("img", src: "open & shut.png")
# # => <img src="open &amp; shut.png" />
#
- # tag("img", {src: "open &amp; shut.png"}, false, false)
+ # 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)})
+ # 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 = nil, options = nil, open = false, escape = true)
if name.nil?
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index 8934a9894c..eef527d36f 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -97,7 +97,7 @@ module ActionView
index = name_and_id_index(options)
options["name"] = options.fetch("name") { tag_name(options["multiple"], index) }
- unless skip_default_ids?
+ if generate_ids?
options["id"] = options.fetch("id") { tag_id(index) }
if namespace = options.delete("namespace")
options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace
@@ -109,11 +109,11 @@ module ActionView
# a little duplication to construct less strings
case
when @object_name.empty?
- "#{sanitized_method_name}#{"[]" if multiple}"
+ "#{sanitized_method_name}#{multiple ? "[]" : ""}"
when index
- "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
+ "#{@object_name}[#{index}][#{sanitized_method_name}]#{multiple ? "[]" : ""}"
else
- "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
+ "#{@object_name}[#{sanitized_method_name}]#{multiple ? "[]" : ""}"
end
end
@@ -170,7 +170,11 @@ module ActionView
option_tags = tag_builder.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 = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), value: "") + "\n" + option_tags
+ tag_options = { value: "" }.tap do |prompt_opts|
+ prompt_opts[:disabled] = true if options[:disabled] == ""
+ prompt_opts[:selected] = true if options[:selected] == ""
+ end
+ option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
end
option_tags
end
@@ -183,8 +187,8 @@ module ActionView
end
end
- def skip_default_ids?
- @skip_default_ids
+ def generate_ids?
+ !@skip_default_ids
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/check_box.rb b/actionview/lib/action_view/helpers/tags/check_box.rb
index 6b34dfef90..4327e07cae 100644
--- a/actionview/lib/action_view/helpers/tags/check_box.rb
+++ b/actionview/lib/action_view/helpers/tags/check_box.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "checkable"
+require "action_view/helpers/tags/checkable"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
index 91c1135d20..455442178e 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "collection_helpers"
+require "action_view/helpers/tags/collection_helpers"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
index 0b0482f74e..16d37134e5 100644
--- a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "collection_helpers"
+require "action_view/helpers/tags/collection_helpers"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb
index 56b48bbd62..02bd099784 100644
--- a/actionview/lib/action_view/helpers/tags/label.rb
+++ b/actionview/lib/action_view/helpers/tags/label.rb
@@ -75,10 +75,6 @@ module ActionView
def render_component(builder)
builder.translation
end
-
- def skip_default_ids?
- false # The id is used as the `for` attribute.
- end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/radio_button.rb b/actionview/lib/action_view/helpers/tags/radio_button.rb
index 3cfdcbea3f..621db2b1b5 100644
--- a/actionview/lib/action_view/helpers/tags/radio_button.rb
+++ b/actionview/lib/action_view/helpers/tags/radio_button.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "checkable"
+require "action_view/helpers/tags/checkable"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/text_area.rb b/actionview/lib/action_view/helpers/tags/text_area.rb
index 9c162b59f5..4519082ff6 100644
--- a/actionview/lib/action_view/helpers/tags/text_area.rb
+++ b/actionview/lib/action_view/helpers/tags/text_area.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "placeholderable"
+require "action_view/helpers/tags/placeholderable"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb
index 3553942048..d92967e212 100644
--- a/actionview/lib/action_view/helpers/tags/text_field.rb
+++ b/actionview/lib/action_view/helpers/tags/text_field.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "placeholderable"
+require "action_view/helpers/tags/placeholderable"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/translator.rb b/actionview/lib/action_view/helpers/tags/translator.rb
index fcf96d2c9c..e81ca3aef0 100644
--- a/actionview/lib/action_view/helpers/tags/translator.rb
+++ b/actionview/lib/action_view/helpers/tags/translator.rb
@@ -16,13 +16,8 @@ module ActionView
translated_attribute || human_attribute_name
end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :object_name, :method_and_value, :scope, :model
-
private
+ attr_reader :object_name, :method_and_value, :scope, :model
def i18n_default
if model
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 3044a2c0ef..34138de00e 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -13,9 +13,9 @@ module ActionView
#
# ==== 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:
+ # Most text helpers that generate HTML output sanitize the given input by default,
+ # 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>"
@@ -128,7 +128,7 @@ module ActionView
# # => You searched for: <a href="search?q=rails">rails</a>
#
# highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
- # # => "<a>ruby</a> on <mark>rails</mark>"
+ # # => <a href="javascript:alert('no!')">ruby</a> on <mark>rails</mark>
def highlight(text, phrases, options = {})
text = sanitize(text) if options.fetch(:sanitize, true)
@@ -422,7 +422,7 @@ module ActionView
def to_s
value = @values[@index].to_s
@index = next_index
- return value
+ value
end
private
@@ -446,7 +446,7 @@ module ActionView
# uses an instance variable of ActionView::Base.
def get_cycle(name)
@_cycles = Hash.new unless defined?(@_cycles)
- return @_cycles[name]
+ @_cycles[name]
end
def set_cycle(name, cycle_object)
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index e663892592..db44fdbfee 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "tag_helper"
+require "action_view/helpers/tag_helper"
require "active_support/core_ext/string/access"
require "i18n/exceptions"
@@ -60,7 +60,11 @@ module ActionView
def translate(key, options = {})
options = options.dup
has_default = options.has_key?(:default)
- remaining_defaults = Array(options.delete(:default)).compact
+ if has_default
+ remaining_defaults = Array(options.delete(:default)).compact
+ else
+ remaining_defaults = []
+ end
if has_default && !remaining_defaults.first.kind_of?(Symbol)
options[:default] = remaining_defaults
@@ -122,9 +126,12 @@ module ActionView
private
def scope_key_by_partial(key)
- if key.to_s.first == "."
+ stringified_key = key.to_s
+ if stringified_key.first == "."
if @virtual_path
- @virtual_path.gsub(%r{/_?}, ".") + key.to_s
+ @_scope_key_by_partial_cache ||= {}
+ @_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
+ "#{@_scope_key_by_partial_cache[@virtual_path]}#{stringified_key}"
else
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 2d5aac6dc7..cae62f2312 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "javascript_helper"
+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"
@@ -139,6 +139,11 @@ module ActionView
# link_to "Profiles", controller: "profiles"
# # => <a href="/profiles">Profiles</a>
#
+ # When name is +nil+ the href is presented instead
+ #
+ # link_to nil, "http://example.com"
+ # # => <a href="http://www.example.com">http://www.example.com</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 %>
@@ -589,10 +594,27 @@ module ActionView
end
def add_method_to_attributes!(html_options, method)
- if method && method.to_s.downcase != "get".freeze && html_options["rel".freeze] !~ /nofollow/
- html_options["rel".freeze] = "#{html_options["rel".freeze]} nofollow".lstrip
+ if method_not_get_method?(method) && html_options["rel"] !~ /nofollow/
+ if html_options["rel"].blank?
+ html_options["rel"] = "nofollow"
+ else
+ html_options["rel"] = "#{html_options["rel"]} nofollow"
+ end
end
- html_options["data-method".freeze] = method
+ html_options["data-method"] = method
+ end
+
+ STRINGIFIED_COMMON_METHODS = {
+ get: "get",
+ delete: "delete",
+ patch: "patch",
+ post: "post",
+ put: "put",
+ }.freeze
+
+ def method_not_get_method?(method)
+ return false unless method
+ (STRINGIFIED_COMMON_METHODS[method] || method.to_s.downcase) != "get"
end
def token_tag(token = nil, form_options: {})
@@ -614,7 +636,7 @@ module ActionView
# to_form_params(name: 'David', nationality: 'Danish')
# # => [{name: :name, value: 'David'}, {name: 'nationality', value: 'Danish'}]
#
- # to_form_params(country: {name: 'Denmark'})
+ # to_form_params(country: { name: 'Denmark' })
# # => [{name: 'country[name]', value: 'Denmark'}]
#
# to_form_params(countries: ['Denmark', 'Sweden']})