diff options
Diffstat (limited to 'actionview/lib')
103 files changed, 1067 insertions, 574 deletions
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index ca586f1d52..c1eeda75f5 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + #-- -# Copyright (c) 2004-2017 David Heinemeier Hansson +# Copyright (c) 2004-2018 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -74,7 +76,6 @@ module ActionView autoload :MissingTemplate autoload :ActionViewError autoload :EncodingError - autoload :MissingRequestError autoload :TemplateError autoload :WrongEncodingError end @@ -92,5 +93,5 @@ 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" + I18n.load_path << File.expand_path("action_view/locale/en.yml", __dir__) end diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb index 5387174467..d41fe2a608 100644 --- a/actionview/lib/action_view/base.rb +++ b/actionview/lib/action_view/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/module/attr_internal" require "active_support/core_ext/module/attribute_accessors" require "active_support/ordered_options" @@ -140,30 +142,25 @@ module ActionView #:nodoc: 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 } + cattr_accessor :field_error_proc, default: 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>) + cattr_accessor :streaming_completion_on_exception, default: %("><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 + cattr_accessor :prefix_partial_path_with_controller_namespace, default: true # Specify default_formats that can be rendered. cattr_accessor :default_formats # Specify whether an error should be raised for missing translations - cattr_accessor :raise_on_missing_translations - @@raise_on_missing_translations = false + cattr_accessor :raise_on_missing_translations, default: false # Specify whether submit_tag should automatically disable on click - cattr_accessor :automatically_disable_submit_tag - @@automatically_disable_submit_tag = true + cattr_accessor :automatically_disable_submit_tag, default: true class_attribute :_routes class_attribute :logger @@ -207,6 +204,7 @@ module ActionView #:nodoc: @view_renderer = ActionView::Renderer.new(lookup_context) end + @cache_hit = {} assign(assigns) assign_controller(controller) _prepare_context diff --git a/actionview/lib/action_view/buffers.rb b/actionview/lib/action_view/buffers.rb index 089daa6d60..18eaee5d79 100644 --- a/actionview/lib/action_view/buffers.rb +++ b/actionview/lib/action_view/buffers.rb @@ -1,6 +1,23 @@ +# frozen_string_literal: true + require "active_support/core_ext/string/output_safety" module ActionView + # Used as a buffer for views + # + # The main difference between this and ActiveSupport::SafeBuffer + # is for the methods `<<` and `safe_expr_append=` the inputs are + # checked for nil before they are assigned and `to_s` is called on + # the input. For example: + # + # obuf = ActionView::OutputBuffer.new "hello" + # obuf << 5 + # puts obuf # => "hello5" + # + # sbuf = ActiveSupport::SafeBuffer.new "hello" + # sbuf << 5 + # puts sbuf # => "hello\u0005" + # class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc: def initialize(*) super diff --git a/actionview/lib/action_view/context.rb b/actionview/lib/action_view/context.rb index 31aa73a0cf..3c605c3ee3 100644 --- a/actionview/lib/action_view/context.rb +++ b/actionview/lib/action_view/context.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module CompiledTemplates #:nodoc: # holds compiled template code @@ -8,16 +10,16 @@ module ActionView # 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). + # In order to work with Action Controller, 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 @@ -27,7 +29,6 @@ module ActionView # 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 diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb index 451eeec9d6..182f6e2eef 100644 --- a/actionview/lib/action_view/dependency_tracker.rb +++ b/actionview/lib/action_view/dependency_tracker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "concurrent/map" require "action_view/path_set" diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 0658d8601d..6d2e471a44 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -1,6 +1,6 @@ -require "concurrent/map" +# frozen_string_literal: true + require "action_view/dependency_tracker" -require "monitor" module ActionView class Digestor @@ -18,9 +18,12 @@ module ActionView # * <tt>name</tt> - Template name # * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt> # * <tt>dependencies</tt> - An array of dependent views - def digest(name:, finder:, dependencies: []) - dependencies ||= [] - cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".") + def digest(name:, finder:, dependencies: nil) + if dependencies.nil? || dependencies.empty? + cache_key = "#{name}.#{finder.rendered_format}" + else + cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".") + end # this is a correctly done double-checked locking idiom # (Concurrent::Map's lookups have volatile semantics) @@ -30,7 +33,7 @@ module ActionView root = tree(name, finder, partial) dependencies.each do |injected_dep| root.children << Injected.new(injected_dep, nil, nil) - end + end if dependencies finder.digest_cache[cache_key] = root.digest(finder) end end @@ -44,10 +47,7 @@ module ActionView def tree(name, finder, partial = false, seen = {}) logical_name = name.gsub(%r|/_|, "/") - options = {} - options[:formats] = [finder.rendered_format] if finder.rendered_format - - if template = finder.disable_cache { finder.find_all(logical_name, [], partial, [], options).first } + if template = find_template(finder, logical_name, [], partial, []) finder.rendered_format ||= template.formats.first if node = seen[template.identifier] # handle cycles in the tree @@ -62,11 +62,22 @@ module ActionView node end else - logger.error " '#{name}' file doesn't exist, so no dependencies" - logger.error " Couldn't find template for digesting: #{name}" + unless name.include?("#") # Dynamic template partial names can never be tracked + logger.error " Couldn't find template for digesting: #{name}" + end + seen[name] ||= Missing.new(name, logical_name, nil) end end + + private + def find_template(finder, name, prefixes, partial, keys) + finder.disable_cache do + format = finder.rendered_format + result = finder.find_all(name, prefixes, partial, keys, formats: [format]).first if format + result || finder.find_all(name, prefixes, partial, keys).first + end + end end class Node @@ -85,7 +96,7 @@ module ActionView end def digest(finder, stack = []) - Digest::MD5.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}") + ActiveSupport::Digest.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}") end def dependency_digest(finder, stack) diff --git a/actionview/lib/action_view/flows.rb b/actionview/lib/action_view/flows.rb index 6d5f57a570..ff44fa6619 100644 --- a/actionview/lib/action_view/flows.rb +++ b/actionview/lib/action_view/flows.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/string/output_safety" module ActionView diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb index 662a85f191..77ae444a58 100644 --- a/actionview/lib/action_view/gem_version.rb +++ b/actionview/lib/action_view/gem_version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView # Returns the version of the currently loaded Action View as a <tt>Gem::Version</tt> def self.gem_version @@ -5,10 +7,10 @@ module ActionView end module VERSION - MAJOR = 5 - MINOR = 1 + MAJOR = 6 + MINOR = 0 TINY = 0 - PRE = "beta1" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionview/lib/action_view/helpers.rb b/actionview/lib/action_view/helpers.rb index c1b4b4f84b..0d77f74171 100644 --- a/actionview/lib/action_view/helpers.rb +++ b/actionview/lib/action_view/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/benchmarkable" module ActionView #:nodoc: @@ -11,6 +13,7 @@ module ActionView #:nodoc: autoload :CacheHelper autoload :CaptureHelper autoload :ControllerHelper + autoload :CspHelper autoload :CsrfHelper autoload :DateHelper autoload :DebugHelper @@ -20,7 +23,6 @@ module ActionView #:nodoc: autoload :JavaScriptHelper, "action_view/helpers/javascript_helper" autoload :NumberHelper autoload :OutputSafetyHelper - autoload :RecordTagHelper autoload :RenderingHelper autoload :SanitizeHelper autoload :TagHelper @@ -44,6 +46,7 @@ module ActionView #:nodoc: include CacheHelper include CaptureHelper include ControllerHelper + include CspHelper include CsrfHelper include DateHelper include DebugHelper @@ -53,7 +56,6 @@ module ActionView #:nodoc: include JavaScriptHelper include NumberHelper include OutputSafetyHelper - include RecordTagHelper include RenderingHelper include SanitizeHelper include TagHelper diff --git a/actionview/lib/action_view/helpers/active_model_helper.rb b/actionview/lib/action_view/helpers/active_model_helper.rb index 4bb5788a16..e41a95d2ce 100644 --- a/actionview/lib/action_view/helpers/active_model_helper.rb +++ b/actionview/lib/action_view/helpers/active_model_helper.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require "active_support/core_ext/module/attribute_accessors" require "active_support/core_ext/enumerable" module ActionView # = Active Model Helpers - module Helpers + module Helpers #:nodoc: module ActiveModelHelper end @@ -15,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, *) @@ -41,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 750f96f29e..cbcce4a4dc 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -1,5 +1,9 @@ +# frozen_string_literal: true + require "active_support/core_ext/array/extract_options" require "active_support/core_ext/hash/keys" +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" @@ -11,7 +15,7 @@ module ActionView # the assets exist before linking to them: # # image_tag("rails.png") - # # => <img alt="Rails" src="/assets/rails.png" /> + # # => <img src="/assets/rails.png" /> # stylesheet_link_tag("application") # # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" /> module AssetTagHelper @@ -35,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 # @@ -72,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 @@ -90,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" /> # @@ -111,20 +137,28 @@ 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 - # 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+. + # an RSS, Atom, or JSON feed. The +type+ can be <tt>:rss</tt> (default), + # <tt>:atom</tt>, or <tt>:json</tt>. Control the link options in url_for format + # using the +url_options+. You can modify the LINK tag itself in +tag_options+. # # ==== Options # @@ -138,6 +172,8 @@ module ActionView # # => <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(:json) + # # => <link rel="alternate" type="application/json" title="JSON" 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"}) @@ -147,8 +183,8 @@ module ActionView # 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.rss" /> def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {}) - if !(type == :rss || type == :atom) && tag_options[:type].blank? - raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss or :atom.") + if !(type == :rss || type == :atom || type == :json) && tag_options[:type].blank? + raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss, :atom, or :json.") end tag( @@ -195,44 +231,124 @@ 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 or a file. + # path, a file, or an Active Storage attachment. # # ==== Options # # You can add HTML attributes using the +options+. The +options+ supports - # two additional keys for convenience and conformance: + # 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. + # * <tt>:srcset</tt> - If supplied as a hash or array of <tt>[source, descriptor]</tt> + # pairs, each image path will be expanded before the list is formatted as a string. # # ==== Examples # + # Assets (images that are part of your app): + # # image_tag("icon") - # # => <img alt="Icon" src="/assets/icon" /> + # # => <img src="/assets/icon" /> # image_tag("icon.png") - # # => <img alt="Icon" src="/assets/icon.png" /> + # # => <img 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" /> + # # => <img src="/icons/icon.gif" width="16" height="16" /> # image_tag("/icons/icon.gif", height: '32', width: '32') - # # => <img alt="Icon" height="32" src="/icons/icon.gif" width="32" /> + # # => <img 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" /> + # # => <img class="menu_icon" src="/icons/icon.gif" /> # image_tag("/icons/icon.gif", data: { title: 'Rails Application' }) # # => <img data-title="Rails Application" src="/icons/icon.gif" /> + # image_tag("icon.png", srcset: { "icon_2x.png" => "2x", "icon_4x.png" => "4x" }) + # # => <img src="/assets/icon.png" srcset="/assets/icon_2x.png 2x, /assets/icon_4x.png 4x"> + # image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw") + # # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw"> + # + # Active Storage (images that are uploaded by the users of your app): + # + # image_tag(user.avatar) + # # => <img src="/rails/active_storage/blobs/.../tiger.jpg" /> + # image_tag(user.avatar.variant(resize_to_fit: [100, 100])) + # # => <img src="/rails/active_storage/variants/.../tiger.jpg" /> + # 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 check_for_image_tag_errors(options) + skip_pipeline = options.delete(:skip_pipeline) - src = options[:src] = path_to_image(source, skip_pipeline: options.delete(:skip_pipeline)) + options[:src] = resolve_image_source(source, skip_pipeline) - unless src.start_with?("cid:") || src.start_with?("data:") || src.blank? - options[:alt] = options.fetch(:alt) { image_alt(src) } + if options[:srcset] && !options[:srcset].is_a?(String) + options[:srcset] = options[:srcset].map do |src_path, size| + src_path = path_to_image(src_path, skip_pipeline: skip_pipeline) + "#{src_path} #{size}" + end.join(", ") end options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size] @@ -257,18 +373,21 @@ module ActionView # image_alt('underscored_file_name.png') # # => Underscored file name def image_alt(src) + ActiveSupport::Deprecation.warn("image_alt is deprecated and will be removed from Rails 6.0. You must explicitly set alt text on images.") + File.basename(src, ".*".freeze).sub(/-[[:xdigit:]]{32,64}\z/, "".freeze).tr("-_".freeze, " ".freeze).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 + # +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+. @@ -285,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) @@ -312,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> @@ -346,6 +470,16 @@ module ActionView end end + def resolve_image_source(source, skip_pipeline) + if source.is_a?(Symbol) || source.is_a?(String) + path_to_image(source, skip_pipeline: skip_pipeline) + else + polymorphic_url(source) + end + rescue NoMethodError => e + raise ArgumentError, "Can't resolve image into URL: #{e}" + end + def extract_dimensions(size) size = size.to_s if /\A\d+x\d+\z/.match?(size) @@ -360,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 03bd1eb008..1808765666 100644 --- a/actionview/lib/action_view/helpers/asset_url_helper.rb +++ b/actionview/lib/action_view/helpers/asset_url_helper.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require "zlib" module ActionView # = Action View Asset URL Helpers - module Helpers + module Helpers #:nodoc: # This module provides methods for generating asset paths and - # urls. + # URLs. # # image_path("rails.png") # # => "/assets/rails.png" @@ -27,7 +29,7 @@ module ActionView # Helpers take that into account: # # image_tag("rails.png") - # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" /> + # # => <img 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" /> # @@ -40,7 +42,7 @@ module ActionView # "assets0.example.com", ..., "assets3.example.com". # # image_tag("rails.png") - # # => <img alt="Rails" src="http://assets0.example.com/assets/rails.png" /> + # # => <img 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" /> # @@ -55,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 @@ -66,7 +68,7 @@ module ActionView # "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" /> + # # => <img 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" /> # @@ -85,7 +87,7 @@ module ActionView # end # } # image_tag("rails.png") - # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" /> + # # => <img 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" /> # @@ -95,9 +97,10 @@ 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 - # 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+. + # Note that the +request+ parameter might not be supplied, e.g. when the assets + # are precompiled with the command `rails assets:precompile`. Make sure to use a + # +Proc+ instead of a lambda, since a +Proc+ allows missing parameters and sets them + # to +nil+. # # config.action_controller.asset_host = Proc.new { |source, request| # if request && request.ssl? @@ -147,13 +150,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" # @@ -322,7 +325,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)) @@ -349,7 +352,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)) @@ -379,7 +382,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)) @@ -405,7 +408,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)) @@ -431,7 +434,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)) @@ -456,7 +459,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/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb index 3538515aee..e6b9878271 100644 --- a/actionview/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + require "set" module ActionView # = Action View Atom Feed Helpers - module 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). diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index 15ab7e304f..b1a14250c3 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + module ActionView # = Action View Cache Helper - module Helpers + module Helpers #:nodoc: module CacheHelper # This helper exposes a method for caching fragments of a view # rather than an entire action or page. This technique is useful @@ -8,10 +10,9 @@ module ActionView # 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://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works + # The best way to use this is by doing recyclable key-based cache expiration + # on top of a cache store like Memcached or Redis that'll automatically + # kick out old entries. # # When using this method, you list the cache dependency as the name of the cache, like so: # @@ -23,10 +24,14 @@ module ActionView # 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 + # views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123 + # ^template path ^template tree digest ^class ^id # - # The cache is thus automatically bumped whenever the project updated_at is touched. + # This cache key is stable, but it's combined with a cache version derived from the project + # record. When the project updated_at is touched, the #cache_version changes, even + # if the key stays stable. This means that unlike a traditional key-based cache expiration + # approach, you won't be generating cache trash, unused keys, simply because the dependent + # record is updated. # # 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: @@ -106,9 +111,9 @@ module ActionView # <%= render_categorizable_events @person.events %> # # This marks every template in the directory as a dependency. To find those - # templates, the wildcard path must be absolutely defined from app/views or paths + # templates, the wildcard path must be absolutely defined from <tt>app/views</tt> or paths # otherwise added with +prepend_view_path+ or +append_view_path+. - # This way the wildcard for `app/views/recordings/events` would be `recordings/events/*` etc. + # This way the wildcard for <tt>app/views/recordings/events</tt> would be <tt>recordings/events/*</tt> etc. # # The pattern used to match explicit dependencies is <tt>/# Template Dependency: (\S+)/</tt>, # so it's important that you type it out just so. @@ -128,14 +133,14 @@ module ActionView # # === Collection Caching # - # When rendering a collection of objects that each use the same partial, a `cached` + # When rendering a collection of objects that each use the same partial, a <tt>:cached</tt> # option can be passed. # # For collections rendered such: # # <%= render partial: 'projects/project', collection: @projects, cached: true %> # - # The `cached: true` will make Action View's rendering read several templates + # The <tt>cached: true</tt> will make Action View's rendering read several templates # from cache at once instead of one call per template. # # Templates in the collection not already cached are written to cache. @@ -196,31 +201,42 @@ module ActionView end # This helper returns the name of a cache key for a given fragment cache - # call. By supplying +skip_digest:+ true to cache, the digestion of cache + # call. By supplying <tt>skip_digest: true</tt> to cache, the digestion of cache # fragments can be manually bypassed. This is useful when cache fragments # cannot be manually expired unless you know the exact key which is the # case when using memcached. # # The digest will be generated using +virtual_path:+ if it is provided. # - def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil) + def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil, digest_path: nil) if skip_digest name else - fragment_name_with_digest(name, virtual_path) + fragment_name_with_digest(name, virtual_path, digest_path) end end - attr_reader :cache_hit # :nodoc: + def digest_path_from_virtual(virtual_path) # :nodoc: + digest = Digestor.digest(name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies) + + if digest.present? + "#{virtual_path}:#{digest}" + else + virtual_path + end + end private - def fragment_name_with_digest(name, virtual_path) + def fragment_name_with_digest(name, virtual_path, digest_path) virtual_path ||= @virtual_path - if virtual_path + + if virtual_path || digest_path name = controller.url_for(name).split("://").last if name.is_a?(Hash) - digest = Digestor.digest name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies - [ name, digest ] + + digest_path ||= digest_path_from_virtual(virtual_path) + + [ digest_path, name ] else name end @@ -228,10 +244,10 @@ module ActionView def fragment_for(name = {}, options = nil, &block) if content = read_fragment_for(name, options) - @cache_hit = true + @view_renderer.cache_hits[@virtual_path] = :hit if defined?(@view_renderer) content else - @cache_hit = false + @view_renderer.cache_hits[@virtual_path] = :miss if defined?(@view_renderer) write_fragment_for(name, options, &block) end end diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb index 719592b5c5..c87c212cc7 100644 --- a/actionview/lib/action_view/helpers/capture_helper.rb +++ b/actionview/lib/action_view/helpers/capture_helper.rb @@ -1,13 +1,15 @@ +# frozen_string_literal: true + require "active_support/core_ext/string/output_safety" module ActionView # = Action View Capture Helper - module Helpers + module Helpers #:nodoc: # 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. + # a way to capture a block of markup for use in a layout through {content_for}[rdoc-ref:ActionView::Helpers::CaptureHelper#content_for]. module CaptureHelper # The capture method extracts part of a template as a String object. # You can then use this object anywhere in your templates, layout, or helpers. @@ -34,6 +36,10 @@ module ActionView # </body> # </html> # + # The return of capture is the string generated by the block. For Example: + # + # @greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500" + # def capture(*args) value = nil buffer = with_output_buffer { value = yield(*args) } @@ -42,7 +48,7 @@ module ActionView end end - # Calling content_for stores a block of markup in an identifier for later use. + # Calling <tt>content_for</tt> stores a block of markup in an identifier for later use. # In order to access this stored content in other templates, helper modules # or the layout, you would pass the identifier as an argument to <tt>content_for</tt>. # @@ -108,7 +114,7 @@ module ActionView # 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 + # Note that <tt>content_for</tt> concatenates (default) the blocks it is given for a particular # identifier in order. For example: # # <% content_for :navigation do %> @@ -125,7 +131,7 @@ module ActionView # # <ul><%= content_for :navigation %></ul> # - # If the flush parameter is true content_for replaces the blocks it is given. For example: + # If the flush parameter is +true+ <tt>content_for</tt> replaces the blocks it is given. For example: # # <% content_for :navigation do %> # <li><%= link_to 'Home', action: 'index' %></li> @@ -145,7 +151,7 @@ module ActionView # # <% 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. + # WARNING: <tt>content_for</tt> 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? @@ -172,7 +178,7 @@ module ActionView result unless content end - # content_for? checks whether any content has been captured yet using `content_for`. + # <tt>content_for?</tt> checks whether any content has been captured yet using <tt>content_for</tt>. # Useful to render parts of your layout differently based on what is in your views. # # <%# This is the layout %> diff --git a/actionview/lib/action_view/helpers/controller_helper.rb b/actionview/lib/action_view/helpers/controller_helper.rb index e86cdca4e4..79cf86c7d1 100644 --- a/actionview/lib/action_view/helpers/controller_helper.rb +++ b/actionview/lib/action_view/helpers/controller_helper.rb @@ -1,14 +1,19 @@ +# frozen_string_literal: true + require "active_support/core_ext/module/attr_internal" module ActionView - module Helpers + module Helpers #:nodoc: # 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 + CONTROLLER_DELEGATES = [:request_forgery_protection_token, :params, + :session, :cookies, :response, :headers, :flash, :action_name, + :controller_name, :controller_path] + + delegate(*CONTROLLER_DELEGATES, to: :controller) def assign_controller(controller) if @_controller = controller @@ -21,6 +26,11 @@ module ActionView def logger controller.logger if controller.respond_to?(:logger) end + + def respond_to?(method_name, include_private = false) + return controller.respond_to?(method_name) if CONTROLLER_DELEGATES.include?(method_name.to_sym) + super + end end end end 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/csrf_helper.rb b/actionview/lib/action_view/helpers/csrf_helper.rb index 2a15d2aa5a..69c59844a6 100644 --- a/actionview/lib/action_view/helpers/csrf_helper.rb +++ b/actionview/lib/action_view/helpers/csrf_helper.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + module ActionView # = Action View CSRF Helper - module Helpers + module Helpers #:nodoc: module CsrfHelper # Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site # request forgery protection parameter and token, respectively. @@ -15,7 +17,7 @@ module ActionView # You don't need to use these tags for regular forms as they generate their own hidden fields. # # For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the - # "X-CSRF-Token" HTTP header. If you are using jQuery with jquery-rails this happens automatically. + # "X-CSRF-Token" HTTP header. If you are using rails-ujs this happens automatically. # def csrf_meta_tags if protect_against_forgery? diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 09dc6ef6bd..4de4fafde0 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "date" require "action_view/helpers/tag_helper" require "active_support/core_ext/array/extract_options" @@ -7,7 +9,7 @@ require "active_support/core_ext/object/acts_like" require "active_support/core_ext/object/with_options" module ActionView - module Helpers + module Helpers #:nodoc: # = Action View Date Helpers # # The Date Helper primarily creates select/option tags for different kinds of dates and times or date and time @@ -95,8 +97,8 @@ module ActionView scope: :'datetime.distance_in_words' }.merge!(options) - 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 = normalize_distance_of_time_argument_to_time(from_time) + to_time = normalize_distance_of_time_argument_to_time(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 @@ -114,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 @@ -129,23 +131,19 @@ 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 - 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 + else + from_year = from_time.year + from_year += 1 if from_time.month >= 3 + to_year = to_time.year + to_year -= 1 if to_time.month < 3 + leap_years = (from_year > to_year) ? 0 : (from_year..to_year).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 remainder = (minutes_with_offset % MINUTES_IN_YEAR) distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR) if remainder < MINUTES_IN_QUARTER_YEAR @@ -207,6 +205,7 @@ module ActionView # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if # you are creating new record. While editing existing record, <tt>:end_year</tt> defaults to # the current selected year plus 5. + # * <tt>:year_format</tt> - Set format of years for year select. Lambda should be passed. # * <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. @@ -277,6 +276,9 @@ module ActionView # # Generates a date select with custom prompts. # date_select("article", "written_on", prompt: { day: 'Select day', month: 'Select month', year: 'Select year' }) # + # # Generates a date select with custom year format. + # date_select("article", "written_on", year_format: ->(year) { "Heisei #{year - 1988}" }) + # # 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 @@ -304,15 +306,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. # @@ -348,8 +350,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. @@ -399,8 +401,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 @@ -438,8 +440,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 @@ -478,8 +480,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 @@ -670,8 +672,6 @@ module ActionView # <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 Date.today, datetime: Date.today.strftime('%G-W%V') # => # <time datetime="2010-W44">November 04, 2010</time> # @@ -683,10 +683,21 @@ 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 + + def normalize_distance_of_time_argument_to_time(value) + if value.is_a?(Numeric) + Time.at(value) + elsif value.respond_to?(:to_time) + value.to_time + else + raise ArgumentError, "#{value.inspect} can't be converted to a Time value" + end + end end class DateTimeSelector #:nodoc: @@ -841,7 +852,7 @@ module ActionView raise ArgumentError, "There are 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) + build_select(:year, build_year_options(val, options)) end end @@ -924,6 +935,21 @@ module ActionView end end + # Looks up year names by number. + # + # year_name(1998) # => 1998 + # + # If the <tt>:year_format</tt> option is passed: + # + # year_name(1998) # => "Heisei 10" + def year_name(number) + if year_format_lambda = @options[:year_format] + year_format_lambda.call(number) + else + number + end + end + def date_order @date_order ||= @options[:order] || translated_date_order end @@ -986,6 +1012,34 @@ module ActionView (select_options.join("\n") + "\n").html_safe end + # Build select option HTML for year. + # If <tt>year_format</tt> option is not passed + # build_year_options(1998, start: 1998, end: 2000) + # => "<option value="1998" selected="selected">1998</option> + # <option value="1999">1999</option> + # <option value="2000">2000</option>" + # + # If <tt>year_format</tt> option is passed + # build_year_options(1998, start: 1998, end: 2000, year_format: ->year { "Heisei #{ year - 1988 }" }) + # => "<option value="1998" selected="selected">Heisei 10</option> + # <option value="1999">Heisei 11</option> + # <option value="2000">Heisei 12</option>" + def build_year_options(selected, options = {}) + start = options.delete(:start) + stop = options.delete(:end) + step = options.delete(:step) + + select_options = [] + start.step(stop, step) do |value| + tag_options = { value: value } + tag_options[:selected] = "selected" if selected == value + text = year_name(value) + select_options << content_tag("option".freeze, 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)]"> @@ -999,7 +1053,7 @@ module ActionView select_options[:disabled] = "disabled" if @options[:disabled] select_options[:class] = css_class_attribute(type, select_options[:class], @options[:with_css_classes]) if @options[:with_css_classes] - select_html = "\n" + select_html = "\n".dup select_html << content_tag("option".freeze, "", value: "") + "\n" if @options[:include_blank] select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt] select_html << select_options_as_html @@ -1081,7 +1135,7 @@ module ActionView # Given an ordering of datetime components, create the selection HTML # and join them with their appropriate separators. def build_selects_from_types(order) - select = "" + select = "".dup 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 diff --git a/actionview/lib/action_view/helpers/debug_helper.rb b/actionview/lib/action_view/helpers/debug_helper.rb index f61ca2c9c2..88ceba414b 100644 --- a/actionview/lib/action_view/helpers/debug_helper.rb +++ b/actionview/lib/action_view/helpers/debug_helper.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + module ActionView # = Action View Debug Helper # # Provides a set of methods for making it easier to debug Rails objects. - module Helpers + module Helpers #:nodoc: module DebugHelper include TagHelper @@ -22,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 26a625e4fe..6e769aa560 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cgi" require "action_view/helpers/date_helper" require "action_view/helpers/tag_helper" @@ -12,12 +14,12 @@ require "active_support/core_ext/string/inflections" module ActionView # = Action View Form Helpers - module Helpers + module Helpers #:nodoc: # 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 + # 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 @@ -164,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 @@ -201,9 +203,9 @@ module ActionView # <%= f.submit %> # <% end %> # - # This also works for the methods in FormOptionHelper and DateHelper that + # This also works for the methods in FormOptionsHelper and DateHelper that # are designed to work with an object as base, like - # FormOptionHelper#collection_select and DateHelper#datetime_select. + # FormOptionsHelper#collection_select and DateHelper#datetime_select. # # === #form_for with a model object # @@ -416,13 +418,13 @@ module ActionView # # 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| + # <%= 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| + # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| %> # ... # <% end %> def form_for(record, options = {}, &block) @@ -474,6 +476,10 @@ module ActionView end private :apply_form_for_options! + 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: @@ -539,6 +545,36 @@ module ActionView # and adds an authenticity token needed for cross site request forgery # protection. # + # === Resource-oriented style + # + # In many of the examples just shown, the +:model+ passed to +form_with+ + # is a _resource_. It corresponds to a set of RESTful routes, most likely + # defined via +resources+ in <tt>config/routes.rb</tt>. + # + # So when passing such a model record, Rails infers the URL and method. + # + # <%= form_with model: @post do |form| %> + # ... + # <% end %> + # + # is then equivalent to something like: + # + # <%= form_with scope: :post, url: post_path(@post), method: :patch do |form| %> + # ... + # <% end %> + # + # And for a new record + # + # <%= form_with model: Post.new do |form| %> + # ... + # <% end %> + # + # is equivalent to something like: + # + # <%= form_with scope: :post, url: posts_path do |form| %> + # ... + # <% end %> + # # ==== +form_with+ options # # * <tt>:url</tt> - The URL the form submits to. Akin to values passed to @@ -554,6 +590,9 @@ module ActionView # Skipped if a <tt>:url</tt> is passed. # * <tt>:scope</tt> - The scope to prefix input field names with and # thereby how the submitted parameters are grouped in controllers. + # * <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>:model</tt> - A model object to infer the <tt>:url</tt> and # <tt>:scope</tt> by, plus fill out input field values. # So if a +title+ attribute is set to "Ahoy!" then a +title+ input @@ -572,10 +611,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. @@ -606,16 +645,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 @@ -632,9 +661,9 @@ module ActionView # <%= form.submit %> # <% end %> # - # Same goes for the methods in FormOptionHelper and DateHelper designed + # Same goes for the methods in FormOptionsHelper and DateHelper designed # to work with an object as a base, like - # FormOptionHelper#collection_select and DateHelper#datetime_select. + # FormOptionsHelper#collection_select and DateHelper#datetime_select. # # === Setting the method # @@ -712,7 +741,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) @@ -791,9 +820,9 @@ module ActionView # _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 + # Note: This also works for the methods in FormOptionsHelper and # DateHelper that are designed to work with an object as base, like - # FormOptionHelper#collection_select and DateHelper#datetime_select. + # FormOptionsHelper#collection_select and DateHelper#datetime_select. # # === Nested Attributes Examples # @@ -988,14 +1017,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| %> @@ -1010,16 +1038,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 @@ -1033,12 +1051,12 @@ module ActionView # <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %> # <% end %> # - # Same goes for the methods in FormOptionHelper and DateHelper designed + # Same goes for the methods in FormOptionsHelper and DateHelper designed # to work with an object as a base, like - # FormOptionHelper#collection_select and DateHelper#datetime_select. + # 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 @@ -1189,7 +1207,7 @@ module ActionView # 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 + Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(options.dup)).render end # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+) @@ -1503,11 +1521,11 @@ module ActionView end private - def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: false, - skip_enforcing_utf8: false, **options) + def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms, + 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) @@ -1517,12 +1535,14 @@ module ActionView html_options[:"accept-charset"] = "UTF-8" html_options[:"data-remote"] = true unless local - if !local && !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, + html_options[:authenticity_token] = options.delete(:authenticity_token) + + if !local && html_options[:authenticity_token].blank? + html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms + end + + if html_options[:authenticity_token] == true + # Include the default authenticity_token, which is only generated when it's 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 @@ -1563,7 +1583,7 @@ module ActionView # In the above block, a +FormBuilder+ object is yielded as the # +person_form+ variable. This allows you to generate the +text_field+ # and +check_box+ fields by specifying their eponymous methods, which - # modify the underlying template and associates the +@person+ model object + # modify the underlying template and associates the <tt>@person</tt> model object # with the form. # # The +FormBuilder+ object can be thought of as serving as a proxy for the @@ -1602,14 +1622,15 @@ module ActionView include ModelNaming # The methods which wrap a form helper call. - class_attribute :field_helpers - self.field_helpers = [:fields_for, :fields, :label, :text_field, :password_field, - :hidden_field, :file_field, :text_area, :check_box, - :radio_button, :color_field, :search_field, - :telephone_field, :phone_field, :date_field, - :time_field, :datetime_field, :datetime_local_field, - :month_field, :week_field, :url_field, :email_field, - :number_field, :range_field] + class_attribute :field_helpers, default: [ + :fields_for, :fields, :label, :text_field, :password_field, + :hidden_field, :file_field, :text_area, :check_box, + :radio_button, :color_field, :search_field, + :telephone_field, :phone_field, :date_field, + :time_field, :datetime_field, :datetime_local_field, + :month_field, :week_field, :url_field, :email_field, + :number_field, :range_field + ] attr_accessor :object_name, :object, :options @@ -1640,6 +1661,7 @@ module ActionView @nested_child_index = {} @object_name, @object, @template, @options = object_name, object, template, options @default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {} + @default_html_options = @default_options.except(:skip_default_ids, :allow_method_names_outside_object) convert_to_legacy_options(@options) @@ -1724,9 +1746,9 @@ module ActionView # _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 + # Note: This also works for the methods in FormOptionsHelper and # DateHelper that are designed to work with an object as base, like - # FormOptionHelper#collection_select and DateHelper#datetime_select. + # FormOptionsHelper#collection_select and DateHelper#datetime_select. # # === Nested Attributes Examples # @@ -1948,11 +1970,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 @@ -2158,11 +2180,11 @@ module ActionView # <%= 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". + # In the example above, if <tt>@post</tt> 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: + # Those labels can be customized using I18n under the +helpers.submit+ key and using + # <tt>%{model}</tt> for translation interpolation: # # en: # helpers: @@ -2170,7 +2192,7 @@ module ActionView # create: "Create a %{model}" # update: "Confirm changes to %{model}" # - # It also searches for a key specific for the given object: + # It also searches for a key specific to the given object: # # en: # helpers: @@ -2191,11 +2213,11 @@ module ActionView # <%= 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". + # In the example above, if <tt>@post</tt> 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: + # Those labels can be customized using I18n under the +helpers.submit+ key + # (the same as submit helper) and using <tt>%{model}</tt> for translation interpolation: # # en: # helpers: @@ -2203,7 +2225,7 @@ module ActionView # create: "Create a %{model}" # update: "Confirm changes to %{model}" # - # It also searches for a key specific for the given object: + # It also searches for a key specific to the given object: # # en: # helpers: @@ -2228,7 +2250,7 @@ module ActionView @template.button_tag(value, options, &block) end - def emitted_hidden_id? + def emitted_hidden_id? # :nodoc: @emitted_hidden_id ||= nil end @@ -2248,7 +2270,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}" @@ -2313,8 +2340,6 @@ module ActionView end ActiveSupport.on_load(:action_view) do - cattr_accessor(:default_form_builder, instance_writer: false, instance_reader: false) do - ::ActionView::Helpers::FormBuilder - end + cattr_accessor :default_form_builder, instance_writer: false, instance_reader: false, default: ::ActionView::Helpers::FormBuilder end end diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb index 07d4310a4e..2b9d55a019 100644 --- a/actionview/lib/action_view/helpers/form_options_helper.rb +++ b/actionview/lib/action_view/helpers/form_options_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cgi" require "erb" require "action_view/helpers/form_helper" @@ -7,14 +9,14 @@ require "active_support/core_ext/array/wrap" module ActionView # = Action View Form Option Helpers - module Helpers + module Helpers #:nodoc: # Provides a number of methods for turning different kinds of containers into a set of option tags. # # 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. # - # select("post", "category", Post::CATEGORIES, {include_blank: true}) + # select("post", "category", Post::CATEGORIES, { include_blank: true }) # # could become: # @@ -28,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: # @@ -41,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: # @@ -67,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: # @@ -80,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"> @@ -105,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: # @@ -212,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 @@ -277,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 @@ -317,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> # @@ -455,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 @@ -788,7 +794,7 @@ module ActionView 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) + public_or_deprecated_send(element, value_method) if selected.call(element) end.compact else selected @@ -796,7 +802,15 @@ module ActionView end def value_for_collection(item, value) - value.respond_to?(:call) ? value.call(item) : item.send(value) + value.respond_to?(:call) ? value.call(item) : public_or_deprecated_send(item, value) + end + + def public_or_deprecated_send(item, value) + item.public_send(value) + rescue NoMethodError + raise unless item.respond_to?(value, true) && !item.respond_to?(value) + ActiveSupport::Deprecation.warn "Using private methods from view helpers is deprecated (calling private #{item.class}##{value})" + item.send(value) end def prompt_text(prompt) @@ -814,7 +828,7 @@ module ActionView # # Please refer to the documentation of the base helper for details. def select(method, choices = nil, options = {}, html_options = {}, &block) - @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options), &block) + @template.select(@object_name, method, choices, objectify_options(options), @default_html_options.merge(html_options), &block) end # Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders: @@ -826,7 +840,7 @@ module ActionView # # Please refer to the documentation of the base helper for details. 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)) + @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options)) end # Wraps ActionView::Helpers::FormOptionsHelper#grouped_collection_select for form builders: @@ -838,7 +852,7 @@ module ActionView # # Please refer to the documentation of the base helper for details. 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)) + @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_html_options.merge(html_options)) end # Wraps ActionView::Helpers::FormOptionsHelper#time_zone_select for form builders: @@ -850,7 +864,7 @@ module ActionView # # Please refer to the documentation of the base helper for details. 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)) + @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_html_options.merge(html_options)) end # Wraps ActionView::Helpers::FormOptionsHelper#collection_check_boxes for form builders: @@ -862,7 +876,7 @@ module ActionView # # Please refer to the documentation of the base helper for details. def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block) - @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block) + @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block) end # Wraps ActionView::Helpers::FormOptionsHelper#collection_radio_buttons for form builders: @@ -874,7 +888,7 @@ module ActionView # # Please refer to the documentation of the base helper for details. def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block) - @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block) + @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block) end end end diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index ffc64e7118..ba09738beb 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cgi" require "action_view/helpers/tag_helper" require "active_support/core_ext/string/output_safety" @@ -5,7 +7,7 @@ require "active_support/core_ext/module/attribute_accessors" module ActionView # = Action View Form Tag Helpers - module Helpers + module Helpers #:nodoc: # 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. # @@ -18,7 +20,9 @@ module ActionView include TextHelper mattr_accessor :embed_authenticity_token_in_remote_forms - self.embed_authenticity_token_in_remote_forms = false + 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. @@ -113,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> @@ -272,7 +276,7 @@ module ActionView # 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.merge(type: :file)) + text_field_tag(name, nil, convert_direct_upload_option_to_url(options.merge(type: :file))) end # Creates a password field, a masked text field that will hide the users input behind a mask character. @@ -385,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" /> @@ -454,7 +458,7 @@ module ActionView # submit tag but it isn't supported in legacy browsers. However, # the button tag does allow for richer labels such as images and emphasis, # so this helper will also accept a block. By default, it will create - # a button tag with type `submit`, if type is not given. + # a button tag with type <tt>submit</tt>, if type is not given. # # ==== Options # * <tt>:data</tt> - This option can be used to add custom data attributes. @@ -532,22 +536,23 @@ module ActionView # # ==== Examples # image_submit_tag("login.png") - # # => <input alt="Login" src="/assets/login.png" type="image" /> + # # => <input src="/assets/login.png" type="image" /> # # image_submit_tag("purchase.png", disabled: true) - # # => <input alt="Purchase" disabled="disabled" src="/assets/purchase.png" type="image" /> + # # => <input disabled="disabled" src="/assets/purchase.png" type="image" /> # # image_submit_tag("search.png", class: 'search_button', alt: 'Find') - # # => <input alt="Find" class="search_button" src="/assets/search.png" type="image" /> + # # => <input class="search_button" src="/assets/search.png" type="image" /> # # image_submit_tag("agree.png", disabled: true, class: "agree_disagree_button") - # # => <input alt="Agree" class="agree_disagree_button" disabled="disabled" src="/assets/agree.png" type="image" /> + # # => <input class="agree_disagree_button" disabled="disabled" src="/assets/agree.png" type="image" /> # # image_submit_tag("save.png", data: { confirm: "Are you sure?" }) - # # => <input alt="Save" src="/assets/save.png" data-confirm="Are you sure?" type="image" /> + # # => <input src="/assets/save.png" data-confirm="Are you sure?" type="image" /> def image_submit_tag(source, options = {}) options = options.stringify_keys - tag :input, { "alt" => image_alt(source), "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. @@ -864,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 @@ -902,6 +907,13 @@ module ActionView tag_options.delete("data-disable-with") end + + def convert_direct_upload_option_to_url(options) + if options.delete(:direct_upload) && respond_to?(:rails_direct_uploads_url) + options["data-direct-upload-url"] = rails_direct_uploads_url + end + options + end end end end diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb index 22e1e74ad6..830088bea3 100644 --- a/actionview/lib/action_view/helpers/javascript_helper.rb +++ b/actionview/lib/action_view/helpers/javascript_helper.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require "action_view/helpers/tag_helper" module ActionView - module Helpers + module Helpers #:nodoc: module JavaScriptHelper JS_ESCAPE_MAP = { '\\' => '\\\\', @@ -13,8 +15,8 @@ module ActionView "'" => "\\'" } - JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = "
" - JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = "
" + JS_ESCAPE_MAP["\342\200\250".dup.force_encoding(Encoding::UTF_8).encode!] = "
" + JS_ESCAPE_MAP["\342\200\251".dup.force_encoding(Encoding::UTF_8).encode!] = "
" # Escapes carriage returns and single and double quotes for JavaScript segments. # @@ -61,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? @@ -70,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/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index b6bc5f4f6f..35206b7e48 100644 --- a/actionview/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/hash/keys" require "active_support/core_ext/string/output_safety" require "active_support/number_helper" @@ -98,6 +100,9 @@ module ActionView # absolute value of the number. # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when # the argument is invalid. + # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). # # ==== Examples # @@ -115,6 +120,8 @@ module ActionView # # => R$1234567890,50 # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u") # # => 1234567890,50 R$ + # number_to_currency(1234567890.50, strip_insignificant_zeros: true) + # # => "$1,234,567,890.5" def number_to_currency(number, options = {}) delegate_number_helper_method(:number_to_currency, number, options) end diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb index 25defd1276..279cde5e76 100644 --- a/actionview/lib/action_view/helpers/output_safety_helper.rb +++ b/actionview/lib/action_view/helpers/output_safety_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/string/output_safety" module ActionView #:nodoc: 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 f7ee573035..0000000000 --- a/actionview/lib/action_view/helpers/record_tag_helper.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ActionView - module Helpers - module RecordTagHelper - def div_for(*) - 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(*) - 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/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb index 7d7f2393ff..1e12aa2736 100644 --- a/actionview/lib/action_view/helpers/rendering_helper.rb +++ b/actionview/lib/action_view/helpers/rendering_helper.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + module ActionView - module Helpers + module Helpers #:nodoc: # = Action View Rendering # # Implements methods that allow rendering from a view context. @@ -11,7 +13,6 @@ module ActionView # * <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. # * <tt>:plain</tt> - Renders the text passed in out. Setting the content # type as <tt>text/plain</tt>. # * <tt>:html</tt> - Renders the HTML safe string passed in out, otherwise diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 1e9b813d3d..f4fa133f55 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -1,18 +1,21 @@ +# frozen_string_literal: true + require "active_support/core_ext/object/try" require "rails-html-sanitizer" module ActionView # = Action View Sanitize Helpers - module 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 - # Sanitizes HTML input, stripping all tags and attributes that aren't whitelisted. + # Sanitizes HTML input, stripping all but known-safe tags and attributes. # # It also strips href/src attributes with unsafe protocols like # <tt>javascript:</tt>, while also protecting against attempts to use Unicode, # ASCII, and hex character references to work around these protocol filters. + # All special characters will be escaped. # # The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information. @@ -20,8 +23,7 @@ module ActionView # Custom sanitization rules can also be provided. # # Please note that sanitizing user-provided text does not guarantee that the - # resulting markup is valid or even well-formed. For example, the output may still - # contain unescaped characters like <tt><</tt>, <tt>></tt>, or <tt>&</tt>. + # resulting markup is valid or even well-formed. # # ==== Options # @@ -38,7 +40,7 @@ module ActionView # # <%= sanitize @comment.body %> # - # Providing custom whitelisted tags and attributes: + # Providing custom lists of permitted tags and attributes: # # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %> # @@ -86,7 +88,7 @@ module ActionView self.class.white_list_sanitizer.sanitize_css(style) end - # Strips all HTML tags from +html+, including comments. + # Strips all HTML tags from +html+, including comments and special characters. # # strip_tags("Strip <i>these</i> tags!") # # => Strip these tags! @@ -96,8 +98,11 @@ module ActionView # # strip_tags("<div id='top-bar'>Welcome to my website!</div>") # # => Welcome to my website! + # + # strip_tags("> A quote from Smith & Wesson") + # # => > A quote from Smith & Wesson def strip_tags(html) - self.class.full_sanitizer.sanitize(html, encode_special_chars: false) + self.class.full_sanitizer.sanitize(html) end # Strips all link tags from +html+ leaving just the link text. @@ -110,6 +115,9 @@ module ActionView # # strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.') # # => Blog: Visit. + # + # strip_links('<<a href="https://example.org">malformed & link</a>') + # # => <malformed & link def strip_links(html) self.class.link_sanitizer.sanitize(html) end @@ -118,7 +126,7 @@ module ActionView attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer # Vendors the full, link and white list sanitizers. - # Provided strictly for compatibility and can be removed in Rails 5.1. + # Provided strictly for compatibility and can be removed in Rails 6. def sanitizer_vendor Rails::Html::Sanitizer end diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb index 306b71c85e..d12989ea64 100644 --- a/actionview/lib/action_view/helpers/tag_helper.rb +++ b/actionview/lib/action_view/helpers/tag_helper.rb @@ -1,4 +1,4 @@ -# frozen-string-literal: true +# frozen_string_literal: true require "active_support/core_ext/string/output_safety" require "set" @@ -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, '"'.freeze)}") + value.gsub!('"'.freeze, """.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="["Chicago","IL"]"></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 & shut.png" /> # - # tag("img", {src: "open & shut.png"}, false, false) + # tag("img", { src: "open & shut.png" }, false, false) # # => <img src="open & 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="["Chicago","IL"]" /> def tag(name = nil, options = nil, open = false, escape = true) if name.nil? diff --git a/actionview/lib/action_view/helpers/tags.rb b/actionview/lib/action_view/helpers/tags.rb index a4f6eb0150..566668b958 100644 --- a/actionview/lib/action_view/helpers/tags.rb +++ b/actionview/lib/action_view/helpers/tags.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + module ActionView - module Helpers + module Helpers #:nodoc: module Tags #:nodoc: extend ActiveSupport::Autoload diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index 0895533a60..eef527d36f 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: @@ -33,7 +35,7 @@ module ActionView private - def value(object) + def value if @allow_method_names_outside_object object.public_send @method_name if object && object.respond_to?(@method_name) else @@ -41,19 +43,19 @@ module ActionView end end - def value_before_type_cast(object) + def value_before_type_cast unless object.nil? method_before_type_cast = @method_name + "_before_type_cast" - if value_came_from_user?(object) && object.respond_to?(method_before_type_cast) + if value_came_from_user? && object.respond_to?(method_before_type_cast) object.public_send(method_before_type_cast) else - value(object) + value end end end - def value_came_from_user?(object) + def value_came_from_user? method_name = "#{@method_name}_came_from_user?" !object.respond_to?(method_name) || object.public_send(method_name) end @@ -95,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 @@ -107,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 @@ -136,7 +138,7 @@ module ActionView end def sanitized_value(value) - value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase + value.to_s.gsub(/\s/, "_").gsub(/[^-[[:word:]]]/, "").mb_chars.downcase.to_s end def select_content_tag(option_tags, options, html_options) @@ -148,7 +150,7 @@ module ActionView options[:include_blank] ||= true unless options[:prompt] end - value = options.fetch(:selected) { value(object) } + value = options.fetch(:selected) { value() } select = content_tag("select", add_options(option_tags, options, value), html_options) if html_options["multiple"] && options.fetch(:include_hidden, true) @@ -168,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 @@ -181,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 02f87fc89f..4327e07cae 100644 --- a/actionview/lib/action_view/helpers/tags/check_box.rb +++ b/actionview/lib/action_view/helpers/tags/check_box.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_view/helpers/tags/checkable" module ActionView @@ -16,7 +18,7 @@ module ActionView options = @options.stringify_keys options["type"] = "checkbox" options["value"] = @checked_value - options["checked"] = "checked" if input_checked?(object, options) + options["checked"] = "checked" if input_checked?(options) if options["multiple"] add_default_name_and_id_for_value(@checked_value, options) diff --git a/actionview/lib/action_view/helpers/tags/checkable.rb b/actionview/lib/action_view/helpers/tags/checkable.rb index 052e9df662..776fefe778 100644 --- a/actionview/lib/action_view/helpers/tags/checkable.rb +++ b/actionview/lib/action_view/helpers/tags/checkable.rb @@ -1,13 +1,15 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: module Checkable # :nodoc: - def input_checked?(object, options) + def input_checked?(options) if options.has_key?("checked") checked = options.delete "checked" checked == true || checked == "checked" else - checked?(value(object)) + checked?(value) end end end 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 7252d4f2d9..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,3 +1,5 @@ +# frozen_string_literal: true + require "action_view/helpers/tags/collection_helpers" module ActionView @@ -10,6 +12,7 @@ module ActionView def check_box(extra_html_options = {}) html_options = extra_html_options.merge(@input_html_options) html_options[:multiple] = true + html_options[:skip_default_ids] = false @template_object.check_box(@object_name, @method_name, html_options, @value, nil) end end diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb index 75d237eb35..e1ad11bff8 100644 --- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb +++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: 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 a5f72af9ff..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,3 +1,5 @@ +# frozen_string_literal: true + require "action_view/helpers/tags/collection_helpers" module ActionView @@ -9,6 +11,7 @@ module ActionView class RadioButtonBuilder < Builder # :nodoc: def radio_button(extra_html_options = {}) html_options = extra_html_options.merge(@input_html_options) + html_options[:skip_default_ids] = false @template_object.radio_button(@object_name, @method_name, @value, html_options) end end diff --git a/actionview/lib/action_view/helpers/tags/collection_select.rb b/actionview/lib/action_view/helpers/tags/collection_select.rb index 4365c714eb..6a3af1b256 100644 --- a/actionview/lib/action_view/helpers/tags/collection_select.rb +++ b/actionview/lib/action_view/helpers/tags/collection_select.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: @@ -13,7 +15,7 @@ module ActionView def render option_tags_options = { - selected: @options.fetch(:selected) { value(@object) }, + selected: @options.fetch(:selected) { value }, disabled: @options[:disabled] } diff --git a/actionview/lib/action_view/helpers/tags/color_field.rb b/actionview/lib/action_view/helpers/tags/color_field.rb index b4bbe92746..39ab1285c3 100644 --- a/actionview/lib/action_view/helpers/tags/color_field.rb +++ b/actionview/lib/action_view/helpers/tags/color_field.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: class ColorField < TextField # :nodoc: def render options = @options.stringify_keys - options["value"] ||= validate_color_string(value(object)) + options["value"] ||= validate_color_string(value) @options = options super end @@ -13,7 +15,7 @@ module ActionView def validate_color_string(string) regex = /#[0-9a-fA-F]{6}/ - if regex.match(string) + if regex.match?(string) string.downcase else "#000000" diff --git a/actionview/lib/action_view/helpers/tags/date_field.rb b/actionview/lib/action_view/helpers/tags/date_field.rb index c22be0db29..b17a907651 100644 --- a/actionview/lib/action_view/helpers/tags/date_field.rb +++ b/actionview/lib/action_view/helpers/tags/date_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/date_select.rb b/actionview/lib/action_view/helpers/tags/date_select.rb index 638c134deb..fe4e3914d7 100644 --- a/actionview/lib/action_view/helpers/tags/date_select.rb +++ b/actionview/lib/action_view/helpers/tags/date_select.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/time/calculations" module ActionView @@ -27,7 +29,7 @@ module ActionView end def datetime_selector(options, html_options) - datetime = options.fetch(:selected) { value(object) || default_datetime(options) } + datetime = options.fetch(:selected) { value || default_datetime(options) } @auto_index ||= nil options = options.dup diff --git a/actionview/lib/action_view/helpers/tags/datetime_field.rb b/actionview/lib/action_view/helpers/tags/datetime_field.rb index b3940c7e44..5d9b639b1b 100644 --- a/actionview/lib/action_view/helpers/tags/datetime_field.rb +++ b/actionview/lib/action_view/helpers/tags/datetime_field.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: class DatetimeField < TextField # :nodoc: def render options = @options.stringify_keys - options["value"] ||= format_date(value(object)) + options["value"] ||= format_date(value) options["min"] = format_date(datetime_value(options["min"])) options["max"] = format_date(datetime_value(options["max"])) @options = options diff --git a/actionview/lib/action_view/helpers/tags/datetime_local_field.rb b/actionview/lib/action_view/helpers/tags/datetime_local_field.rb index b4a74185d1..d8f8fd00d1 100644 --- a/actionview/lib/action_view/helpers/tags/datetime_local_field.rb +++ b/actionview/lib/action_view/helpers/tags/datetime_local_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/datetime_select.rb b/actionview/lib/action_view/helpers/tags/datetime_select.rb index 563de1840e..dc5570931d 100644 --- a/actionview/lib/action_view/helpers/tags/datetime_select.rb +++ b/actionview/lib/action_view/helpers/tags/datetime_select.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/email_field.rb b/actionview/lib/action_view/helpers/tags/email_field.rb index 7ce3ccb9bf..0c3b9224fa 100644 --- a/actionview/lib/action_view/helpers/tags/email_field.rb +++ b/actionview/lib/action_view/helpers/tags/email_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/file_field.rb b/actionview/lib/action_view/helpers/tags/file_field.rb index 476b820d84..0b1d9bb778 100644 --- a/actionview/lib/action_view/helpers/tags/file_field.rb +++ b/actionview/lib/action_view/helpers/tags/file_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb b/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb index 20e312dd0f..f24cb4beea 100644 --- a/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb +++ b/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: @@ -15,7 +17,7 @@ module ActionView def render option_tags_options = { - selected: @options.fetch(:selected) { value(@object) }, + selected: @options.fetch(:selected) { value }, disabled: @options[:disabled] } diff --git a/actionview/lib/action_view/helpers/tags/hidden_field.rb b/actionview/lib/action_view/helpers/tags/hidden_field.rb index c3757c2461..e014bd3aef 100644 --- a/actionview/lib/action_view/helpers/tags/hidden_field.rb +++ b/actionview/lib/action_view/helpers/tags/hidden_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb index cab15ae201..02bd099784 100644 --- a/actionview/lib/action_view/helpers/tags/label.rb +++ b/actionview/lib/action_view/helpers/tags/label.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: @@ -73,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/month_field.rb b/actionview/lib/action_view/helpers/tags/month_field.rb index 4c0fb846ee..93b2bf11f0 100644 --- a/actionview/lib/action_view/helpers/tags/month_field.rb +++ b/actionview/lib/action_view/helpers/tags/month_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/number_field.rb b/actionview/lib/action_view/helpers/tags/number_field.rb index 4f95b1b4de..41c696423c 100644 --- a/actionview/lib/action_view/helpers/tags/number_field.rb +++ b/actionview/lib/action_view/helpers/tags/number_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/password_field.rb b/actionview/lib/action_view/helpers/tags/password_field.rb index 444ef65074..9f10f5236e 100644 --- a/actionview/lib/action_view/helpers/tags/password_field.rb +++ b/actionview/lib/action_view/helpers/tags/password_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/placeholderable.rb b/actionview/lib/action_view/helpers/tags/placeholderable.rb index cf7b117614..e9f7601e57 100644 --- a/actionview/lib/action_view/helpers/tags/placeholderable.rb +++ b/actionview/lib/action_view/helpers/tags/placeholderable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/radio_button.rb b/actionview/lib/action_view/helpers/tags/radio_button.rb index 43dbd32083..621db2b1b5 100644 --- a/actionview/lib/action_view/helpers/tags/radio_button.rb +++ b/actionview/lib/action_view/helpers/tags/radio_button.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_view/helpers/tags/checkable" module ActionView @@ -15,7 +17,7 @@ module ActionView options = @options.stringify_keys options["type"] = "radio" options["value"] = @tag_value - options["checked"] = "checked" if input_checked?(object, options) + options["checked"] = "checked" if input_checked?(options) add_default_name_and_id_for_value(@tag_value, options) tag("input", options) end diff --git a/actionview/lib/action_view/helpers/tags/range_field.rb b/actionview/lib/action_view/helpers/tags/range_field.rb index f98ae88043..66d1bbac5b 100644 --- a/actionview/lib/action_view/helpers/tags/range_field.rb +++ b/actionview/lib/action_view/helpers/tags/range_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/search_field.rb b/actionview/lib/action_view/helpers/tags/search_field.rb index a848aeabfa..f209348904 100644 --- a/actionview/lib/action_view/helpers/tags/search_field.rb +++ b/actionview/lib/action_view/helpers/tags/search_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb index 667c7e945a..790721a0b7 100644 --- a/actionview/lib/action_view/helpers/tags/select.rb +++ b/actionview/lib/action_view/helpers/tags/select.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: @@ -13,7 +15,7 @@ module ActionView def render option_tags_options = { - selected: @options.fetch(:selected) { value(@object) }, + selected: @options.fetch(:selected) { value }, disabled: @options[:disabled] } @@ -33,7 +35,7 @@ module ActionView # [nil, []] # { nil => [] } def grouped_choices? - !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last + !@choices.blank? && @choices.first.respond_to?(:last) && Array === @choices.first.last end end end diff --git a/actionview/lib/action_view/helpers/tags/tel_field.rb b/actionview/lib/action_view/helpers/tags/tel_field.rb index 987bb9e67a..ab1caaac48 100644 --- a/actionview/lib/action_view/helpers/tags/tel_field.rb +++ b/actionview/lib/action_view/helpers/tags/tel_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/text_area.rb b/actionview/lib/action_view/helpers/tags/text_area.rb index 31e3a9e9b1..4519082ff6 100644 --- a/actionview/lib/action_view/helpers/tags/text_area.rb +++ b/actionview/lib/action_view/helpers/tags/text_area.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_view/helpers/tags/placeholderable" module ActionView @@ -14,7 +16,7 @@ module ActionView 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) + content_tag("textarea", options.delete("value") { value_before_type_cast }, options) end end end diff --git a/actionview/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb index 613cade7b3..d92967e212 100644 --- a/actionview/lib/action_view/helpers/tags/text_field.rb +++ b/actionview/lib/action_view/helpers/tags/text_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_view/helpers/tags/placeholderable" module ActionView @@ -10,7 +12,7 @@ module ActionView 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"] = options.fetch("value") { value_before_type_cast } unless field_type == "file" add_default_name_and_id(options) tag("input", options) end diff --git a/actionview/lib/action_view/helpers/tags/time_field.rb b/actionview/lib/action_view/helpers/tags/time_field.rb index 0e90a3aed7..9384a83a3e 100644 --- a/actionview/lib/action_view/helpers/tags/time_field.rb +++ b/actionview/lib/action_view/helpers/tags/time_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/time_select.rb b/actionview/lib/action_view/helpers/tags/time_select.rb index 0b06311d25..ba3dcb64e3 100644 --- a/actionview/lib/action_view/helpers/tags/time_select.rb +++ b/actionview/lib/action_view/helpers/tags/time_select.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/time_zone_select.rb b/actionview/lib/action_view/helpers/tags/time_zone_select.rb index 80d165ec7e..1d06096096 100644 --- a/actionview/lib/action_view/helpers/tags/time_zone_select.rb +++ b/actionview/lib/action_view/helpers/tags/time_zone_select.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: @@ -11,7 +13,7 @@ module ActionView def render select_content_tag( - time_zone_options_for_select(value(@object) || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options + time_zone_options_for_select(value || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options ) end end diff --git a/actionview/lib/action_view/helpers/tags/translator.rb b/actionview/lib/action_view/helpers/tags/translator.rb index ced835eaa8..e81ca3aef0 100644 --- a/actionview/lib/action_view/helpers/tags/translator.rb +++ b/actionview/lib/action_view/helpers/tags/translator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: @@ -14,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/tags/url_field.rb b/actionview/lib/action_view/helpers/tags/url_field.rb index d76340178d..395fec67e7 100644 --- a/actionview/lib/action_view/helpers/tags/url_field.rb +++ b/actionview/lib/action_view/helpers/tags/url_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/tags/week_field.rb b/actionview/lib/action_view/helpers/tags/week_field.rb index 835d1667d7..572535d1d6 100644 --- a/actionview/lib/action_view/helpers/tags/week_field.rb +++ b/actionview/lib/action_view/helpers/tags/week_field.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Helpers module Tags # :nodoc: diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index bc922f9ce8..a338d076e4 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/string/filters" require "active_support/core_ext/array/extract_options" @@ -11,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>" @@ -126,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) @@ -186,7 +188,7 @@ module ActionView unless separator.empty? text.split(separator).each do |value| - if value.match(regex) + if value.match?(regex) phrase = value break end @@ -226,7 +228,7 @@ module ActionView # pluralize(2, 'Person', locale: :de) # # => 2 Personen def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale) - word = if (count == 1 || count =~ /^1(\.0+)?$/) + word = if count == 1 || count =~ /^1(\.0+)?$/ singular else plural || singular.pluralize(locale) @@ -420,7 +422,7 @@ module ActionView def to_s value = @values[@index].to_s @index = next_index - return value + value end private @@ -444,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 47ed41a129..ba82dcab3e 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -1,18 +1,19 @@ +# frozen_string_literal: true + require "action_view/helpers/tag_helper" require "active_support/core_ext/string/access" require "i18n/exceptions" module ActionView # = Action View Translation Helpers - module Helpers + module Helpers #:nodoc: module TranslationHelper extend ActiveSupport::Concern include TagHelper included do - mattr_accessor :debug_missing_translation - self.debug_missing_translation = true + mattr_accessor :debug_missing_translation, default: true end # Delegates to <tt>I18n#translate</tt> but also performs three additional @@ -58,11 +59,9 @@ module ActionView # they can provide HTML values for. 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.first.kind_of?(Symbol) - options[:default] = remaining_defaults + if options.has_key?(:default) + remaining_defaults = Array(options.delete(:default)).compact + options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol) end # If the user has explicitly decided to NOT raise errors, pass that option to I18n. @@ -84,8 +83,11 @@ module ActionView end end translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise)) - - translation.respond_to?(:html_safe) ? translation.html_safe : translation + if translation.respond_to?(:map) + translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element } + else + translation.respond_to?(:html_safe) ? translation.html_safe : translation + end else I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise)) end @@ -96,7 +98,7 @@ module ActionView raise e if raise_error keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) - title = "translation missing: #{keys.join('.')}" + title = "translation missing: #{keys.join('.')}".dup interpolations = options.except(:default, :scope) if interpolations.any? @@ -121,9 +123,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 a306903c60..52bffaab84 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_view/helpers/javascript_helper" require "active_support/core_ext/array/access" require "active_support/core_ext/hash/keys" @@ -137,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 %> @@ -542,7 +549,7 @@ module ActionView return false unless request.get? || request.head? - check_parameters ||= !options.is_a?(String) && options.try(:delete, :check_parameters) + check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters) url_string = URI.parser.unescape(url_for(options)).force_encoding(Encoding::BINARY) # We ignore any extra parameters in the request_uri if the @@ -552,7 +559,10 @@ module ActionView request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path request_uri = URI.parser.unescape(request_uri).force_encoding(Encoding::BINARY) - url_string.chomp!("/") if url_string.start_with?("/") && url_string != "/" + if url_string.start_with?("/") && url_string != "/" + url_string.chomp!("/") + request_uri.chomp!("/") + end if %r{^\w+://}.match?(url_string) url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}" @@ -584,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: {}) @@ -607,9 +634,9 @@ module ActionView # suitable for use as the names and values of form input fields: # # to_form_params(name: 'David', nationality: 'Danish') - # # => [{name: :name, value: 'David'}, {name: 'nationality', value: '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']}) @@ -621,11 +648,6 @@ module ActionView # # => [{name: 'country[name]', value: 'Denmark'}] def to_form_params(attribute, namespace = nil) attribute = if attribute.respond_to?(:permitted?) - unless attribute.permitted? - raise ArgumentError, "Attempting to generate a buttom from non-sanitized request parameters!" \ - " Whitelist and sanitize passed parameters to be secure." - end - attribute.to_h else attribute @@ -644,7 +666,7 @@ module ActionView params.push(*to_form_params(value, array_prefix)) end else - params << { name: namespace, value: attribute.to_param } + params << { name: namespace.to_s, value: attribute.to_param } end params.sort_by { |pair| pair[:name] } diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index 81feb90486..3e6d352c15 100644 --- a/actionview/lib/action_view/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + require "action_view/rendering" -require "active_support/core_ext/module/remove_method" +require "active_support/core_ext/module/redefine_method" module ActionView # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in @@ -204,9 +206,9 @@ module ActionView include ActionView::Rendering included do - class_attribute :_layout, :_layout_conditions, instance_accessor: false - self._layout = nil - self._layout_conditions = {} + class_attribute :_layout, instance_accessor: false + class_attribute :_layout_conditions, instance_accessor: false, default: {} + _write_layout_method end @@ -277,7 +279,7 @@ module ActionView # 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 # :nodoc: - remove_possible_method(:_layout) + silence_redefinition_of_method(:_layout) prefixes = /\blayouts/.match?(_implied_layout_name) ? [] : ["layouts"] default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, [], { formats: formats }).first || super" diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb index d03e1a51b8..d4ac77e10f 100644 --- a/actionview/lib/action_view/log_subscriber.rb +++ b/actionview/lib/action_view/log_subscriber.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/log_subscriber" module ActionView @@ -14,7 +16,7 @@ module ActionView def render_template(event) info do - message = " Rendered #{from_rails_root(event.payload[:identifier])}" + message = " Rendered #{from_rails_root(event.payload[:identifier])}".dup message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] message << " (#{event.duration.round(1)}ms)" end @@ -22,10 +24,10 @@ module ActionView def render_partial(event) info do - message = " Rendered #{from_rails_root(event.payload[:identifier])}" + message = " Rendered #{from_rails_root(event.payload[:identifier])}".dup message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] message << " (#{event.duration.round(1)}ms)" - message << " #{cache_message(event.payload)}" if event.payload.key?(:cache_hit) + message << " #{cache_message(event.payload)}" unless event.payload[:cache_hit].nil? message end end @@ -73,16 +75,17 @@ module ActionView end def cache_message(payload) # :doc: - if payload[:cache_hit] + case payload[:cache_hit] + when :hit "[cache hit]" - else + when :miss "[cache miss]" end end def log_rendering_start(payload) info do - message = " Rendering #{from_rails_root(payload[:identifier])}" + message = " Rendering #{from_rails_root(payload[:identifier])}".dup message << " within #{from_rails_root(payload[:layout])}" if payload[:layout] message end diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index f385a7cd04..0e56eca35c 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "concurrent/map" require "active_support/core_ext/module/remove_method" require "active_support/core_ext/module/attribute_accessors" @@ -14,11 +16,9 @@ module ActionView class LookupContext #:nodoc: attr_accessor :prefixes, :rendered_format - mattr_accessor :fallbacks - @@fallbacks = FallbackFileSystemResolver.instances + mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances - mattr_accessor :registered_details - self.registered_details = [] + mattr_accessor :registered_details, default: [] def self.register_detail(name, &block) registered_details << name diff --git a/actionview/lib/action_view/model_naming.rb b/actionview/lib/action_view/model_naming.rb index b6ed13424e..23cca8d607 100644 --- a/actionview/lib/action_view/model_naming.rb +++ b/actionview/lib/action_view/model_naming.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module ModelNaming #:nodoc: # Converts the given object to an ActiveModel compliant one. diff --git a/actionview/lib/action_view/path_set.rb b/actionview/lib/action_view/path_set.rb index 6688519ffd..691b53e2da 100644 --- a/actionview/lib/action_view/path_set.rb +++ b/actionview/lib/action_view/path_set.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView #:nodoc: # = Action View PathSet # diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index d344d98f4b..12d06bf376 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_view" require "rails" @@ -5,8 +7,10 @@ module ActionView # = Action View Railtie class Railtie < Rails::Engine # :nodoc: config.action_view = ActiveSupport::OrderedOptions.new - config.action_view.embed_authenticity_token_in_remote_forms = false + config.action_view.embed_authenticity_token_in_remote_forms = nil config.action_view.debug_missing_translation = true + config.action_view.default_enforce_utf8 = nil + config.action_view.finalize_compiled_template_methods = true config.eager_load_namespaces << ActionView @@ -17,6 +21,38 @@ module ActionView end end + initializer "action_view.form_with_generates_remote_forms" do |app| + ActiveSupport.on_load(:action_view) do + form_with_generates_remote_forms = app.config.action_view.delete(:form_with_generates_remote_forms) + ActionView::Helpers::FormHelper.form_with_generates_remote_forms = form_with_generates_remote_forms + end + end + + initializer "action_view.form_with_generates_ids" do |app| + ActiveSupport.on_load(:action_view) do + form_with_generates_ids = app.config.action_view.delete(:form_with_generates_ids) + unless form_with_generates_ids.nil? + ActionView::Helpers::FormHelper.form_with_generates_ids = form_with_generates_ids + end + end + end + + initializer "action_view.default_enforce_utf8" do |app| + ActiveSupport.on_load(:action_view) do + default_enforce_utf8 = app.config.action_view.delete(:default_enforce_utf8) + unless default_enforce_utf8.nil? + ActionView::Helpers::FormTagHelper.default_enforce_utf8 = default_enforce_utf8 + end + end + end + + initializer "action_view.finalize_compiled_template_methods" do |app| + ActiveSupport.on_load(:action_view) do + ActionView::Template.finalize_compiled_template_methods = + app.config.action_view.delete(:finalize_compiled_template_methods) + end + end + initializer "action_view.logger" do ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger } end diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb index 48bea315a9..1310a1ce0a 100644 --- a/actionview/lib/action_view/record_identifier.rb +++ b/actionview/lib/action_view/record_identifier.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/module" require "action_view/model_naming" diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb index 0b315eb569..20b2523cac 100644 --- a/actionview/lib/action_view/renderer/abstract_renderer.rb +++ b/actionview/lib/action_view/renderer/abstract_renderer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView # This class defines the interface for a renderer. Each class that # subclasses +AbstractRenderer+ is used by the base +Renderer+ class to diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 647b15ea94..d7f97c3b50 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "concurrent/map" require "action_view/renderer/partial_renderer/collection_caching" @@ -50,12 +52,12 @@ module ActionView # <%= 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. + # This would first render <tt>advertiser/_account.html.erb</tt> with <tt>@buyer</tt> passed in as the local variable +account+, then + # render <tt>advertiser/_ad.html.erb</tt> 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. + # By default ActionView::PartialRenderer 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 %> @@ -83,7 +85,7 @@ module ActionView # # <%= render partial: "ad", collection: @advertisements %> # - # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An + # This will render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display. An # iteration object will automatically be made available to the template with a name of the form # +partial_name_iteration+. The iteration object has knowledge about which index the current object has in # the collection and the total size of the collection. The iteration object also has two convenience methods, @@ -98,7 +100,7 @@ module ActionView # # <%= 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 + # 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 be displayed instead by using this form: # # <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %> @@ -112,18 +114,18 @@ module ActionView # # <%= 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. + # This will render the partial <tt>advertisement/_ad.html.erb</tt> regardless of which controller this is being called from. # - # == \Rendering objects that respond to `to_partial_path` + # == \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. + # 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`, + # # @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 %> @@ -143,7 +145,7 @@ module ActionView # # <%= 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`, + # # @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 %> @@ -344,7 +346,7 @@ module ActionView end content = layout.render(view, locals) { content } if layout - payload[:cache_hit] = view.cache_hit + payload[:cache_hit] = view.view_renderer.cache_hits[@template.virtual_path] content end end @@ -353,7 +355,7 @@ module ActionView # finds the options and details and extracts them. The method also contains # logic that handles the type of object passed in as the partial. # - # If +options[:partial]+ is a string, then the +@path+ instance variable is + # If +options[:partial]+ is a string, then the <tt>@path</tt> instance variable is # set to that string. Otherwise, the +options[:partial]+ object must # respond to +to_partial_path+ in order to setup the path. def setup(context, options, block) @@ -361,7 +363,7 @@ module ActionView @options = options @block = block - @locals = options[:locals] || {} + @locals = options[:locals] ? options[:locals].symbolize_keys : {} @details = extract_details(options) prepend_formats(options[:formats]) diff --git a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb index 1fbe209200..3c10e0452f 100644 --- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb +++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module CollectionCaching # :nodoc: extend ActiveSupport::Concern @@ -5,7 +7,7 @@ module ActionView included do # Fallback cache store if Action View is used without Rails. # Otherwise overridden in Railtie to use Rails.cache. - mattr_accessor(:collection_cache) { ActiveSupport::Cache::MemoryStore.new } + mattr_accessor :collection_cache, default: ActiveSupport::Cache::MemoryStore.new end private @@ -38,10 +40,14 @@ module ActionView end def expanded_cache_key(key) - key = @view.fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path)) + key = @view.combined_fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path, digest_path: digest_path)) key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0. end + def digest_path + @digest_path ||= @view.digest_path_from_virtual(@template.virtual_path) + end + def fetch_or_cache_partial(cached_partials, order_by:) order_by.map do |cache_key| cached_partials.fetch(cache_key) do diff --git a/actionview/lib/action_view/renderer/renderer.rb b/actionview/lib/action_view/renderer/renderer.rb index 2a3b89aebf..3f3a97529d 100644 --- a/actionview/lib/action_view/renderer/renderer.rb +++ b/actionview/lib/action_view/renderer/renderer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView # This is the main entry point for rendering. It basically delegates # to other objects like TemplateRenderer and PartialRenderer which @@ -46,5 +48,9 @@ module ActionView def render_partial(context, options, &block) #:nodoc: PartialRenderer.new(@lookup_context).render(context, options, block) end + + def cache_hits # :nodoc: + @cache_hits ||= {} + end end end diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb index 62ce985243..276a28ce07 100644 --- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "fiber" module ActionView @@ -31,7 +33,7 @@ module ActionView logger = ActionView::Base.logger return unless logger - message = "\n#{exception.class} (#{exception.message}):\n" + message = "\n#{exception.class} (#{exception.message}):\n".dup message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) message << " " << exception.backtrace.join("\n ") logger.fatal("#{message}\n\n") @@ -63,7 +65,9 @@ module ActionView yielder = lambda { |*name| view._layout_for(*name) } instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do + outer_config = I18n.config fiber = Fiber.new do + I18n.config = outer_config if layout layout.render(view, locals, output, &yielder) else diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index 54317199de..ce8908924a 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/object/try" module ActionView diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index cf18562c45..4e5fdfbb2d 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_view/view_paths" module ActionView @@ -73,8 +75,7 @@ module ActionView end # Returns an object that is able to render templates. - # :api: private - def view_renderer + def view_renderer # :nodoc: @_view_renderer ||= ActionView::Renderer.new(lookup_context) end @@ -90,7 +91,6 @@ module ActionView private # Find and render a template based on the options given. - # :api: private def _render_template(options) variant = options.delete(:variant) assigns = options.delete(:assigns) @@ -112,7 +112,6 @@ module ActionView # Normalize args by converting render "foo" to render :action => "foo" and # render "foo/bar" to render :template => "foo/bar". - # :api: private def _normalize_args(action = nil, options = {}) options = super(action, options) case action @@ -135,7 +134,6 @@ module ActionView end # Normalize options. - # :api: private def _normalize_options(options) options = super(options) if options[:partial] == true diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb index 687ba7c1b4..fd563f34a9 100644 --- a/actionview/lib/action_view/routing_url_for.rb +++ b/actionview/lib/action_view/routing_url_for.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_dispatch/routing/polymorphic_routes" module ActionView diff --git a/actionview/lib/action_view/tasks/cache_digests.rake b/actionview/lib/action_view/tasks/cache_digests.rake index d30b3f7797..dd8e94bd88 100644 --- a/actionview/lib/action_view/tasks/cache_digests.rake +++ b/actionview/lib/action_view/tasks/cache_digests.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + namespace :cache_digests do desc "Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)" task nested_dependencies: :environment do diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index b0e2f1e54e..ee1cd61f12 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/object/try" require "active_support/core_ext/kernel/singleton_class" require "thread" @@ -7,6 +9,8 @@ module ActionView class Template extend ActiveSupport::Autoload + mattr_accessor :finalize_compiled_template_methods, default: true + # === Encodings in ActionView::Template # # ActionView::Template is one of a few sources of potential @@ -282,7 +286,7 @@ module ActionView # Make sure that the resulting String to be eval'd is in the # encoding of the code - source = <<-end_src + source = <<-end_src.dup 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 @@ -305,7 +309,9 @@ module ActionView end mod.module_eval(source, identifier, 0) - ObjectSpace.define_finalizer(self, Finalizer[method_name, mod]) + if finalize_compiled_template_methods + ObjectSpace.define_finalizer(self, Finalizer[method_name, mod]) + end end def handle_render_error(view, e) @@ -328,13 +334,13 @@ module ActionView locals = @locals - Module::RUBY_RESERVED_KEYWORDS locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/) - # Double assign to suppress the dreaded 'assigned but unused variable' warning - locals.each_with_object("") { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" } + # Assign for the same variable is to suppress unused variable warning + locals.each_with_object("".dup) { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" } end def method_name @method_name ||= begin - m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}" + m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".dup m.tr!("-".freeze, "_".freeze) m end diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb index cc90477190..4e3c02e05e 100644 --- a/actionview/lib/action_view/template/error.rb +++ b/actionview/lib/action_view/template/error.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/enumerable" module ActionView @@ -8,9 +10,6 @@ module ActionView class EncodingError < StandardError #:nodoc: end - class MissingRequestError < StandardError #:nodoc: - end - class WrongEncodingError < EncodingError #:nodoc: def initialize(string, encoding) @string, @encoding = string, encoding diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb index f4301f6f07..7ec76dcc3f 100644 --- a/actionview/lib/action_view/template/handlers.rb +++ b/actionview/lib/action_view/template/handlers.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + module ActionView #:nodoc: # = Action View Template Handlers - class Template + class Template #:nodoc: module Handlers #:nodoc: autoload :Raw, "action_view/template/handlers/raw" autoload :ERB, "action_view/template/handlers/erb" diff --git a/actionview/lib/action_view/template/handlers/builder.rb b/actionview/lib/action_view/template/handlers/builder.rb index e99b921cb7..61492ce448 100644 --- a/actionview/lib/action_view/template/handlers/builder.rb +++ b/actionview/lib/action_view/template/handlers/builder.rb @@ -1,9 +1,9 @@ +# frozen_string_literal: true + module ActionView module Template::Handlers class Builder - # Default format used by Builder. - class_attribute :default_format - self.default_format = :xml + class_attribute :default_format, default: :xml def call(template) require_engine @@ -14,7 +14,6 @@ module ActionView end private - def require_engine # :doc: @required ||= begin require "builder" diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb index 58c7fd1a88..270be0a380 100644 --- a/actionview/lib/action_view/template/handlers/erb.rb +++ b/actionview/lib/action_view/template/handlers/erb.rb @@ -1,24 +1,30 @@ +# frozen_string_literal: true + module ActionView class Template module Handlers - autoload :Erubis, "action_view/template/handlers/erb/deprecated_erubis" - class ERB autoload :Erubi, "action_view/template/handlers/erb/erubi" - autoload :Erubis, "action_view/template/handlers/erb/erubis" # Specify trim mode for the ERB compiler. Defaults to '-'. # See ERB documentation for suitable values. - class_attribute :erb_trim_mode - self.erb_trim_mode = "-" + class_attribute :erb_trim_mode, default: "-" # Default implementation used. - class_attribute :erb_implementation - self.erb_implementation = Erubi + class_attribute :erb_implementation, default: Erubi # Do not escape templates of these mime types. - class_attribute :escape_whitelist - self.escape_whitelist = ["text/plain"] + class_attribute :escape_ignore_list, default: ["text/plain"] + + [self, singleton_class].each do |base| + base.send(:alias_method, :escape_whitelist, :escape_ignore_list) + base.send(:alias_method, :escape_whitelist=, :escape_ignore_list=) + + base.deprecate( + escape_whitelist: "use #escape_ignore_list instead", + :escape_whitelist= => "use #escape_ignore_list= instead" + ) + end ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*") @@ -51,7 +57,7 @@ module ActionView self.class.erb_implementation.new( erb, - escape: (self.class.escape_whitelist.include? template.type), + escape: (self.class.escape_ignore_list.include? template.type), trim: (self.class.erb_trim_mode == "-") ).src end diff --git a/actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb b/actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb deleted file mode 100644 index 427ea20064..0000000000 --- a/actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb +++ /dev/null @@ -1,9 +0,0 @@ -::ActiveSupport::Deprecation.warn("ActionView::Template::Handlers::Erubis is deprecated and will be removed from Rails 5.2. Switch to ActionView::Template::Handlers::ERB::Erubi instead.") - -module ActionView - class Template - module Handlers - Erubis = ERB::Erubis - end - end -end diff --git a/actionview/lib/action_view/template/handlers/erb/erubi.rb b/actionview/lib/action_view/template/handlers/erb/erubi.rb index 755cc84015..db75f028ed 100644 --- a/actionview/lib/action_view/template/handlers/erb/erubi.rb +++ b/actionview/lib/action_view/template/handlers/erb/erubi.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "erubi" module ActionView diff --git a/actionview/lib/action_view/template/handlers/erb/erubis.rb b/actionview/lib/action_view/template/handlers/erb/erubis.rb deleted file mode 100644 index f3c35e1aec..0000000000 --- a/actionview/lib/action_view/template/handlers/erb/erubis.rb +++ /dev/null @@ -1,81 +0,0 @@ -gem "erubis" -require "erubis" - -module ActionView - class Template - module Handlers - class ERB - class Erubis < ::Erubis::Eruby - # :nodoc: all - def add_preamble(src) - @newline_pending = 0 - src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;" - end - - def add_text(src, text) - return if text.empty? - - if text == "\n" - @newline_pending += 1 - else - src << "@output_buffer.safe_append='" - src << "\n" * @newline_pending if @newline_pending > 0 - src << escape_text(text) - src << "'.freeze;" - - @newline_pending = 0 - end - end - - # Erubis toggles <%= and <%== behavior when escaping is enabled. - # We override to always treat <%== as escaped. - def add_expr(src, code, indicator) - case indicator - when "==" - add_expr_escaped(src, code) - else - super - end - end - - BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/ - - def add_expr_literal(src, code) - flush_newline_if_pending(src) - if BLOCK_EXPR.match?(code) - src << "@output_buffer.append= " << code - else - src << "@output_buffer.append=(" << code << ");" - end - end - - def add_expr_escaped(src, code) - flush_newline_if_pending(src) - if BLOCK_EXPR.match?(code) - src << "@output_buffer.safe_expr_append= " << code - else - src << "@output_buffer.safe_expr_append=(" << code << ");" - end - end - - def add_stmt(src, code) - flush_newline_if_pending(src) - super - end - - def add_postamble(src) - flush_newline_if_pending(src) - src << "@output_buffer.to_s" - end - - def flush_newline_if_pending(src) - if @newline_pending > 0 - src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;" - @newline_pending = 0 - end - end - end - end - end - end -end diff --git a/actionview/lib/action_view/template/handlers/html.rb b/actionview/lib/action_view/template/handlers/html.rb index ccaa8d1469..27004a318c 100644 --- a/actionview/lib/action_view/template/handlers/html.rb +++ b/actionview/lib/action_view/template/handlers/html.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Template::Handlers class Html < Raw diff --git a/actionview/lib/action_view/template/handlers/raw.rb b/actionview/lib/action_view/template/handlers/raw.rb index e7519e94f9..5cd23a0060 100644 --- a/actionview/lib/action_view/template/handlers/raw.rb +++ b/actionview/lib/action_view/template/handlers/raw.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActionView module Template::Handlers class Raw diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb index 0ffae10432..a262c6d9ad 100644 --- a/actionview/lib/action_view/template/html.rb +++ b/actionview/lib/action_view/template/html.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + module ActionView #:nodoc: # = Action View HTML Template - class Template + class Template #:nodoc: class HTML #:nodoc: attr_accessor :type diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index d3905b5f23..5a86f10973 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "pathname" require "active_support/core_ext/class" require "active_support/core_ext/module/attribute_accessors" @@ -14,7 +16,7 @@ module ActionView alias_method :partial?, :partial def self.build(name, prefix, partial) - virtual = "" + virtual = "".dup virtual << "#{prefix}/" unless prefix.empty? virtual << (partial ? "_#{name}" : name) new name, prefix, partial, virtual @@ -125,8 +127,7 @@ module ActionView end end - cattr_accessor :caching - self.caching = true + cattr_accessor :caching, default: true class << self alias :caching? :caching @@ -309,13 +310,13 @@ module ActionView # ==== 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},}` + # looking for <tt>users/new</tt> it will produce query glob: <tt>users/new{.{en},}{.{html,js},}{.{erb,haml},}</tt> # # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}") # # This one allows you to keep files with different formats in separate 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. + # eg. <tt>users/new.html</tt> will be loaded from <tt>users/html/new.erb</tt> or <tt>users/new.html.erb</tt>, + # <tt>users/new.js</tt> from <tt>users/js/new.erb</tt> or <tt>users/new.js.erb</tt>, etc. # # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}") # diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb index 380528d6ef..f8d6c2811f 100644 --- a/actionview/lib/action_view/template/text.rb +++ b/actionview/lib/action_view/template/text.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + module ActionView #:nodoc: # = Action View Text Template - class Template + class Template #:nodoc: class Text #:nodoc: attr_accessor :type diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb index 21959a3798..67b7a62de6 100644 --- a/actionview/lib/action_view/template/types.rb +++ b/actionview/lib/action_view/template/types.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require "active_support/core_ext/module/attribute_accessors" module ActionView - class Template + class Template #:nodoc: class Types class Type SET = Struct.new(:symbols).new([ :html, :text, :js, :css, :xml, :json ]) diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index ae4fec4337..e1cbae5845 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -1,4 +1,6 @@ -require "active_support/core_ext/module/remove_method" +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" require "action_controller" require "action_controller/test_case" require "action_view" @@ -71,7 +73,7 @@ module ActionView def helper_method(*methods) # Almost a duplicate from ActionController::Helpers methods.flatten.each do |method| - _helpers.module_eval <<-end_eval + _helpers.module_eval <<-end_eval, __FILE__, __LINE__ + 1 def #{method}(*args, &block) # def current_user(*args, &block) _test_case.send(%(#{method}), *args, &block) # _test_case.send(%(current_user), *args, &block) end # end @@ -101,10 +103,11 @@ module ActionView def setup_with_controller @controller = ActionView::TestCase::TestController.new @request = @controller.request + @view_flow = ActionView::OutputFlow.new # empty string ensures buffer has UTF-8 encoding as # new without arguments returns ASCII-8BIT encoded buffer like String#new @output_buffer = ActiveSupport::SafeBuffer.new "" - @rendered = "" + @rendered = "".dup make_test_case_available_to_view! say_no_to_protect_against_forgery! @@ -168,7 +171,7 @@ module ActionView def say_no_to_protect_against_forgery! _helpers.module_eval do - remove_possible_method :protect_against_forgery? + silence_redefinition_of_method :protect_against_forgery? def protect_against_forgery? false end @@ -244,6 +247,7 @@ module ActionView :@test_passed, :@view, :@view_context_class, + :@view_flow, :@_subscribers, :@html_document ] @@ -266,7 +270,7 @@ module ActionView begin routes = @controller.respond_to?(:_routes) && @controller._routes rescue - # Dont call routes, if there is an error on _routes call + # Don't call routes, if there is an error on _routes call end if routes && @@ -277,6 +281,18 @@ module ActionView super end end + + def respond_to_missing?(name, include_private = false) + begin + routes = @controller.respond_to?(:_routes) && @controller._routes + rescue + # Don't call routes, if there is an error on _routes call + end + + routes && + (routes.named_routes.route_defined?(name) || + routes.mounted_helpers.method_defined?(name)) + end end include Behavior diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb index 3188526b63..68186c3bf8 100644 --- a/actionview/lib/action_view/testing/resolvers.rb +++ b/actionview/lib/action_view/testing/resolvers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "action_view/template/resolver" module ActionView #:nodoc: @@ -20,7 +22,7 @@ module ActionView #:nodoc: private def query(path, exts, _, _) - query = "" + query = "".dup EXTENSIONS.each_key do |ext| query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)" end diff --git a/actionview/lib/action_view/version.rb b/actionview/lib/action_view/version.rb index 315404864d..be53797a14 100644 --- a/actionview/lib/action_view/version.rb +++ b/actionview/lib/action_view/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "gem_version" module ActionView diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb index f0fe6831fa..d5694d77f4 100644 --- a/actionview/lib/action_view/view_paths.rb +++ b/actionview/lib/action_view/view_paths.rb @@ -1,11 +1,11 @@ +# frozen_string_literal: true + module ActionView module ViewPaths extend ActiveSupport::Concern included do - class_attribute :_view_paths - self._view_paths = ActionView::PathSet.new - _view_paths.freeze + class_attribute :_view_paths, default: ActionView::PathSet.new.freeze end delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=, |